From a7e4f3a265a5f08e6ed3431084ee8aa9fa04d587 Mon Sep 17 00:00:00 2001 From: Daniele Bartolini Date: Thu, 5 Dec 2024 00:02:19 +0100 Subject: [PATCH] tools: project-browser: add sorting modes to folder view Closes: #204 --- docs/changelog.rst | 5 + tools/level_editor/project_browser.vala | 213 ++++++++++++++++++++++-- 2 files changed, 200 insertions(+), 18 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index ec7b4241c..d3ead13ca 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,11 @@ Changelog 0.54.0 --- DD MMM YYYY ---------------------- +**Tools** + +* Items in the Project Browser can now be sorted by name, type, size or last modification time. +* Added a list-view mode to the Project Browser. + 0.53.0 --- 30 Nov 2024 ---------------------- diff --git a/tools/level_editor/project_browser.vala b/tools/level_editor/project_browser.vala index 658683d9e..eb2eb2646 100644 --- a/tools/level_editor/project_browser.vala +++ b/tools/level_editor/project_browser.vala @@ -371,7 +371,6 @@ public class ProjectFolderView : Gtk.Bin , typeof(uint64) // Column.SIZE , typeof(uint64) // Column.MTIME ); - _list_store.set_sort_column_id(Column.TYPE, Gtk.SortType.ASCENDING); _icon_view = new Gtk.IconView(); _icon_view.set_model(_list_store); @@ -726,6 +725,44 @@ public class ProjectFolderView : Gtk.Bin public class ProjectBrowser : Gtk.Bin { + public enum SortMode + { + NAME_AZ, + NAME_ZA, + TYPE_AZ, + TYPE_ZA, + SIZE_MIN_MAX, + SIZE_MAX_MIN, + LAST_MTIME, + FIRST_MTIME, + + COUNT; + + public string to_label() + { + switch (this) { + case NAME_AZ: + return "Name A-Z"; + case NAME_ZA: + return "Name Z-A"; + case TYPE_AZ: + return "Type A-Z"; + case TYPE_ZA: + return "Type Z-A"; + case SIZE_MIN_MAX: + return "Size min-Max"; + case SIZE_MAX_MIN: + return "Size Max-min"; + case LAST_MTIME: + return "Last Modified"; + case FIRST_MTIME: + return "First Modified"; + default: + return "Unknown"; + } + } + } + // Data public ProjectStore _project_store; public ThumbnailCache _thumbnail_cache; @@ -741,6 +778,12 @@ public class ProjectBrowser : Gtk.Bin public Gtk.Button _toggle_folder_view; public Gtk.Box _tree_view_content; public Gtk.Button _toggle_icon_view; + public Gtk.ListStore _folder_list_store; + public Gtk.TreeModelSort _folder_list_sort; + public SortMode _sort_mode; + public Gtk.Box _sort_items_box; + public Gtk.Popover _sort_items_popover; + public Gtk.MenuButton _sort_items; public Gtk.Box _folder_view_content; public Gtk.ScrolledWindow _scrolled_window; public Gtk.Paned _paned; @@ -952,6 +995,22 @@ public class ProjectBrowser : Gtk.Bin _tree_view_content.pack_start(_tree_view_control, false); _tree_view_content.pack_start(_scrolled_window, true, true); + // Setup sort menu button popover. + _sort_items_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0); + + Gtk.RadioButton? button = null; + for (int i = 0; i < SortMode.COUNT; ++i) + button = add_sort_item(button, (SortMode)i); + + _sort_items_box.show_all(); + _sort_items_popover = new Gtk.Popover(null); + _sort_items_popover.add(_sort_items_box); + _sort_items = new Gtk.MenuButton(); + _sort_items.add(new Gtk.Image.from_icon_name("list-sort", Gtk.IconSize.SMALL_TOOLBAR)); + _sort_items.get_style_context().add_class("flat"); + _sort_items.can_focus = false; + _sort_items.set_popover(_sort_items_popover); + bool _show_icon_view = true; _toggle_icon_view = new Gtk.Button.from_icon_name("browser-list-view", Gtk.IconSize.SMALL_TOOLBAR); _toggle_icon_view.get_style_context().add_class("flat"); @@ -982,6 +1041,7 @@ public class ProjectBrowser : Gtk.Bin var _folder_view_control = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0); _folder_view_control.pack_end(_toggle_icon_view, false, false); + _folder_view_control.pack_end(_sort_items, false, false); _folder_view_content = new Gtk.Box(Gtk.Orientation.VERTICAL, 0); _folder_view_content.pack_start(_folder_view_control, false); @@ -996,6 +1056,78 @@ public class ProjectBrowser : Gtk.Bin _hide_core_resources = true; + _folder_list_store = new Gtk.ListStore(ProjectStore.Column.COUNT + , typeof(string) // ProjectStore.Column.NAME + , typeof(string) // ProjectStore.Column.TYPE + , typeof(uint64) // ProjectStore.Column.SIZE + , typeof(uint64) // ProjectStore.Column.MTIME + ); + + _folder_list_sort = new Gtk.TreeModelSort.with_model(_folder_list_store); + _folder_list_sort.set_default_sort_func((model, iter_a, iter_b) => { + Value type_a; + Value type_b; + model.get_value(iter_a, ProjectStore.Column.TYPE, out type_a); + model.get_value(iter_b, ProjectStore.Column.TYPE, out type_b); + Value name_a; + Value name_b; + model.get_value(iter_a, ProjectStore.Column.NAME, out name_a); + model.get_value(iter_b, ProjectStore.Column.NAME, out name_b); + + // Folders are always on top. + if ((string)type_a == "" && (string)type_b != "") { + return -1; + } else if ((string)type_a != "" && (string)type_b == "") { + return 1; + } else if ((string)type_a == "" && (string)type_b == "") { + // Special folders always first. + if ((string)name_a == "..") + return -1; + else if ((string)name_b == "..") + return 1; + } + + switch (_sort_mode) { + case SortMode.NAME_AZ: + case SortMode.NAME_ZA: { + int cmp = strcmp((string)name_a, (string)name_b); + return _sort_mode == SortMode.NAME_AZ ? cmp : -cmp; + } + + case SortMode.TYPE_AZ: + case SortMode.TYPE_ZA: { + int cmp = strcmp((string)type_a, (string)type_b); + return _sort_mode == SortMode.TYPE_AZ ? cmp : -cmp; + + } + + case SortMode.SIZE_MIN_MAX: + case SortMode.SIZE_MAX_MIN: { + Value size_a; + Value size_b; + model.get_value(iter_a, ProjectStore.Column.SIZE, out size_a); + model.get_value(iter_b, ProjectStore.Column.SIZE, out size_b); + + int cmp = (uint64)size_a <= (uint64)size_b ? -1 : 1; + return _sort_mode == SortMode.SIZE_MIN_MAX ? cmp : -cmp; + } + + case SortMode.LAST_MTIME: + case SortMode.FIRST_MTIME: { + Value mtime_a; + Value mtime_b; + model.get_value(iter_a, ProjectStore.Column.MTIME, out mtime_a); + model.get_value(iter_b, ProjectStore.Column.MTIME, out mtime_b); + + int cmp = (uint64)mtime_a >= (uint64)mtime_b ? -1 : 1; + return _sort_mode == SortMode.LAST_MTIME ? cmp : -cmp; + } + + default: + return 0; + } + }); + // Actions. GLib.ActionEntry[] action_entries = { @@ -1183,12 +1315,13 @@ public class ProjectBrowser : Gtk.Bin private void update_folder_view() { + // Return if selection is empty. Gtk.TreeModel selected_model; Gtk.TreeIter selected_iter; if (!_tree_selection.get_selected(out selected_model, out selected_iter)) return; - // If there is a selected node. + _folder_list_store.clear(); _folder_view._list_store.clear(); // Get the selected node's type and name. @@ -1206,21 +1339,21 @@ public class ProjectBrowser : Gtk.Bin // Add parent folder. if (selected_name != "") { Gtk.TreeIter dummy; - _folder_view._list_store.insert_with_values(out dummy + _folder_list_store.insert_with_values(out dummy , -1 - , ProjectFolderView.Column.TYPE + , ProjectStore.Column.TYPE , "" - , ProjectFolderView.Column.NAME + , ProjectStore.Column.NAME , ".." - , ProjectFolderView.Column.SIZE + , ProjectStore.Column.SIZE , 0u - , ProjectFolderView.Column.MTIME + , ProjectStore.Column.MTIME , 0u , -1 ); } - // Fill the icon view list with paths matching the selected node's name. + // Fill the intermediate icon view list with paths matching the selected node's name. _project_store._list_store.foreach((model, path, iter) => { string type; string name; @@ -1262,15 +1395,15 @@ public class ProjectBrowser : Gtk.Bin // Add the path to the list. Gtk.TreeIter dummy; - _folder_view._list_store.insert_with_values(out dummy + _folder_list_store.insert_with_values(out dummy , -1 - , ProjectFolderView.Column.TYPE + , ProjectStore.Column.TYPE , type - , ProjectFolderView.Column.NAME + , ProjectStore.Column.NAME , name - , ProjectFolderView.Column.SIZE + , ProjectStore.Column.SIZE , size - , ProjectFolderView.Column.MTIME + , ProjectStore.Column.MTIME , mtime , -1 ); @@ -1303,21 +1436,53 @@ public class ProjectBrowser : Gtk.Bin // Add the path to the list. Gtk.TreeIter dummy; - _folder_view._list_store.insert_with_values(out dummy + _folder_list_store.insert_with_values(out dummy , -1 - , ProjectFolderView.Column.TYPE + , ProjectStore.Column.TYPE , type - , ProjectFolderView.Column.NAME + , ProjectStore.Column.NAME , name - , ProjectFolderView.Column.SIZE + , ProjectStore.Column.SIZE , size - , ProjectFolderView.Column.MTIME + , ProjectStore.Column.MTIME , mtime , -1 ); return false; }); } + + // Now, fill the actual icon view list with correctly sorted paths. + _folder_list_sort.foreach((model, path, iter) => { + string type; + string name; + uint64 size; + uint64 mtime; + model.get_value(iter, ProjectStore.Column.TYPE, out val); + type = (string)val; + model.get_value(iter, ProjectStore.Column.NAME, out val); + name = (string)val; + model.get_value(iter, ProjectStore.Column.SIZE, out val); + size = (uint64)val; + model.get_value(iter, ProjectStore.Column.MTIME, out val); + mtime = (uint64)val; + + // Add the path to the list. + Gtk.TreeIter dummy; + _folder_view._list_store.insert_with_values(out dummy + , -1 + , ProjectFolderView.Column.TYPE + , type + , ProjectFolderView.Column.NAME + , name + , ProjectFolderView.Column.SIZE + , size + , ProjectFolderView.Column.MTIME + , mtime + , -1 + ); + return false; + }); } public void select_project_root() @@ -1366,6 +1531,18 @@ public class ProjectBrowser : Gtk.Bin cell.set_property("text", ResourceId.path((string)type, basename)); } } + + private Gtk.RadioButton add_sort_item(Gtk.RadioButton? group, SortMode mode) + { + var button = new Gtk.RadioButton.with_label_from_widget(group, mode.to_label()); + button.toggled.connect(() => { + _sort_mode = mode; + update_folder_view(); + _sort_items_popover.popdown(); + }); + _sort_items_box.pack_start(button, false, false); + return button; + } } } /* namespace Crown */