Who Wrote GTK4

GTK 4 has been a colossal, multi-year development endeavor that started in October 2016 and ended in December 2020. Now that the 4.0 release is finally out, it’s time to look back to the incredible amount of work done by hundreds of contributors over these four years.

Back in 2016 we were definitely a bit optimistic on the time table, and thought we would be able to release 4.0 in three years, by the end of 2019. The plan was to start by changing the rendering pipeline of GTK, by moving it to a retained graph of operations that could be submitted to the GPU, as opposed to the immediate mode rendering that we had since the very beginning of the toolkit, and which survived two major API cycles—first by abstracting Xlib drawing commands, and then by moving to Cairo operations. Of course, we also knew we wanted to improve other sub-systems, like input and the windowing system API, to move away from X11-isms and towards a design more in line with the requirements of Wayland (and other windowing systems). What we got, after all was said and done, is a deep redesign of the internals of the toolkit, as well as a different programming model that favors more delegation through ancillary objects, and fewer leaky abstractions and deep type hierarchies; additionally, we pared down the exposed internals, to ensure that the toolkit, and the applications using it, will be more maintainable in the future. The downside is that GTK is less of a “meta toolkit”, whose internal state can be poked at from the outside while expecting to work across multiple releases; that approach was, in the long term, unsustainable given the available resources, and left us unable to optimise or improve the internals of GTK, to the detriment of every user.

We don’t expect the next major development cycle to take this much time, but you know as they say: no plan of operations extends with any certainty beyond the first contact with the main hostile force. The important thing, though, is that we still see potential for improvement; and while we want to celebrate the release of GTK4, as well as everyone who contributed to it, we also want to communicate that the plan outlined in 2016 is still very much in effect. We already started collecting items for a GTK5 development cycle in our GitLab project page, and we plan to have more discussions with various stakeholders for future work in that direction.

Some statistics

Starting from commit 4cce6104 on October 5th, 2016:

  • Total commits: 16852
  • Developers: 256
  • Employers: 10
  • Lines added: 1053492, removed: 1053542 (delta: -50)

The most interesting thing is that, for all that work, we ended up with a total of 50 lines of code removed from the existing code base, which includes tests and documentation.

Per year breakdown

Year Developers Commits Lines added Lines removed Delta
2016 50 1422 64014 152103 -88089
2017 84 3446 186024 166198 19826
2018 111 2332 129726 140526 -10800
2019 85 3244 200476 207623 -7147
2020 109 6384 470907 386761 84206

We only had a couple of months in 2016, and the largest effort between October and December was the removal of the deprecated API, and various changes to cope with the renaming from GTK3 to GTK4. Nevertheless, GSK initially landed in November/December. On November 21st, GTK 3.89.1 was released, the first development snapshot of the new GTK 4 API.

In 2017, GSK had with multiple iterations over its API: new render nodes were added to implement CSS drawing primitives, and widgets were moved to the GtkSnapshot API from the old Cairo rendering primitives. Additionally, the new clipboard API was introduced, with a stream-based implementation. On March 31st, GTK 3.90.0 was released, followed by 3.92.0 in October.

In 2018, things seem to slow down, but mostly because we moved to GitLab. Suddenly, we had access to a CI pipeline and merge requests, and we became progressively more confident in our development process; lots of work happened in development branches, especially large refactorings, like the renaming of GdkWindow to GdkSurface; the move to GtkGesture objects and the removal of per-widget event signals; or making leaf classes not derivable. GTK 3.94.0 was released in June.

In 2019, development pace picked up once again:

  • widget transformations
  • layout managers
  • constraint layouts
  • no more GtkMenu
  • scope object for GtkBuilder
  • GtkText widget
  • GtkNative and GtkRoot interfaces
  • per-widget actions
  • simplified GdkSurface sub-types

Plus, of course, lots of performance and functionality improvements in the rendering pipeline. GTK 3.96.0 was released in May.

Finally, we arrive at 2020:

  • new drag and drop API, based on event controllers
  • new macOS backend in GDK, replacing the old Quartz one
  • new accessibility API, dropping ATK
  • no more GtkContainer
  • no more GtkRadioButton
  • simplified GdkDevice API
  • new keyboard shortcuts API
  • new expressions and filter models
  • new model-based list and tree widgets

GTK 3.98.0 was released in February; and, finally, 3.99.0 in July, as the first true beta towards GTK 4.0.

 

Developers

Top 20 by commit
Matthias Clasen 6519 (38.7%)
Timm Bäder 3229 (19.2%)
Benjamin Otte 2596 (15.4%)
Emmanuele Bassi 1094 (6.0%)
Carlos Garnacho 494 (2.9%)
Daniel Boles 383 (2.3%)
Alexander Larsson 313 (1.9%)
Jonas Ådahl 167 (1.0%)
Chun-wei Fan 162 (1.0%)
Christian Hergert 158 (0.9%)
Jakub Steiner 134 (0.8%)
Piotr Drąg 132 (0.8%)
Руслан Ижбулатов 120 (0.7%)
Alexander Mikhaylenko 93 (0.6%)
Rico Tzschichholz 78 (0.5%)
nana-4 66 (0.4%)
Christoph Reiter 62 (0.4%)
Tim-Philipp Müller 60 (0.4%)
Mohammed Sadiq 57 (0.3%)
Olivier Fourdan 42 (0.2%)
Top 20 by changes
Matthias Clasen 620248 (36.8%)
Benjamin Otte 466996 (27.7%)
Timm Bäder 187516 (11.1%)
Emmanuele Bassi 165354 (9.8%)
Alexander Larsson 53065 (3.1%)
Carlos Garnacho 27227 (1.6%)
Christian Hergert 26964 (1.6%)
Руслан Ижбулатов 21760 (1.3%)
Jakub Steiner 18388 (1.1%)
Jonas Ådahl 12824 (0.8%)
Chun-wei Fan 12518 (0.7%)
Daniel Boles 12371 (0.7%)
Lapo Calamandrei 9995 (0.6%)
Christoph Reiter 8391 (0.5%)
Alexander Mikhaylenko 4936 (0.3%)
Tim-Philipp Müller 3932 (0.2%)
Rico Tzschichholz 3108 (0.2%)
William Hua 2900 (0.2%)
Jason Francis 1908 (0.1%)
Peter Bloomfield 1727 (0.1%)

As usual, Matthias towers over every other contributor, both in terms of sheer number of commits and in terms of code changes.

Employers

By commit
Red Hat 13706 (81.3%)
(Unknown) 1185 (7.0%)
GNOME Foundation 1095 (6.5%)
GNOME 571 (3.4%)
Purism 93 (0.6%)
Canonical 84 (0.5%)
Centricular 75 (0.4%)
Endless 20 (0.1%)
Collabora 14 (0.1%)
Intel 6 (0.0%)
Novell 3 (0.0%)
By changes
Red Hat 1415771 (84.0%)
GNOME Foundation 165355 (9.8%)
(Unknown) 58220 (3.5%)
GNOME 33442 (2.0%)
Purism 4936 (0.3%)
Centricular 4205 (0.2%)
Canonical 3347 (0.2%)
Novell 336 (0.0%)
Intel 222 (0.0%)
Collabora 208 (0.0%)
Endless 121 (0.0%)
By contributor
(Unknown) 199 (72.4%)
GNOME 32 (11.6%)
Red Hat 22 (8.0%)
Endless 6 (2.2%)
Collabora 4 (1.5%)
Centricular 3 (1.1%)
Novell 3 (1.1%)
GNOME Foundation 2 (0.7%)
Canonical 2 (0.7%)
Intel 1 (0.4%)
Purism 1 (0.4%)

While Red Hat employees affect the most changes, we still have a very large number of contributors from the community—both using their GNOME email address and using their work address even when contributing on a personal level. We also have multiple contributors from various companies in the GNOME and free software at large ecosystems, signalling a healthy community around the project.

Comparing numbers with GTK3 is a bit more tricky:

  • the GTK 2 → 3 development phase only took about a year and change, instead of four years
  • we were still using the old GNOME infrastructure, which meant fewer, larger patches, and a higher barrier to contribution in the form of Bugzilla

Nevertheless, the amount of contributors more than doubled compared to 2011, with almost three times the amount of unaffiliated contributors.

¹ – Paid staff of the GNOME Foundation.

² – Contributors using a gnome.org email address with no clear employment status.

GTK 4.0

2020 has been a very long year. What better way to end it than with a major release!  Today, we released GTK 4.0.

GTK 4.0 is the result of a lot of hard work by a small team of dedicated developers. We will have a separate post to go over the statistics, but the short summary is that since the 3.89.1 release in November 2016, we’ve added over 18000 commits and made more than 20 development releases.

Congratulations and a big thank you to everybody who has participated in this effort, and in particular to Benjamin, Emmanuele, Timm, Carlos, Jonas and Christian!

What’s new

It is impossible to summarize 4 years of development in a single post. We’ve written detailed articles about many of the new things in this release over the past year: Data transfers, Event controllers, Layout managers, Render nodes, Media playback, Scalable lists, Shaders, Accessibility.  Here are some of the highlights, in visual form:

Media playback:

Drag-and-Drop:

Layout managers and transforms:

Scalable lists and grids:

Shaders:

What’s old

GTK 4 is now stable, and we consider it ready for consumption. That does not mean GTK 3 is dead – we will continue to support and update it for the foreseeable future (the latest release, 3.24.24, quietly went out a few days ago). It does mean, however, that GTK 2 has reached the end of its life. We will do one final 2.x release in the coming days, and we encourage everybody to port their GTK 2 applications to GTK 3 or 4.

How to get it

The source tarballs are available in the usual place. Binary packages should appear in major distributions soon.

The GNOME 40 release will have a number of applications ported to GTK 4.  If you want to try GTK 4.0 today, you can use the nightly flatpak builds of gtk4-demo and gtk4-widget-factory:

$ flatpak install https://nightly.gnome.org/repo/appstream/org.gtk.Demo4.flatpakref
$ flatpak run org.gtk.Demo4

If you are itching to port your application to GTK 4, our migration guide is available as part of the documentation.

How to support GTK

GTK could not be developed without the many volunteers who contribute bug reports, patches, translations or ideas. Thanks to all of you. We are also grateful to the GNOME foundation for supporting GTK with development resources, infrastructure, and travel assistance.

Donating to the GNOME foundation is a good way to support future GTK development.

What comes next

We are very thankful for all the early testers that have provided us with bug reports and feedback, which made this release much better. But we fully expect that there will be a quick 4.0.1 release to fix up the oversights and gotchas that only come to light after a .0 release.

Now that we have a 4.0 release, we need to bring along the library ecosystem to enable applications to use it. vte, webkit and gtksourceview are some of the most notable libraries that tend to be used together with GTK. We expect GTK 4 ports of these to be available soon.

If more serious issues show up, we will do a 4.2 release in time for GNOME 40, otherwise we might wait until the summer for that.

What the future will bring beyond GTK 4 remains to be seen. We have some fun things in the pipeline, but we would also like to hear what features application developers would like to see in GTK. Tell us!

A celebration

We will celebrate the 4.0 release with an (online) gathering this coming Friday. Feel free to drop by!

Accessibility in GTK 4

The big news in last weeks GTK 3.99.3 release is that we have a first non-trivial backend for our new accessibility implementation. Therefore, now is a good time to take a deeper look at accessibility in GTK 4.

Overview

Lets start with a quick review of how accessibility works on Linux. The actors in this are applications and assistive technologies (ATs) such as screen readers (for instance, Orca), magnifiers and the like.

The purpose of ATs generally is to provide users with alternative ways to interact with the application that are tailored to their needs (say, an enlarged view, text read out aloud, or voice commands). To do this, ATs need a lot of detailed information about the applications UI, and this is where the accessibility stack comes into play—it is the connecting layer between the application (or its toolkit) and the ATs.

Applications and ATs talk to each other on the accessibility bus, which is a separate instance of a D-Bus session bus, using the interfaces described by the AT-SPI project. The UI elements of the application are represented on as objects on the bus that implement somewhat abstracted interfaces, such as Text or Value. Applications emit signals to communicate changes in the UI, and ATs can call methods on the objects to get information or make changes (e.g. change the current value of a Value interface to move the GtkScale that it represents).

What has changed

In GTK 2 and 3, this was done in an awkwardly indirect way: GTK widgets have auxiliary accessible objects that are implementations of ATK interfaces (translation 1: GTK ➙ ATK). These are then turned in AT-SPI objects (translation 2: ATK ➙ AT-SPI) that are represented on the accessibility bus by adapter code in at-spi2-atk. On the other end, ATs then use pyatspi to convert the AT-SPI interfaces into Python objects (translation 3: AT-SPI ➙ Python).

This multi-step process was inefficient, lossy, and hard to maintain; it required implementing the same functionality over at least three components, and it led to discrepancies between the documented AT-SPI methods and properties, and the ones actually sent over the accessibility bus.

In GTK 4, we are simplifying the application side by cutting out ATK and at-spi2-atk. Widgets now implement a GtkAccessible interface that lets them set a number of roles, states, properties and relations that are more or less directly taken from the WAI-ARIA spec published by the W3C. The AT-SPI backend for GTKs accessibility API then takes these ARIA-inspired attributes (and the knowledge of the widgets themselves) and represents the widgets as objects on the accessibility bus, implementing the relevant AT-SPI interfaces for them.

This is a much more direct approach, and matches what Qt and web browsers already do.

Application API

Here are the highlights of the accessibility API that you are most likely to run into when using GTK 4 in applications:

Setting an accessible role. A role is a description of the semantics of a widget, and ATs will use it to decide what kind of behavior should be presented to their users. Setting a role is a one-time operation, which means it has to be done at widget creation time, either in class_init, or during instance initialization:

gtk_widget_class_set_accessible_role (widget_class,  
                                      GTK_ACCESSIBLE_ROLE_BUTTON);

Updating a widgets acccessible state or properties. This should be done whenever the widget’s accessible representation changes:

gtk_accessible_update_property (GTK_ACCESSIBLE (widget),
                       GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, minimum,
                       GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, value,
                       GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, maximum,
                       -1);

The GTK reference documentation has an overview of the accessibility API, which includes guidance for application developers and widget writers.

What’s next ?

For GTK 4.0, we are focusing on completing the AT-SPI backend. But with the new API and the backend separation, we have a clear path towards making accessibility backends for other platforms, which is something we want to look into for subsequent GTK 4 releases.

On Linux, we want to work with other stakeholders on modernizing the AT-SPI interfaces to finally overcome the CORBA legacy that is still visible in some places. A part of that will be moving away from an accessibility bus toward peer-to-peer connections between the application and the ATs; this would enhance the security of the accessibility stack and plug a hole in the sandboxing used by technologies such as Flatpak.

In the future we want to introduce tools to ensure that application developers will be aware of missing accessibility annotations, such as providing a label attribute, or a labelled-by relation, to icons and images in their UIs; or ensure that every UI element is correctly represented in the accessible tree. We already have a test backend for the GtkAccessible interface which can be used to write unit tests and verify that roles and attributes are updated where necessary.

GTK 3.99.2

The GTK 3.99.2 release continues the topics from 3.99.1: api cleanup, new and polished demos, better documentation. You can see the details here.

One small note on the topic of documentation is that we are relying on some unreleased gtk-doc features. Therefore, we now include gtk-doc as a subproject in the gtk release tarball. If you are a distributor, don’t be surprised that building GTK installs gtk-doc tools now.

The big news in this snapshot is our work on exposing the power of the new GL-based rendering stack a bit more.

Warmup: Shadertoy

gtk4-demo includes a Shadertoy demo now.

The demo using a GtkGLArea widget to run GLSL snipplets that are compatible with the ones found on shadertoy.com. Many of the examples found there will work, if you paste them into the editor of this demo.

This is fun, but somewhat limited. The GLSL is confined to its ‘sandbox’, the GtkGLArea widget, which is using GL api to compile and use the shaders.

Shaders as first-class objects

This is not our first attempt to make a shadertoy lookalike. When we first looked at it, we thought that we would make a shader abstraction that applications could use. We put it to the side when it turned out that making it work across different renderers and backends would require us to write our own shader compiler—too much work.

But after our shadertoy success, we revisited the idea of shaders as first-class objects, with more modest goals: We use GLSL, and don’t attempt to make the shaders work with anything but the OpenGL renderer.

In 3.99.2, we now have:

With these pieces in place, we made a demo that shows various uses of shaders. It is maybe a bit overloaded, and some of the effects are a bit over-the-top, but it gets the point across: you can use shaders in your widgets.

 

What we haven’t done yet is adding widgets that have shader support built-in. The demo showcases a few likely candidates:

A shader paintable. As you may recall, GdkPaintable is a very flexible interface for anything that can ‘paint’. Shaders certainly qualify. The GskShaderPaintable in gtk-demo uses a shader without input textures to just produce pixels, and we add it to a GtkPicture widget to make it appear in the widget tree.

A shader bin. This is a very simple container that can use shaders to draw effects on top of a child widget. It works with shaders that take a single input texture (for the child widget).

A shader stack. This is a stack-like container that shows one of many child widgets, and uses a shader for the transition whenever the visible child changes. It works with shaders that expect two input textures (for the old and new active child).

Thankfully, making custom widgets is a lot easier in GTK 4 than it used to be, so the render node API should be enough to get you started on some fun experiments. You can of course take the gtk4-demo code as a starting point.

You can debug it

Apart from widgets, the shader support is fully integrated. The GTK inspector can handle shader nodes like any other render nodes, you can serialize them and e.g. load the resulting file in gtk4-node-editor:

If you need to see the input that GTK sends to the shader compiler,  setting the environment variable

GDK_DEBUG=shaders

can be helpful.

Whats next?

After this GL adventure we’ll now focus on landing more of the new accessibility infrastructure.

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.