Getting the details right

I’ve recently explained how GTK+ does quite a few things for you out of the box.

  • Theming ? You got it.
  • Accessibility ? You’re covered.
  • Keynav ? Sure.

But as it turns out, default implementations can’t always provide the optimum. To go from an application that works ok to one that is gets the details just right, some fine-tuning may be required.

Today, I want to take a look at a few examples of such fine-tuning for keyboard navigation, in particular around lists. I hope this also shows how you can learn tricks and borrow well-working code from other applications. If you ask yourself

How did they do this, and why does my app not do this ?’

look at the source! We all do it, and don’t feel bad about it.

Why lists ? They come in all sizes and shapes, from straightforward and simple to interactive and complex. It is no wonder that GtkTreeView with its supporting classes has around 40000 lines of code.

Connected lists

My first example is about segmented lists of controls. These have become more common in gnome-control-center panels. Here is the accessibility panel:

Accessibility panelWhen you use the arrow keys to navigate among the buttons, the default behavior of GTK+ is to stop when you come to the edge of the container. But in the situation above, we all would expect the focus to jump from the first list to the second.

Thankfully, GTK+ emits a ::keynav-failed signal when you use the arrow keys to go beyond the end of a container, and we can use this to our advantage:

static gboolean
keynav_failed (GtkWidget        *list,
               GtkDirectionType  direction,
               CcUaPanel        *self)
{
  CcUaPanelPrivate *priv = self->priv;
  GList *item, *sections;
  gdouble value, lower, upper, page;

  /* Find the list in the list of GtkListBoxes */
  if (direction == GTK_DIR_DOWN)
    sections = priv->sections;
  else
    sections = priv->sections_reverse;

  item = g_list_find (sections, list);
  g_assert (item);
  if (item->next)
    {
      gtk_widget_child_focus (GTK_WIDGET (item->next->data), direction);
      return TRUE;
    }
...
}

We use this signal handler on every list:

g_signal_connect (list, "keynav-failed",
                  G_CALLBACK (keynav_failed),
                  self);

And thats all! Here is a quick video of this in action (I’m repeatedly using the Down arrow key):

If you watch closely, you’ll notice another fine point of this example – we scroll the panel to keep the focus location visible. This functionality is built into GTK+’s container widgets, and we activate it by setting a focus adjustment on the box that contains all the lists:

adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (panel));
gtk_container_set_focus_vadjustment (GTK_CONTAINER (content), adjustment);

These code examples were taken from cc-ua-panel.c in gnome-control-center.

The same trick is also used in the gnome-control-center overview to allow arrow keys to move between several icon views.

Tabbing out

GTK+ uses the Tab key to connect all active UI elements into a focus chain.  The default behavior of GtkListBox is to put all rows into the focus chain – that makes a lot of sense for the previous example where each row contains controls such as buttons, or brings up a dialog when activated.

Sometimes, it is more natural to treat a list as a single item in the focus chain, so that the next Tab key press takes you out of the list. The list content will still be keyboard-accessible with the arrow keys.

A sidebar like in gnome-logs is an example where this makes sense:

A sidebar listTo achieve this behavior, we can override the focus vfunc of our GtkListBox subclass:

widget_class->focus = gl_category_list_focus;

with a function that special-cases Tab key presses:

static gboolean
gl_category_list_focus (GtkWidget *listbox, 
                        GtkDirectionType direction)
{
  switch (direction)
    {
    case GTK_DIR_TAB_BACKWARD:
    case GTK_DIR_TAB_FORWARD:
      if (gtk_container_get_focus_child (GTK_CONTAINER (listbox)))
        {
          /* Force tab events which jump to
           * another child to jump out of the
           * category list.
           */
          return FALSE;
        }
...
}

This code example was adapted from gl-categorylist.c

A back button

The last example does not involve lists, but a simple Back button. For example, gnome-software has one:

A back buttonYou will probably add a mnemonic to the button label, so it can be activated using the Alt-B shortcut. But your users will also expect the Back key on their keyboard to work, and many will probably try Alt-Left as well, since that is what they use in their web browser.

Key events in GTK+ bubble up from the focus widget, and until they are definitively handled by one of the intermediate containers, they eventually reach the toplevel GtkWindow. Therefore, to make the Back key work regardless where the focus currently is, we can override the key_press vfunc of the window:

static gboolean
window_key_press_event (GtkWidget *win,
                        GdkEventKey *event,
                        GsShell *shell)
{
...
  state = event->state & 
       gtk_accelerator_get_default_mod_mask ();
  is_rtl = gtk_widget_get_direction (button) == GTK_TEXT_DIR_RTL;

  if ((!is_rtl && state == GDK_MOD1_MASK &&
        event->keyval == GDK_KEY_Left) ||
      (is_rtl && state == GDK_MOD1_MASK && 
        event->keyval == GDK_KEY_Right) ||
      event->keyval == GDK_KEY_Back)
    {
      gtk_widget_activate (button);
      return GDK_EVENT_STOP;
    }

  return GDK_EVENT_PROPAGATE;
}

If you pay attention to detail, you’ll notice that we use Alt-Left or Alt-Right, depending on the text direction — your Hebrew-speaking users will appreciate.

This code example was taken from gs-shell.c

On portability

There has been a lot of hand-wringing lately about how GNOME is supposedly forcing everybody to use systemd, and is not caring about portability.

That is of course not true at all.

Theory

Portability has been a recurring topic in the discussions of the GNOME release team (of which I am a member). We don’t make it a secret that modern Linux is the primary target that we are developing on and for. And we are aiming to use technologies that are best suited for the task at hand – which has led us to use more of the services implemented by systemd (some of which are direct descendants of earlier mechanisms shipped with gnome-settings-daemon).

But we are happy for everybody who wants to try GNOME on other platforms, and we have a strong interest in making GNOME work well there.

Reasonable patches to help with this are always welcome. The shape of those patches can vary from case to case: it could be reduced functionality, alternative backends, or a shim that puts systemd-like interfaces in place.

Practice

To show that these are not just empty words, here is a screenshot of GNOME running on FreeBSD:

GNOME on FreeBSDThe screenshot was provided by Ryan Lortie, who has done a lot of work on making jhbuild work on FreeBSD.

And here is a video showing GNOME running on OpenBSD, courtesy of Antoine Jacoutot:

GNOME on OpenBSD

Pointers

Go here  to read more about the release team position on portability.

If you are interested in helping out with making and keeping GNOME running on more platforms, this page is another good place to go. It lists platforms that are well supported by GLib.

Connecting the old with the new

I spent some time recently on modernizing the look of  the application chooser in GTK+. Here is how it will look in 3.12:

appchooser-new

appchooser-fail2

As you can see, I added a search button, as it was showing up in the mockup I was working from.  Of course, I had to come up with some answer for how to make it do the expected thing. The application chooser has always supported typeahead search: if the focus is on the list, just type to search. This is an old feature of GtkTreeView:

appchooser-typeahead

But we’ve recently added a nice new search bar widget to GTK+, and I wanted to see if I can’t combine the old treeview search with the new search bar:

appchooser-searchbarThankfully, it turns out that this is very easy. You just  call

gtk_tree_view_set_search_entry (treeview,
                                search_entry)

and all the right things are happening automatically behind the scenes. The full commit that does this in the app chooser looks a bit more complicated, but that is mainly because the app chooser is broken up into separate dialog and widget classes.

If you have a treeview that could benefit from a more explicit search option, you should consider doing something like this.

Here is a video that shows this in action:

A quick glance at GNOME 3.11.5

The GNOME 3.12 cycle was a little lighter in terms of screenshot-friendly feature work, with lots of effort going ‘under the covers’: Wayland porting, developer documentation improvements, application installation infrastructure, etc. But I still managed to find a few things worth showing while smoketesting the 3.11.5 release this morning.

System status refinements

The system status area was all new in 3.10, so naturally, there was some follow-up to incorporate feedback that we’ve received on the new implementation. One point that was raised by many people is that they rely on the system status area to know about wired network connections. So, we’re bringing it back:

System status refinements

 

Airplane mode improvements

Another thing we’re correcting is the subpar integration of airplane mode in the wifi submenu. In 3.10, the ‘Select network’ dialog was unaware of airplane mode. Now, it offers to turn wifi on when needed:

Wifi selectionDesktop file actions

In another corner, applications can now provide ‘static actions’ in their desktop files. This is useful for actions that are meaningful when the application is not running, mainly alternative ways to launch the application.

These actions are now included in the right-click menu of applications in the overview:

Application actionsIt looks like this in the desktop file:

[Desktop Action NewDocument]
Name=New Document
Exec=libreoffice --writer
X-TryExec=oowriter

Thats all! GNOME 3.11.5 will be out later today, so you can try these things out for yourself.