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

The GTK+ Inspector

Many GTK+ users and developers have already heard of the GTK+ Inspector, a tool to inspect, modify and understand GTK+ applications. The Inspector is extremely powerful, and allows theme designers to test CSS changes on-the-fly and magnify widgets to see even the smallest details, lets developers check the application widgets and their properties, and lets users to play (and eventually break) applications.

In this article, we’ll explore the GTK+ Inspector and show what can you do with it.

Prelude

Since the Inspector is a debugging tool, it is disabled by default. To start using the Inspector, you first have to enable it. You can easily do that with DConf Editor:

Enabling the Gtk+ Inspector with DConf Editor
Enabling the GTK+ Inspector with DConf Editor

Alternatively, you can use the terminal to enable it. To do that, run the following command:

$ gsettings set org.gtk.Settings.Debug enable-inspector-keybinding true

Done! The Inspector is now enabled!

Opening the Inspector

Now that the Inspector is enabled, you want to run it. The Inspector is always associated with an application. Let’s use GNOME Calendar for example:

GNOME Calendar
The GNOME Calendar application

There are multiple ways to bring up the Inspector. You can open it while using the application, by typing <Ctrl> + <Shift> + D (or <Ctrl> + <Shift> + I to automatically select the widget under the mouse pointer). Alternatively, you can launch the application from the terminal with the environment variable GTK_DEBUG=interactive.

The Inspector will open, and you’ll see the following window:

Inspector on Calendar
The Inspector window over GNOME Calendar

And that’s all you have to do. Now let’s explore the various functions that the Inspector has to offer.

Exploring the Inspector

At first, the overwhelming number of buttons and tabs can confuse those who are not well-versed in the art of inspecting applications. A quick explanation of the tabs, in order:

  • Objects: exposes the widgets of the application, and allows editing properties and seeing detailed information about each widget. Explained below.
  • Statistics: shows miscellaneous statistics of the application. You need to run the application with GOBJECT_DEBUG=instance-count.
  • Resources: shows the various resources that are embedded in the application binary, such as custom icons or GtkBuilder files, among others.
  • CSS: allows testing CSS live. Explained below.
  • Visual: controls some visual aspects of the application, such as the text direction, dark/light variant, the theme, the scaling factor, etc.
  • General: shows miscellaneous information about the GTK+ application (and the session it is running in).

Let’s dissect the main window of the GTK+ Inspector:

Inspector window
The main Inspector window

Those 4 annotated sections of the Inspector are the most commonly used ones. Theme designers will want to check (3) and (4), while developers usually use (1) and (2).

Inspecting widgets

For developers, the Inspector shows its usefulness by letting you change the properties of any widget on the screen. Let’s start by clicking the first button and selecting a widget using the mouse cursor:

Selecting widgets
Selecting a widget with the Inspector

You can now easily change the properties of that widget by browsing the Objects > Properties tab. You can change, for example, the visibility of a widget, the text of a label, and much more!

Editing a widget property
Editing a widget property

Now that you know how to inspect a GTK+ application, play around and explore how many applications are organized. Change the widgets’ properties and see what happens. Most of the times, this is safe and won’t break your GNOME session, or freeze your computer!

Editing CSS

The Inspector is a powerful tool for designers too. One of the greatest features it has is the live CSS editor. Let’s start by going to the CSS tab:

CSS Editor
The Inspector CSS Editor view

Let’s play with CSS! Paste the following CSS code and see what happens:

window stack {
    background-color: orange;
}

Whoa! The window became alien! That CSS code changes the background color of any GtkStack widget inside a GtkWindow. If you want to learn more about CSS selectors and how GTK+ is using CSS for theming, there are some useful links at the end of this article.

The cautious reader may ask: what is the hierarchy of CSS elements? How can I see which CSS elements are available?

Fear not! GTK+ Inspector allows you to easily check the CSS hierarchy at the Objects > CSS Nodes tab.

CSS Nodes
The CSS Nodes tab

GTK+ widgets have documented CSS names. You can browse the GTK+ documentation to see how widgets are organized, and how you can use CSS to control the various aspects of the widgets.

Not sure if your CSS changes are perfect? Let’s magnify the widget to make sure we don’t miss any detail:

Zooming widget using Magnifier
Zooming a widget using the Magnifier tab

Looking good? Join -design and share your awsome CSS snipplets with the community!

Wrapping up

While this article explores some of the biggest aspects of the GTK+ Inspector, this is by far not an exhaustive list of all the features of the Inspector. After reading this article, however, you’ll hopefully be able to open the Inspector and explore more of its awesome power on your own.

Doubts? Comments? Suggestions? Stop by and leave a comment, join #gtk+ channel at the GNOME IRC network and let us know what you think!

Useful Links

This week in GTK+ – 33

The past two weeks we’ve had DevConf and FOSDEM back to back, so the development slowed down a bit. Expect it to pick up again, now that we’re close to the GNOME 3.24 release.

In these last two weeks, the master branch of GTK+ has seen 34 commits, with 20973 lines added and 21593 lines removed.

Planning and status
Notable changes

On the master branch:

  • Timm Bäder removed gtk_widget_class_list_style_properties() in the continuing effort to deprecate the style properties inside GtkWidget and replace them with CSS properties
  • Timm also moved some of the state used only by GtkToggleButton subclasses into those types
  • William Hua improved the Mir GDK backend for proper positioning of menus
Bugs fixed
  • 777547 Notebook arrow icon wrong color after closing final tab
  • 773686 Software when launched shows in dash with wrong icon, name and menu
  • 775864 getting-started: typo tie->the
  • 778009 menu drawn on top of menubar in Fedora
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.

This week in GTK+ – 32

In this last week, the master branch of GTK+ has seen 106 commits, with 7340 lines added and 12138 lines removed.

Planning and status
  • Matthias Clasen released GTK+ 3.89.3
  • The GTK+ road map is available on the wiki.
Notable changes

On the master branch:

  • Benjamin Otte simplified the clipping shaders for the Vulkan renderers
  • Benjamin also removed the “assume numbers without dimensions are pixels” fallback code from the CSS parser
  • Daniel Boles landed various fixes to the GtkMenuGtkComboBox and GtkScale widgets
  • Daniel also simplified the internals of GtkComboBox and moved most of its internal widgets to GtkBuilder UI files
  • Matthias Clasen removed command line argument handling from the GTK+ initialization functions; gtk_init() now takes no arguments. Additionally, gdk_init() has been removed, as GDK ceased to be a separate shared library. The recommended way to write GTK+ applications remains using GtkApplication, which handles library initialization and the main loop
  • Timm Bäder merged his branch that makes GtkWidget visible by default, except for the GtkWindow and GtkPopover classes; Timm also removed gtk_widget_show_all() from the API, as it’s not useful any more
  • Timm modified GtkShortcutsShortcut, GtkFileChooserButton, and GtkFontButton to inherit directly from GtkWidget, taking advantage of the new scene graph API inside the base GtkWidget class

On the gtk-3-22 stable branch:

  • Ruslan Izhbulatov fixed the Windows backend for GDK to ensure that it works with remote displays
Bugs fixed
  • 777527 GDK W32: Invisible drop-down menus in GTK apps when working via RDP
  • 770112 The documented <alt>left shortcut doesn’t work on Wayland
  • 776225 [wayland] dropdown placed somewhere in the screen
  • 777363 [PATCH] wayland: avoid an unnecessary g_list_length call
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.

This week in GTK+ – 31

In this last week, the master branch of GTK+ has seen 52 commits, with 10254 lines added and 9466 lines removed.

Planning and status
  • Alex Larsson is working on two separate branches to optimize the memory allocation and fragmentation when building the GSK render tree, after profiling the tree building code
  • Timm Bäder is working on a topic branch to switch widgets to be visible by default
  • The GTK+ road map is available on the wiki.
Notable changes

On the master branch:

  • Rui Matos added support in the Wayland backend for the gtk-enable-primary-paste settings key; this requires a newer version of the gsettings-desktop-schemas
  • Matthias Clasen and Alex Larsson refactored some of the GSK,  GtkWidget, and CSS internals to avoid excessive type casting and type checking after profiling the rendering code
  • Matthias added a “system” tab to the GtkAboutDialog widget, for free-form, system-related information
  • Matthias also updated the porting documentation for -gtk-icon-filter
  • Benjamin Otte changed the X11 backend to always call XInitThreads() unconditionally, in order to safely use the Vulkan rendering API; this should be safe, but testing is encouraged
  • Benjamin updated the GtkSnapshot API to ensure that render nodes are available only after the snapshot is complete
  • Benjamin also fixed the handling of CSS images that have no explicit size but should be scaled according to their aspect ratio
  • Timm Bäder added a revealed property to the GtkInfoBar widget and ported the GtkActionBar code to use it

On the gtk-3-22 stable branch:

  • Ruslan Izhbulatov worked on fixing various cases of keyboard handling under Windows, including interaction with AeroSnap; Ruslan also fixed bug 165385, which was going to be 12 years old in 10 days
  • Carlos Garnacho changed the EGL handling inside the Wayland backend to disable swap interval, as the compositor is in charge of timing the rendering
  • Matthias Clasen deprecated additional API that has been removed from the development branch
Bugs fixed
  • 776031 W32: Winkey+down minimizes maximized window instead of restoring it
  • 165385 Win32 keyboard handling still incomplete
  • 769835 On Wayland, application containing GtkGLArea stops responding if it’s not on current workspace
  • 774726 GtkTreeView dnd: gtk_drag_finish remove row when reorder sinse 3.20
  • 769214 keyval field not filled correctly for Pause key
  • 776485 GDK W32: Impossible to restore maximized window via system menu
  • 776604 about dialog: Add a “system” tab
  • 775846 gdk/wayland: Add support for the gtk-enable-primary-paste gsetting
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.

This week in GTK+ – 30

In this last week, the master branch of GTK+ has seen 81 commits, with 12205 lines added and 12625 lines removed.

Planning and status
  • Welcome back to This Week in GTK+ after the end of the year break
  • The GTK+ road map is available on the wiki.
Notable changes

On the master branch:

  • Timm Bädert merged his work on moving the scene graph of widgets directly into the GtkWidget class; this allows widgets to have internal children without necessarily subclassing GtkContainer
  • Timm also worked on porting widgets currently using the internal CSS gadget API to be composite widgets, like GtkSwitch
  • Benjamin Otte and Georges Basile Stavracas Neto have been working on making the Vulkan GSK renderer work on Wayland
  • Benjamin also worked on improving the efficiency of the Vulkan renderer
  • William Hua worked on improving the Mir backend of GDK with regards to clipboard support

On the gtk-3-22 stable branch:

  • Matthias Clasen released GTK+ 3.22.6
Bugs fixed
  • 776627 Correct PostScript capitalization
  • 776868 Improve the documentation of GtkEntry:attributes
  • 776560 icon-browser: window opens at very narrow size, only showing 1 column of icons
  • 775732 mir: clipboard support missing
  • 776736 build: Fix vulkan detection
  • 776807 GtkInspector doesn’t show up when Gtk is initialized through option group
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.

This week in GTK+ – 29

In this last week, the master branch of GTK+ has seen 20 commits, with 883 lines added and 2740 lines removed.

Planning and status
  • Alex Larsson worked on a simplification of GdkWindow that removed native and foreign child windows; the long term plan is to have native windowing system surfaces only for top levels
  • Alex also sent a review on gtk-devel-list of Benjamin’s wip/otte/rendernode branch, with ideas on future work for the GSK rendering API
  • Chun-wei Fan updated the Windows backend to ensure that it continues to build and work on the master branch
  • Benjamin Otte implemented the snapshot() virtual function in more GtkWidget subclasses.
  • The GTK+ road map is available on the wiki.
Notable changes

On the master branch:

  • Olivier Fourdan updated the Wayland backend to ensure that empty input shapes are updated on sub-surfaces when needed; this allows other toolkits, like Clutter, to use the GDK sub-surface API
  • Alex Larsson removed gdk_window_reparent() from the GDK API, since it’s unused and allows for the goal of only having top level GDK windows
  • Benjamin Otte removed the ad hoc code from GdkCellView to modify its background, as the cell view can use CSS to achieve the same (or better) results
  • Benjamin also removed the border node from the GtkFrame CSS nodes, as it performed additional immediate mode clipping that complicates the rendering

On the gtk-3-22 stable branch:

  • Emmanuele pushed fixes for the GL rendering when using GtkGLArea with OpenGL ES 2.0 implementations that are missing the GL_EXT_framebuffer_blit extension
Bugs fixed
  • 776132 Mention the difference between gdk_window_create_similar_image_surface and cairo_surface_create_similar_image
  • 774534 [wayland] input shape and opaque region not applied without begin_paint()/end_paint()
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.

This week in GTK+ – 28

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

Planning and status
  • Benjamin Otte is working on a refactoring of the GSK render node API
  • Emmanuele Bassi worked on a 3.x-specific branch that allows GDK to use EGL instead of GLX on X11 platforms
  • The GTK+ road map is available on the wiki.
Notable changes

On the master branch:

  • Benjamin merged the Vulkan renderer for GSK, as comparison point for the GL and Cairo renderers. The Vulkan renderer adds a new optional dependency on a working Vulkan implementation, as well as glslc
  • The Vulkan renderer also resulted in a large refactoring of the GL drawing code inside GDK, though this should not have caused any user-visible changes in the API
  • Benjamin also implemented support for the CSS border-spacing property following the CSS 2.1 specification

On the gtk-3-22 stable branch:

  • Matthias released GTK+ 3.22.5.
Bugs fixed
  • 775651 GdkX11-4.0.gir, GdkWin32-4.0.gir, and Gsk-4.0.gir are generated before Gdk-4.0.gir is ready
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.

This week in GTK+ – 27

In this last week, the master branch of GTK+ has seen 65 commits, with 1780 lines added and 2429 lines removed.

Planning and status
  • A GTK+ hackfest is planned for March 2017; the main topic is going to be improving performance, especially with regards with rendering
  • Benjamin Otte is reworking the GL renderer for GSK to ensure it’s well integrated into the GDK rendering
  • Additionally, Benjamin is also working on an experimental Vulkan-based renderer for GSK
  • Timm Bäder is changing the GtkWidget and GtkContainer API to converge the two classes into a single scene graph
  • The GTK+ road map is available on the wiki.
Notable changes

On the master branch:

  • Matthias Clasen changed GTK to exit cleanly when it loses the display connection under X11 and Wayland; this change has also been backported to the stable gtk-3-22 branch
  • Timm Bäder cleaned up various widgets to consistently use gtk_widget_measure() when measuring the size of their children
  • Daniel Boles fixed a bug in GtkComboBox that caused a warning in Debian-based distribution that applied a downstream patch; see bug 771242 for more information; this change has also been backported to the stable gtk-3-22 branch

On the gtk-3-22 stable branch:

  • Lapo Calamandrei backported the GtkProgressbar theme fixes for correctly identifying empty and full states
Bugs fixed
  • 774915 [Wayland]: Destroying the parent of a subsurface causes _gdk_window_destroy_hierarchy: assertion failed
  • 771242 3.21: opening menu for certain types of GtkComboBox causes Gdk-CRITICAL assertion ‘GDK_IS_WINDOW (window)’ failed
  • 775410 gsk/Makefile.am runs g-ir-scanner before libgsk-4.la is linked
  • 775319 [Wayland]: gdk_window_get_toplevel() fails to return the toplevel of a child subsurface
  • 775316 gtk_drag_source_set_icon_pixbuf references the pixbuf received once too much
  • 775212 GtkScaleButton does not unref all GtkAdjustment it references
  • 775525 gtk_flow_box_get_child_at_index shouldn’t crash with an invalid index
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.

This week in GTK+ – 26

In this last week, the master branch of GTK+ has seen 40 commits, with 1551 lines added and 1998 lines removed.

Planning and status
  • Matthias Clasen released the first GTK+ 3.89 development snapshot
  • The GTK+ road map is available on the wiki.
Notable changes

On the master branch:

  • Andrew Chadwick landed a series of fixes for graphic tablets support on Windows
  • Benjamin Otte removed the gtk_cairo_should_draw_window() utility function; the function was introduced for compatibility in the 3.x API, but now it’s not necessary any more
  • Benjamin also removed gdk_window_process_updates() and gdk_window_process_all_updates(); GDK has long since been switched to a frame clock; additionally, only top level GdkWindow can be used as a rendering surface
  • Lapo Calamandrei updated the High Contrast and Adwaita theme with the recent round of CSS improvements for progress bars and gradients
Bugs fixed
  • 774114 Window shadows are repainted even if only the contents of the window change
  • 774695 GtkProgressbar needs full and empty classes
  • 774265 No tilt for wintab devices
  • 774699 [wintab, potential segfault]: list iteration regression causes odd-indexed devices to be ignored during lookup & e.g. present no pressure
  • 775038 Build: Add wayland to GSKs dependencies
  • 774917 [wayland] child subsurfaces need to be placed relative to their parent
  • 774893 Application font sizes scaling gets clamped to 1.00 when starting GtkInspector
  • 774939 GtkLabelAccessible: Initialize link before setting parent
  • 774760 inspector: ensure controller is a GtkGesture
  • 774686 GtkMenu does not unref all GtkCheckMenuItem it creates
  • 774743 GtkNotebook does not unref all GtkBuiltinIcon it creates
  • 774790 GtkTextHandle does not unref all GtkAdjustment it references
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.