Thursday, March 20, 2014

Using Qt Designer

Two unknowns I was fretting about have become less foggy: the work-flow that connects the graphic layout one creates with Qt Designer to the executable code; and the connection between user-visible strings defined to Qt Designer and the Qt translation mechanism.

Qt Designer to code

This part was of course laid out clearly in Summerfield's book. That book was my bible through the early days of building PPQT in 2011-12. Now it shows its age a little because there are minor differences between the PyQt4/Python 2 syntax of its examples, and the PyQt5/Python 3 syntax I'm using. But chapter 7, "Using Qt Designer", covered it all. Here's the sequence.

Run Qt Designer, select a template (in this case, just plain QWidget), and start dragging widgets onto it and laying them out. It's reasonably intuitive, especially if you know the names and uses of most of the widgets and their properties, as I do. It helps a lot to have a big screen. Qt Designer is almost unusable on the macbook because with all its various windows there's no room left for the widget you're designing. I used my desktop system with a 23-inch monitor and it was fine. It took an hour to lay out a satisfactory Edit panel as I described previously. Much of that time was spent in the Properties Editor, checking and specifying and re-specifying the many, many properties of each widget.

In the course of this I quickly discovered part of the answer to the question on translation. The Property Editor entry for any user-visible string—label text, tool-tip, status-tip, whats-this?—has a check-box "Translatable". I checked most of them. This widget has two QLabels and both will have their text filled in dynamically. But all tool-tips need to be translatable.

Having the check-box there keeps one aware that the English text you compose now will have to be translated. My experience in writing for translation, and writing tech material for people who have English as a second language, goes way back, to 1975-6 when I was at IBM World Trade in London and writing material to be read colleagues who were Brits, Swedes, Dutch and Italians. I learned then to keep going over my text to make sure it was simple, terse, and unambiguous; used no colloquialisms or metaphors; used the smallest vocabulary that would express the thought. (Which isn't a bad mindset for any expository writing.)

You save your design to a file of type .ui and invoke pyuic5, a command-line utility that reads it and writes some python source. (Summerfield invokes it by way of a version of make, makepyqt.pyw, but I don't see that in my installation. TB investigated.) The contents of this source were at first a little baffling to me. Here's the start:

class Ui_EditViewWidget(object):
    def setupUi(self, EditViewWidget):
        EditViewWidget.setObjectName("EditViewWidget")
        # ...and 140 more lines of setting-up code such as
        self.DocName = QtWidgets.QLabel(self.frame)
        # ...and the other sub-widgets...

What do I do with this? I wondered. Do I need to instantiate an object of this class? But it doesn't have an __init__; and what's this EditViewWidget being passed to this setupUi method?

Read the manual, doofus. Or in this case, keep reading in chapter 7 of Summerfield.

What I am supposed to do with this is to invoke it in—for the first time in my Pythonic career—a multiple-inheritance class definition, like this:

class EditView(QWidget, Ui_EditViewWidget):
    def __init__(self, my_book, parent=None):
        super().__init__(parent) # initialize QWidget
        self.setupUI(self) # invoke the initializer in class Ui_EditViewWidget

I am creating a QWidget subclass that also incorporates the code prepared by pyuic5. Following the call to setupUI, "self" incorporates all the widgets I defined to Qt Designer, under the object names I gave them in the Properties editor. Further initialization code is needed, for example to connect signals, set up the syntax highlighter, etc. But 150-odd lines of detailed GUI initialization are taken care of separately and more important, can be reviewed and altered at any time with Qt Designer, with no impact on the code.

Translation de-fog

To implement translation, the setupUi method ends with this:

    def retranslateUi(self, EditViewWidget):
        _translate = QtCore.QCoreApplication.translate

...which is followed by a line like this for every string that got the "Translatable" tick-mark:

        self.DocName.setToolTip(_translate("EditViewWidget", "Document filename"))

That pushes the fog of unknowns back a bit. It remains for me to learn how translation actually works.

No comments: