From 186b76e7657ce6667a24d5e0b8df539dcdb8909a Mon Sep 17 00:00:00 2001 From: Jinhao Date: Thu, 13 Dec 2018 06:43:11 +0800 Subject: [PATCH] improve filebox appearance on Linux --- source/detail/posix/shared_icons.cpp | 50 ----- source/detail/posix/shared_icons.hpp | 28 --- source/detail/posix/theme.cpp | 298 +++++++++++++++++++++++++++ source/detail/posix/theme.hpp | 31 +++ source/gui/filebox.cpp | 111 +++++++++- source/gui/widgets/listbox.cpp | 2 +- source/gui/widgets/treebox.cpp | 9 +- 7 files changed, 441 insertions(+), 88 deletions(-) delete mode 100644 source/detail/posix/shared_icons.cpp delete mode 100644 source/detail/posix/shared_icons.hpp create mode 100644 source/detail/posix/theme.cpp create mode 100644 source/detail/posix/theme.hpp diff --git a/source/detail/posix/shared_icons.cpp b/source/detail/posix/shared_icons.cpp deleted file mode 100644 index c6345db7..00000000 --- a/source/detail/posix/shared_icons.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "shared_icons.hpp" - -namespace nana -{ - namespace detail - { - shared_icons::shared_icons(): - path_("/usr/share/icons/"), - ifs_("/usr/share/icons/default/index.theme") - { - } - - std::string shared_icons::cursor(const std::string& name) - { - auto theme = _m_read("Icon Theme", "Inherits"); - return path_ + theme + "/cursors/" + name; - } - - std::string shared_icons::_m_read(const std::string& category, const std::string& key) - { - ifs_.seekg(0, std::ios::beg); - - bool found_cat = false; - while(ifs_.good()) - { - std::string text; - std::getline(ifs_, text); - - if(0 == text.find('[')) - { - if(found_cat) - break; - - if(text.find(category + "]") != text.npos) - { - found_cat = true; - } - } - else if(found_cat && (text.find(key + "=") == 0)) - { - return text.substr(key.size() + 1); - } - } - - return {}; - } - - } - -} \ No newline at end of file diff --git a/source/detail/posix/shared_icons.hpp b/source/detail/posix/shared_icons.hpp deleted file mode 100644 index 1b225fcf..00000000 --- a/source/detail/posix/shared_icons.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef NANA_DETAIL_SHARED_ICONS_INCLUDED -#define NANA_DETAIL_SHARED_ICONS_INCLUDED - -#include -#include - -namespace nana -{ - namespace detail - { - class shared_icons - { - public: - shared_icons(); - - std::string cursor(const std::string& name); - private: - std::string _m_read(const std::string& category, const std::string& key); - private: - std::string path_; - std::ifstream ifs_; - }; - - }//end namespace detail - -}//end namespace nana - -#endif \ No newline at end of file diff --git a/source/detail/posix/theme.cpp b/source/detail/posix/theme.cpp new file mode 100644 index 00000000..417bc744 --- /dev/null +++ b/source/detail/posix/theme.cpp @@ -0,0 +1,298 @@ +#include "theme.hpp" +#include +#include +#include + +#include //debug +namespace nana +{ + namespace detail + { + static int gschema_override_priority(const std::string& filename) + { + if(filename.size() < 3) + return -1; + + auto str = filename.substr(0, 2); + if('0' <= str[0] && str[0] <= '9' && '0' <= str[1] && str[1] <= '9') + return std::stoi(str); + + return 0; + } + + //Removes the wrap of ' and " character. + std::string remove_decoration(const std::string& primitive_value) + { + auto pos = primitive_value.find_first_of("'\""); + if(pos == primitive_value.npos) + return primitive_value; + + auto endpos = primitive_value.find(primitive_value[pos], pos + 1); + if(endpos == primitive_value.npos) + return primitive_value; + + return primitive_value.substr(pos + 1, endpos - pos - 1); + } + + std::string find_value(std::ifstream& ifs, const std::string& category, const std::string& key) + { + ifs.seekg(0, std::ios::beg); + + std::string dec_categ = "[" + category + "]"; + + bool found_cat = false; + while(ifs.good()) + { + std::string text; + std::getline(ifs, text); + + if((text.size() > 2) && ('[' == text[0])) + { + if(found_cat) + break; + + found_cat = (text == dec_categ); + } + else if(found_cat && (text.find(key + "=") == 0)) + { + return remove_decoration(text.substr(key.size() + 1)); + } + } + return {}; + } + + std::vector split_value(const std::string& value_string) + { + std::vector values; + + std::size_t start_pos = 0; + while(start_pos != value_string.npos) + { + auto pos = value_string.find(',', start_pos); + if(value_string.npos == pos) + { + if(start_pos < value_string.size()) + values.emplace_back(value_string.substr(start_pos)); + break; + } + + values.emplace_back(value_string.substr(start_pos, pos - start_pos)); + + start_pos = value_string.find_first_not_of(',', pos); + } + return values; + } + + std::string find_gnome_theme_name() + { + namespace fs = std::filesystem; + try + { + //Searches all the gschema override files + std::vector overrides; + for(fs::directory_iterator i{"/usr/share/glib-2.0/schemas"}, end; i != end; ++i) + { + auto filename = i->path().filename().string(); + if(filename.size() > 17 && filename.substr(filename.size() - 17) == ".gschema.override") + { + auto priority = gschema_override_priority(filename); + if(priority < 0) + continue; + + auto i = std::find_if(overrides.cbegin(), overrides.cend(), [priority](const std::string& ovrd){ + return (priority > gschema_override_priority(ovrd)); + }); + + overrides.emplace(i, std::move(filename)); + //overrides.insert(i, filename); + } + } + + //Searches the org.gnome.desktop.interface in override files. + for(auto & gschema_override : overrides) + { + std::ifstream ifs{"/usr/share/glib-2.0/schemas/" + gschema_override}; + auto value = find_value(ifs, "org.gnome.desktop.interface", "icon-theme"); + if(!value.empty()) + return value; + } + + //Return the value from org.gnome.desktop.interface.gschema.xml + fs::path xml = "/usr/share/glib-2.0/schemas/org.gnome.desktop.interface.gschema.xml"; + auto bytes = fs::file_size(xml); + if(0 == bytes) + return {}; + + std::ifstream xml_ifs{"/usr/share/glib-2.0/schemas/org.gnome.desktop.interface.gschema.xml", std::ios::binary}; + if(xml_ifs) + { + std::string data; + data.resize(bytes); + + xml_ifs.read(&data.front(), bytes); + + auto pos = data.find("\"icon-theme\""); + if(pos != data.npos) + { + + pos = data.find("", pos + 22); + if(pos != data.npos) + { + pos += 9; + auto endpos = data.find("", pos); + if(endpos != data.npos) + { + return remove_decoration(data.substr(pos, endpos - pos)); + } + } + } + } + } + catch(...){} + + return {}; + } + + + class icon_theme + { + public: + icon_theme(const std::string& name): + theme_name_(name), + ifs_("/usr/share/icons/" + name + "/index.theme") + { + //First of all, read the Inherits and Directories + inherits_ = split_value(find_value(ifs_, "Icon Theme", "Inherits")); + directories_ = split_value(find_value(ifs_, "Icon Theme", "Directories")); + + } + + std::string find(const std::string& name, std::size_t size_wanted) const + { + namespace fs = std::filesystem; + //candidates + std::vector> first, second, third; + + fs::path theme_path = "/usr/share/icons/" + theme_name_; + + std::string base_path = "/usr/share/icons/" + theme_name_ + "/"; + std::string filename = "/" + name + ".png"; + + std::error_code err; + for(auto & dir : directories_) + { + if(!fs::exists(theme_path / dir / (name + ".png"), err)) + continue; + + auto size = find_value(ifs_, dir, "Size"); + auto type = find_value(ifs_, dir, "Type"); + auto scaled = find_value(ifs_, dir, "Scale"); + + if(size.empty() || ("Fixed" != type)) + continue; + + int int_size = std::stoi(size); + + if(!scaled.empty()) + int_size *= std::stoi(scaled); + + auto distance = std::abs(static_cast(size_wanted) - int_size); + + if(0 == distance) + { + if(scaled.empty() || scaled == "1") + return base_path + dir + filename; + else + first.emplace_back(dir, 0); + } + else + { + if(scaled.empty() || scaled == "1") + second.emplace_back(dir, distance); + else + third.emplace_back(dir, distance); + } + } + + using pair_type = std::pair; + auto comp = [](const pair_type& a, const pair_type& b){ + return a.second < b.second; + }; + + std::sort(first.begin(), first.end(), comp); + std::sort(second.begin(), second.end(), comp); + std::sort(third.begin(), third.end(), comp); + + std::string closer_dir; + if(!first.empty()) + closer_dir = first.front().first; + else if(!second.empty()) + closer_dir = second.front().first; + else if(!third.empty()) + closer_dir = third.front().first; + + + if(closer_dir.empty()) + { + for(auto & inh : inherits_) + { + auto dir = icon_theme{inh}.find(name, size_wanted); + if(!dir.empty()) + return dir; + } + + return icon_theme{"hicolor"}.find(name, size_wanted); + } + + return base_path + closer_dir + filename; + } + private: + const std::string theme_name_; + mutable std::ifstream ifs_; + std::vector inherits_; + std::vector directories_; + }; + + void test() + { + theme thm; + auto png = thm.icon("folder", 30); + std::cout<<"Icon Theme="<second) + { + if(p.first == size_wanted) + return p.second; + } + } + + //Cache is missed. + auto file = icon_theme{find_gnome_theme_name()}.find(name, size_wanted); + if(!file.empty()) + iconcache_[name].emplace_back(size_wanted, file); + + return file; + } + + } + +} \ No newline at end of file diff --git a/source/detail/posix/theme.hpp b/source/detail/posix/theme.hpp new file mode 100644 index 00000000..6b4f7a75 --- /dev/null +++ b/source/detail/posix/theme.hpp @@ -0,0 +1,31 @@ +#ifndef NANA_DETAIL_THEME_INCLUDED +#define NANA_DETAIL_THEME_INCLUDED + +#include +#include +#include +#include + +namespace nana +{ + namespace detail + { + class theme + { + public: + theme(); + + std::string cursor(const std::string& name) const; + std::string icon(const std::string& name, std::size_t size_wanted) const; + private: + std::string path_; + mutable std::ifstream ifs_; + + mutable std::map>> iconcache_; + }; + + }//end namespace detail + +}//end namespace nana + +#endif \ No newline at end of file diff --git a/source/gui/filebox.cpp b/source/gui/filebox.cpp index e89d8af2..d31ceb62 100644 --- a/source/gui/filebox.cpp +++ b/source/gui/filebox.cpp @@ -35,13 +35,12 @@ # include # include # include -# include "../detail/posix/shared_icons.hpp" +# include "../detail/posix/theme.hpp" #endif namespace fs = std::filesystem; namespace fs_ext = nana::filesystem_ext; - namespace nana { #if defined(NANA_POSIX) @@ -159,6 +158,15 @@ namespace nana pick_directory_(pick_directory), mode_(dialog_mode) { + images_.folder.open(theme_.icon("folder", 16)); + images_.file.open(theme_.icon("empty", 16)); + images_.exec.open(theme_.icon("exec", 16)); + images_.package.open(theme_.icon("package", 16)); + images_.text.open(theme_.icon("text", 16)); + images_.xml.open(theme_.icon("text-xml", 16)); + images_.image.open(theme_.icon("image", 16)); + images_.pdf.open(theme_.icon("application-pdf", 16)); + internationalization i18n; path_.create(*this); path_.splitstr("/"); @@ -215,6 +223,16 @@ namespace nana tree_.create(*this); + //Configure treebox icons + auto & fs_icons = tree_.icon("icon-fs"); + fs_icons.normal.open(theme_.icon("drive-harddisk", 16)); + + auto & folder_icons = tree_.icon("icon-folder"); + folder_icons.normal.open(theme_.icon("folder", 16)); + folder_icons.expanded.open(theme_.icon("folder-open", 16)); + + tree_.icon("icon-home").normal.open(theme_.icon("folder_home", 16)); + ls_file_.create(*this); ls_file_.append_header(i18n("NANA_FILEBOX_HEADER_NAME"), 190); ls_file_.append_header(i18n("NANA_FILEBOX_HEADER_MODIFIED"), 145); @@ -449,9 +467,13 @@ namespace nana private: void _m_layout() { + unsigned ascent, desent, ileading; + paint::graphics{nana::size{1, 1}}.text_metrics(ascent, desent, ileading); + + auto text_height = ascent + desent + 16; place_.bind(*this); place_.div( "vert" - "" + "" "" "" "" @@ -479,8 +501,10 @@ namespace nana //"FS.HOME", "FS.ROOT". Because a key of the tree widget should not be '/' nodes_.home = tree_.insert("FS.HOME", "Home"); nodes_.home.value(kind::filesystem); + nodes_.home.icon("icon-home"); nodes_.filesystem = tree_.insert("FS.ROOT", "Filesystem"); nodes_.filesystem.value(kind::filesystem); + nodes_.filesystem.icon("icon-fs"); std::vector> paths; paths.emplace_back(fs_ext::path_user().native(), nodes_.home); @@ -496,7 +520,7 @@ namespace nana continue; item_proxy node = tree_.insert(p.second, name, name); - if (false == node.empty()) + if (!node.empty()) { node.value(kind::filesystem); break; @@ -706,7 +730,70 @@ namespace nana { if(_m_filter_allowed(fs.name, fs.directory, filter, ext_types)) { - cat.append(fs).value(fs); + auto m = cat.append(fs); + m.value(fs); + + if(fs.directory) + m.icon(images_.folder); + else + { + std::string filename = fs.name; + for(auto ch : fs.name) + { + if('A' <= ch && ch <= 'Z') + ch = ch - 'A' + 'a'; + + filename += ch; + } + + auto size = filename.size(); + paint::image use_image; + + if(size > 3) + { + auto ext3 = filename.substr(size - 3); + if((".7z" == ext3) || (".ar" == ext3) || (".gz" == ext3) || (".xz" == ext3)) + use_image = images_.package; + } + + if(use_image.empty() && (size > 4)) + { + auto ext4 = filename.substr(size - 4); + + if( (".exe" == ext4) || + (".dll" == ext4)) + use_image = images_.exec; + else if((".zip" == ext4) || (".rar" == ext4) || + (".bz2" == ext4) || (".tar" == ext4)) + use_image = images_.package; + else if(".txt" == ext4) + use_image = images_.text; + else if ((".xml" == ext4) || (".htm" == ext4)) + use_image = images_.xml; + else if((".jpg" == ext4) || + (".png" == ext4) || + (".gif" == ext4) || + (".bmp" == ext4)) + use_image = images_.image; + else if(".pdf" == ext4) + use_image = images_.pdf; + } + + if(use_image.empty() && (size > 5)) + { + auto ext5 = filename.substr(size - 5); + if(".lzma" == ext5) + use_image = images_.package; + else if(".html" == ext5) + use_image = images_.xml; + } + + if(use_image.empty()) + m.icon(images_.file); + else + m.icon(use_image); + + } } } ls_file_.auto_draw(true); @@ -917,6 +1004,7 @@ namespace nana auto child = node.append(name, name, kind::filesystem); if(!child.empty()) { + child->icon("icon-folder"); //The try-catch can be eleminated by using //directory_iterator( const std::filesystem::path& p, std::error_code& ec ) noexcept; //in C++17 @@ -981,6 +1069,19 @@ namespace nana static std::string saved_init_path; static std::string saved_selected_path; + nana::detail::theme theme_; + + struct images + { + paint::image folder; + paint::image file; + paint::image exec; + paint::image package; + paint::image text; + paint::image xml; + paint::image image; + paint::image pdf; + }images_; };//end class filebox_implement std::string filebox_implement::saved_init_path; std::string filebox_implement::saved_selected_path; diff --git a/source/gui/widgets/listbox.cpp b/source/gui/widgets/listbox.cpp index eb302d9a..4f93a660 100644 --- a/source/gui/widgets/listbox.cpp +++ b/source/gui/widgets/listbox.cpp @@ -3862,7 +3862,7 @@ namespace nana { nana::rectangle imgt(item.img_show_size); img_r = imgt; - img_r.x = content_pos + coord.x + (16 - static_cast(item.img_show_size.width)) / 2; // center in 16 - geom scheme? + img_r.x = content_pos + coord.x + 2 + (16 - static_cast(item.img_show_size.width)) / 2; // center in 16 - geom scheme? img_r.y = coord.y + (static_cast(essence_->item_height()) - static_cast(item.img_show_size.height)) / 2; // center } content_pos += 18; // image width, geom scheme? diff --git a/source/gui/widgets/treebox.cpp b/source/gui/widgets/treebox.cpp index b5830180..cf5c4ea8 100644 --- a/source/gui/widgets/treebox.cpp +++ b/source/gui/widgets/treebox.cpp @@ -1522,11 +1522,12 @@ namespace nana { const nana::paint::image * img = nullptr; auto & item_attr = compset->item_attribute(); - if (item_attr.mouse_pointed) - img = &(item_attr.icon_hover); - else if (item_attr.expended) - img = &(item_attr.icon_expanded); + if (item_attr.expended) + img = &(item_attr.icon_expanded); + else if (item_attr.mouse_pointed) + img = &(item_attr.icon_hover); + if((nullptr == img) || img->empty()) img = &(item_attr.icon_normal);