Tuesday, June 29, 2010

Porting Gonvert: The Actual Port

Starting from scratch made it a bit easier to redo the architecture and pieces of the UI.

I started off with one class named Gonvert that acted as my application class.  It owned all of the global QActions (which I coupled to my settings) and top-level windows.  Ironically enough I did not have it own the QApplication.  I left that to my main function.


I dislike circular references but with Gonvert owning all of the global QActions I passed the instance down into its children so they could add them to their QWidgets and menus.


Gonvert also managed loading and saving of settings.  I normally manage settings manually and then load/save them through Python's ini handling code.  This time I created dictionaries with QAction values being the values.  I then serialized/deserialized using simplejson.  This worked well.


I decided to take better advantage of stacked windows and made Categories a window rather than a dialog.  Like with other GTK applications, I ran into issues with modality and stacked windows when deploying to Maemo so I had to restructure things to allow everything to run on the desktop without being modal.


GTK's way of creating list views and tree views requires a lot of boilerplate but uses minimal inheritance internally and requires minimal inheritance to customize.  I think all you need to inherit for is creating custom cell renderers and creating your on model if you decide to not re-use theirs.  I loved that I could put any data type, including complex python types into the default models.  I never created my own model (though I probably should have in some places).


I must admit, I lied earlier.  I had to use concrete inheritance to do a custom model with Qt.  The models I was seeing were very restrictive and seemed only focused on QStrings.  With Gonvert I need to do math on data in the model and I like to center on the decimal place.  So I had to create a custom model to internally store the raw value and then to have a column for left of the decimal place and a column for the right of the decimal place.  The customizing I did in GTK with CellRenderers seems to be coupled into the Qt model which is really sad.  Why does my model care about presentation?  Why?  Why?  So it looks like for a lot of things I'll have to create custom models.  Sadly creating a model was confusing but since I never did it in GTK I can't say if it is easier or harder.


Gonvert tends to have a lot of units.  For some this is cool in a geeky sort of way.  For others it means they have to scroll through more to find what they want.  I went ahead and implemented favorites.  It worked out pretty easy to write it all.  The favorites editor uses strings only so I took advantage of the built in model.  I originally implemented it by having the user select the row.  This played a tad bit better than GTK with scrolling on Maemo but it was still a bit finicky.  A drag to scroll was sometimes interpreted as a drag to select or unselect.  I eventually switched it to togglable items which got the favorites editor to work smoothly.  I then would have the category and unit selection windows hide the non-favorite rows.  There is a filter to both sort and filter but I had read some bad things about it and just decided to do both manually.


On a related note to scrolling and lists I noticed strange behavior when adding my "Quick Convert" or "Condensed View".  When you drag a list to scroll, the widget fires a selection changed event for the row you started the drag on and then it fires an event to put you back where you were.  Kind of hacky.  I shared code between programmaticly setting a unit type and the user selecting one.  In the programmatic case we want to scroll to the unit.  In the user case this caused their scrolling to jump around because of these selection events firing.


The main view in Gonvert has tended to be a bit slow.  In the Qt version it was even slower especially with Favorites added.  The main view has an edit box where the user types in a value and then below that is a list of all units and what that value gets converted into.  The unit values get updated, and resorted on every keypress.  This with sorting caused odd issues with me setting rows as hidden for favorites so I had to update that every time the user put in a value.  The slow downs:
  • Marked the model as modified more times than needed
  • Modifying setRowHidden more times than needed
  • Doing a lot of calls back into Qt in the innerloop of Qt calling into my code.
When updating all the values I limited re-sorts to only happen when the value column is selected for sort.  I also made it so we would not signal the model as modified first for the modification and then again for the sort.  I also throttled updating of favorites.  When the user edits the value a QTimer gets started if it hasn't already.  At the end of the timer the model gets updated.  This introduces a minor delay in the updating (set to 100ms right now) but reduces the amount of time we block the users editing by good enough amount.  Sadly whenever the timer goes off and the user is still pressing a lot of numbers, there is a slight pause.

Other things
  • I played around with the favorites.
  • I removed a lot of the error checking in my model (like checking to see if the index was valid).  This proved very beneficial (as long as the View is written correctly).
  • I think I also did some other optimizations but those were the main ones.
Yes, I guided myself in this with the use of a profiler.

One nice thing about this port has been that I've not had to touch anything not related to UI like the loading of units and converting a single unit.  Sadly my model before and after has been coupled to the UI.