diff --git a/gtk/FileList.cc b/gtk/FileList.cc index 441754519..451f9efe7 100644 --- a/gtk/FileList.cc +++ b/gtk/FileList.cc @@ -6,7 +6,11 @@ #include /* INT_MAX */ #include #include // strchr +#include +#include #include +#include +#include #include #include @@ -23,6 +27,8 @@ #include "Session.h" #include "Utils.h" +using namespace std::literals; + namespace { @@ -145,51 +151,32 @@ bool refreshFilesForeach( bool const is_file = iter->children().empty(); auto const old_enabled = iter->get_value(file_cols.enabled); - auto const old_priority = iter->get_value(file_cols.priority); - auto const index = iter->get_value(file_cols.index); auto const old_have = iter->get_value(file_cols.have); - auto const size = iter->get_value(file_cols.size); - auto const old_prog = iter->get_value(file_cols.prog); + auto const old_priority = iter->get_value(file_cols.priority); + auto const old_progress = iter->get_value(file_cols.prog); + auto const old_size = iter->get_value(file_cols.size); + + auto new_enabled = old_enabled; + auto new_have = old_have; + auto new_priority = old_priority; + auto new_progress = old_progress; + auto new_size = old_size; if (is_file) { - auto const* tor = refresh_data.tor; - auto const file = tr_torrentFile(tor, index); - int const enabled = file.wanted; - int const priority = file.priority; - auto const progress = file.progress; - uint64_t const have = file.have; - int const prog = std::clamp(int(100 * progress), 0, 100); + auto const index = iter->get_value(file_cols.index); + auto const file = tr_torrentFile(refresh_data.tor, index); - if (priority != old_priority || enabled != old_enabled || have != old_have || prog != old_prog) - { - /* Changing a value in the sort column can trigger a resort - * which breaks this foreach () call. (See #3529) - * As a workaround: if that's about to happen, temporarily disable - * sorting until we finish walking the tree. */ - if (!refresh_data.resort_needed && - (((refresh_data.sort_column_id == file_cols.priority.index()) && (priority != old_priority)) || - ((refresh_data.sort_column_id == file_cols.enabled.index()) && (enabled != old_enabled)))) - { - refresh_data.resort_needed = true; - - store->set_sort_column(GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, Gtk::SORT_ASCENDING); - } - - (*iter)[file_cols.priority] = priority; - (*iter)[file_cols.enabled] = enabled; - (*iter)[file_cols.have] = have; - (*iter)[file_cols.prog] = prog; - (*iter)[file_cols.prog_str] = fmt::format(FMT_STRING("{:d}%"), prog); - } + new_enabled = file.wanted; + new_priority = file.priority; + new_have = file.have; + new_progress = std::clamp(int(100 * file.progress), 0, 100); } else { - uint64_t sub_size = 0; - uint64_t have = 0; - int prog; - int enabled = NOT_SET; - int priority = NOT_SET; + new_size = 0; + new_enabled = NOT_SET; + new_priority = NOT_SET; /* since gtk_tree_model_foreach() is depth-first, we can * get the `sub' info by walking the immediate children */ @@ -203,43 +190,75 @@ bool refreshFilesForeach( if ((child_enabled != false) && (child_enabled != NOT_SET)) { - sub_size += child_size; - have += child_have; + new_size += child_size; + new_have += child_have; } - if (enabled == NOT_SET) + if (new_enabled == NOT_SET) { - enabled = child_enabled; + new_enabled = child_enabled; } - else if (enabled != child_enabled) + else if (new_enabled != child_enabled) { - enabled = MIXED; + new_enabled = MIXED; } - if (priority == NOT_SET) + if (new_priority == NOT_SET) { - priority = child_priority; + new_priority = child_priority; } - else if (priority != child_priority) + else if (new_priority != child_priority) { - priority = MIXED; + new_priority = MIXED; } } - prog = sub_size != 0 ? (int)(100.0 * have / sub_size) : 1; + new_progress = new_size != 0 ? (int)(100.0 * new_have / new_size) : 1; + } - if (size != sub_size || have != old_have || priority != old_priority || enabled != old_enabled || prog != old_prog) + if (new_priority != old_priority || new_enabled != old_enabled) + { + /* Changing a value in the sort column can trigger a resort + * which breaks this foreach () call. (See #3529) + * As a workaround: if that's about to happen, temporarily disable + * sorting until we finish walking the tree. */ + if (!refresh_data.resort_needed && + (((refresh_data.sort_column_id == file_cols.priority.index()) && (new_priority != old_priority)) || + ((refresh_data.sort_column_id == file_cols.enabled.index()) && (new_enabled != old_enabled)))) { - (*iter)[file_cols.size] = sub_size; - (*iter)[file_cols.size_str] = tr_strlsize(sub_size); - (*iter)[file_cols.have] = have; - (*iter)[file_cols.priority] = priority; - (*iter)[file_cols.enabled] = enabled; - (*iter)[file_cols.prog] = prog; - (*iter)[file_cols.prog_str] = fmt::format(FMT_STRING("{:d}%"), prog); + refresh_data.resort_needed = true; + + store->set_sort_column(GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, Gtk::SORT_ASCENDING); } } + if (new_enabled != old_enabled) + { + (*iter)[file_cols.enabled] = new_enabled; + } + + if (new_priority != old_priority) + { + (*iter)[file_cols.priority] = new_priority; + } + + if (new_size != old_size) + { + (*iter)[file_cols.size] = new_size; + (*iter)[file_cols.size_str] = tr_strlsize(new_size); + } + + if (new_have != old_have) + { + (*iter)[file_cols.have] = new_have; + } + + if (new_progress != old_progress) + { + (*iter)[file_cols.prog] = new_progress; + (*iter)[file_cols.prog_str] = fmt::format(FMT_STRING("{:d}%"), new_progress); + } + return false; /* keep walking */ } @@ -270,9 +289,7 @@ void gtr_tree_model_foreach_postorder(Glib::RefPtr const& model, void FileList::Impl::refresh() { - tr_torrent* tor = core_->find_torrent(torrent_id_); - - if (tor == nullptr) + if (tr_torrent* tor = core_->find_torrent(torrent_id_); tor == nullptr) { widget_.clear(); } @@ -424,14 +441,14 @@ void buildTree(FileRowNode& node, build_data& build) auto const& child_data = node.data(); bool const isLeaf = node.child_count() == 0; - auto const mime_type = isLeaf ? gtr_get_mime_type_from_filename(child_data.name) : DirectoryMimeType; + auto const mime_type = isLeaf ? tr_get_mime_type_for_filename(child_data.name.raw()) : DirectoryMimeType; auto const icon = gtr_get_mime_type_icon(mime_type, Gtk::ICON_SIZE_MENU, *build.w); auto const file = isLeaf ? tr_torrentFile(build.tor, child_data.index) : tr_file_view{}; int const priority = isLeaf ? file.priority : 0; bool const enabled = isLeaf ? file.wanted : true; auto name_esc = Glib::Markup::escape_text(child_data.name); - auto const child_iter = build.store->append(build.iter->children()); + auto const child_iter = build.store->prepend(build.iter->children()); (*child_iter)[file_cols.index] = child_data.index; (*child_iter)[file_cols.label] = child_data.name; (*child_iter)[file_cols.label_esc] = name_esc; @@ -449,21 +466,6 @@ void buildTree(FileRowNode& node, build_data& build) } } -FileRowNode* find_child(FileRowNode* parent, Glib::ustring const& name) -{ - for (auto* child = parent->first_child(); child != nullptr; child = child->next_sibling()) - { - auto const& child_data = child->data(); - - if (child_data.name == name) - { - return child; - } - } - - return nullptr; -} - } // namespace void FileList::set_torrent(int torrent_id) @@ -471,6 +473,15 @@ void FileList::set_torrent(int torrent_id) impl_->set_torrent(torrent_id); } +struct PairHash +{ + template + auto operator()(std::pair const& pair) const + { + return std::hash{}(pair.first) ^ std::hash{}(pair.second); + } +}; + void FileList::Impl::set_torrent(int torrentId) { /* unset the old fields */ @@ -486,32 +497,34 @@ void FileList::Impl::set_torrent(int torrentId) if (auto* const tor = core_->find_torrent(torrent_id_); tor != nullptr) { // build a GNode tree of the files - FileRowNode root; + auto root = FileRowNode{}; auto& root_data = root.data(); root_data.name = tr_torrentName(tor); root_data.index = -1; root_data.length = 0; + auto nodes = std::unordered_map, FileRowNode*, PairHash>{}; + for (tr_file_index_t i = 0, n_files = tr_torrentFileCount(tor); i < n_files; ++i) { auto* parent = &root; auto const file = tr_torrentFile(tor, i); - for (char const *last = file.name, *next = strchr(last, '/'); last != nullptr; - last = next != nullptr ? next + 1 : nullptr, next = last != nullptr ? strchr(last, '/') : nullptr) + auto path = std::string_view{ file.name }; + auto token = std::string_view{}; + while (tr_strvSep(&path, &token, '/')) { - bool const isLeaf = next == nullptr; - auto name = Glib::ustring(isLeaf ? last : std::string(last, next - last)); - auto* node = find_child(parent, name); + auto*& node = nodes[std::make_pair(parent, token)]; if (node == nullptr) { - node = new FileRowNode(); - auto& row = node->data(); - row.name = std::move(name); - row.index = isLeaf ? (int)i : -1; - row.length = isLeaf ? file.length : 0; - parent->append(*node); + auto const is_leaf = std::empty(path); + + node = parent->prepend_data({}); + auto& node_data = node->data(); + node_data.name = std::string{ token }; + node_data.index = is_leaf ? (int)i : -1; + node_data.length = is_leaf ? file.length : 0; } parent = node; @@ -537,7 +550,8 @@ void FileList::Impl::set_torrent(int torrentId) /* set default sort by label */ store_->set_sort_column(file_cols.label, Gtk::SORT_ASCENDING); - view_->expand_all(); + view_->expand_row(Gtk::TreeModel::Path("0"), false); + // view_->expand_all(); } /*** @@ -731,7 +745,7 @@ bool FileList::Impl::on_rename_done_idle(Glib::ustring const& path_string, Glib: if (auto const iter = store_->get_iter(path_string); iter) { bool const isLeaf = iter->children().empty(); - auto const mime_type = isLeaf ? gtr_get_mime_type_from_filename(newname) : DirectoryMimeType; + auto const mime_type = isLeaf ? tr_get_mime_type_for_filename(newname.raw()) : DirectoryMimeType; auto const icon = gtr_get_mime_type_icon(mime_type, Gtk::ICON_SIZE_MENU, *view_); (*iter)[file_cols.label] = newname; diff --git a/gtk/IconCache.cc b/gtk/IconCache.cc index 07deb3615..dfb380b57 100644 --- a/gtk/IconCache.cc +++ b/gtk/IconCache.cc @@ -6,8 +6,10 @@ */ #include +#include #include -#include +#include +#include #include #include @@ -17,8 +19,8 @@ using namespace std::literals; -Glib::ustring const DirectoryMimeType = "folder"s; -Glib::ustring const UnknownMimeType = "unknown"s; +std::string_view const DirectoryMimeType = "folder"sv; +std::string_view const UnknownMimeType = "unknown"sv; namespace { @@ -29,7 +31,7 @@ struct IconCache { Glib::RefPtr icon_theme; int icon_size; - std::unordered_map> cache; + std::map, std::less<>> cache; }; std::array, 7> icon_cache; @@ -58,28 +60,6 @@ std::unique_ptr icon_cache_new(Gtk::Widget& for_widget, Gtk::IconSize return icons; } -std::string _icon_cache_get_icon_key(Glib::RefPtr const& icon) -{ - std::string key; - - if (auto const* const ticon = dynamic_cast(gtr_get_ptr(icon)); ticon != nullptr) - { - std::ostringstream names; - for (auto const& name : ticon->get_names()) - { - names << name << ','; - } - - key = names.str(); - } - else if (auto* const ficon = dynamic_cast(gtr_get_ptr(icon)); ficon != nullptr) - { - key = ficon->get_file()->get_path(); - } - - return key; -} - Glib::RefPtr get_themed_icon_pixbuf(Gio::ThemedIcon& icon, int size, Gtk::IconTheme& icon_theme) { auto const icon_names = icon.get_names(); @@ -134,25 +114,21 @@ Glib::RefPtr _get_icon_pixbuf(Glib::RefPtr const& icon, return {}; } -Glib::RefPtr icon_cache_get_mime_type_icon(IconCache& icons, Glib::ustring const& mime_type) +Glib::RefPtr icon_cache_get_mime_type_icon(IconCache& icons, std::string_view mime_type) { - auto icon = Gio::content_type_get_icon(mime_type); - auto key = _icon_cache_get_icon_key(icon); - if (key.empty()) + auto& cache = icons.cache; + + if (auto mime_it = cache.find(mime_type); mime_it != std::end(cache)) { - key = VoidPixbufKey; + return mime_it->second; } - if (auto pixbuf_it = icons.cache.find(key); pixbuf_it != icons.cache.end()) - { - return pixbuf_it->second; - } - - auto const pixbuf = _get_icon_pixbuf(icon, icons.icon_size, *gtr_get_ptr(icons.icon_theme)); - + auto mime_type_str = std::string{ mime_type }; + auto icon = Gio::content_type_get_icon(mime_type_str); + auto pixbuf = _get_icon_pixbuf(icon, icons.icon_size, *gtr_get_ptr(icons.icon_theme)); if (pixbuf != nullptr) { - icons.cache.try_emplace(key, pixbuf); + cache.try_emplace(std::move(mime_type_str), pixbuf); } return pixbuf; @@ -160,10 +136,7 @@ Glib::RefPtr icon_cache_get_mime_type_icon(IconCache& icons, Glib:: } // namespace -Glib::RefPtr gtr_get_mime_type_icon( - Glib::ustring const& mime_type, - Gtk::IconSize icon_size, - Gtk::Widget& for_widget) +Glib::RefPtr gtr_get_mime_type_icon(std::string_view mime_type, Gtk::IconSize icon_size, Gtk::Widget& for_widget) { int n; @@ -205,9 +178,3 @@ Glib::RefPtr gtr_get_mime_type_icon( return icon_cache_get_mime_type_icon(*icon_cache[n], mime_type); } - -Glib::ustring gtr_get_mime_type_from_filename(std::string const& file) -{ - bool result_uncertain; - return Gio::content_type_guess(file, {}, result_uncertain); -} diff --git a/gtk/IconCache.h b/gtk/IconCache.h index b570015d9..a5c7a36cf 100644 --- a/gtk/IconCache.h +++ b/gtk/IconCache.h @@ -7,16 +7,11 @@ #pragma once -#include +#include #include -extern Glib::ustring const DirectoryMimeType; -extern Glib::ustring const UnknownMimeType; +extern std::string_view const DirectoryMimeType; +extern std::string_view const UnknownMimeType; -Glib::ustring gtr_get_mime_type_from_filename(std::string const& file); - -Glib::RefPtr gtr_get_mime_type_icon( - Glib::ustring const& mime_type, - Gtk::IconSize icon_size, - Gtk::Widget& for_widget); +Glib::RefPtr gtr_get_mime_type_icon(std::string_view mime_type, Gtk::IconSize icon_size, Gtk::Widget& for_widget); diff --git a/gtk/TorrentCellRenderer.cc b/gtk/TorrentCellRenderer.cc index d32f64206..c8f880bf2 100644 --- a/gtk/TorrentCellRenderer.cc +++ b/gtk/TorrentCellRenderer.cc @@ -350,7 +350,7 @@ namespace Glib::RefPtr get_icon(tr_torrent const* tor, Gtk::IconSize icon_size, Gtk::Widget& for_widget) { - Glib::ustring mime_type; + auto mime_type = std::string_view{}; if (auto const n_files = tr_torrentFileCount(tor); n_files == 0) { @@ -364,7 +364,7 @@ Glib::RefPtr get_icon(tr_torrent const* tor, Gtk::IconSize icon_siz { auto const* const name = tr_torrentFile(tor, 0).name; - mime_type = strchr(name, '/') != nullptr ? DirectoryMimeType : gtr_get_mime_type_from_filename(name); + mime_type = strchr(name, '/') != nullptr ? DirectoryMimeType : tr_get_mime_type_for_filename(name); } return gtr_get_mime_type_icon(mime_type, icon_size, for_widget);