Gtk4.jl
Gtk4.jl copied to clipboard
Cannot use GListStore - Boxed Any Type
Gtk4 Utilizes a new storage model GListStore which takes in a GObject.
I cannot find any documentation on how to insert in any data. I was wondering if a boxed julia value could be introduced.
mutable struct MyWrappedType
x::Int
end
model = GLib.GListStore(MyWrappedType)
factory = GtkSignalListItemFactory()
signal_connect((f, li) -> set_child(li, GtkLabel("")), factory, "setup")
signal_connect((f, li) -> get_child(li).label = string(li[].x), factory, "bind")
push!(model, MyWrappedType(1)) #Cannot due for any data type atm
cv = GtkColumnView(model = GtkSelectionModel(GtkSingleSelection(Gtk4.GListModel(model))))
cvc = GtkColumnViewColumn("Test", factory)
append!(cv, cvc)
My thinking on implementation is to create a G_TYPE that wraps an integer that points back to an Any[] on the julia side of things.
Let me know if you can do something like this at the moment.
I've only used the new list views using GtkStringList
, which wraps strings as GObject
s. You can use this to store a list of keys of a Julia Dict
, and then use the key to look up Julia values in the "bind" callback. I'm not sure how to define a GObject
wrapper for Julia values without writing some C code... There's probably a way that I'm not seeing. If you come up with something that works, I'd be happy to discuss further.
Thanks! Ill try this :)
For others pursuing this deeper, I reccommend just creating a custom GListModel that is backed by a Julia Array for better memory & performance.
It looks like gtk is having more emphasis towards using their type system so it might be worth it to take a look at the define class macro as I am sure more stuff will use it in the future
It seems extreme to have to define a new GObject to properly use the new list views, but it looks like that's what you do in the Javascript bindings: https://rmnvgr.gitlab.io/gtk4-gjs-book/application/list-widgets/
Being able to do this would also allow defining custom widgets. I wonder if it could be done in pure Julia...
I built an efficient manager with what we have with the GString idea. On my computer it can create a 2x100k row columnview in ~.5 seconds. I can PR it if anyone is interested in it.
Below is the current version:
append!(cv::GtkColumnView, cvc::GtkColumnViewColumn) = G_.append_column(cv, cvc)
GtkNoSelection(model) = G_.NoSelection_new(model)
mutable struct GtkJuliaStore
items::Dict{Ptr{GObject}, Any}
store::GListStore
freeNames::Array{Ptr{GObject}}
GtkJuliaStore() = new(Dict{Ptr{GObject}, Any}(), GLib.GListStore(:GObject), Ptr{GObject}[])
GtkJuliaStore(items::AbstractArray) = (g = GtkJuliaStore(); append!(g, items))
GtkJuliaStore(items...) = GtkJuliaStore(collect(items))
Gtk4.GListModel(g::GtkJuliaStore) = Gtk4.GListModel(g.store)
Base.getindex(g::GtkJuliaStore, i::Integer) = g.items[unsafegetname(g.store, i)]
Base.setindex!(g::GtkJuliaStore, v, i::Integer) = g.items[unsafegetname(g.store, i)] = v
Base.keys(lm::GtkJuliaStore) = keys(g.store)
Base.eltype(::Type{GtkJuliaStore}) = Any
Base.iterate(g::GtkJuliaStore, i=0) = (i == length(g) ? nothing : (getindex(g, i + 1), i + 1))
Base.length(g::GtkJuliaStore) = length(g.store)
Base.empty!(g::GtkJuliaStore) = (empty!(g.items); empty!(g.store); empty!(freeNames))
Base.pushfirst!(g::GtkJuliaStore, item) = insert!(g, 1, item)
Base.append!(g::GtkJuliaStore, items) = foreach(x -> push!(g, x), items)
Base.getindex(g::GtkJuliaStore, i::GtkListItem) = g.items[ccall(("gtk_list_item_get_item", libgtk4), Ptr{GObject}, (Ptr{GObject},), i)]
Base.setindex!(g::GtkJuliaStore, v, i::GtkListItem) = g.items[ccall(("gtk_list_item_get_item", libgtk4), Ptr{GObject}, (Ptr{GObject},), i)] = v
unsafegetname(ls::GListStore, i) = ccall(("g_list_model_get_object", libgio), Ptr{GObject}, (Ptr{GObject}, UInt32), ls, i-1)
function nextname(g::GtkJuliaStore)
name = length(g.freeNames) == 0 ? Symbol("$(length(g))") : pop!(g.freeNames)
return ccall(("gtk_string_object_new", libgtk4), Ptr{GObject}, (Cstring,), name)
end
function Base.push!(g::GtkJuliaStore, item)
name = nextname(g)
ccall(("g_list_store_append", libgio), Nothing, (Ptr{GObject}, Ptr{GObject}), g.store, name)
g.items[name] = item
return nothing
end
function Base.insert!(g::GtkJuliaStore, i::Integer, item)
name = nextname(g)
ccall(("g_list_store_insert", libgio), Nothing, (Ptr{GObject}, UInt32, Ptr{GObject}), g.store, i-1, name)
g.items[name] = item
return nothing
end
function Base.deleteat!(g::GtkJuliaStore, i::Integer)
name = unsafegetname(g.store, i)
push!(freeNames, name)
delete!(g.items, name)
ccall(("g_list_store_remove", libgio), Nothing, (Ptr{GObject}, UInt32), g.store, i-1)
return nothing
end
end
function GtkJuliaColumnViewColumn(store::GtkJuliaStore, name::String, @nospecialize(init_child::Function), @nospecialize(update_child::Function))
factory = GtkSignalListItemFactory()
signal_connect((f, li) -> set_child(li, init_child()), factory, "setup")
signal_connect((f, li) -> update_child(get_child(li), store[li]), factory, "bind")
return GtkColumnViewColumn(name, factory)
end
mutable struct MyTestStruct
num::Integer
name::String
end
##Testing
win = GtkWindow()
sw = GtkScrolledWindow()
win[] = sw
store = GtkJuliaStore()
@time for w in 1:100000
push!(store, MyTestStruct(w, "Number:$w"))
end
name_c = GtkJuliaColumnViewColumn(store, "Name", () -> GtkLabel(""), (c, i) -> c.label = i.name)
num_c = GtkJuliaColumnViewColumn(store, "Number", () -> GtkLabel(""), (c, i) -> c.label = string(i.num))
cv = GtkColumnView(model = GtkSelectionModel(GtkSingleSelection(Gtk4.GListModel(store))))
append!(cv, name_c)
append!(cv, num_c)
sw[] = cv
What I had in mind was to use GtkStringList
as the model and use the string items to look up items from a Julia dictionary in the bind
callback, like this (adapted from the listview.jl example):
using Gtk4, Gtk4.GLib
win = GtkWindow("Listview demo")
win[] = sw = GtkScrolledWindow()
struct MyTestStruct
num::Int
name::String
end
dict = Dict{String,MyTestStruct}()
for w=1:100000
dict[string(w)] = MyTestStruct(w,"Number:$w")
end
model = GtkStringList(string.(1:100000))
factory = GtkSignalListItemFactory()
setup_cb(f, li) = set_child(li,GtkLabel(""))
function bind_cb(f, li)
text = li[].string
label = get_child(li)
label.label = string(dict[text].name)
end
signal_connect(setup_cb, factory, "setup")
signal_connect(bind_cb, factory, "bind")
sw[] = GtkListView(GtkSelectionModel(GtkSingleSelection(GLib.GListModel(model))), factory)
Looking at the constructor for GtkStringList
I'm concerned about the list of strings being garbage collected. But this seems to work.
I think the API for GtkListView
, GtkColumnView
, etc. should be wrapped in a nicer way and I like how you created a constructor that takes "setup" and "bind" functions.
Ah I see what you meant
I think my version my be safe from collecting since I am using handles to the GString objects as the keys.
I use unsafe functions to bypass all collecting so its not boxing/unboxing many times between calls.
I think the GtkStringStore is also immutable (or atleast not meant to be changed often due to the internal structure) which is why I used the GtkListStore
Yeah, I think GtkStringList is mostly meant to support simple situations like GtkDropDown. For optimal efficiency it seems like you're supposed to create your own GListModel that produces GObjects on command, rather than creating a ton of GObjects ahead of time. I think the purpose of the new list views is to avoid having to copy your data into a special model data structure (like GtkListStore or GtkTreeStore), which is pretty redundant. Instead you fetch the data needed to render the row in a callback. It sounds nice but they want the data in the form of a GObject...
I have also been experimenting with building powerful observables.
on_update_signal_name(::GtkButton) = "clicked"
on_update_signal_name(::GtkComboBoxText) = "changed"
on_update_signal_name(::GtkAdjustment) = "value-changed"
on_update_signal_name(::GtkEntry) = "activate"
Observables.on(@nospecialize(cb::Function), w::GtkWidget) = signal_connect(cb, w, on_update_signal_name(w))
Observables.connect!(w::GtkWidget, o::AbstractObservable) = on(v -> w[] = v, o)
Base.getindex(g::GtkEntry, ::Type{String}) = g.text
Base.getindex(g::GtkLabel, ::Type{String}) = g.label
Base.getindex(g::GtkComboBoxText, ::Type{String}) = Gtk4.active_text(g)
Base.getindex(g::GtkAdjustment, ::Type{Number}) = Gtk4.value(g)
Base.getindex(g::Union{GtkEntry, GtkLabel, GtkComboBoxText}, t::Type = String) = parse(g, t)
Base.setindex!(g::GtkLabel, v) = g.label = string(v)
Base.setindex!(g::GtkEntry, v) = g.text = string(v)
Base.setindex!(g::GtkAdjustment, v) = Gtk4.value(g, v)
function Gtk4.set_gtk_property!(o::GObject, name::String, value::AbstractObservable)
set_gtk_property!(o, name, value[])
on(v -> set_gtk_property!(o, name, v), value)
end
function Observables.ObservablePair(w::GtkWidget, o::AbstractObservable{T}) where T
done = Ref(false)
on(w) do w
if !done[]
done[] = true
o[] = w[T]
done[] = false
end
end
on(o) do val
if !done[]
done[] = true
w[] = val
done[] = false
end
end
end
What this snippet allows you to do is something like this....
w = GtkWindow()
v = GtkEntry()
w[] = v
o = Observable(3)
on(v -> println("Changed:$v"), o)
connect!(w, o) #Update the widget entry when the observable changes (not when widget changes)
Observables.ObservablePair(v, o) #Update the the observable if the entry changes, update the entry if the observable changes.
You can also do stuff on non widgets since I overrided the set_gtk_prop.
my_random_g_object.prop = o #Will update the property when o is updated
Not sure if this is something that others may want to use in the future / something for this library, just something that may be good discussion
Thanks, PR's are generally welcome. My goal for this package is to keep the layers of stuff on top of GObject introspection relatively light. If we can make the GtkListViews easier to use in Julia without defining new types, that would be ideal IMO. Other packages could do more sophisticated stuff on top of this one.
In Gtk.jl, Observable support is in GtkObservables.jl, and I was thinking of keeping the same separation here to keep dependencies to a minimum (I'm not thrilled with the Graphics.jl dependency inherited from Gtk.jl). I have ported GtkObservables.jl to Gtk4 to test Gtk4.jl, but haven't used it much myself yet.