Qt QMainWindow.statusBar(): don’t use it

Don’t use the QMainWindow’s builtin QStatusBar (created and accessed using the statusBar() method.)

That status bar has problems on the OSX platform.  In other words, while Qt is generally cross-platform, this particular status bar is not.   But even if you don’t support that platform, its a bad idea…

That status bar is always at the bottom of the window.  That’s bad because it constrains the user.   Also its bad as UI because it divides the user’s attention among:

  • the top of the window (the menubar)
  • the bottom of the window (the statusbar)
  • the middle of the window (where most action occurs

This is well known.  The Apple HIG discusses it.  Thats why the QMainWindow’s status bar is problematic on the OSX platform.

In the Apple HIG they call a status bar at the bottom of a window a Placard, and say:

Note: Placards are not recommended for use in apps that run in OS X v10.7 and later.

A status bar itself is not always a good idea, no matter where it is or how you implement it.  See a discussion in the GUI Design Handbook.

But the status bar at the top of the screen  is often a good idea on a phone: see the iOS HIG.

If you must have a status bar in a Qt app, a better approach is to put a QStatusBar widget as a child of the toolbar area of a QMainWindow.  Briefly (in Python):

toolbar = mainWindow.addToolbar("Foo")
statusBar = QStatusBar()
toolbar.addWidget(statusBar)

Then:

  • the user can move it (to any edge of the parent main window)
  • the user can float it (to a separate window)
  • tooltips for context menus go to it

while it still is a status bar in the sense “displays a running stream of status.”

Advertisements

Converting from PySide to PyQt

This is about choosing among, and converting between,  two alternative bindings of  Python to Qt:

  • PyQt: open source but commercial and GPL but not LGPL
  • PySide: open source and non-commercial and LGPL

The PySide project also tells about some of the technical differences.  This post is a little more pragmatic, and discusses non-technical issues: licensing and politics.  This post is mostly about converting from PySide to PyQt, but might be useful for the other direction.

This post may be wrong, and is certainly incomplete.

What the different licensings mean

Licensing is complicated, this is just a summary:

You can use PyQt (since it is GPL):

  • for free as long as your app is also GPL (you must reveal your source code.)
  • for a fee if your app is proprietary (you don’t reveal your source code)

There are other stipulations, such as displaying copyrights, and whether your app reveals PyQt to your users (lets your users script using PyQt.)

You can use PySide (since it is LGPL):

Shortcomings of PySide

I have experienced these flaws:

  • PySide Qt enums are weird and don’t pickle
  • PyInstaller support for PySide is weak (it doesn’t work on some platforms (e.g. OSX and Windows) for some features (e.g. Qt plugins such as for jpeg image formats.))
  • PySide lags Qt development (as of this writing PySide supports Qt4 and not Qt5, whereas PyQt already supports Qt5)
  • PySide support is spotty (since it is voluntary, and since bindings are complicated, high tech.)

Business Strategy

The owners of PyQt (Riverbank Computing PLC) have an effective business strategy: by releasing their code GPL, they have gained as users all the open source authors (since PyQt works well and is well supported.)  Only the users who are not open source but don’t want to pay for tools (or want to expose scripting) are forced to the PySide camp , and that is a small number of users.  This strategy is similar to the Red Hat strategy.

The situation is different from say, an open source compiler gcc.  Both compilers and bindings are high tech.  But the pool of users of a Python Qt binding is small.

There is a long history, and ongoing turmoil, surrounding the situation of having alternative, open source Python Qt bindings.

Politics

No matter what your politics on the matter of open source software, you must respect the opinions and rights of others.  If you decide to use PyQt, you should meet the stipulations of their license.   They are providing a valuable service, if you want that service and are obligated to pay their fee (your app is NOT GPL), please pay.

Summary of process for converting from PySide to PyQt

It is easy, but not trivial.

It can be reversed, but you probably don’t want to do it in a reversible way  (don’t monkey patch the code with ‘if PYSIDE’ statements.)

The conversions can be categorized:

  • import
  • signatures:  where PyQt uses a different name for keyword parameters
  • type-checking : where PyQt does more strict type checking
  • Qt enum
  • signals

Import differences

  • a small preamble to set up PyQt API version
  • import PySide.* -> import PyQt4.*  (a few global search/replace regexes will reliably do this, back and forth.)
  • from …. import Signal ->  from … import pyqtSignal as Signal  (and similarly for Slot)
  • PyQt raises exception on failed connect() whereas PySide returns a sucess/failure result

Qt enum differences

The more general issue “what is an enum type” is contentious.  Does defining an enum define a new type?  Are instances of that type compatible with type “Int”?

This code illustrates the differences:

qtEnum = Qt   # PyQt
qtEnum = Qt.BrushStyle   # PySide
foo = qtEnum.SolidPattern  # an instance of the enum

In PySide, a Qt enum is represented by a class and enum instances are class attributes.  They don’t pickle (because pickle does not do classes.)

(I can’t yet discuss Qt enums in PyQt.)

Signature differences:

These are the signature differences I found in execution (so it is not a complete list):

  • QStatusBar.showMessage(…, timeout -> msecs
  • QFileDialog(dir->directory)
  • QFileDialog.getOpenFilename() has different signature

Type-checking differences

PyQt does more strict type checking.   When converting from PyQt to PySide, PyQt will complain more.  I think this is useful, often pointing out potential bugs, or portability problems, in my code.

In general,  both C++  and Python do strict type checking, but C++ does it statically (at compile time)  and Python does it dynamically (at run-time).

Example: this code seems to work in PySide, but PyQt complains:

QKeySequence(event.key() | event.modifiers())

Example: PyQt gives errors for keyword parameters that don’t match a keyword, whereas PySide lets it slide:

QDialog.exec_( foo, atAction=bar)        # the keyword should be "at"

Signal differences

I expected PyQt and PySide’s treatment of signals to be portable, but found a few problems:

PyQt seems a little more strict about type checking parameters to signals, (which I think is a good thing.)   (TODO: example.)

In one case, I needed to restructure multiple inheritance of a mixin class, to get a PyQt signal to connect.  (TODO: example.)

PySide accepts: @Slot(QPrinter) while PyQt wants @Slot(‘QPrinter’).  That is, all c++ types (that is, Qt types, contrasted with Python types, should be quoted strings for PyQt, when used as a parameter to pyqtSlot.

Other strategies for converting imports

PyQt supports two versionsof its API.    The two versions come from a history of advancement: the new version is better than the old.  If you have and support older versions of your app, you might need to be concerned about the two versions.  There are ways to support both versions of the PyQt API as well as the PySide API.  In other words, you can make your code agnostic of the binding brand (PyQt versus PySide) and version of the binding.  See Supporting both APIs.

Other notes

Multiple inheritance of Qt classes.