From f49b9b82bc86a5cbed4bfc760be00768cd1585ec Mon Sep 17 00:00:00 2001 From: Jared Wahlstrand Date: Fri, 10 Nov 2023 12:23:29 -0500 Subject: [PATCH] support sorting in lists using `GtkCustomSorter` --- docs/src/manual/listtreeview.md | 17 ++++++++++ examples/README.md | 7 ++-- examples/listbox.jl | 8 ++--- examples/sortedlistview.jl | 59 +++++++++++++++++++++++++++++++++ src/lists.jl | 58 +++++++++++++++++++++++--------- test/gui/examples.jl | 6 ++++ 6 files changed, 131 insertions(+), 24 deletions(-) create mode 100644 examples/sortedlistview.jl diff --git a/docs/src/manual/listtreeview.md b/docs/src/manual/listtreeview.md index 3cbd2cc0..94b4b153 100644 --- a/docs/src/manual/listtreeview.md +++ b/docs/src/manual/listtreeview.md @@ -87,6 +87,23 @@ signal_connect(entry, :search_changed) do w end ``` +## Sorting + +It's also useful to sort the list. This can be done using another model wrapper, `GtkSortListModel`. + +Here is an example that sorts the list in reverse alphabetical order: +```julia +model = GtkStringList(string.(names(Gtk4))) + +ralpha_compare(item1, item2) = isless(item1.string, item2.string) ? 1 : 0 + +sorter = GtkCustomSorter(match) +sortedModel = GtkSortListModel(GListModel(model), sorter) +``` + +We create a `GtkCustomSorter` using a `compare` callback that takes two arguments `item1` and `item2` and returns -1 if `item1` is before `item2`, 0 if they are equal, and 1 if `item1` is after `item2`. We construct a `GtkSortListModel` using this filter and use it instead of the `GListModel` in the constructor +for `GtkSingleSelection`. + ## GtkTreeView The `GtkTreeView` was the widget used to display table-like or hierarchical data and trees in version 3 of GTK. It's also present in version 4 but is being deprecated in the C library in favor of the widgets discussed above. diff --git a/examples/README.md b/examples/README.md index 09bf37b7..8700d131 100644 --- a/examples/README.md +++ b/examples/README.md @@ -11,9 +11,10 @@ - `glarea.jl` shows how to use the `GtkGLArea` widget to draw using OpenGL. ## Lists -- `filteredlistview.jl` demonstrates `GtkListView` to show a huge list of strings, with a `GtkSearchEntry` to filter what's shown. -- `listbox.jl` demonstrates `GtkListBox` to show a huge list of strings. This widget is a little easier to use than `GtkListView`. -- `listview.jl` demonstrates a simple way of using `GtkListView`. +- `listview.jl` demonstrates using `GtkListView` to show a huge list of strings. +- `filteredlistview.jl` demonstrates `GtkListView` with a `GtkSearchEntry` to filter what's shown. +- `sortedlistview.jl` demonstrates `GtkListView` with a `GtkDropDown` widget to control how the list is sorted. +- `listbox.jl` demonstrates `GtkListBox` to show a huge list of strings. This widget is a little easier to use than `GtkListView` but may be less performant. ## Applications diff --git a/examples/listbox.jl b/examples/listbox.jl index e9a5a56d..683cb011 100644 --- a/examples/listbox.jl +++ b/examples/listbox.jl @@ -11,17 +11,15 @@ push!(win, box) listBox = GtkListBox() foreach(x-> push!(listBox, GtkLabel(x)), string.(names(Gtk4))) -function match(ptr) - row = convert(GtkListBoxRowLeaf, ptr) +function match(row) label = row.child - result = startswith(label.label, entry.text) - return result ? Cint(1) : Cint(0) + return startswith(label.label, entry.text) end Gtk4.set_filter_func(listBox, match) signal_connect(entry, :search_changed) do w - @idle_add Gtk4.G_.invalidate_filter(listBox) + @idle_add Gtk4.G_.invalidate_filter(listBox) end sw[] = listBox diff --git a/examples/sortedlistview.jl b/examples/sortedlistview.jl new file mode 100644 index 00000000..b89b50e7 --- /dev/null +++ b/examples/sortedlistview.jl @@ -0,0 +1,59 @@ +using Gtk4 + +win = GtkWindow("Listview demo with sort", 300, 800) +box = GtkBox(:v) +sort_control = GtkDropDown(["Alphabetical","Reverse alphabetical","# methods (most to least)"]) +sw = GtkScrolledWindow() +push!(box, sort_control) +push!(box, sw) +push!(win, box) + +modelValues = string.(names(Gtk4)) +model = GtkStringList(modelValues) + +function setup_cb(f, li) + set_child(li,GtkLabel("")) +end + +function bind_cb(f, li) + text = li[].string + label = get_child(li) + label.label = text +end + +factory = GtkSignalListItemFactory(setup_cb, bind_cb) + +# define sorting functions (should return -1 if item1 is before item2, 0 if they are equal, and 1 if item1 is after item2 +alpha_compare(item1, item2) = isless(item1.string, item2.string) ? -1 : 1 # alphabetical +ralpha_compare(item1, item2) = -alpha_compare(item1, item2) # reverse alphabetical +function methods_compare(item1, item2) + n1 = length(methods(eval(Symbol(item1.string)))) + n2 = length(methods(eval(Symbol(item2.string)))) + if n1 == n2 + return 0 + end + isless(n1,n2) ? 1 : -1 +end + +sorter = GtkCustomSorter(alpha_compare) +sortedModel = GtkSortListModel(GListModel(model), sorter) + +list = GtkListView(GtkSelectionModel(GtkSingleSelection(GListModel(sortedModel))), factory) +list.vexpand = true + +signal_connect(sort_control, "notify::selected") do w, others... + sel = Gtk4.selected_string(w) + c = if sel == "Alphabetical" + alpha_compare + elseif sel == "Reverse alphabetical" + ralpha_compare + elseif sel == "# methods (most to least)" + methods_compare + end + @idle_add begin + Gtk4.set_sort_func(sorter, c) + changed(sorter) + end +end + +sw[] = list diff --git a/src/lists.jl b/src/lists.jl index d5b8fb6c..8695d615 100644 --- a/src/lists.jl +++ b/src/lists.jl @@ -96,6 +96,15 @@ function GtkSignalListItemFactory(@nospecialize(setup_cb::Function), @nospeciali factory end +# the GI-generated version of this is currently broken +function CompareDataFunc(a, b, user_data) + item1 = convert(GObject, a, false) + item2 = convert(GObject, b, false) + f = user_data + ret = f(item1, item2) + convert(Cint, ret) +end + ## GtkListBox setindex!(lb::GtkListBox, w::GtkWidget, i::Integer) = (G_.insert(lb, w, i - 1); lb[i]) getindex(lb::GtkListBox, i::Integer) = G_.get_row_at_index(lb, i - 1) @@ -114,6 +123,17 @@ function set_filter_func(lb::GtkListBox, match::Function) ccall(("gtk_list_box_set_filter_func", libgtk4), Nothing, (Ptr{GObject}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), lb, cfunc, ref, deref) return nothing end +function set_filter_func(cf::GtkListBox, ::Nothing) + ccall(("gtk_list_box_set_filter_func", libgtk4), Nothing, (Ptr{GObject}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), cf, C_NULL, C_NULL, C_NULL) +end +function set_sort_func(cf::GtkListBox, compare::Function) + cfunc = @cfunction(CompareDataFunc, Cint, (Ptr{GObject}, Ptr{GObject}, Ref{Function})) + ref, deref = GLib.gc_ref_closure(compare) + ccall(("gtk_list_box_set_sort_func", libgtk4), Nothing, (Ptr{GObject}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), cf, cfunc, ref, deref) +end +function set_sort_func(cf::GtkListBox, ::Nothing) + ccall(("gtk_list_box_set_sort_func", libgtk4), Nothing, (Ptr{GObject}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), cf, C_NULL, C_NULL, C_NULL) +end ## GtkFlowBox setindex!(fb::GtkFlowBox, w::GtkWidget, i::Integer) = (G_.insert(fb, w, i - 1); fb[i]) @@ -131,7 +151,17 @@ function set_filter_func(fb::GtkFlowBox, match::Function) ccall(("gtk_flow_box_set_filter_func", libgtk4), Nothing, (Ptr{GObject}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), fb, cfunc, ref, deref) return nothing end - +function set_filter_func(cf::GtkFlowBox, ::Nothing) + ccall(("gtk_flow_box_set_filter_func", libgtk4), Nothing, (Ptr{GObject}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), cf, C_NULL, C_NULL, C_NULL) +end +function set_sort_func(cf::GtkFlowBox, compare::Function) + cfunc = @cfunction(CompareDataFunc, Cint, (Ptr{GObject}, Ptr{GObject}, Ref{Function})) + ref, deref = GLib.gc_ref_closure(compare) + ccall(("gtk_flow_box_set_sort_func", libgtk4), Nothing, (Ptr{GObject}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), cf, cfunc, ref, deref) +end +function set_sort_func(cf::GtkFlowBox, ::Nothing) + ccall(("gtk_flow_box_set_sort_func", libgtk4), Nothing, (Ptr{GObject}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), cf, C_NULL, C_NULL, C_NULL) +end ## GtkCustomFilter @@ -148,35 +178,31 @@ function set_filter_func(cf::GtkCustomFilter, match::Function) ccall(("gtk_custom_filter_set_filter_func", libgtk4), Nothing, (Ptr{GObject}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), cf, cfunc, ref, deref) return nothing end +function set_filter_func(cf::GtkCustomFilter, ::Nothing) + ccall(("gtk_custom_filter_set_filter_func", libgtk4), Nothing, (Ptr{GObject}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), cf, C_NULL, C_NULL, C_NULL) +end -changed(cf::GtkCustomFilter) = G_.changed(cf, _change) +changed(cf::GtkCustomFilter, _change = Gtk4.FilterChange_DIFFERENT) = G_.changed(cf, _change) ## GtkCustomSorter -# the GI-generated version of this is currently broken -function CompareDataFunc(a, b, user_data) - item1 = convert(GObject, a, false) - item2 = convert(GObject, b, false) - f = user_data - ret = f(item1, item2) - convert(Cint, ret) -end - function GtkCustomSorter(compare::Function) cfunc = @cfunction(CompareDataFunc, Cint, (Ptr{GObject}, Ptr{GObject}, Ref{Function})) - ref, deref = GLib.gc_ref_closure(match) + ref, deref = GLib.gc_ref_closure(compare) ret = ccall(("gtk_custom_sorter_new", libgtk4), Ptr{GObject}, (Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), cfunc, ref, deref) GtkCustomSorterLeaf(ret, true) end -function set_filter_func(cf::GtkCustomSorter, compare::Function) +function set_sort_func(cf::GtkCustomSorter, compare::Function) cfunc = @cfunction(CompareDataFunc, Cint, (Ptr{GObject}, Ptr{GObject}, Ref{Function})) - ref, deref = GLib.gc_ref_closure(match) + ref, deref = GLib.gc_ref_closure(compare) ccall(("gtk_custom_sorter_set_sort_func", libgtk4), Nothing, (Ptr{GObject}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), cf, cfunc, ref, deref) - return nothing +end +function set_sort_func(cf::GtkCustomSorter, ::Nothing) + ccall(("gtk_custom_sorter_set_sort_func", libgtk4), Nothing, (Ptr{GObject}, Ptr{Nothing}, Ptr{Nothing}, Ptr{Nothing}), cf, C_NULL, C_NULL, C_NULL) end -changed(cs::GtkCustomSorter) = G_.changed(cs, _change) +changed(cs::GtkCustomSorter, _change = Gtk4.FilterChange_DIFFERENT) = G_.changed(cs, _change) ## GtkExpressions diff --git a/test/gui/examples.jl b/test/gui/examples.jl index 49970911..68522136 100644 --- a/test/gui/examples.jl +++ b/test/gui/examples.jl @@ -44,6 +44,12 @@ end destroy(win) end +@testset "Sorted List View" begin + include(joinpath(@__DIR__, "..", "..", "examples", "sortedlistview.jl")) + @test Gtk4.G_.compare(sorter,Gtk4.GLib.G_.get_item(GListModel(model),0), Gtk4.GLib.G_.get_item(GListModel(model),1)) + destroy(win) +end + @testset "Listbox" begin include(joinpath(@__DIR__, "..", "..", "examples", "listbox.jl")) destroy(main_window)