Qt Radial Menu for Quill

April 4th, 2008

In my forays into the world of creating a Qt journal application, part of what I wanted to achieve was a really easy way to get to common actions like tool changes. My two biggest gripes with Xournal were always (a) that I had to keep adding an extra page after the one I was working on if I wanted to be able to see the bottom of this one (I fixed this in Quill, and made adding a new page easier, by introducing a `virtual’ last page, which does not appear on any output but gives you scrolling space and, when drawn upon, becomes a regular full-blown page) and (b) that if I wanted to change tools or colors, I had to shoot up to the toolbar to do it. That’s just no fun.

So, my latest foray into Quill has been adding a radial menu to the canvas area, so that you can get a menu for doing stuff in a way optimized for the stylus. The coolest aspect of this was that it let me develop my own Qt widget, of course. The results, though, were pretty good, I thought:

Details follow…

Part of the awesomeness that is Qt is that every QWidget has an associated list of QActions. QWidgets in Qt are the base graphical unit—equivalent to Swing’s Component, for example. QActions are an encapsulated way of representing various actions. They are used equally in menus and toolbar, so, for example, the File>New action and the New toolbar button would be separate representations of the same QAction object. A model, if you will, for actions the user can take, where the toolbar and the menu represent views.

Bearing this in mind, I decided to model the RadialMenu class’s API closely after QMenu’s API. The advantage of this approach is that I had a model for what functions I should have. And, indeed, I implemented most of them. The exceptions were few and involved things that made sense for a QMenu but not for the radial variant, and a few things that I decided to wait on until later if they became necessary. I also added a couple of useful functions specific to RadialMenu (such as centerOn() to center it on a particular point). I did not, however, extend QMenu, as the provided functionality is, I believe, too different. Perhaps in the future the functionality will align more and such an inheritance chain might be merited.

The truly painful part was implementing painting. Because Qt has styles that can style the interface differently, and some of these styles use native painting routines (as with Windows and OS X), I had to model the radial gradient I needed for the menu using the Plastique style as a guide. It won’t look quite as similar to the current style’s widgets in styles other than Plastique, but it’s a good approximation. Also problematic in this context was positioning the icons. I wanted to make RadialMenu flexible, so it would adjust to the number of actions needed correctly. Thus, we have the effect of something that works equally well with the five items seen above, or with three or even six:

Notice that at six we start running into some issues. Basically, the current painting code, when determining the size of the icons, only takes into account the distance between the outer radius and the inner radius—it does not take into account the distance between the right and left button boundaries at their narrowest. The painting code, however, is clearly adjustable.

Finally, RadialMenu takes care of highlighting hovered items and pressed items. Plus, thanks to QAction’s signals, the currently selected item amongst a list of, say, tools, is kept synced with the equivalent entries in the toolbar, even when both are open at the same time.

RadialMenu currently also supports, in the QAction, having a QAction with an associated RadialMenu. QActions by default can be associated with a menu, but I did not want to overload that concept—I wanted a QAction associable with both a QMenu and a RadialMenu at the same time. To this end, QActions have the concept of arbitrary user data set via the excellent QVariant class that can be used to transport essentially arbitrary data. For RadialMenu’s purposes, QAction’s user data is currently expected to be a RadialMenu when a RadialMenu should be associated to it. It is very likely that this will switch to being one of the arbitrary properties QObjects can have set and retrieved via setProperty() and property() with the name `radialMenu’. Note that the submenus aren’t currently popped up yet.

Finally, you may have noticed a small wrench in the middle of some of the screenshots. Currently there are two ways to get a hold of this menu. The first is to press the middle mouse button, which on my stylus at least is mapped to holding the side button down and pressing it. This will bring up the radial menu centered on the mouse position. The second way is that wrench. When the stylus pressure goes to zero, Quill waits for a short timeout and then displays, offset slightly from the location where the stylus was lifted, a small button:

This button, when clicked, will also bring up the radial menu. I’m still not sure about this button and how much it gets in the way. I want to take a wait-and-see attitude on it and see what kind of feedback I get on its usefulness. I think it will be particularly nice when a stylus does not have an alternate button on it and the only way to get the menu will be to click on this one.

Also in the works for the radial menu, in addition to full support for submenus, is support for drag-based navigation of the menus; that is to say, clicking, holding, and then moving through to the final menu item you desire. This basically will make menu item selection a one-press affair, though the release will take a while to come. I also want to implement an alternate form of the pen width spinner more suited to the radial paradigm: basically a widget upon which drawing a circle clockwise will increase the value and counter-clockwise decrease it (this might need to be flipped in RTL locales, as well). The idea is borrowed from InkSeine:http://research.microsoft.com/inkseine/, as referred to me on this bug by Kovid Goyal.

Sorry, comments are closed for this article.