Thursday, July 15, 2010

Porting ejpi: Custom Widgets in Qt

Because of my off-repeated distaste for making custom widgets unless needed, the pie menus for ejpi are my first real custom widget.  Learning to write custom widgets is the reason I chose to port ejpi.

I started off with the C++ pie menu code from Nokia's add-ons.  I ported it to Python (I doubt anyone would package the binary and making Python bindings available for Maemo any time soon).  While at it I simplified the code and made it more re-usable.

Qt Pie Menu Design Evolution

QtPieItem is the client-provided object to store everything related to a slice.  The two subclasses are QtPieAction and QtPieMenu.

QtPieMenu works well as a subclass of QtPieItem for when you want submenus but doesn't make sense with the parent QtPieMenu.  What does the weight, icon, and text mean?  Why not use composition instead?  Besides I don't even need sub-menus in my application.  So I dropped this (could easily be added later).

QtPieAction starts to look a lot like QAction.  I figured I could just use QActions for all of my slices with a wrapper to track the weight (slice weight / all slices weight gives the percentage of a circle a slice takes up).

Moving on to QtPieMenu...

The center of the menu and outside the menu cause a cancellation.  I like actions being connected to the center (one of these actions could be a cancel).  I also like to throw the mouse so I wanted an option for infinite outer radius.

Overall I was seeing there were several pieces of code in here that could be reused and built up to make several variants of Pie Menus, depending on what was wanted.  So I broke out pie locations (QPieFiling, basically the Model), rendering (QPieArtist, kind of like a View), and then the actual widget (QPieDisplay, QPieButton, QPieMenu).

This worked really well.  I do not use QPieMenu so oh well.  A QPieButton can have its representation set with a QActionPieItem.


When the QPieButton is clicked it shows a QPieDisplay that is filled up with delicious goodness.


Life Implementing a Custom Widget

This was actually a fairly pleasant experience.

With the original GTK version of ejpi I struggled to grab the current theme's colors but never was able to.  I followed Qt Pie Menu's example in grabbing colors and it actually worked!  There was one bug in the original C++ code, they used a "dark" for unselected text when "mid" is what is called for but luckily Qt actually had great documentation on when to use what color.

The other major challenge on the desktop I had was sizing.  The QtPieMenu only used sizeHint which gives a suggested default size for widgets.  For the buttons I wanted a minimum size of what is needed to draw the item with a suggested size of the client specified size.  I wanted to then listen to resizes and scale my box accordingly so the user would have button separators.

For this I overrode minimumSizeHint and resizedEvent.  I have the suspicion that widgets are supposed to implement sizeHint, minimumSizeHint, and maximumSizeHint rather than setting the sizes directly to leave it in the wisdom of the client to possibly bypass the hints by setting the actual minimum, actual, and maximum sizes.

    def minimumSizeHint(self):
        return self._buttonArtist.centerSize()

    @misc_utils.log_exception(_moduleLogger)
    def resizeEvent(self, resizeEvent):
        self.setButtonRadius(min(resizeEvent.size().width(), resizeEvent.size().height()) / 2 - 1)
        QtGui.QWidget.resizeEvent(self, resizeEvent)

When checking how things ran on Maemo I ran into a couple of strange issues.  So they wouldn't become modal I registered the menus as SplashScreen.  I think the hacking around modality of popups was what killed performance on my GTK version.

Unlike the GTK version I was getting the frost effect to the whole window minus the square around the pie. That provided quite an awkward look.  I didn't find anything for this so I just drop the round pies.

The other problem which is yet to be resolved is that if an edge button is selected the pie appears off-center from the user's click.  The odd part to me is even the middle buttons have the pie go partially off-screen so it must be some kind of how much is it off the screen.

You can find the source code for these pie menus in ejpi's git repo.