Widget foundations: QWidget

All Qt widgets and all visible components are founded upon QWidget — this monster class provides all event handling, all style handling and countless other chores. To help with the handling of these tasks, there are other classes, such as QPixmap, QColor, QFont or QStyle.

QWidget can be useful to build your own widgets on, provided you are prepared to do all your own painting — this includes buffering in case your widget gets a paintEvent call! Consider the next snippet, which is an extension of the event1.py example:

Example 10-4. event2.py - using QWidget to create a custom, double-buffered drawing widget.

#
# event2.py
#
from qt import *
import sys

class Painting(QWidget):

    def __init__(self, *args):
        apply(QWidget.__init__,(self, ) + args)
        self.buffer = QPixmap()

    def paintEvent(self, ev):
        # blit the pixmap
        bitBlt(self, 0, 0, self.buffer)

    def mouseMoveEvent(self, ev):
        self.p = QPainter()
        self.p.begin(self.buffer)
        self.p.drawLine(self.currentPos, ev.pos())
        self.currentPos=QPoint(ev.pos())
        self.p.flush()
        self.p.end()
        bitBlt(self, 0, 0, self.buffer)
                
    def mousePressEvent(self, ev):
        self.p = QPainter()
        self.p.begin(self.buffer)
        self.p.drawPoint(ev.pos())
        self.currentPos=QPoint(ev.pos())
        self.p.flush()
        self.p.end()
        bitBlt(self, 0, 0, self.buffer)
        
    def resizeEvent(self, ev):
        tmp = QPixmap(self.buffer.size())
        bitBlt(tmp, 0, 0, self.buffer)
        self.buffer.resize(ev.size())
        self.buffer.fill()
        bitBlt(self.buffer, 0, 0, tmp)
                           
class MainWindow(QMainWindow):

    def __init__(self, *args):
        apply(QMainWindow.__init__, (self,) + args)
        self.painting=Painting(self)
        self.setCentralWidget(self.painting)
        
def main(args):
  app=QApplication(args)
  win=MainWindow()
  win.show()
  app.connect(app, SIGNAL("lastWindowClosed()")
                 , app
                 , SLOT("quit()")
                 )
  app.exec_loop()
  
if __name__=="__main__":
  main(sys.argv)
      

event2.py - persistent drawing

By drawing to QPixmap instead of to QWidget, and blitting the contents of that pixmap to the widget, the drawing will be kept. Note also how much smoother the drawing feels, despite the extra work the script has to do. This technique is called double buffering, and is the alpha and the omega of graphics programming. Of course, there's still a small problem with resizing... In fact, if you want to build your own widgets from the ground up using QWidget, you're always in for more work than you reckoned with.

QColor

The QColor class represents any color that can be used in PyQt. You can instantiate a new color either by using an RGB (red-green-blue) value, an HSV (hue-saturation-value) value, or a name. The X11 system used on Unix provides a database full of rather poetic color names like ‘Old Lace', ‘Royal Blue' and ‘Peach Puff' —you can use these names instead of hexadecimal numbers. The Windows version of PyQt has a copy of this database, so it's quite portable. If you replace the resizeEvent() in the event2.py example with the following code, you'll see the effect:

Example 10-5. snippet from event3.py - a peach puff drawing board

...
    def resizeEvent(self, ev):
        tmp = QPixmap(self.buffer.size())
        bitBlt(tmp, 0, 0, self.buffer)
        self.buffer.resize(ev.size())
        self.buffer.fill(QColor("peachpuff"))
        bitBlt(self.buffer, 0, 0, tmp)
...
        

event3.py

A final note on colors: the way you set the colors of a widget have been changed between Qt2 and Qt3. Where you first used setBackgroundColor(), you'd now use setEraseColor(). Yes, there is a logic behind this change of name, but it is very specious, and the change broke almost all my code. The erase color is the color that Qt uses to clear away, or erase, all the pixels that had been painted just before they are painted again in a paint event.

When you're designing complex widgets, you will want to investigate setBackgroundMode and the BackgroundMode flags.

QPixmap, QBitmap and QImage

We have already been using a QPixMap to double buffer the scribblings in the previous two examples. QPixmap is not the only image class PyQt offers: there's also QBitmap, which is just like QPixmap, but for black and white images only, and QImage. Where QPixmap and QBitmap are optimized for drawing (and then showing on screen or on a printer), QImage is optimized for reading and writing (together with the QImageIO class), and for manipulating the actual pixels of an image. There's another image-related class, QPicture, which can be used to record drawing operations and replay them later. The recorded paint events can then be stored in a file and reloaded later on. Those files are called meta-files — but they're in a special Qt format. In Qt 3, QPicture also supports the standard scalable vector graphics format, svg. If you want to create a complex vector-drawing application you'd be well advised to stick to this standard.

QPainter

A QPainter object is used to efficiently paint on any paintdevice using a variety of primitive graphics, such as simple dots or lines, bezier curves, polygons, strings of text (using a particular font) or pixmaps. Drawings can be modified, for instance by shearing or rotating, and parts can be erased or clipped. We have already used a QPainter to draw the scribbly lines in previous examples.

Paint devices can be:

What can be drawn on one device, can be drawn on all devices, so it's uncommonly easy to print on paper what can be drawn on screen. Copying batches of pixels from one paint device to another is blindingly fast if you use the bitBlt global function, as we did above for our double-buffered graphics editor.

Note that you cannot create any paint device until you have created a QApplication. This includes QPixmaps. The following variant on action.py won't work, even though it seems a good idea to pre-create the pixmap, instead of converting the xpm data on constructing the QAction:

Example 10-6. fragment from action2.py - You cannot create a QPixmap before a QApplication

#
# action2.py
#

import sys
from qt import *

connectIcon=QPixmap(["16 14 5 1",
             " 	c None",
             ".	c black",
             "X	c gray50",
             "o	c red",
             "O	c yellow",
             "                ",
             "          .     ",
             "       X .X     ",
             "      XooX  .   ",
             "     Xoooo .X   ",
             "    XooooooX    ",
             "    XooooooX    ",
             "    XoooooX.    ",
             "    XooooX.     ",
             "   XOXXXX.      ",
             "  XOXX...       ",
             " XOXX           ",
             "  XX            ",
             "  X             "
             ])

class MainWindow(QMainWindow):

    def __init__(self, *args):
        apply(QMainWindow.__init__, (self, ) + args)
...
        self.action.setIconSet(QIconSet(connectIcon))
...
        

Running this gives the following result:

boudewijn@maldar:~ > python action2.py
QPaintDevice: Must construct a QApplication before a QPaintDevice
Aborted
      

Chapter 21 deals with painters and paintdevices in quite a lot of detail, while Chapter 24 deals with printing to paper.

QFont

There is no other area where there are so many and profound differences between operating systems as there is with fonts. And if you take into account the difference in font handling between printers and screens, you will get a feeling for how difficult it is to get proper and dependable cross-platform multi-lingual font support in a toolkit.

Fortunately, Qt's font support has steadily improved, and is now at the point where, provided good quality fonts are available on a system, it can offer the same excellent screen and printer support on all platforms.

The first issue is the font used for drawing labels and other application texts — sometimes called the system font. This naturally differs for each system: Windows uses Arial these days, while KDE uses Helvetica, CDE Times and OS X a bold Helvetica. Furthermore, the system font is also often customized by the user. Text in one font takes more room than text in another font — possibly giving ugly display errors. By using Qt's layout managers, instead of positioning widgets with pixel-precision yourself, you will have little trouble dealing with the geometry differences between Window's Arial font and KDE's Helvetica standard — all controls will reflow neatly.

For handling fonts in your application you can work with QFont. Qt builds its own database of available fonts from whatever the system provides. You can then access these fonts in a system-independent manner, without having to juggle X11 font resource names yourself.

QFont provides all necessary functions to select encodings (or scripts in Qt3), font families, styles and sizes. There's also a standard dialog available, QFontDialog that you can use to let the user select a certain font.

There are serious differences between the font system in Qt2 and Qt3. In Qt2, you need to determine which character set encoding you need; and you can only use the character set encodings that the particular font supports. For instance, if your font supports the KOI8 Cyrillic encoding, then that is the encoding you can use. The font you request has a one-to-one relation with the font files on your system.

In Qt3, you select fonts by name, style and script (like Cyrillic), and Qt will select the closest fitting font. If your widget needs to present text on screen that uses characters that cannot be retrieved from the selected font, Qt will query all other fonts on your system, and assemble a composite, synthetic font that includes all characters you need. You lose some control but you gain a correct representation of all possible texts— you can use any font for any text in any script.

If you want to set a certain font for the entire application, you can use the QApplication.setFont class function. Likewise, everything that descends from QWidget also has a setFont() function.

You can use QFontInfo to determine the exact font Qt uses for a certain QFont — but this might be quite slow. An important use of QFontInfo with Qt3 is to determine whether the font you get was exactly the font you asked for. For instance, if you desire a Bembo font, which might not be present on your system, you could get something closeish: a Times New Roman. Especially for drawing and dtp applications it's important to be sure which font is actually used.

QFontMetrics can be used to determine metrics information about a font. For instance, how high the ascenders and descenders are, and how wide the widest character is. This is useful if you need to determine how much space a line of text takes when printed on paper.

Font metrics