Container secrets: size allocation, part 2

Right-to-left languages

As the first thing in this series, we add back support for right-to-left languages.

It may be a little surprising if you are only used to writing software in English, but GTK+ has traditionally tried to ‘do the right thing’ automatically for languages that are written from right to left like Hebrew. Concretely, that means that we interpret the starting position in horizontal layouts to be on the right in these languages, i.e. we ‘flip’ horizontal arrangements. This can of course be overridden, by setting the ::text-direction property of the container. By default, the text direction gets determined from the locale.

This is of course very easy to do. The code I showed in the first post assumes left-to-right order of the children. We keep it that way and just reorder the children when necessary. Note that the measure() code doesn’t need any change, since it does not depend on the order at all. So, we just change size_allocate():

if (text_direction == rtl) {
  child[0] = last;
  child[1] = center;
  child[2] = first;
} else {
  child[0] = first;
  child[1] = center;
  child[2] = last;
}

One small gotcha that I haven’t mentioned yet: CSS assumes that :first-child is always the leftmost element, regardless of text direction. So, when we are moving child widgets from the left side to the right side in RTL context, we need to reorder the corresponding CSS nodes when the text direction changes.

if (direction == GTK_TEXT_DIR_LTR) {
  first = gtk_widget_get_css_node (start_widget);
  last = gtk_widget_get_css_node (end_widget);
} else {
  first = gtk_widget_get_css_node (end_widget);
  last = gtk_widget_get_css_node (start_widget);
}

parent = gtk_widget_get_css_node (box);
gtk_css_node_insert_after (parent, first, NULL);
gtk_css_node_insert_before (parent, last, NULL);

Natural size

Since this was easy, we’ll press on and make our size allocation respect natural size too.

In GTK+ every widget has not just a minimum size, which is the smallest size that it can usefully present itself in, but also a preferred or natural size. The measure() function returns both of these sizes.

For natural size, we can do slightly better than the code we showed last time. Back then, we simply calculated the natural size of the box by adding up the natural sizes of all children. But we want to center the middle child, so lets ask for enough room to give all children their natural size and put the middle child in the center:

*natural = child2_nat + 2 * MAX (child1_nat, child3_nat);

The size_allocate() function needs more work, and here we have some decisions to make. With 3 children vying for the available space, and the extra constraint imposed by centering, we could prefer to give more space to the outer children, or make the center child bigger. Since centering is the defining feature of this widget, I went with the latter choice.

So, how much space can we give to the center widget ? We don’t want to make it smaller than the minimum size, or bigger than the natural size, and we need to leave at least the minimum required space for the outer children.

center_size = CLAMP (width - (left_min + right_min),
                     center_min, center_nat);

Next, lets figure out how much space we can give to the outer children. We obviously can’t hand out more than we have left after giving the center child its part, and we have to split the remaining space evenly in order for centering to work (which is what avail in the code below is about). Again, want to respect the childs minimum and natural size.

avail = MIN ( (width - center_size) / 2,
              width - (center_size + right_min));
left_size = CLAMP (avail, left_min, left_nat);

And similarly for the right child. After determining the child sizes, all that is left is to assign the positions in the same way we’ve seen in part one: put the outer children at the very left and right, then center the middle child and push it to the right or left to avoid overlap.

Expanded
Natural size
Below natural size
Smaller
Minimum size

References

  1. Container secrets, size allocation
  2. The code with these changes

Container secrets: size allocation

I recently had an opportunity to reimplement size allocation for a container with all the things that GTK+ supports:

  • RTL support
  • Natural size
  • Align and expand
  • Height-for-width
  • Orientation support
  • Baselines

If you do a one-off container in an application, most of these may not matter, but in a general-purpose GTK+ widget, all of them are likely to become relevant sooner or later.

Since this is quite a lot of ground to cover, it will take a few posts to get through this. Lets get started!

The starting point

GtkCenterBox is a simple widget that can contain three child widgets – it is not a GtkContainer, at least not currently. In GTK+ 4, any widget can be a parent of other widgets. The layout that GtkCenterBox applies to its children is to center the middle child, as long as that is possible.  This is functionality that GtkBox provides in GTK+ 3, but the center child handling complicates an already complex container. Therefore we are moving it into a separate widget in GTK+ 4.

Expanded
Natural size
Below natural size
Minimum size

When I started looking at the GtkCenterBox size allocation code, it was very simple. The two methods to look at are measure() and size_allocate().

The measure implementation was just measuring the three children, adding up the minimum and natural sizes in horizontal direction, and taking their maximum in vertical direction. In rough pseudo-code:

if (orientation == GTK_ORIENTATION_HORIZONTAL) {
  *minimum = child1_min + child2_min + child3_min;
  *natural = child1_nat + child2_nat + child3_nat;
} else {
  *minimum = MAX(child1_min, child2_min, child3_min);
  *natural = MAX(child1_min, child2_min, child3_min);
}

The size_allocate implementation was putting the first child to the left, the last child to the right, and then placed the middle child in the center, eliminating overlaps by pushing it to the right or left, as needed.

child1_width = child1_min;
child2_width = child2_min;
child3_width = child3_min;
child1_x = 0;
child3_x = total_width - child3_width;
child2_x = total_width/2 - child2_width/2;
if (child2_x < child1_x + child1_width)
  child2_2 = child1_x + child1_width;
else if (child2_x + child2_width > child3_x)
  child2_x = child3_x - child2_width;

As you can see, this is pretty straightforward. Sadly, it does not have any of the features I listed above:

  • Children always get their minimum size
  • The ::expand property is not taken into account
  • The first child is always placed at the left, regardless of text direction
  • No vertical orientation
  • No height-for-width support
  • Baselines are ignored

Over the next few posts, I’ll try to show how to add these features back, hopefully clarifying some of the mysteries of how GTK+ size allocation works, along the way.

References

  1. First commit in the series
  2. Documentation for GtkWidget size allocation
  3. The code that we start from

This week in GTK+ – 35

In this last week, the master branch of GTK+ has seen 33 commits, with 5011 lines added and 8140 lines removed.

Planning and status
  • The GTK+ road map is available on the wiki
  • Patrick Griffis is experimenting with a feature branch to deprecate and remove gtk_dialog_run(); see this comment on the pitfalls of nested main loops with regards to UI threads, IPC threads, and I/O threads
  • Matthias Clasen is experimenting with re-using the fuzzy search in libdazzle in the icon browser
Notable changes

On the master branch:

  • Matthias Clasen added the ability to copy the icon name to the clipboard to the icon browser utility
  • Matthias also made the GtkCenterBox widget public; this widget replaces the equivalent functionality of GtkBox to have a centered widget
  • Olivier Fourdan fixed various bugs in the Wayland backend, and backported the fixes to the gtk-3-22 stable branch
  • Chun-wei Fan pushed various fixes to ensure that GTK+ keeps building with MSVC on Windows
  • Emmanuele Bassi modified the Meson build to ensure that all the SASS-based themes are regenerated when building GTK+, if sassc is installed; Lapo Calamandrei removed the Gem file for Ruby/Sass, and thus GTK+ switched to sassc as the preferred SASS compiler
Bugs fixed
  • 770513 – MainToolbar in full-screen mode has rounded corners, which show video pixel bleed-thru underneath it
  • 783347 – gtkfilechoosernativewin32: Fix support for non-ASCII paths
  • 781945 – SIGSEGV dragging window on Wayland when toplevel window set_transient_for is set to another toplevel
  • 782283 – Wayland: Crash when dismissing a menu when a tooltip is visible
  • 781285 – Key repeat cancel under Wayland should depend on which key is repeating
  • 783397 – Remove unused code in gtktextdisplay.c
Getting involved

Interested in working on GTK+? Look at the list of bugs for newcomers and join the IRC channel #gtk+ on irc.gnome.org.

Drag-and-Drop in lists, revisited

My previous post on Drag-and-Drop in lists made some compromises in order to present the simplest, fully functional implementation. One of these was that we could only draw the drop target highlight around entire rows, whereas the drop really inserts the dropped row between rows. Lets try to do better!

Changing the target

In the simplified version, we made every row a drop target. This time around, we will change this and have just the list itself accept drops.

gtk_drag_dest_set (list,
                   GTK_DEST_DEFAULT_MOTION|GTK_DEST_DEFAULT_DROP, 
                   entries, 1,
                    GDK_ACTION_MOVE);
g_signal_connect (list, "drag-data-received",
                  G_CALLBACK (drag_data_received), NULL);

If you compare this to what was done before, you may notice another change: Instead of GTK_DEST_DEFAULT_ALL, we now just request the default behavior for motion and drop. We no longer request the default behavior for highlighting, since we want to handle that ourselves.

In order to do so, we need to connect to the drag-motion and drag-leave signals. These are emitted on the drop target, i.e. the list:

 g_signal_connect (list, "drag-motion",
                   G_CALLBACK (drag_motion), NULL);
 g_signal_connect (list, "drag-leave",
                   G_CALLBACK (drag_leave), NULL);

Mind the gap

The basic idea for our improved drag highlighting is that we keep track of the two adjacent rows between which the drop will happen, and use the GTK+ CSS machinery to create a suitable highlight of the gap.

This requires a bit too much code to show in full here, but the idea is as follows: Find the row that is currently under the cursor with gtk_list_box_get_row_at_y(). Look at its allocation to to find out if the cursor is in the top or bottom half of the row. Depending on that, we pick either the following or the preceding row as the other member for our pair of rows.

row = gtk_list_box_get_row_at_y (list, y);
gtk_widget_get_allocation (row, &alloc);
if (y < alloc.y + alloc.height/2)
  {
    row_after = row;
    row_before = get_row_before (list, row);
  }
else
  {
    row_before = row;
    row_after = get_row_after (list, row);
  }

There are some corner cases which I am omitting here, e.g. when the hovered row is the first or last row, or when we are hovering over ’empty space’ in the list.

CSS Highlights

I said we would be using CSS for creating the highlight. The easiest way to do so is to just add style classes to the two rows we’ve found:

gtk_style_context_add_class (
                      gtk_widget_get_style_context (row_before),
                      "drag-hover-bottom");
gtk_style_context_add_class (
                      gtk_widget_get_style_context (row_after),
                      "drag-hover-top");

And then we will use some custom CSS to create our highlight. In case you are wondering: #4e9a06 is the drag highlight color used in the Adwaita theme.

.row.drag-hover-top {
  border-top: 1px solid #4e9a06; 
}
.row.drag-hover-bottom {
  border-bottom: 1px solid #4e9a06; 
}

This is again omitting the corner cases that I’ve mentioned before.

How did we get here ?

Dropping the row onto the place it came from does not achieve anything. It makes sense to mark the place where the drag started in some way to make this obvious. We can again use CSS for this, and add a style class to the dragged row in our drag_begin() method:

gtk_style_context_add_class (gtk_widget_get_style_context (row),
                             "drag-row");

In order to give the row a bit a highlight when we are hovering over it, we add an extra style class to it in our drag_motion() handler:

if (row == drag_row)
  {
    gtk_style_context_add_class (gtk_widget_get_style_context (row),
                                 "drag-hover");
    row_before = get_row_before (row);
    row_after = get_row_after (row);
  }
else …

And here is the CSS for these classes:

.row.drag-row {
  color: gray;
  background: alpha(gray,0.2);
 }
 .row.drag-row.drag-hover {
  border-top: 1px solid #4e9a06;
  border-bottom: 1px solid #4e9a06;
  color: #4e9a06;
}

Putting it all together

(Sorry about the broken cursors. We should really fix this in gnome-shell’s screen recorder.)

References

  1. The complete example, testlist3.c
  2. The GTK+ DND documentation

This week in GTK+ – 34

After quite a long break around the GNOME 3.24 release, we’re finally back. Sorry for the wait!

In this last week, the master branch of GTK+ has seen 103 commits, with 2355 lines added and 5482 lines removed.

Planning and status
  • The GTK+ road map is available on the wiki.
  • Matthias Clasen released GTK+ 3.91.0, the first snapshot of the development cycle that will lead to the 3.92 release. This is still part of the development cycle towards the API stable 4.0.
  • Timm Bäder is working on his drawing branch which aims to replace all the internal uses of CSS gadgets with real widgets. See this article on this blog for more information.
Notable changes

On the master branch:

  • Carlos Garnacho merged his event-delivery branch, which moves the event handling from the GDK window hierarchy to the GTK widget one; this is the first step towards the removal of all GdkWindow instances outside of the top level one, and which will ultimately lead to improved input handling.
Bugs fixed
  • 745289 – wayland: do not use g_error() on connection errors
Getting involved

Interested in working on GTK+? Look at the list of bugs for newcomers and join the IRC channel #gtk+ on irc.gnome.org.

Container secrets

I recently spent some time tracking down a problem with GTK+ containers and drawing. Here is what I found out.

CSS drawing

In GTK+ 3.22, most, if not all, containers support the full CSS drawing model with multiple layers of backgrounds and borders. This is how, for example, GtkFrame draws its frame nowadays. But also containers that normally only arrange their children, such as GtkBox, can draw backgrounds and borders. The possibilities are endless!

For example, we can use a GtkBox to put a frame around a list box and a label, to make the label visually appear as part of the list. You can even make it colorful and fun, using some CSS like:

box.frame {
 border: 5px solid magenta;
}

Allocation and resizing

Traditionally, most containers in GTK+ are not doing any drawing of their own and just arrange their children, and thus there is no real need for them to do a full redraw when their size changes – it is enough to redraw the children. This is what gtk_container_set_reallocate_redraws() is about. And it defaults to FALSE in GTK+ 3, since we did not want to risk adding excessive redraws  whenever allocations change.

You can see where this is going: If I use the delete button to remove Butter and Salt from the list of ingredients, the allocation of the list, and thus of the box around it, will shrink, and we get a redraw problem.

The solution

If you plan to make plain layout containers draw backgrounds or borders, make sure to set reallocate-redraws to TRUE for the right widgets (in this case, the parent of the fun box).

gtk_container_reallocate_redraws (GTK_CONTAINER (parent), TRUE);

Note that gtk_container_reallocate_redraws() is deprecated in GTK+ 3.22, since we will get rid of it in GTK+ 4 and do the right thing automatically. But that shouldn’t stop you from using it to fix this issue.

Another (and maybe better) alternative is to use a container that is meant to draw a border, such as GtkFrame.

Logging and more

A while ago, GLib gained a new facility for ‘structured logging’. At the same time, it also gained support for writing logs to the systemd journal. Clearly, logging in GLib got a bit more complicated, and it can be a bit confusing.

This article is an attempt to clarify things.

Structured or not

The traditional GLib logging facilities are the g_message(), g_debug(), etc macros, which eventually call the g_log() function, which then uses the log handler that has been set with g_log_set_handler() to do the actual writing. You can put any information you like into your logs, but it has to all be formatted as a single string, the message.

g_debug ("You have %d eggs", 12 + 2);

g_log (G_LOG_DOMAIN,
       G_LOG_LEVEL_DEBUG,
       "You have %d eggs", 12 + 2);

With the new structured logging facilities, you call g_log_structured(), which then uses a log writer function to do the writing. So far, this is very similar to the older logging facility. The advantage of structured logs is that you can put multiple fields into the log without resorting to formatting it all into a string. Instead, you pass an array of log fields, which are key-value pairs.

g_log_structured (G_LOG_DOMAIN,
                  G_LOG_LEVEL_DEBUG,
                  "CODE_FILE", "mysource.c",
                  "CODE_LINE", 312,
                  "MESSSAGE_ID", "06d4df59e6c24647bfe69d2c27ef0b4e",
                  "MESSAGE", "You have %d eggs", 12 + 2);

CODE_FILE, CODE_LINE and MESSAGE_ID here are just examples for “standard” fields. You can also invent your own fields. Note that you can still use printf-style formatting for the MESSAGE field.

So GLib has two separate logging facilities now. To make matters a bit more interesting, we allow you to redirect the g_message(), g_debug(), etc wrapper macros to use g_log_structured() instead of g_log() under the covers. To do so, define the G_LOG_USE_STRUCTURED macro before including glib.h.

Why is this useful? For one thing, it saves you the trouble of replacing all your g_debug() and still lets you take some advantage of the structured logging – when used in this fashion, the traditional macros use separate fields for the log domain, the code file and line and some other fields, which can be helpful for filtering and searching in the resulting logs, in particular with the systemd journal.

Another advantage is that you can use a single backend, the log writer function, to control where both old and new logging calls end up.

Where have all my logs gone ?

Structured logging is frequently associated with the systemd journal. So, it is not surprising that people expect the g_log_structured() output to go the journal. And that is a really useful thing for services, or when you are launching an application e.g. from a desktop icon. But if you run it from the terminal, you probably expect to see its output right there.

To satisfy these competing demands, the GLib default log writer function tries to be smart. If it detects that stderr is redirected to the journald socket, then it writes its structured output to the journal. Otherwise, it formats a message and writes it to stderr.

Both GNOME Shell and DBus arrange for stderr to be redirected to the journal when they start apps or services. A way to explicitly redirect stderr to the journal is to run your application under systemd-cat:

systemd-cat my-app-that-logs

If you are sure that you want your logs to always go to the journal, you can tell GLib to use a log writer that does that:

g_log_set_writer_func (g_log_writer_journald, NULL, NULL)

Beyond the default

Even though the log writer functions that GLib provides by default should serve many needs, you might need to write your own. In that case, GLib has a number of useful functions that can help you, such as g_log_writer_format_fields(), g_log_writer_is_journald() or g_log_writer_supports_color().

Happy logging!

References

First steps with GSettings

We sometimes get questions about GSettings in #gtk+, and whether it is a good idea to use this API for ‘simple’ cases. The answer is a very clear Yes, in my opinion, and this article is trying to explain why.

Benefits

One of the nice things about GSettings is that it is a high-level API with backends for various native configuration systems. So, if you ever get around to porting your application to OS X or Windows, your application will automatically use the expected platform API to store its settings (the registry on Windows, and plists on OS X).

And even if your application will never be ported to those platforms, the dconf backend that is used on Linux has powerful features such as profiles and locks that let system administrators configure your application without you having to worry about it.

The documentation for GSettings unfortunately makes it appear more complicated than it is, since it doesn’t really try to hide the powerful features that are available to you (profiles, vendor overrides, translated defaults, complex types, bindings, etc).

So, here is a guide to first steps with GSettings, in a simple case.

Getting started

Lets get started with the simplest possible setting: a boolean.

The biggest hurdle to overcome is that GSettings insists on having a schema, which defines the datatype and default value for each key.

<schemalist>
  <schema path="/org/gnome/recipes/"       
         id="org.gnome.Recipes">
    <key type="b" name="use-metric">
      <default>true</default>
      <summary>Prefer metric units</summary>
      <description>
        This key determines whether values
        such as temperatures or weights will
        be displayed in metric units.
      </description>
    </key>
  </schema>
</schemalist>

Schemas need to be installed (the advantage of this is that tools such as dconf-editor can use the schema information). If you are using autotools, this is supported with macros. Just add

GLIB_GSETTINGS

to your configure.ac, and

gsettings_SCHEMAS = org.gnome.Recipes.gschema.xml
@GSETTINGS_RULES@

to Makefile.am. The setup with meson is similar.

Now that we’ve defined our key, we can obtain its value like this:

s = g_settings_new ("org.gnome.Recipes");
if (g_settings_get_boolean (s, "use-metric"))
  g_print ("Using metric units");

and we can set it like this:

s = g_settings_new ("org.gnome.Recipes");g_settings_set_boolean (s, "use-metric", TRUE);

Using GSettings for other basic types such as integers, floating point numbers, strings, etc. is very similar. Just use the appropriate getters and setters.

You may be wondering about the settings objects that we are creating here. It is fine to just create these whenever you need them. You can also create a global singleton if you prefer, but there is not real need to do so unless you want to monitor the setting for changes.

Next Steps: complex types

Beyond basic types, you have the full power of the GVariant type system available to store complex types. For example, if you need to store information about a circle in the plane, you could store it as a triple of type (ddd) storing the x, y coordinates of the center and the radius.

To handle settings with complex types in your code, use g_settings_get() and g_settings_set(), which return and accept the value in the form of a GVariant.

Next Steps: relocatable schemas

If your application uses accounts, you may want to look at relocatable schemas. A relocatable schema is what you need when you need multiple instances of the same configuration, stored separately. A typical example for this is accounts: your application allows to create more than one, and each of them has the same kind of configuration information associated with it.

GSettings handles this by omitting the path in the schema:

<schemalist>
  <schema id="org.gnome.Recipes.User">
    <key type="b" name="use-metric">
      <default>true</default>
    </key>
  </schema>
</schemalist>

Instead, we need to specify a path when we create the GSettings object:

s = g_settings_new_with_path ("org.gnome.Recipes.User",
                              "/org/gnome/Recipes/Account/mclasen");
if (g_settings_get_boolean (s, "use-metric"))
  g_print ("User mclasen is using metric units");

It is up to you to come up with a schema to map your accounts to unique paths.

Stumbling blocks

There are a few things to be aware of when using GSettings. One is that GLib needs to be able to find the compiled schema at runtime. This can be an issue when running your application out of the build directory without installing it. To handle this situation, you can set the GSETTINGS_SCHEMA_DIR environment variable to tell GLib where to find the compiled schema:

GSETTINGS_SCHEMA_DIR=build/data ./build/src/gnome-recipes

Another stumbling block is that GSettings reads the default values in the XML in the form of a serialized GVariant. This can be a bit surprising for the common case of a string, since it means that we need to put quotes around the string:

<default>'celsius'</default>

But these are minor issues, and easily avoided once you know about them.

Widget hierarchies in GTK+ 4.0

Today we’re going to have guest author Timm Bäder, maintainer of Corebird and a contributor of GTK+, talk about the changes in store for writing composite widgets in GTK+ 4.0.

(Note: Some of the information here is based on branches that have not been merged into master yet, but I’m confident that they will be in the near future)

In GTK+3, only GtkContainer subclasses can have child widgets. This makes a lot of sense for “public” container children like we know them, e.g. GtkBox — i.e. the developer can add, remove and reorder child widgets arbitrarily and the container just does layout.

However, there are more complicated widgets in GTK+3 which don’t inherit from GtkContainer, e.g. GtkSpinButton or GtkSwitch. These never have real GtkWidget children. Consider, for example, the two clickable areas in a GtkSpinButton. I’m not calling them “buttons” here for a reason, since in GTK+3, they are not actual GtkButton instances, as GtkSpinButton is not a GtkContainer. Instead, GtkSpinButton has to work around that fact, and create two GdkWindows for the up/down areas and then render two icons in there; care about hover and CSS states; various button up/down events; and the GdkWindow lifetime, etc. In order to work around the GtkContainer requirement, in GTK+3 we introduced gadgets (GtkCssGadget). On the styling side, a gadget corresponds to a CSS box and therefore represents one node in the CSS tree. On the widget side they were being used to have “widget-like”, CSS-stylable, children for non-container widgets.

GtkWidget Changes

Of course there were plenty of changes needed to support these use cases in GTK+4. I’m not going to list all of them here (in particular the more ugly ones like focus handling), but I think most of them are quire interesting and important for application developers and custom widget authors. Generally, we’re trying to get a away from special cases and go a more general way by re-using widgets wherever we can. So, instead of using a PangoLayout to display text, widgets should use a GtkLabel. If you have a clickable area with button-like semantics, try to use a GtkButton. If you want to lay out widgets in horizontal or vertical orientation, use a GtkBox. This way we have one widget tree that both input and rendering can operate on. In practice, this means mostly getting rid of all the gadgets used inside widgets, as well as standalone GtkCssNode instances.

Iterating over child widgets

While in GTK+3, every container had to implement GtkContainer::forall and one could easily iterate over all the child widgets using gtk_container_foreach() and gtk_container_forall(), in GTK+4, every GtkWidget can have child widgets, so every widget potentially has a list of children we need to draw, measure, size-allocate, etc. In the widget hierarchy, these children and sibling widgets can be accessed through:
  • gtk_widget_get_first_child()
  • gtk_widget_get_last_child()
  • gtk_widget_get_prev_sibling()
  • gtk_widget_get_next_sibling()
So, the easiest possible way of iterating over the child widgets of a given GtkWidget would be (in C):
GtkWidget *widget;
GtkWidget *child;
for (child = gtk_widget_get_first_child (widget);
     child != NULL;
     child = gtk_widget_get_next_sibling (child))
  {
    /* Do stuff with @child */
    g_assert (gtk_widget_get_parent (child) == widget);
  }

Adding widgets to a non-container parent

Every widget in GTK+ (both 3 and 4) saves a pointer to its parent widget. This parent can be set using gtk_widget_set_parent(), and all the GtkContainer::add implementations eventually had to use this function to set the parent of the given child widget.

In GTK+4, gtk_widget_set_parent() still works and adds the widget to the end of the child widget list of the parent. However, we clearly also want to manage the order of the child widgets, as well as where we add new children in the list, so we have:

  • gtk_widget_insert_before()
  • gtk_widget_insert_after()
to add new child widgets before or after a child widget that’s already in the parent’s list. These can also be used to reorder children child widgets by passing a child that already has the given parent set.
Since lots of widgets inside GTK+ currently use composite widget templates in XML form, GtkWidget now also has its very own GtkBuildable::add_child() implementation to support this use case. This is what e.g. GtkFileChooserWidget uses which is almost exclusively defined in XML.

Widget CSS names

Since we frequently need to use arbitrary CSS node names for arbitrary widgets, GtkWidget now has a construct-only property called GtkWidget:css-name that will be used as the css node name if specified. If not, the one passed to the widget’s gtk_widget_class_set_css_name() call will be used. If none of the 2 have been used, the CSS node will simply be called “widget”.

Examples of Converted Widgets

There are already a few widgets in current master (and side-branches) that were converted from using a variety of GtkCssGadgets, GdkWindows and PangoLayouts to using actual widgets. The end-goal being, of course, to re-use widgets as much as possible, therefore reducing code size and maintenance burden.

GtkSwitch

In GTK+3, GtkSwitch is a direct GtkWidget subclass (yay!) that uses a GdkWindow for the input (clicking on the switch will enable/disable it), one GtkCssGadget for the widget itself, two PangoLayouts for the ON/OFF text and another GtkCssGadget for the slider.
In GTK+ master, the switch still has its widget-level GtkCssGadget so it supports min-width/min-height CSS properties and CSS margins, but the slider gadget has been replaced by a GtkButton and the two PangoLayouts are replaced with GtkLabels. This way we can save roughly 300 lines in gtkswitch.c. Theoretically we also have more features and can e.g. use the limited support for the text-decoration CSS property that GtkLabel supports, but I’m just going to doubt that this is very useful.

GtkSpinButton

As noted earlier, GtkSpinButton could easily use actual GtkButtons for the up/down areas, and it does so in GTK+ master (which will become GTK+4 at some point). This gets rid of another 300 lines in gtkspinbutton.c. Through using GtkButtons, the old icon helper gadgets also become actual GtkImage instances. Unfortunately we have to implement some of the GtkGesture magic ourselves here since GtkSpinButton also supports middle and right clicks on its buttons while GtkButton::clicked only reacts to single, primary mouse button clicks.

GtkLevelBar

GtkLevelBar manages a set of blocks and assigns different style classes to them. In GTK+3, these blocks are all GtkCssGadget instances. All of them are “dumb” in the sense that they don’t do anything special — they are just CSS boxes and that’s it. That’s why converting it to use GtkWidgets for all blocks didn’t gain much of a reduction in file size.

 

GtkProgressBar

GtkProgressBar uses gadgets for the trough and the progress nodes. It also uses a PangoLayout to display the percentage or a user-given string.

In master, both trough and progress are widgets and the PangoLayout is a GtkLabel of course. Not having to listen to GtkWidget::style-changed (which gets done automatically for widgets) and not having to draw the PangoLayout ourselves (which the GtkLabel now takes care of) yields a nice code size saving of around 200 lines, however.

 

GtkExpander

GtkExpander is more complex than it looks like. In GTK+3, it consists of 2 GtkBoxGadgets (which is like a GtkBox, but not a widget…), one gadget for the arrow to the left of the title widget, the title widget and the actual content widget. In master, this is done using an actual GtkBox, and a GtkIcon (an internal widget) for the arrow. I’m not sure if this is the best way to express the GtkExpander functionality, e.g. we could also use a GtkButton for the arrow+title widget combination.
Since GtkBoxGadget is already almost a perfect GtkBox clone, the code savings here aren’t very interesting, but not having to listen to GtkWidget::direction-changed once again saves around 30 lines.

Accidental GtkBox & GtkButton subclasses

GTK+3 contains quit a lot of widgets that inherit from another widget for the sole purpose of looking and behaving like them. The problem here is that these widgets also inherit all the API of the parent class, which is only rarely wanted.
For GtkBox, almost all subclasses in GTK+3 are “accidental” in the sense that actually using them as a GtkBox doesn’t make any sense and people usually don’t do it, but they had to be GtkBox subclasses to satisfy GTK+’s GtkContainer requirement. One example of such a widget would be GtkFileChooserWidget. This is already one of the most complex widget to ever exist, but have you ever considered using gtk_container_add() or gtk_box_pack_{start,end}() to add widgets to it? It doesn’t make a lot of sense. It’s a closed entity with its own API. So, in GTK+4 it will be a direct GtkWidget subclass that contains a GtkBox. Or maybe not. That’s just an implementation detail you don’t have to care about. (Fun Fact on the side: GtkFileChooserButton is a GtkBox in GTK+3)
The same applies to GtkButton. In GTK+3, GtkButton has lots of subclasses that inherited all the GtkButton API without actually supporting it. What happens if you remove the child widget from a GtkLinkButton? What if you set the GtkButton:label property of a GtkFontButton? Again, these are closed entities that have their own API to set and get various data and change behavior and/or looks based on them, but that doesn’t mean they support all the GtkButton/GtkContainer shenanigans.

General Restructuring Rules and Future

For this refactoring work we try to keep the CSS node structure as it was in GTK+3, i.e. we try not to break the CSS node tests we currently have in testsuite/css/nodes.c.
A few of the more complex widgets inside GTK+ still heavily rely on gadgets and porting them away to use only actual widgets will be quite a lot of work. GtkRange is historically one of the most complex non-container widgets inside GTK+. It’s used both for scrollbars and scales so porting it to widgets might first need another round of refactoring.
Another interesting case is GtkNotebook, which combines gadget and widget usage. Here we could e.g. use a real GtkStack to switch between pages and effortlessly support page switching transitions.
Another exciting look into the future is of course Carlos’ wip/carlosg/event-delivery branch that gets rid of a ton of GdkWindow instances and makes widget input easier than ever before.

Drag-and-drop in lists

I’ve recently had an occasion to implement reordering of a GtkListBox via drag-and-drop (DND). It was not that complicated. Since I haven’t seen drag-and-drop used much with list boxes, here is a quick summary of what is needed to get the basics working.

Setting up the drag source

There are two ways to make a GTK+ widget a drag source (i.e. a place where clicking and dragging will initiate a DND operation). You can decide dynamically to initiate a drag by calling gtk_drag_begin(). But we go for the simpler approach here: we just declare statically that our list rows should be drag sources, and let GTK+ handle all the details:

handle = gtk_event_box_new ();
gtk_container_add (GTK_CONTAINER (handle),
        gtk_image_new_from_icon_name ("open-menu-symbolic", 1));
gtk_drag_source_set (handle,
        GDK_BUTTON1_MASK, entries, 1, GDK_ACTION_MOVE);

Note that I choose to create a visible drag handle here instead of allowing drags to start anywhere on the row. It looks like this:

The entries tell GTK+ what data we want to offer via drags from this source. In our case, we will not offer a standard mime type like text/plain, but instead make up our own, private type, and also hint GTK+ that we don’t want to support drags to other applications:

static GtkTargetEntry entries[] = {
   { "GTK_LIST_BOX_ROW", GTK_TARGET_SAME_APP, 0 }
};

A little gotcha here is that the widget you set up as drag source must have a GdkWindow. A GtkButton or a GtkEventBox (as in this example) will work. GTK4 will offer a different API to create drag sources that avoids the need for a window.

With this code in place, you can already drag your rows, but so far, there’s nowhere to drop them. Lets fix that.

Accepting drops

In contrast to drags, where we created a visible drag handle to give users a hint that drag-and-drop is supported, we want to just accept drops anywhere in the list. The easiest way to do that is to just make each row a drop target (i.e. a place that will potentially accept drops).

gtk_drag_dest_set (row,
        GTK_DEST_DEFAULT_ALL, entries, 1, GDK_ACTION_MOVE);

The entries are the same that we discussed above. GTK_DEST_DEFAULT_ALL tells GTK+ to handle all aspects of the DND operation for us, so we can keep this example simple.

Now we can start a drag on the handle, and we can drop it on some other row. But nothing happens after that. We need to do a little bit of extra work to make the reordering happen. Lets do that next.

Transferring the data

Drag-and-drop is often used to transfer data between applications. GTK+ uses a data holder object called GtkSelectionData for this. To send and receive data, we need to connect to signals on both the source and the target side:

g_signal_connect (handle, "drag-data-get",
        G_CALLBACK (drag_data_get), NULL);
g_signal_connect (row, "drag-data-received",
        G_CALLBACK (drag_data_received), NULL);

On the source side, the drag-data-get signal is emitted when GTK+ needs the data to send it to the drop target. In our case, the function will just put a pointer to the source widget in the selection data:

gtk_selection_data_set (selection_data,
        gdk_atom_intern_static_string ("GTK_LIST_BOX_ROW"),
        32,
        (const guchar *)&widget,
        sizeof (gpointer));

On the target side, drag-data-received is emitted on the drop target when GTK+ passes the data it received on to the application. In our case, we will pull the pointer out of the selection data, and reorder the row.

handle = *(gpointer*)gtk_selection_data_get_data (selection_data);
source = gtk_widget_get_ancestor (handle, GTK_TYPE_LIST_BOX_ROW);

if (source == target)
  return;

source_list = gtk_widget_get_parent (source);
target_list = gtk_widget_get_parent (target);
position = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (target));

g_object_ref (source);
gtk_container_remove (GTK_CONTAINER (source_list), source);
gtk_list_box_insert (GTK_LIST_BOX (target_list), source, position);
g_object_unref (source);

The only trick here is that we need to take a reference on the widget before removing it from its parent container, to prevent it from getting finalized.

And with this, we have reorderable rows. Yay!

As a final step, lets make it look good.

A nice drag icon

So far, during the drag, you just see just the cursor, which is not very helpful and not very pretty. The expected behavior is to drag a visual representation of the row.

To make that happen, we connect to the drag-begin signal on the drag source:

g_signal_connect (handle, "drag-begin",
        G_CALLBACK (drag_begin), NULL);

…and do some extra work to create a nice ‘drag icon’:

row = gtk_widget_get_ancestor (widget, GTK_TYPE_LIST_BOX_ROW);
gtk_widget_get_allocation (row, &alloc);
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
                                      alloc.width, alloc.height);
cr = cairo_create (surface);
gtk_widget_draw (row, cr);

gtk_drag_set_icon_surface (context, surface);

cairo_destroy (cr);
cairo_surface_destroy (surface);

This looks more complicated than it is – we are creating a cairo surface of the right size, render the row widget to it (the signal is emitted on the handle, so we have to find the row as an ancestor).

Unfortunately, this does not yet yield a perfect result, since list box rows generally don’t render a background or frame. To work around that, we can temporarily add a custom style class to the row’s style context, and use some custom CSS to ensure we get a background and frame:

context = gtk_widget_get_style_context (row);
gtk_style_context_add_class (context, "drag-icon");
gtk_widget_draw (row, cr);
gtk_style_context_remove_class (context, "drag-icon")

As an extra refinement, we can set an offset on the surface, to prevent a visual ‘jump’ at the beginning of the drag, by placing this code before the gtk_drag_set_icon_surface() call:

gtk_widget_translate_coordinates (widget, row, 0, 0, &x, &y);
cairo_surface_set_device_offset (surface, -x, -y);


Voila!

Next steps

This article just shows the simplest possible setup for row reordering by drag-and-drop. Many refinements are possible, some easy and some not so easy.

An obvious enhancement is to allow dragging between different lists in the same application. This is just a matter of being careful about the handling of the list widgets in the drag_data_received() call, and the code I have shown here should already work for this.

Another refinement would be to drop the row before or after the target row, depending on which edge is closer. Together with this, you probably want to modify the drop target highlighing to indicate the edge where the drop will happen. This could be done in different ways, but all of them will require listening to drag-motion events and juggling event coordinates, which is not something I wanted to get into here.

Finally, scrolling the list during the drag. This is important for long lists, if you want to drag a row from the top to bottom – if the list doesn’t scroll, you have to do this in page increments, which is just too cumbersome. Implementing this may be easiest by moving the drag target to be the list widget itself, instead of the individual rows.

References