Tuesday, August 12, 2014

How wide is a button?

Reviewing imageview.py I discovered a bit of code commented-out. In the setup of the UI, imageview creates two QPushButtons for instant zooms, labelled (in English) "to Height" and "to Width". Obviously these label strings are not the same width. And, if they are translated to some other language, they will have different widths still.

(Well, in German they would be zu Breite and zu Höhe, thank you Google Translate, the Width one still the shorter, but in French à Largeur and à Hauteur, almost equal. Can't quickly find a language in which the Height one is the shorter, but I'm sure it exists.)

Point is, I would like these buttons to always have identical widths, and not go from o_O to O_o as the translated UI changes. So, get the max of the two widths and assign it as the minimum width to both. Right? Right?

Seems not. The original code, which I'd commented out and then forgotten about, read

        w = max(self.zoom_to_height.width(),self.zoom_to_width.width())
        self.zoom_to_height.setMinimumWidth(w)
        self.zoom_to_width.setMinimumWidth(w)

That was a disaster, I don't know what Qt was returning for a width but the buttons ended up about 500 pixels wide each. No wonder I'd commented it out. But how to do it right? I just want to know the width of the label-text of the button, at run-time, after the locale has selected for the language.

Cutting short an hour's browsing around the Qt forums and the Qt Assistant, I find the answer is quite simple, really. One gets the fontMetrics from the widget. Then you can ask the fontMetrics object for the width() of any string of text, or specifically of the widget's current text. So I end up with an inner subroutine:

        # Function to return the actual width of the label text
        # of a widget. Get the fontMetrics and ask it for the width.
        def _label_width(widget):
            fm = widget.fontMetrics()
            return fm.width(widget.text())

And then apply it like so:

        w = 20 + max(_label_width(self.zoom_to_height),_label_width(self.zoom_to_width))
        self.zoom_to_height.setMinimumWidth(w)
        self.zoom_to_width.setMinimumWidth(w)

The 20-pixel extra is to make sure that even at a minimum squeeze, there will be some margin around the label text. The labels should be pushed to their minimum because they are in an HBoxLayout with stretch items on both sides.

Other than this, and the usual obsessive nit-picking of comment wording, all I had to change was some out-of-date code in the unit test driver. One more down.

No comments: