Skip to content

Commit

Permalink
tools: project-browser: add sorting modes to folder view
Browse files Browse the repository at this point in the history
Closes: #204
  • Loading branch information
dbartolini committed Dec 4, 2024
1 parent 8d30b5e commit a7e4f3a
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 18 deletions.
5 changes: 5 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------------

Expand Down
213 changes: 195 additions & 18 deletions tools/level_editor/project_browser.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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);
Expand All @@ -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 == "<folder>" && (string)type_b != "<folder>") {
return -1;
} else if ((string)type_a != "<folder>" && (string)type_b == "<folder>") {
return 1;
} else if ((string)type_a == "<folder>" && (string)type_b == "<folder>") {
// 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 =
{
Expand Down Expand Up @@ -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.
Expand All @@ -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
, "<folder>"
, 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;
Expand Down Expand Up @@ -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
);
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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 */

0 comments on commit a7e4f3a

Please sign in to comment.