The first stab at a totally cool gui will be an imitation of a remote control. This will involve a nice brushed steel background with rounded buttons and a deep bevel. The remote control used in this instance is part of a system for medical imagery, and we want to duplicate the actual control on screen so that users familiar with the hardware won't have to learn a new interface for use with the software.
To create the interface, I first scanned a picture of the actual remote control unit. This gives us the picture shown below:
A scan of the remote control. Thanks to Cameron Laird for permission to use this image.
Note the subtle play of light on the buttons: no two buttons are the same. The photo-realistic effect would be very hard to achieve without using an actual photograph. This means that we will have to cut out each separate button, and save it in a separate file. The buttons should give feedback to the user when depressed, so we make a copy of each button and save that, too. Then, using a bitmap manipulation program such as the Gimp, the highlight in the up button is changed to a bit of shadow, and the text is shifted a few pixels to the right and left.
Changing the shadows with the Gimp
This part of the proceedings is a bit of a bore: let's skip drawing a few of those buttons, and start with the implementation. A real remote control doesn't have window borders, so ours shouldn't have those, either. We can achieve this effect by using window flags: first the Qt.WStyle_Customize flag to indicate that we are customizing the appearance of the window, and then the Qt.WStyle_NoBorderEx flag to tell the window system that we don't want borders.
Example 22-1. remote.py - remote control application
# # remote.py - remote control application # import os, sys from qt import * from view import * class Window(QMainWindow): def __init__(self, *args): QMainWindow.__init__(self, None, 'RemoteControl', Qt.WStyle_Customize | Qt.WStyle_NoBorderEx) self.initView() def initView(self): self.view = View(self, "RemoteControl") self.setCentralWidget(self.view) self.setCaption("Application") self.view.setFocus() def main(argv): app=QApplication(sys.argv) window=Window() window.show() app.connect(app, SIGNAL('lastWindowClosed()'), app, SLOT('quit()')) app.exec_loop() if __name__=="__main__": main(sys.argv)
Next, we create the main view:
Example 22-2. view.py - the main view of the remote control application
# # view.py = the main view to go with remote.py # from qt import * from button import Button class View(QWidget): buttondefs=[ ('register', 80, 111) , ('freeze', 22, 388) , ('minus', 22, 457) , ('toolbox', 130 , 166)] def __init__(self, *args): apply(QWidget.__init__,(self,)+args) self.setFixedSize(245,794) self.setBackgroundPixmap(QPixmap("remote.gif")) self.buttons=[] for bndef in self.buttondefs: print bndef bn=Button(self, bndef[0], bndef[1], bndef[2], bndef[0]+'up.gif', bndef[0]+'down.gif') self.buttons.append(bn) QObject.connect(bn, PYSIGNAL("pressed"), self.Pressed) def Pressed(self, name): print "Button", name, "pressed."
There is a class variable buttondef that contains the set of buttons we will use. Each definition consists of a base name from which the filenames of the up and down buttons will be deduced, and the X and Y position of the button on the window. These hardcoded positions make it impossible to use this technique together with layout managers.
A background image is set in the constructor, using setBackgroundPixmap; after this, all actual button objects are created. The button objects are instances of the button class:
Example 22-3. button.py - the class that implements the pixmapped buttons
# # button.py - pixmapped button definition to go with remote.py # from qt import * class Button(QWidget): def __init__(self, parent, name, x, y, up, down, *args): apply(QWidget.__init__,(self, parent)+args) self.pxUp=QPixmap(up) self.pxDown=QPixmap(down) self.setBackgroundPixmap(self.pxUp) self.name=name self.x=x self.y=y self.move(x, y) self.setFixedSize(self.pxUp.size()) def mousePressEvent(self, ev): self.setBackgroundPixmap(self.pxDown) def mouseReleaseEvent(self, ev): self.setBackgroundPixmap(self.pxUp) self.emit(PYSIGNAL("pressed"), (self.name, ))
The button class is interesting because of its extreme simplicity. All it really does is move itself to the right place and then wait for mouse clicks. When a mouse button is pressed, the down pixmap is shown, using setBackgroundPixmap(). When the mouse button is released, the up pixmap is restored. In order to be able to catch the press event in view.py, a signal is generated.
Creating a set of graphics for the number displays and updating those when the buttons are pressed has been left out of this book. (It's extremely tedious, I'm afraid to say).
Using pixmaps to create a distinctive user interface works well for smaller projects, and in situations where every detail has to be just right. However, the tedium involved in creating the individual pixmaps, and the lack of flexibility, makes the technique unsuitable for more complex interfaces.