Faking it with bitmaps
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:
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.
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.