allow ColumnView sorting via CustomSorter with CompareDataFunc
I'd like to implement a Gtk.ColumnView with sortable columns.
One way to do this is to create a Gtk.CustomSorter object. To build one, I need to provide a CompareDataFunc, which is defined like this:
public delegate int CompareDataFunc(nint a, nint b)
If the arguments a and b had type object, I could cast them to the appropriate type and perform my own comparison. But they have type nint. Shouldn't nint be object here? Or is there some way (e.g. using unsafe code) that I can cast an nint to an object?
Unfortunately I can't tell you the best way how to solve it as I'm not a GTK dev. So I don't know the alternatives.
At least in regard to the API it is correct as it is: https://docs.gtk.org/glib/callback.CompareDataFunc.html
Perhaps there could be some optimisation later on but this would need to be thought through carefully.
There recently was a discussion. But I don't know the outcome of it.
It would be great to see a C sample how to properly sort a column. From the docs I would think it should be a ColumnViewSorter. If I have sample I could take a look what is needed to make it work as this is the 2nd request for this. But I really need more information to keep things efficient for me. If I have to do all the GTK stuff myself I prefer to do other things with less overhead first.
As nint is just a pointer you are automatically on a lower level than the regular binding as you would need to create dotnet objects from pointers first. (This is what the binding is doing all the time to make it easy to use GTK. But if the API says "the parameter is a pointer" instead of "The parameter is an object" I can't do much currently.)
Perhaps there could be some convenience API here to make life easier but then I would need to know more details:
If you manage to get the CustomSorter to work and provide a minimal sample I can figure out how to make life easier for you in a later GirCore version.
But again without any support I'm currently not willing to invest my time into it. You can check out the milestones to see the roadmap. There are more than enough points for the coming months and probably years.
So if you care to fundamentally improve the column sorter experience for GirCore let's do this together. I can support with GirCore details and support finding a solution but your part would be the analysis of the GTK column view sorter possibilities so I get an overview on how to do sorting with different c samples. Then I can verify what is needed to get it working and start working on it or even support you getting your hands dirty in GirCore if you care.
Thanks for the quick replies.
I changed the title of this issue to be a bit more descriptive.
At the GTK level, if you want the columns of a Gtk.ColumnView to be sortable, essentially there are two options.
First, you can use a pre-existing sorter class such as Gtk.StringSorter or Gtk.NumericSorter. Those classes evaluate a Gtk.Expression to retrieve the values to compare. But that won't work in gircore at the moment: you can't make any sort of expression, e.g. a PropertyExpression or a ClosureExpression, because those expression classes only have constructors that take an nint.
The second option is to use a Gtk.CustomSorter. This is attractive because it's very general: it will simply call your function (a CompareDataFunc) with the data items to compare.
As I wrote above, in gircore currently CompareDataFunc has type "int CompareDataFunc(nint a, nint b)". I tried an experiment in which I wrote a CompareDataFunc that used unsafe C# code to cast 'a' and 'b' to the type (object *), but it failed. If there's some other way (even using unsafe code) to get an object from an nint that will actually work, I'd be curious to hear about it.
Sure, I could provide a minimal example of using a CustomSorter in C if that would be helpful.
Here's a demo in C that creates a GtkColumnView with two columns:
https://gist.github.com/medovina/0ad9da73d3c0972f058cc1b726a18eed
You can sort by clicking a column header. The sorting uses GtkCustomSorter objects.
In the functions sort_by_year() and sort_by_title(), you'll see that I use type casts to cast a gconstpointer to a specific object type. This is exactly what I'd like to be able to do in a CompareDataFunc in C#, but currently cannot.
Thank you! This looks pretty helpful. I need to dig into all the GTK hierarchies myself for this but it helps enormously.
From a first glance the custom sorter will not be completly straight forward in C#. But as there are several APIs which return a pointer instead of an object there could at least be some helper in the public API which makes things easier.
In C the pointer to the data can simply be casted because the pointer points directly to the instance struct. For C# the pointer points just to some native memory. Just having the pointer does not allows to have a C# object as dotnet has its own memory. GirCore is creating like "dotnet objects" which internally have a pointer to the native memory. So the native code knows nothing about the dotnet memory.
If you have some more spare time, could you provide a sample which involes a ColumnViewSorter, too? Or is this already covered by your solution? I understood from the docs that this ColumnViewSorter itself does the sorting. But from your code it looks like it could just be the connection between the sorters and the UI?
From the GTK docs:
The sorter returned by gtk_column_view_get_sorter() is a GtkColumnViewSorter. In column views, sorting can be configured by associating sorters with columns, and users can invert sort order by clicking on column headers. The API of GtkColumnViewSorter is designed to allow saving and restoring this configuration. If you are only interested in the primary sort column (i.e. the column where a sort indicator is shown in the header), then you can just look at GtkColumnViewSorter:primary-sort-column and GtkColumnViewSorter:primary-sort-order.
If I misinterpreted the docs and this is already included could you provide a sample which involves GTK Expressions? Expressions would be a pretty neat feature to have. But again I have zero knowledge here.
Actually the example I provided does use a ColumnViewSorter. The GtkColumnView API is a bit complicated, but basically it works like this:
- You create a Gtk.ColumnView with no model.
- You add various columns to the ColumnView, each of which has a Gtk.Sorter (such as a Gtk.CustomSorter or Gtk.StringSorter) attached to it.
- You call gtk_column_view_get_sorter, which returns a ColumnViewSorter that combines the actions of all the Gtk.Sorters that you have already attached to columns.
- You create a SortListModel from the ColumnViewSorter plus your Gio.ListModel (which holds the actual data you want to display).
- You wrap the SortListModel in a SelectionModel that implements the selection model you want (e.g. single or multi-selection), and possibly in a FilterListModel if you want to filter rows dynamically as the user types in a search box.
- Finally, you set the ColumnView's model to the wrapped model that you just created.
If it would be helpful, I could probably make a variant of my demo program that uses expressions when I can find some time for it.
Yes having an expression variant would be much appreciated as I don't have to do all the research.
Thanks for your work so far 👍
Above, I wrote that there are two ways to get the sorting to work: either using a premade sorter class such as StringSorter, or write a CustomSorter.
Here's another possible path. StringSorter and CustomSorter are both subclasses of Sorter:
https://docs.gtk.org/gtk4/class.Sorter.html
If I could write my own subclass of Sorter and override the Compare(Object, Object) method, then I could easily sort in any way I like. But that doesn't seem to be possible in Gir.Core today: the Compare() method is not virtual so I can't override it.
Yes currently you can't override methods because this effectively means overriding native functions.
If some native code calls an overridden function the pointer to that function must point to managed dotnet code.
E.g. if a user clicks a button no managed dotnet code is involved (except perhaps for events). If there is something overridden suddenly native code must call dotnet code and the native code needs to know where the managed function is. So this part is missing currently as native code has no info about locations of managed functions.
In regard to virtual method support: I think this is technically possible but will need to be worked out. There is a pretty old issue (#182) for this.
In regard to expressions I think I have all I need. I read a bit through the docs and as your hint about stringsorter needs expressions it should be not hard to adopt.
If you want to provide some copy and paste code it's still fine but not mandatory. I will take a look at this topic for 0.7.0
Thanks for the explanation. OK - I won't provide an expressions example in C right now, since it would take me some time and you said you can live without it. I appreciate your looking at this for 0.7.0. If you have more questions about the GTK classes for column sorting, just let me know.
@medovina I just saw that you created a project making it easy to use a column view, great!
I plan to create a eco system site on the homepage (see #950). If you have a nuget available and plan to support it for future releases and are willing to contribute to the documentation your project could be listed there. I would love to see an evolving eco system around GirCore.
Because I saw your project I thought again about the custom sorter. I said I wanted to provide some easy API to use. But those API would only be a helper around internal classes. For GirCore I created an Internal namespace for internal classes which are actually all public. So anyone can access the internal code and make things work if something is missing. The only drawback is that there are no API stability guaranties for the internal namespace. But even the public API is not guaranteed to be stable before 1.0 anyway.
I did not test the following code (just wrote it down manually) but this should do the trick for you:
...
var sorter = Gtk.CustomSorter.New(SortByYear);
...
private int SortByYear(IntPtr a, IntPtr b)
{
var movieA = (DemoMovie) GObject.Internal.InstanceWrapper.WrapHandle<GObject.Object>(a, false);
var movieB = (DemoMovie) GObject.Internal.InstanceWrapper.WrapHandle<GObject.Object>(b, false);
return movieA.Year - movieB.Year
}
You need to create a new class DemoMovie which derives from GObject.Object. If you create the instances in C# and afterwards the sorter is is called the GirCore instance cache knows about the pointers and can restore them as shown above.
Happy Coding!
P.S.: If you want to dig deeper into the project you can either use an IDE like Rider and just step into the nuget code as it get's decompiled automatically or simply build the project and see how things work internally.
@badcel Ah, you found my AGtk repository:
https://github.com/medovina/agtk
Yes, I did create the ColView class there because Gtk.ColumnView is a huge pain to use. AGtk.ColView is intended to be much easier.
I haven't yet published AGtk as a nuget package, but could do that pretty easily if there is interest. Sure, I'd be happy if you wanted to link to it from your ecosystem page. At the moment AGtk contains only the ColView class, but I might add more helper classes to it as I explore GTK 4 more (I only recently switched to it from GTK 3).
Thanks a lot for letting me know that I may actually be able to cast an IntPtr to an object using the black magic you outlined above. If possible, I'd like to add this to AGtk.ColView to enable sorting. I'll try this soon.
However, in your explanation above I don't completely understand what a DemoYear would be. If DemoMovie derives from GObject, then can't it have a C# property "Year" of type int without wrapping it in a DemoYear class? Or is a wrapper class necessary?
It would only be a custom subclass similar to ItemData in the GridView sample. I named it "DemoMovie" because in your demo C code you called it this way.
ItemData is providing data for the grid similar to the things you want to do.
In the C code there's a class DemoMovie, not DemoYear. Maybe when you wrote DemoYear above you meant DemoMovie?
To make a nice api for your code you would probably need to make the code support generics: in this way a user can sort by their own provided objects.
I updated my previous comment, it is now called "DemoMovie" instead of "DemoYear".
By the way, AGtk is now available in nuget:
https://www.nuget.org/packages/AGtk
Today I updated my class AGtk.ColView to allow sorting by clicking on a column header. It's available in AGtk 0.2.0, which I just published to nuget. @badcel, your hint above about using GObject.Internal.InstanceWrapper.WrapHandle was exactly what I needed. Thanks so much - it works great!