Showing off GtkComboBox rendering

This is the next installment on the subject of GtkCellArea hacking (which I introduced last week).

After a raw struggle with GtkComboBox and some final brain-wracking problem solving I was able to finally deliver a new and improved GtkComboBox with much more flexibility in terms of rendering cells.

Of course you’ve all seen what GtkTreeMenu does last week, so there’s not much left to show… except I did have to do a bit of problem solving to make sure it renders properly when cells are oriented vertically as well, even when their height is not fixed over a collection of GtkTreeModel rows (or, menu items).

The end compromise is that cell alignments (i.e. particularly the alignments of cells relative to the same cells in adjacent rows) can only be done when the collection of rows have a fixed size in the said orientation. If that’s not clear enough, take for example a treeview; in a treeview column every row gets the same width… in this case it’s possible to align cells with the same cell’s position above and below it in adjacent rows. However if the column cells are oriented vertically, space is not reserved vertically to match the space used in rows above and below… that can only be done if the treeview is set to use a fixed row height. (actually, it would be technically possible to do where only the last cell defines the difference in height, but that would be very tricky both for the GtkCellArea to handle as well as for the calling GtkTreeView widget, for reasons I wont get into in this post, in the end I think its a very logical tradeoff).

So, here’s some illustration of the combo box in action, delegating a large portion of the work to GtkTreeMenu.

GtkComboBox using GtkTreeMenu using GtkCellArea

For convenience sake, instead of adding lots of code to testcombo.c I just added a combo-box reflecting the same model and cell area already defined in testtreemenu:

The initial test case seen when starting up ./tests/testtreemenu

Note in this shot that the combo box is taller than the contents but is still at it’s minimum size. This was always a requirement because the combo box should not start changing size depending on what item is selected (of course). However now instead of some jumbled iterative code in gtkcombobox.c, this is achieved by telling the cellview it packs onto itself to “fit-model”. The GtkCellView in turn assumes the job of properly requesting enough space to fit the entire GtkTreeModel for any selection it might receive.

Heres another basic shot with another row selected (with the tallest / longest wrapping text):

Here the first cell has some short text and the last cell wraps to the tallest height.

This row constitutes the tallest size for the combo box and is the reason for requesting so much space. Note that since we’ve set the third cell to “align” with adjacent cells, there is a gap between the second and third cell (Actually if you fire the test up yourself you will find the effect quite nice when using keynav or mouse scroller to navigate through items, since the last cell always stays aligned with previously shown selections).

But the part that hurt my brain the most to get right was…

Vertically stacked Cells

The same combo box with cells stacked vertically

In the case of vertically stacked cells, alignments only work on the GtkCellView that is displayed on top of the combo box. Since the GtkCellView on top needs to request enough space for any row in the model, it can afford to allocate a fixed height to the underlying GtkCellArea which does the rendering (i.e. the height used to display every row of data is a constant height).

However a compromise is made for the menu items, since they naturally use a different height for every menu item the alignments are thrown away and ignored (expand space is also irrelevant here because a menu never receives more space than it requested).

The combo-box's drop-down GtkTreeMenu with vertically stacked cells

As you can see here every individual menu item uses only exactly the height which it needs.

Now, of course this example is only here to show the potential of what can be done with new menus and combo boxes… the above example is not exactly a “nice” menu to render with cells vertically stacked, however vertically stacking menus might still be attractive.

Or better yet, if your really hard-core you could take it to the next level and write a tabular GtkCellArea and use that to render the cells in your combo-boxes and menus (for instance, a thumbnail on the left and then some vertically stacked text/icon on the right might be interesting).

Although at Openismus we were only particularly interested in achieving proper alignments of cells in combo boxes and menus (for the normal case of multiple cells rendered horizontally), GtkCellArea brings alot of new stuff into the game of rendering cells.

Stay tuned for the actual GtkTreeView using GtkCellArea to render its cells ๐Ÿ™‚

Update

Murray wanted a normal screen shot displaying how a basic combo-box drop down menu can be properly aligned so I went ahead and added a (simpler) test case to testcombo this time displaying aligned cells in the drop down menu.

This is the smallest size of the testcombo application because of other widgets in the window. In this case both cells are set to "align" and to "expand".
Both cells in the combo box are set to align and expand here.

Here we have a normal combo box displaying more than a single column and the cells are setup to “align” with cells in adjacent rows (note that the “align” child property of cells is meaningless for the first cell in a box, because it has no preceding cells it is always placed at the beginning of the allocation… however this might not be the case for right-to-left text direction, I still need to finalize the details of rtl layouting).

Introducing GtkCellArea

Good morning, afternoon, night Planet… I haven’t been regularly blogging and so sorry to start off with a weekend post but surely I will have some more posts following in the week.

This post is something of a follow up on this post, the height-for-width GtkTreeViews which never landed in GTK+ last summer are now on the verge of landing… after a lot of work internally refactoring GTK+ cell layouting algorithms, here’s a peek at what we’ve been doing at Openismus for GTK+.

What is GtkCellArea ?

You can read a detailed thread on gtk-devel-list where I originally proposed the new API, however I’ll include a brief overview here in this post.

Essentially GtkCellArea is an abstract container class for rendering GtkCellRenderers and is meant to abstract the layouting of cells in an area to be rendered onto a widget… one of the goals here is to completely centralize the cell layouting code in GTK+ so that widgets like GtkTreeView and GtkIconView don’t have to do the work of rendering individual cells manually. Another main goal is to provide an abstract coding interface to an area of cells; this opens up the road for cells to be rendered in different ways than your typical horizontally oriented list of cells that GtkTreeViewColumn currently does.

GtkCellArea “stuff” is composed of the following classes:

  • GtkCellArea: an abstract class to render cells in an area
  • GtkCellAreaContext: an abstract class to hold geometrical context of size requests and allocations over a series of GtkTreeModel rows.
  • GtkCellAreaBox: the first concrete GtkCellArea which is an orientable ‘box’, when oriented horizontally it behaves like a GtkTreeViewColumn
  • GtkCellAreaBoxContext: the GtkCellAreaContext created and used by GtkCellAreaBox.

Some things the GtkCellArea currently does:

  • Implements packing “cell properties” for GtkCellRenderers. GtkCellArea subclasses can declare packing properties to define how a cell is to be layed out in the area (this is analogous to how GtkContainer subclasses define child placement with its packing properties).
  • Provides GtkBuilder support to setup packing “cell properties” when <cell-packing> tags are specified in the children of a GtkCellLayout widget (also in the same way GtkContainer does it).
  • Provides a focus navigation interface analogous to GtkWidgetClass->focus so that cell area can navigate focus in a semantically similar way that GtkWidget does (this of course simplifies the work for widgets that render cells when navigating focus internally from cell to cell and area to area).
  • Provides cell-editing logic to bookkeep what is the currently edited cell and editing widget and a method to cancel the current edit, essentially a widget that renders cells only has to handle the GtkCellArea::add_editable/GtkCellArea::remove_editable signals.
  • Provides a method to handle events, currently this is only used to activate/start editing cells inside an area where only the area itself can know where the cell is positioned, but potentially it can provide a way for GtkCellArea to also render widgets into a treeview or another cell layouting widget (provided that the widget sends all the important events to the cell area in focus or at the pointer position).

So, enough of the terse technical jargon, lets move on to some snapshots of the GtkCellArea in action.

GtkCellArea in action

The following screen shots were taken from the treeview-refactor branch which includes the initial GtkCellArea work. The initial test case for GtkCellArea is tests/testcellarea; it’s compiled with a ‘mock treeview’ called cellareascaffold.c, without being scrollable or doing any of the fancy treeview features such as rubberband selction or drag-n-drop, I’m happy to say it gets the treeview basics done in less than 1500 lines of code.

Here is the first shot of what you see when you fire up testcellarea:

The "testcellarea" test at its smallest width

An interesting thing to note here is that the second GtkCellRendererPixbuf cellย  (the icons) is not “aligned” while the third cell with the wrapping text is aligned, alignments with the GtkCellAreaBox are optional and implemented as a packing property of the area.

As the CellAreaScaffold testing widget does not scroll, I’ve just packed it into a GtkFrame to show how vertical space can be relinquished when given a wider allocation:

Stretched test case, note the entire widget does height-for-width geometry by calculating the height-for-width of the are for every row.

The GtkCellArea of course also properly handles configuration of expanding cells (also via a child “cell packing” property):

In this test case the second cell "expands", however the test program allows configuration of the cell properties so you can see what are the effects of aligning and expanding individual cells for yourself.

And of course, the alignment of every cell can also be configured:

In this case we've "aligned" both the second and third cells while the second cell is still receiving some expand space.

The GtkCellAreaBox is also GtkOrientable:

This case has the box area vertically oriented, the scaffold currently draws them in a row from left to right in this case but that is just a detail. Whether the area is oriented vertically or horizontally the layouting widget can decide to render the rows however it chooses.

Moving on to another test case, the GtkCellArea also implements internal keyboard navigation and the painting of focus on cells:

The editable text renderer now has focus, note that more than one cell in a given area can receive focus.

There is also the concept of “focus siblings” implemented in GtkCellArea, this lets you place focus on a cell’s neighbor while a given cell is in focus:

The toggle renderer has focus all alone

This lets you decide which cells in the area should also have focus when a given cell has focus… siblings of focusable cells also cause the focusable cell to activate when they are “clicked”:

Now we can clearly see that the static text beside the toggle renderer is associated with the toggle renderer when focus is painted.

The GtkCellArea as I mentioned above also makes cell editing much easier, the CellAreaScaffold only has to recieve the ::add-editable and ::remove-editable signals, when ::add-editable is invoked, it comes with a brand new editable widget and a GdkRectangle dictating the area which the editable widget should be placed, the editable widget needs to be a GtkContainer of sorts and just add the editable widget in that size and position at the right time and then remove it when ::remove-editable is invoked.

Shows a GtkCellRendererText being edited by the CellAreaScaffold test

That pretty much concludes chapter one in the GtkCellArea saga, Kristian Rietveld, our GtkTreeView master has been working with me in parallel over the past weeks to get GtkCellArea integrated as the rendering delegate of GtkTreeViewColumn.

But that’s not all ! while I’ve been waiting on GtkTreeView integration I’ve got a head start on refactoring GtkComboBox to render it’s menus with the new framework, so far I’ve come up with GtkTreeMenu which will reduce the huge mess of a codebase that is GtkComboBox considerably… and also open up many new avenues by making it possible to generate menus from GtkTreeModel. The GtkTreeMenu code is available in the combo-refactor branch.

GtkTreeMenu Basics

GtkTreeMenu starts off with all of the rich benefits of GtkCellArea, areas can be rendered with various cells, various alignments and expand configurations, in various orientations etc. Here are some shots from tests/testtreemenu from the afore mentioned combo-refactor branch:

GtkTreeMenu rendering a similar model with the second cell unaligned and the third cell aligned.

Now also we can align all the cells:

GtkTreeMenu with all cells aligned.

In the above screen shot we can observe the usefulness of GtkCellAreaContext, note here that every GtkTreeMenu (each submenu is itself a GtkTreeMenu) share the same GtkCellArea, however they each have a private GtkCellAreaContext which is used to request / align / allocate the size to use when rendering the area. In this way leafs of each tree get the same cell alignments however the overall width of each submenu can be different (cells in submenus are aligned with cells in the same submenu but not aligned with cells in parent menus).

Another detail of GtkTreeMenu is that it uses a GtkTreeMenuHeaderFunc to decide whether it should include a menu header for the parent item which had a submenu. This is important for combo-boxes since combo boxes need to be able to actually select every row in the GtkTreeModel including rows which have children:

This shot shows a combo-box style menu where every submenu includes a selectable "header" item to allow selection of the GtkTreeModel row that has children.

Pie in the Sky

Whether or not GtkTreeMenu should be an exposed class or only an internal detail of implementing GtkComboBox is a question that we’ve been tossing around on irc these past days. Interestingly, with some more work in this area GtkMenuBar and GtkToolBar could also be rendered using cellviews and GtkCellArea… in other words the main application toolbar or menubar could be simply built off of a GtkTreeModel. Even fancier still, would be to have GtkApplication actually implement GtkTreeModel for its menu and toolbar actions and just build the application menus automagically, possibly easing the integration of menubars on OSX and making things make alot more sense in general. Ofcourse this kind of approach has a lot of details that need consideration, menu accelerator key and the like are one of them… but it does sound like a tempting approach to just get rid of most of the hand-built GtkMenuItem API (and also finally drop GtkUIManager in favor of using GtkTreeMenus…).

Those are just some ideas… that might inspire one of you hackers… to come and make GTK+ 3.0 more rocking than ever ๐Ÿ™‚

Stay tuned for more news on GtkTreeView refactoring !