GtkColumnView

One thing that I left unfinished in my recent series on list views and models in GTK 4 is a detailed look at GtkColumnView. This will easily be the most complicated part of the series. We are entering into the heartland of GtkTreeView—anything aiming to replace most its features will be a complicated beast.

Overview

As we did for GtkListView, we’ll start with a high-level overview and with a picture.

If you look back at the listview picture, you’ll remember that we use a list item factory to create a widget for each item in our model that needs to be displayed.

In a column view, we need multiple widgets for each item—one for each column. The way we do this is by giving each column its own list item factory. Whenever we need to display a new item, we combine the widgets from each columns factory into a row for the new item.

Internally, the column view is actually using a list view to hold the rows. This is nice in that all the things I explained in the previous post about item reuse and about how to use list item factories apply just the same.

Of course, some things are different. For example, the column view has organize the size allocation so that the widgets in all rows line up to form proper columns.

Note: Just like GtkListView, the colum view only creates widgets for the segment of the model that is currently in view, so it shares the vertical scalability. The same is not true in the horizontal direction—every row is fully populated with a widget for each column, even if they are out of view to the left or right. So if you add lots of columns, things will get slow.

Titles, and other complications

The column objects contain other data as well, such as titles. The column view is using those to display a header for each column. If the column view is marked as reorderable, you can rearrange the columns by drag-and-drop of the the header widgets. And if the columns are marked as resizable, you can drag the border between two columns to resize them.

If you payed attention, you may now wonder how this resizing goes together with the fact that the cells in the rows can be arbitrary widgets which expect to have at least their minimum size available for drawing their content. The answer is that we are using another new feature of the GTK 4 rendering machinery: Widgets can control how drawing outside their boundaries (by child widgets) is treated, with

 gtk_widget_set_overflow (cell, GTK_OVERFLOW_HIDDEN)

Sorting, selections, and the quest for treeview parity

Since we want to match GtkTreeview, feature-wise, we are not done yet. Another thing that users like to do in tree views is to click on headers, to sort the content by that column. GtkColumnView headers allow this, too.

You may remember from the last post that sorting is done by wrapping your data in a GtkSortListModel, and giving it a suitable sorter object. Since we want to have a different sort order, depending on what column header you clicked, we give each column its own sorter, which you can set with

gtk_column_view_column_set_sorter (column, sorter)

But how do we get the right sorter from the column you just clicked, and attach it to the sort model? Keep in mind that the sort model is not going to be the outmost model that we pass to the column view, since that is always a selection model, so the column view can’t just switch the sorter on the sort list model on its own.

The solution we’ve come up with is to make the column view provide a sorter that internally uses the column sorters, with

gtk_column_view_get_sorter (view)

You can give this sorter to your sort model once, when you set up your model, and then things will automagically updates when the user clicks on column headers to activate different column sorters.

This sounds complicated, but it works surprisingly well. A nice benefit of this approach is that we can actually sort by more than one column at a time—since we have all the column sorters available, and we know which one you clicked last.

Selection handling is easy, by comparison. It works just the same as it does in GtkListView.

Summary

GtkColumnView is a complex widget, but I hope this series of posts will  make it a little easier to start using it.

On list models

In the previous post, I promised to take a deeper look at list models and what GTK 4 offers in this area. Lets start be taking a look at the GListModel interface:

struct _GListModelInterface
{
  GTypeInterface g_iface;

  GType    (* get_item_type) (GListModel *list);
  guint    (* get_n_items)   (GListModel *list);
  gpointer (* get_item)      (GListModel *list,
                              guint       position);
};

An important part of implementing the interface is that you need to emit
the ::items-changed signal when required, using the helper function that
GLib has for this purpose:

void g_list_model_items_changed (GListModel *list,
                                 guint       position,
                                 guint       removed,
                                 guint       added)

A few things to note about this interface:

  • It is very minimal; which makes it easy to implement
  • The API is in terms of positions and only deals with changes in list membership—keeping track of changes to the items themselves is up to you

A list model zoo

GTK ships a sizable collection of list model implementations. Under closer inspection, they fall into several distinct groups.

List model construction kit

The first group is what could be called the list model construction kit: models that let you build new models by modifying or combining models that you already have.

The first model in this group, GtkSliceListModel, take a slice of an existing model, given by an offset and a size, and makes a new model containing just those items. This is useful if you want to present a big list in a paged view—the forward and back buttons will simply increase or decrease the offset by the size. A slice model can also be used to incrementally populate a list, by making the slice bigger over time. GTK is using this technique in some places.

The next model in this group, GtkFlattenListModel, takes several list models and combines them into one. Since this is all about list models, the models to combine are handed to the flatten model in the form of a list model of list models. This is useful whenever you need to combine data from multiple sources, as for example GTK does for the paper sizes in the print dialog.

Paper size list in print dialog
A flattened list

Note that the original models continue to exist behind the flatten model, and their updates will be propagated by the flatten list model, as expected.

Sometimes, you have your data in a list model, but it is not quite in the right form. In this case, you can use a GtkMapListModel replace every item in the original model with different one.

Concrete models

GTK and its dependencies include a number of concrete models for the types of data that we deal with ourselves.

The first example here are Pango objects that are implementing the list model interface for their data: PangoFontMap is a list model of PangoFontFamily objects, and PangoFontFamily is a list model of PangoFontFace objects. The font chooser is using these models.

font chooser dialog
A Pango list model

The next example are the GtkDirectoryList and GtkBookmarkList objects that will be used in the file chooser to represent directory contents and bookmarks. An interesting detail about these is that they both need to do IO to populate their content, and they do it asynchronously to avoid blocking the UI for extended times.

The last model in this group is a little less concrete: GtkStringList is a simple list model wrapper around the all-too-common string arrays. An example where this kind of list model will be frequently used is with GtkDropDown. This is so common that GtkDropDown has a convenience constructor that takes a string array and creates the GtkStringList for you:

GtkWidget *
    gtk_drop_down_new_from_strings (const char * const * strings)

Selection

The next group of models extends GListModel with a new interface: GtkSelectionModel. For each item in the underlying model, a GtkSelectionModel maintains the information whether it is selected or not.

We won’t discuss the interface in detail, since  it is unlikely that you need to implement it yourself, but the most important points are:

gboolean gtk_selection_model_is_selected (GtkSelectionModel *model)
                                          guint              pos)
GtkBitset *
       gtk_selection_model_get_selection (GtkSelectionModel *model)

So you can get the selection information for an individual item, or as a whole, in the form of a bitset. Of course, there is also a ::selection-changed signal that works in a very similar way to the ::items-changed signal of GListModel.

GTK has three GtkSelectionModel implementations: GtkSingleSelection, GtkMultiSelection and GtkNoSelection, which differ in the number of items that can be simultaneously selected (1, many, or 0).

The GtkGridView colors demo shows a multi-selection in action, with rubberbanding:

 

You are very likely to encounter selection models when working with GTK’s new list widgets, since they all expect their models to be selection models.

The big ones

The last group of models I want to mention are the ones doing the typical operations you expect in lists: filtering and sorting. The models are GtkFilterListModel and GtkSortListModel. The both use auxiliary objects to implement their operations: GtkFilter and GtkSorter. Both of these have subclasses to handle common cases: sorting and filtering strings or numbers, or using callbacks.

We have spent considerable effort on these two models in the run-up to GTK 3.99, and made them do their work incrementally, to avoid blocking the UI for extended times when working with big models.

The GtkListView words demo show interactive filtering of a list of 500.000 words:

The leftovers

There are some more list model implementations in GTK that do not fit neatly in any of the above groups, such as GtkTreeListModel, GtkSelectionFilterModel or GtkShortcutController. I’ll skip these today.

Models everywhere

I’ll finish with a brief list of GTK APIs that return list models:

  • gdk_display_get_monitors
  • gtk_widget_observe_children
  • gtk_widget_observe_controllers
  • gtk_constraint_layout_observe_constraints
  • gtk_constraint_layout_observe_guides
  • gtk_file_chooser_get_files
  • gtk_drop_down_get_model
  • gtk_list_view_get_model
  • gtk_grid_view_get_model
  • gtk_column_view_get_model
  • gtk_column_view_get_columns
  • gtk_window_get_toplevels
  • gtk_assistant_get_pages
  • gtk_stack_get_pages
  • gtk_notebook_get_pages

In summary, list models are everywhere in GTK 4. They are flexible and fun, you should use them!

A primer on GtkListView

Some of the early adopters of GTK4 have pointed out that the new list widgets are not the easiest to learn. In particular,  GtkExpression and GtkBuilderListItemFactory are hard to wrap your head around. That is not too surprising – a full list widget, with columns, and selections and sorting, and tree structure, etc is a complicated beast.

But lets see if we can unpack things one-by-one, and make it all more understandable.

Overview

Lets start with a high-level view of the relevant components and their interactions: the model, the list item factory and the list view.

They are the three things that occur when we create a list view:

view = gtk_list_view_new (model, factory);

The models we use are GListModels. These always contain GObjects, so you will have to provide your data in the form of objects. This is a first notable difference from GtkTreeview, which is using GtkTreeModels directly containing basic types.

For some simple cases, GTK provides ready-made models, such as GtkStringList. But in general, you will have to make your own model. Thankfully, GListModel is a much simpler interface than GtkTreeModel, so this is not too hard.

The responsibility of the list item factory is to produce a row widget and connect it to an item in the model, whenever the list view needs it.

The list view will create a few more rows than it needs to fill its visible area, to get a better estimate for the size of the scrollbars, and in order to have some “buffer”for when you decide to scroll the view.

Once you do scroll, we don’t necessarily need to ask the factory to make more rows —we can recycle the rows that are being scrolled out of view on the other end.

Thankfully, all of this happens automatically behind the scenes. All you have to do is provide a list item factory.

Creating items

GTK offers two different approaches for creating items. You can either do it manually, with GtkSignalListItemFactory, or you can instantiate your row widgets from a ui file, using GtkBuilderListItemFactory.

The manual approach is easier to understand, so lets look at that first.

factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup", setup_listitem_cb, NULL);
g_signal_connect (factory, "bind", bind_listitem_cb, NULL);

The “setup” signal is emitted when the factory needs to create a new row widget, “bind” is emitted when a row widget needs to be connected to an item from the model.

Both of these signals take a GtkListItem as argument, which is a wrapper object that lets you get at the model item (with gtk_list_item_get_item()) and also lets you deliver the new row widget (with gtk_list_item_set_child()).

static void
setup_listitem_cb (GtkListItemFactory *factory,
                   GtkListItem        *list_item)
{
  GtkWidget *label = gtk_label_new ("");
  gtk_list_item_set_child (list_item, label);
}

Typically, your rows will be more complicated than a single label. You can create complex widgets and group them in containers, as needed.

static void
bind_listitem_cb (GtkListItemFactory *factory,
                  GtkListItem        *list_item)
{
  GtkWidget *label;
  MyObject *obj;

  label = gtk_list_item_get_child (list_item);
  obj = gtk_list_item_get_item (list_item);
  gtk_label_set_label (GTK_LABEL (label),
                       my_object_get_string (obj));
}

If your “bind” handler connects to signals on the item or does other things that require cleanup, you can use the “unbind” signal to do that cleanup. The “setup” signal has a similar counterpart called “teardown”.

The builder way

Our “setup” handler is basically a recipe for creating a small widget hierarchy. GTK has a more declarative way of doing this: GtkBuilder ui files. That is the way GtkBuilderListItemFactory works: you give it a ui file, and it instantiates that ui file whenever it needs to create a row.

ui = "<interface><template class="GtkListItem">...";
bytes = g_bytes_new_static (ui, strlen (ui));
gtk_builder_list_item_factory_new_from_bytes (scope, bytes);

You might now be wondering: Wait a minute, you are parsing an xml file for each of the thousands of items in my model, isn’t that expensive?

There are two answers to this concern:

  • We are not literally parsing the xml for each item; we parse it once, store the callback sequence, and replay it later.
  • The  most expensive part of GtkBuilder is actually not the xml parsing, but the creation of objects; recycling rows helps for this.

It is relatively easy to see how a ui file can replace the “setup” handler, but what about “bind”? In the example above, the bind callback was getting properties of the item (the MyObject:string property) and using their values to set properties of the widgets (the GtkLabel:label property). In other words, the “bind” handler is doing property bindings. For simplicity, we just created a one-time binding here, but we could have just as well used g_object_bind_property() to create a lasting binding.

GtkBuilder ui files can set up property bindings between objects, but there is one problem: The model item is not ‘present’ in the ui file, it only gets associated with the row widget later on, at “bind” time.

This is where GtkExpression comes in. At its core, GtkExpression is a way to describe bindings between objects that don’t necessarily exist yet.  In our case, what we want to achieve is:

label->label = list_item->item->string

Unfortunately, this gets a little more clumsy when it is turned into xml as part of our ui file:

<interface>
  <template class="GtkListItem">
    <property name="child">
      <object class="GtkLabel">
        <binding name="label">
          <lookup name="string">
            <lookup name="item">GtkListItem</lookup>
          </lookup>
        </binding>
      </object>
    </property>
  </template>
</interface>

Remember that the classname (GtkListItem) in a ui template is used as the “this” pointer referring to the object that is being instantiated.

So, <lookup name=”item”>GtkListItem</lookup> means: the value of the “item” property of the list item that is created. <lookup name=”string”> means: the “string” property of that object. And <binding name=”label”> says to set the “label” property of the widget to the value of that property.

Due to the way expressions work, all of this will be reevaluated when the list item factory sets the “item” property on the list item to a new value, which is exactly what we need to make recycling of row widgets work.

Expert level confusion

GtkBuilderListItemFactory and GtkExpression can get really confusing when you nest things—list item factories can be constructed in ui files themselves, and they can get given their own UI files as a property, so you end up with constructions like

<object class="GtkListView">
  <property name="factory">
    <object class="GtkBuilderListItemFactory">
      <property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
  <property name="child">
  ...
]]>
      </property>
    </object>
  </property>
...

This can be confusing even to GTK experts.

My advice would be avoid this when starting out with GtkListView—you don’t have to create the list item factory in the UI file, and you can specify its UI template as a resource instead of embedding it directly.

Going deeper

Everything we’ve described here today applies to grid views as well, with minimal adjustments.

So far, we’ve focused on the view side of things. There’s a lot to say about models too.

And then there is the column view, which deserves a post of its own.

GTK 3.99.1

It has been a month since we released GTK 3.99, high time for another snapshot. Here it is: https://download.gnome.org/sources/gtk/3.99/gtk-3.99.1.tar.xz

This snapshot is focused on polish and completion.

Loose ends

We’ve tied up a number of loose ends in our APIs.

The most user-visible change was probably the simplification of the button class hierarchy. Instead of deriving GtkCheckButton from GtkToggleButton, they are now two independent widgets, and they both can be grouped to be mutually exclusive (aka as “radio group”). In this new setup, GtkRadioButton is not really needed anymore, and therefore gone.

The API of our new list widgets (GtkListView and GtkGridView) has seen some small tweaks as well. We are now explicitly requiring the models to be of type GtkSelectionModel, to make it clear that the widgets handle selections.  And we’ve gotten rid of the extra “with_factory” constructors, and just take a nullable factory argument in new(), leaving us with

GtkWidget * gtk_list_view_new (GtkSelectionModel  *model,
                               GtkListItemFactory *factory);

As more of an  API cleanup, we’ve dropped all the defines for CSS style classes – which style classes our widgets support is defined in their documentation, and these extra defines were not really well-defined or useful.

Our theme is now rounding the corners of the frame that is drawn by GtkFrame widgets. This required us to make frames clip their children – not really an API change, but a behavior change that is worth mentioning.

More demos

A lot of effort has gone into gtk4-demo over the past month.

We have modernized the source code highlighting. We are now using the highlight commandline utility. Among other things, this allows us to have syntax highlighting for xml and css, as well as dark theme support.

Highlighting XML in a dark theme
Highlighting

The list of demos has better filtering and a better appearance. The new look is one of a few predefined list styles that Adwaita supports now: rich list, navigation sidebar and data table.

 

Rich List list style
Rich List
Navigation Sidebar list style
Navigation Sidebar
Data Table list style
Data Table

We have dropped a few outdated demos from gtk4-demo, and polished many of the existing ones. Here is how our Drag-and-Drop demo looks now:

Drag-and-Drop demo
Drag-and-Drop demo

A number of new demos have been added as well. Here is the new demo for layout managers and transformations:

Performance, and other bugs

Many bugs have been squashed; thanks to our eager testers and bug reporters.

A long-standing issue that we finally tracked down recently caused our GL renderer to get clipping wrong in the presence of non-trivial projective transformations. This has now been corrected (and the result can be seen in the transformation demo above).

As part of the highlighting improvements that have been mentioned before, gtk_text_view_buffer_insert_markup() has been made much faster. This improvement only happened because the highlight utility can produce Pango markup. Kudos to whoever implemented that!

Another performance problem that we’ve addressed is the loading time of the font chooser dialog on systems with many fonts. We are now populating the font list incrementally. Apart from this change, the investigation has led to performance improvements in fontconfig and Pango that will benefit any user of those libraries.

Can I port already?

The answer is: yes!

Now is a great time to take a look at GTK4, start porting your app, and give us feedback on our APIs, new and old. We are also eager to see what ideas you have for using GTK4 in unexpected ways – the few demos we’ve shown above can maybe give you some inspiration.

Whats next?

We are looking to land the at-spi backend for our new accessibility interfaces soon; it should be included in the next GTK snapshot.

GTK 3.99

This week, we’re releasing GTK 3.99, which can only mean one thing: GTK4 is getting really close!

Back in February, when 3.98 was released, we outlined the features that we wanted to land before making a feature-complete 3.99 release. This was the list:

  • Event controllers for keyboard shortcuts
  • Movable popovers
  • Row-recycling list and grid views
  • Revamped accessibility infrastructure
  • Animation API
How have we done?

We’ve dropped animation API from our 4.0 blocker list, since  it requires more extensive internal restructuring, and we can’t complete it in time. But all the other features have found their way into the various 3.98.x snapshots, with the accessibility infrastructure being the last hold-out that landed very recently.

Some of the features have already been covered here, for example Movable popovers and  Scalable Lists. The others will hopefully receive some detailed review here in the future. Until then, you can look at Emmanuele’s GUADEC talk if you are curious about the new accessibility infrastructure.

What else is new ?

One area I want to highlight is the amount of work that has gone into fleshing out the new scalable list infrastructure. Our filter and sort models do their work incrementally now, so the UI can remain responsive while large lists are getting filtered or sorted in the background.

A new macOS GDK backend has been merged. It still has some rough corners that we hope to smooth over between now and the 4.0 release.

And many small regressions have been fixed, from spinbutton sizing to treeview cell editing to autoscrolling to Inspector navigation to slightly misrendered shadows.

Can I port yet?

GTK 3.99 is the perfect time to take a first look at porting applications.

We are very thankful to the early adopters who have braved the 3.96 or 3.98 snapshots with trial ports and provided us with valuable feedback. With so many changes, it is inevitable that we’ve gotten things wrong in the API, and getting that feedback while we can still address things will really help us. Telling us about things we forgot to cover in the docs, missing examples or gaps in the migration guide is also very much appreciated.

We are aware that some porting efforts will be stopped short by indirect dependencies on GTK 3. For example, if you are using a webkit webview or GtkSourceView or vte, you might find it difficult to try out GTK 4.

Thankfully, porting efforts are already well underway for some of these libraries. Other libraries, such as libgweather will need some work to separate their core functionality from the GTK 3 dependency.

Can I help?

As mentioned in the previous section any feedback on new APIs, documentation and the porting guide is very welcome and helpful.

There are many other areas where we could use help. If you are familiar with OS X APIs, you could make a real difference in completing the macOS backend.

We have also started to integrate an ANGLE-based GL renderer, but our shaders need to be made to work with EGL before we can take advantage of it. Help with that would be greatly appreciated.

Whats next?

We are committed to releasing GTK 4 before the end of year. Between now and then, we are doing more work on accessibility backends, improving the macOS backend, writing documentation, and examples.

More on lists in GTK 4

The previous post introduced the overall list view framework that was introduced in the 3.98.5 release, and showed some examples. In this post, we’ll dive deeper into some technical areas, and look at some related topics.

Trees

One important feature of GtkTreeView is that it can display trees. That is what it is named for, after all. GtkListView puts the emphasis on plain lists, and makes handling those easier. In particular the GListModel api is much simpler than GtkTreeModel—which is why there have been comparatively few custom tree model implementations, but GTK 4 already has a whole zoo of list models.

But we still need to display trees, sometimes. There are some complexities around this, but we’ve figured out a way to do it.

Models

The first ingredient that we need is a model. Since GListModel represents a linear list of items, we have to work a bit harder to make it handle trees.

GtkTreeListModel does this by adding a way to expand items, by creating new child list models on demand. The items in a GtkTreeListModel are instances of GtkTreeListRow which wrap the actual items in your model, and there are functions such as gtk_tree_list_row_get_children() to get the items of the child model and gtk_tree_list_row_get_item() to get the original item. GtkTreeListRow has an :expanded property that is used to keep track of whether the children are currently shows or not.

At the core of  a tree list model is the GtkTreeListModelCreateModelFunc which takes an item from your list and returns a new list model containing items of the same type that should be the children of the given item in the tree.

Here is an example for a tree list model of GSettings objects. The function enumerates the child settings of a given GSettings object, and returns a new list model for them:

static GListModel *
create_settings_model (gpointer item,
                       gpointer unused)
{
  GSettings *settings = item;
  char **schemas;
  GListStore *result;
  guint i;

  schemas = g_settings_list_children (settings);

  if (schemas == NULL || schemas[0] == NULL)
    {
      g_free (schemas);
      return NULL;
    }

  result = g_list_store_new (G_TYPE_SETTINGS);
  for (i = 0; schemas[i] != NULL; i++)
    {
      GSettings *child = g_settings_get_child (settings, schemas[i]);
      g_list_store_append (result, child);
      g_object_unref (child);
    }

  g_strfreev (schemas);

  return G_LIST_MODEL (result);
}

Expanders

The next ingredient that we need is a widget that displays the expander arrow that users can click to control the :expanded property. This is provided by the GtkTreeExpander widget. Just as the GtkTreeListRow items wrap the underlying items in your model, you use GtkTreeExpander widgets to wrap the widgets used to display your items.

Here is how the tree expanders look in action, for our GSettings example:

The full example can be found here.

Sorting

One last topic to touch on before we leave trees is sorting. Lists often have more than one way of being sorted: a-z, z-a, ignoring case, etc, etc. Column views support this by letting you associate sorters with columns, which the user can then activate by clicking on the column headers. The API for this is

gtk_column_view_column_set_sorter (column, sorter)

When sorting trees, you typically want the sort order to apply to the items at a given level in the tree, but not across levels, since that would scramble the tree structure. GTK supports this with the GtkTreeListRowSorter, which wraps an existing sorter and makes it respect the tree structure:

sorter = gtk_column_view_get_sorter (view);
tree_sorter = gtk_tree_list_row_sorter_new (sorter);
sort_model = gtk_sort_list_model_new (tree_model,           
                                      tree_sorter);
gtk_column_view_set_model (view, sort_model);

In conclusion, trees have been de-emphasized a bit in the new list widgets, and they add quite a bit of complication to the machinery, but they are fully supported, in list views as well as in column views:

Combo boxes

One of the more problematic areas where cell renderers are used is our single-selection control: GtkComboBox.  This was never a great fit, in particular in combination with nested menus. So, we were eager to try the new list view machinery on a replacement for GtkComboBox.

From the design side, there has also been a longstanding wishlist item to do better for combo boxes, as can be seen in this mockup from 2015:

Five years later, we finally have a replacement widget. It is called GtkDropDown. The API of the new widget is as minimal as possible, almost all the work is done by list model and item factory machinery.  Basically, you create a dropdown with gtk_drop_down_new(), then you give it a factory and a model, and you are done.

Since most choices consist of simple strings, there is a convenience method that creates the model and factory for you, from a string array:

const char * const times[] = {
  "1 minute",
  "2 minutes",
  "5 minutes",
  "20 minutes",
  NULL
};

button = drop_down_new ();
gtk_drop_down_set_from_strings (GTK_DROP_DOWN (button), times);

This convenience API is very similar to GtkComboBoxText, and the GtkBuilder support is also very similar. You can specify a list of strings in the ui file like this:

<object class="GtkDropDown">
  <items>
    <item translatable="yes">Factory</item>
    <item translatable="yes">Home</item>
    <item translatable="yes">Subway</item>
  </items>
</object>

Here are a few GtkDropDowns in action:

Summary

To stress this point again: all of this is brandnew API, and we’d love to hear your feedback on what works well, what doesn’t, and what is missing.

Scalable lists in GTK 4

One of the last big missing pieces in GTK 4 is the infrastructure for new list and grid widgets. It has just been merged and is included in the 3.98.5 release. So it is time to take a closer look.

History: tree views and list boxes

Since ancient times (ie GTK 2), GtkTreeView has been the go-to data display widget in GTK. It uses a model-view pattern to keep the data separate from the display infrastructure. Over the years, it has grown a grid-display sibling (GtkIconView) and a selection cousin (GtkComboBox), using the same infrastructure (tree models and cell renderers).

Unfortunately, the approach taken for rendering in GtkTreeView with cell renderers left us with a schism: widgets use one set of vfuncs and technologies for size allocation and rendering, and cell renderers use another. One of the unfortunate consequences of this split is that it is very hard to do animations in tree views (since cell renderers don’t keep state). Another is that most of the advances of the GTK CSS rendering machinery are unavailable in cell renderers.

Therefore, we would really like to use widgets for displaying the data in lists. During the GTK 3 era, we have introduced a number of containers for this purpose: GtkListBox for lists, and GtkFlowBox for grids. They don’t use cell renderers, so the aforementioned limitations are not a concern. And they can even use list models to hold the data. But they generate a widget for each data item, and this severely limits their scalability. As a rule of thumb, GtkListBox can handle 1 000 items well, while GtkTreeView works fine for 100 000.

Overcoming the scalability limitations, while still using widgets for all rendering has been on our roadmap for a long time.

Scalability limits
Widget Scalability
GtkIconView 100
GtkListBox 1 000
GtkTreeView 100 000
GtkListView unlimited

New infrastructure

With list view family of widgets, we hope to  finally achieve that. The goal is to handle unlimited numbers of items well. If you’ve worked with things like the Android recycler view, you will recognize the basic ideas behind list views:

  • The data for items is provided in the form of a model (containing objects)
  • Widgets are created just for the viewable range of items
  • Widgets can be recycled by binding them to different items
  • Avoid iterating over all items in the model as much as possible, and just deal with the items in the visible range which are bound to widgets

Models

For the model side of our model-view architecture, we’ve moved away from GtkTreeModel, and are now using GListModel. There are several reasons for this. One is that we want the items to be objects with properties, so we can use property bindings.  Another one is that GtkTreeModel is not all that scalable in the first place (see e.g. this case of unintentional quadratic behavior).

GTK 4 comes with a rich assortment of GListModel implementations, from various ways to combine or modify existing models to filtering and sorting.  Filtering and sorting is supported by several GtkFilter and GtkSorter classes. Selections are also handled on the model side, with GtkSelectionModel and its subclasses.

Finally, there are concrete models for the kinds of data that we are dealing with: GtkDirectoryList for files, PangoFontMap for fonts.

APIs that have traditionally returns GLists of various items have been changed or supplemented with APIs returning a GListModel, for example gdk_display_get_monitors(), gtk_window_get_toplevels() and gtk_stack_get_pages().

Factories

Since we are talking about automatically creating widgets on demand, there will be factories involved. GtkListItemFactory is the object that is tasked with creating widgets for the items in the model.

There are different implementations of this factory. One of them, GtkBuilderListItemFactory, is using ui files as templates for the list item widgets.  Here is a typical example:

<interface>
  <template class="GtkListItem">
    <property name="child">
      <object class="GtkLabel">
        <property name="xalign">0</property>
        <property name="wrap">1</property>
        <property name="width-chars">50</property>
        <property name="max-width-chars">50</property>
        <binding name="label">
          <lookup name="summary" type="SettingsKey">
            <lookup name="item">GtkListItem</lookup>
          </lookup>
        </binding>
      </object>
    </property>
  </template>
</interface>

The full example can be found in gtk4-demo.

Another list item factory implementation, GtkSignalListItemFactory, takes callbacks to setup, teardown, bind and unbind widgets from the items.

static void
setup_listitem_cb (GtkListItemFactory *factory,
                   GtkListItem        *list_item)
{
  GtkWidget *image;

  image = gtk_image_new ();
  gtk_image_set_icon_size (GTK_IMAGE (image), GTK_ICON_SIZE_LARGE);
  gtk_list_item_set_child (list_item, image);
}

static void
bind_listitem_cb (GtkListItemFactory *factory,
                  GtkListItem *list_item)
{
  GtkWidget *image;
  GAppInfo *app_info;

  image = gtk_list_item_get_child (list_item);
  app_info = gtk_list_item_get_item (list_item);
  gtk_image_set_from_gicon (GTK_IMAGE (image),
                            g_app_info_get_icon (app_info));
}

static void
activate_cb (GtkListView  *list,
             guint         position,
             gpointer      unused)
{
  GListModel *model;
  GAppInfo *app_info;

  model = gtk_list_view_get_model (list);
  app_info = g_list_model_get_item (model, position);
  g_app_info_launch (app_info, NULL, NULL, NULL);
  g_object_unref (app_info);
}

...

factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup", setup_listitem_cb, NULL);
g_signal_connect (factory, "bind", bind_listitem_cb, NULL);

list = gtk_list_view_new_with_factory (factory);
g_signal_connect (list, "activate", activate_cb, NULL);

model = create_application_list ();
gtk_list_view_set_model (GTK_LIST_VIEW (list), model);
g_object_unref (model);

gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), list);

The full example can be found here.

Expressions

To bind the widgets that are created by the factory to the data in your items, we need a flexible mechanism for binding properties. GObject’s GBinding mechanism goes in the right direction, but is not flexible enough to handle situations where you may need to bind properties of sub-objects or widgets deep inside the hierarchy of your widget, and where the objects in questions may not even exist at the time you are setting up the binding.

To handle this, we’ve introduced GtkExpression as a more flexible binding system which can express things like:

label = this->item->value

where this is a GtkListItem, which has an item property (of type SettingsKey), whose value property we want to bind to the label property. Expressing the same in a GtkBuilder ui file looks a bit more unwieldy:

<binding name="label">
  <lookup name="value" type="SettingsKey">
    <lookup name="item">GtkListItem</lookup>
  </lookup>
</binding>

New widgets

GtkListView is a simple list, without columns or headers. An example where this kind of list is used is GtkFontChooser. One little way in which GtkListView breaks new ground is that it can be set up as a horizontal list, as well as the usual vertical orientation.

GtkGridView puts the widgets in a reflowing grid, much like GtkFlowBox or GtkIconView.

GtkColumnView is the equivalent of a full GtkTreeView, with multiple columns and headers, and features such as interactive resizing and reordering.  Just like a GtkTreeView has GtkTreeViewColumns, a GtkColumnView has a list of GtkColumnViewColumns. Each column has a factory that produces a cell for each item. The cells are then combined into the row for the item.

Examples

Many of the lists in complex GTK dialogs (although not all yet) have been replaced with the new list widgets. For example, the font chooser is now using a GtkListView, and most of the lists in the GTK inspector use GtkColumnView.

But gtk4-demo contains a number of examples for the new widgets. Here are a few:

The clocks examples shows the advantages of having the full flexibility of widget rendering available.

The colors example shows the a medium-size dataset rendered in  various ways.

The settings example shows that the column view more or less matches GtkTreeView, feature-wise.

Summary

This post gave an introduction to the new list widgets. There’s more that we haven’t touched on here, such as trees or the combo box replacement. One place to learn more about the new apis is the detailed introduction in the GTK documentation.

We’ve merged the listview infrastructure to master now. That doesn’t mean that it is finished. But we think it is ready for some wider use, and we hope to get feedback from you on what works, what doesn’t and what is missing.

And, to be clear, it also does not mean that we are removing treeviews and combo boxes from GTK 4—it is too late for that, and they are still used in many places inside GTK. That may be a GTK 5 goal.

 

Media in GTK 4

Showing moving pictures is ever more important. GTK 4 will make it easier for GTK apps to show animations; be that a programmatic animation, a webm file or a live stream.

Everything is paintable

Before looking at animations, it is worth spending a little bit of time on the underlying abstractions that GTK uses for content that can be drawn. In GTK 2 and 3, that was mainly GdkPixbuf: you load a file, and you get a block of pixel data (more or less in a single format).  If you wanted to animate it, there is GdkPixbufAnimation, but it is fair to say that it was not a very successful API.

GTK 4 brings a new API called GdkPaintable that was inspired by the CSS Houdini effort. It is very flexible—anything that you can plausibly draw can be a GdkPaintable. The content can be resizable (like svg), or change over time (like webm).

Widgets that typically show image content, like GtkImage or GtkPicture know how to use paintables. And many things that in the past would have produced pixel data in some form can now be represented as paintables: textures, icons, and even widgets.

If you have more specialized needs, anything that can be captured in a GtkSnapshot can be turned into a paintable with gtk_snapshot_to_paintable(). If you make a custom widget that wants to draw a paintable, that is very straightforward. Just call gdk_paintable_snapshot().

Getting animated

As I’ve said earlier, paintables can change their content over time. All it takes is for them to emit the ::contents-changed signal, and widgets like GtkPicture will do the right thing and update their display.

So, where do we get a GdkPaintable that changes its content? We can load it from a file, using GTK 4’s builtin GtkMediaFile api. This is a high-level api, comparable to GstPlayer: you stuff in a uri, and you get an object that has a play() function and a pause() function, and works as a paintable.

GTK ships with two implementations of GtkMediaFile, one using gstreamer and another one using ffmpeg. Since we don’t want to make either of these a hard dependency of GTK,  they are loadable modules.

You can open the GTK inspector to find out which one is in use:

Keeping control

The GtkMediaFile API is what gtk4-widget-factory demos with the animated GTK logo on its frontpage:

As you can see, it is not just a moving picture, there are media controls there too – you get these for free by using the GtkVideo widget.

Beyond the basics

Loading animations from files is maybe not that exciting, so here is another example that goes a little further. It is a little weekend project that combines GtkVideo, libportal and pipewire to demonstrate how to show a video stream in a GTK application.

The bad news is that we haven’t found a permanent home for the supporting glue code yet (a GstSink, a GdkPaintable and a GtkMediaStream). It doesn’t fit into GTK since, as mentioned, we don’t want to depend on gstreamer, and it doesn’t fit into gstreamer since GTK 4 isn’t released yet. We will certainly work that out before too long, since it is very convenient to turn a gstreamer pipeline into a paintable with a few lines of code.

The good news is that the core of the code is just a few lines:

fd = xdp_portal_open_pipewire_remote_for_camera (portal);
stream = gtk_gst_media_stream_new_for_pipewire_fd (fd, NULL);
gtk_video_set_media_stream (video, stream);

 

Custom widgets in GTK 4 – Actions

(This is the fifth part of a series about custom widgets in GTK 4. Part 1, part 2, part 3, part 4).

Activate all the things

Many things in GTK can be activated: buttons, check boxes, switches, menu items, and so on. Often, the same task can be achieved in multiple ways, for example copying the selection to the clipboard is available both via the Control-C shortcut and an item in the context menu.

Inside  GTK, there are many ways things can proceed: a signal may be emitted (::activate, or ::mnemonic-activate, or a keybinding signal), a callback may be called, or a GAction may be activated. None of this is entirely new in GTK 4, but we are moving towards using GActions as the primary mechanism for connecting actions.

Actions

Actions can appear in various forms in a GTK application.

First, there are global application actions, added to GtkApplication or GtkApplicationWindow (both of these implement the GActionGroup interface). This is where actions first appeared in GTK 3, mainly for the purpose of exporting them on the session bus for use with the app menu.

We also allow to associate actions with widgets by calling gtk_widget_insert_action_group(). Actions that are added in this way are only considered for activation when it originates in below the widget in the hierarchy.

A new way to create actions in GTK 4 is to declare actions in the class_init function, via gtk_widget_class_install_action(), similar to how properties are declared with g_object_class_install_property(). Actions created in this way are available for every instance of the widget.

Here is an example from GtkColorSwatch:

gtk_widget_class_install_action (widget_class,
                                 "color.customize", "(dddd)",
                                 customize_color);

The customize_color function is called when the color.customize action is activated. As you can see, actions can declare that they expect parameters. This is using GVariant syntax; you need to provide four double values.

A convenient shorthand allows you to create a stateful action to  set a property of your widget class:

gtk_widget_class_install_property_action (widget_class,
                                         "misc.toggle-visibility",
                                         "visibility");

This declares an action with the name misc.toggle-visibility, which toggles the value of the boolean visibility property.

Actionables and Menus

Declaring actions only goes so far, you also need to connect your actions to the UI in some form. For widgets like buttons or switches that implement the actionable interface, this is as easy as setting the action-name property:

gtk_actionable_set_action_name (GTK_ACTIONABLE (button),
                                "misc.toggle-visibility");

Of course, you can also do this in a ui file.

If you want to activate your actions from a menu, you will likely use a menu model that is constructed from XML, such as this:

<menu id="menu">
  <section>
    <item>
      <attribute name="label">Show text</attribute>
      <attribute name="action">misc.toggle-visibility</attribute>
    </item>
  </section>
</menu>

In GTK 3, you would connect to the ::populate-popup signal to add items to the context menus of labels or entries. In GTK 4, this is done by adding a menu model to the widget:

gtk_entry_set_extra_menu (entry, menu_model);

Going deeper

To learn more about actions in GTK 4, you can read the action overview in the GTK documentation.

Custom widgets in GTK 4 – Input

(This is the fourth part of a series about custom widgets in GTK 4. Part 1, part 2, part 3).

Event handlers take over

In the previous parts, we’ve seen a few examples where handling GtkWidget signals was replaced by some auxiliary objects. This trend is even stronger in the input area, where we’ve traditionally had a number of signals to handle: ::button-press-event, ::key-press-event,  ::touch-event, and so on. All of these signals are gone in GTK 4, and instead you are expected to add event controllers to your widget, and listen to their signals. For example, there are GtkGestureClick, GtkEventControllerKey, GtkGestureLongPress, and many more.

Event controllers can be created in ui files, but it is more common to do that in the init() function:

static void click_cb (GtkGestureClick *gesture,
                      int              n_press,
                      double           x,
                      double           y)
{
  GtkEventController *controller = GTK_EVENT_CONTROLLER (gesture);
  GtkWidget *widget = gtk_event_controller_get_widget (controller);

  if (x < gtk_widget_get_width (widget) / 2.0 &&
      y < gtk_widget_get_height (widget) / 2.0)
     g_print ("Red!\n");
}

...

  controller = gtk_gesture_click_new ();
  g_signal_handler_connect (controller, "pressed",
                            G_CALLBACK (click_cb), NULL);
  gtk_widget_add_controller (widget, controller);

gtk_widget_add_controller() takes ownership of the controller and GTK cleans controllers up automatically when the widget is finalized, so there is nothing more to do.

Complex event handlers

The examples of event handlers in the previous sections are simple and handle only individual events, one at a time. Gestures are a bit more involved, since they handle sequences of related events, and generally keep state.

Examples of much more complex event handlers include things like DND, and keyboard shortcuts.   We may cover some of these in a later article.

Going deeper

The unifying  principle behind all the different event handlers is that GTK propagates the events it receives from the windowing system from the root of the widget tree to a target widget, and back up again, in a pattern commonly referred to as capture-bubble.

In the case of keyboard events, the target widget is the current focus. For pointer events, it is the hovered widget under the pointer.

To read more about input handling in GTK, visit the input handling overview in the GTK documentation.

Outlook

We’ve reached the end of the prepared material for this series. It may continue at some point in the future, if there is interest. Possible topics include: shortcuts, actions and activation, drag-and-drop, focus handling, or accessibility.