Skip to content

Commit

Permalink
support sorting in lists using GtkCustomSorter
Browse files Browse the repository at this point in the history
  • Loading branch information
jwahlstrand committed Nov 10, 2023
1 parent 850e055 commit f49b9b8
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 24 deletions.
17 changes: 17 additions & 0 deletions docs/src/manual/listtreeview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 4 additions & 3 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 3 additions & 5 deletions examples/listbox.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
59 changes: 59 additions & 0 deletions examples/sortedlistview.jl
Original file line number Diff line number Diff line change
@@ -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
58 changes: 42 additions & 16 deletions src/lists.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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])
Expand All @@ -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

Expand All @@ -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

Expand Down
6 changes: 6 additions & 0 deletions test/gui/examples.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit f49b9b8

Please sign in to comment.