We've just created a beautifully sculpted interface, with
all the highlights exactly right. Of course, the process lacked any
flexibility. Fortunately, PyQt offers complete access to the Qt
theming mechanism. Using this mechanism we can achieve almost the
same effect, but in a more flexible and generic manner.
The key class here is QStyle.
This class provides an enormous number of methods that are
used by Qt's widgets to express themselves on screen — and a
few hooks that you can use to intervene just before a widget
paints itself.
QCommonStyle is based on QStyle,
and implements most of
the actual drawing routines. On top of
QCommonStyle is a whole tree of styles
for Motif, Windows, MacOS-platinum, SGI and
CDE. A desktop environment like KDE adds a whole host of other
styles - and we can add one, too, once we have designed it.
Designing the style
Designing a successful widget style is an artistic
endeavor that calls for sensitive handling and an acute
awareness of usability principles, as can easily be seen from
the thousands of horrible styles available from websites like
www.themes.org. But then, designing a user interface calls for
the same expertise. That's the reason most software houses
employ interaction designers and graphic artists. Software
developers who don't have access to these specialists can
refer to the excellent books on interface design published by
Apple and Microsoft. These are the Macintosh Human
Interface Guidelines
(http://www.devworld.apple.com/techpubs/mac/HIGuidelines/HIGuidelines-2.html)
and Microsoft Guidelines for Interface
design, respectively. The Interface Hall of Shame
website (http://www.iarchitect.com/mshame.htm) has some
hilarious examples of interface design!
On the other hand, making something
look like something else is a lot easier.
For this project, I've pondered a few example styles
that would illustrate the capabilities of the
QStyle mechanism: the old Athena widget
look and feel, or the old ‘flat' MacOS look and feel. The new
Aqua style was right out, for legal reasons.
Perhaps the look of the rubber keyboard
or the old ZX-Spectrum? For my example, we'll try the old, flat MacOS look.
This one has the advantage of being visually simple. You can
complicate the implementation of a style enormously
by using complex gradients to fill buttons, or bitmaps
and masks to fill widget backgrounds. The
QStyle is flexible enough that you can
use anything the QPainter class offers
to paint your widgets. On the other hand, some parts are
exceedingly difficult to adapt, as we shall see.
Setting up
The system that is used to implement custom styles changed
completely between Qt 2 and Qt 3, which is a pity, because at
the time of writing this book the new styling system had not
been completely finished (and not wrapped for PyQt), but the
old system has been removed.
New styles are implemented by subclassing one of the
descendants of QStyle (this holds for
both systems).
Under the old system, most of the widget drawing code
would have been placed in the polish()
and unpolish() methods of the style
implementation.
The new system demands that you re-implement the
primitive drawing functions, such as
drawItem(),
drawPrimitive() or
drawComplexControl.
In both cases, you can decide to either use bitmaps for
buttons and backgrounds. This is shown in the
themes.py example in your
BlackAdder or
PyQt distribution. Alternatively, you can
write your own drawing routines. It is surprising that
Python is fast enough to allow re-implementing the most basic
drawing routines.
A Qt 2 custom style
Example 22-4. A Qt 2 custom style - a minimalist
implementation of the classic Mac style in PyQt.
#
# macstyle.py - A minimalist implementation of the Mac Classic style for PyQt
# and Qt 2.
# Use with styletester.py
#
from qt import *
import time
FALSE=0
TRUE=1
class MacStyle(QWindowsStyle):
def __init__(self):
QWindowsStyle.__init__(self)
self.setButtonDefaultIndicatorWidth(0)
def polish(self, object):
# Overloading doesn't work here, for Python cannot distinguish
# between QApplication and QWidget - Python can only overload based
# on the number of arguments.
if isinstance(object, QApplication):
self.polish_qapplication(object)
elif isinstance(object, QWidget):
self.polish_qwidget(object)
else:
QPlatinumStyle.polish(self, object)
def unPolish(self, object):
if isinstance(object, QApplication):
self.unPolish_qapplication(object)
elif isinstance(object, QWidget):
self.unPolish_qwidget(object)
else:
QPlatinumStyle.unPolish(self, object)
def polish_qapplication(self, app):
# Set a font that's an approximation of Chicago: keep a ref to
# the old font
self.oldFont=app.font()
app.setFont(QFont("chicago",
app.font().pointSize()-2, QFont.Bold),
TRUE)
# keep a reference to the old color palette, otherwise
# it cannot be restored
self.oldPalette=app.palette()
# create a new palette - black, white and 50% gray for text and buttons
# color definitions
white=QColor("white")
lightgray=QColor(210,210,210)
gray=QColor(190,190,190)
darkgray=QColor(120,120,120)
black=QColor("black")
active=QColorGroup()
#
# Basic colors
#
active.setColor(QColorGroup.Background,
white) # general background color
active.setColor(QColorGroup.Foreground,
black) # general foreground color
active.setColor(QColorGroup.Base,
white) # lighter background for text widgets
active.setColor(QColorGroup.Text,
black) # foreground to go with Base
active.setColor(QColorGroup.Button,
white) # button background color
active.setColor(QColorGroup.ButtonText,
black) # button text color
#
# Used for bevels and shadows
#
active.setColor(QColorGroup.Light,
lightgray ) # a bit lighter than Button
active.setColor(QColorGroup.Midlight,
gray)
active.setColor(QColorGroup.Dark,
darkgray) # depressed button state
active.setColor(QColorGroup.Mid,
gray) # mid tone
active.setColor(QColorGroup.Shadow,
black) # shadow tone
#
# Selections
#
active.setColor(QColorGroup.Highlight,
black)
active.setColor(QColorGroup.HighlightedText,
white)
#
# Text color that shows well on Dark
#
active.setColor(QColorGroup.BrightText,
white)
disabled=QColorGroup(active)
disabled.setColor(QColorGroup.Base, gray)
disabled.setColor(QColorGroup.Text, darkgray)
inactive=QColorGroup(active)
inactive.setColor(QColorGroup.Text, darkgray)
self.newPalette=QPalette(active, disabled, inactive)
app.setPalette(self.newPalette, TRUE)
def unPolish_qapplication(self, app):
# Restore the old palette
app.setFont(self.oldFont, TRUE)
app.setPalette(self.oldPalette, TRUE)
def polish_qwidget(self, w):
# Hook to set attributes of certain widgets
# the polish function will set some widgets to transparent mode, to get the
# full benefit from the nice pixmaps in the color group.
if w.inherits("QTipLabel"):
return
if w.inherits("QLCDNumber"):
return
if not w.isTopLevel():
if w.inherits("QLabel") \
or w.inherits("QButton") \
or w.inherits("QComboBox") \
or w.inherits("QGroupBox") \
or w.inherits("QSlider") \
or w.inherits("QTabWidget") \
or w.inherits("QPanel"):
w.setAutoMask(TRUE)
def unPolish_qwidget(self, w):
# Undo what we did in polish_qwidget
if w.inherits("QTipLabel"):
return
if w.inherits("QLCDNumber"):
return
if not w.isTopLevel():
if w.inherits("QLabel") \
or w.inherits("QButton") \
or w.inherits("QComboBox") \
or w.inherits("QGroupBox") \
or w.inherits("QSlider") \
or w.inherits("QTabWidget") \
or w.inherits("QPanel"):
w.setAutoMask(FALSE)
#
# Panel, rectangles and lines
#
def drawPopupPanel(self, painter,x, y, w, h, colorGroup, lineWidth, fill):
self.drawPanel(painter, x, y, w, h, colorGroup, FALSE, lineWidth, fill)
def drawPanel(self, painter, x, y, w, h, colorGroup, sunken, lineWidth, fill):
oldpen=painter.pen()
oldbrush=painter.brush()
if sunken:
painter.setPen(QPen(colorGroup.foreground(), 2, QPen.DotLine))
else:
painter.setPen(QPen(colorGroup.foreground(), 2))
if fill:
oldbrush=painter.brush()
painter.setPen(colorGroup.foreground())
painter.setBrush(fill)
painter.drawRect(x + 2, y + 2, w - 2, h - 2)
painter.setPen(oldpen)
painter.setBrush(oldbrush)
def drawRect(self, painter, x, y, w, h, color, lineWidth, fill):
qDrawPlainRect(painter, x, y, w, h, color, lineWidth, fill)
def drawRectStrong(self, painter, x, y, w, h, colorGroup,
sunken, lineWidth, midLineWidth, fill):
qDrawPlainRect(painter, x, y, w, h, colorGroup.foreground(),
sunken, lineWidth *2, fill)
def drawSeparator(self, painter, x1, y1, x2, y2, colorGroup,
sunken, lineWidth, midLineWidth):
painter.save()
painter.setPen(colorGroup.foreground, lineWidth)
painter.drawLine(x1, y1, x2, y2)
painter.restore()
def drawroundrect(self, painter, x, y, w, h):
painter.drawRoundRect(x, y, w, h, 5, 50)
def roundRectRegion(self, rect, r):
x=rect.x()
y=rect.y()
right=x1+rect.right()
bottom=y1+rect.bottom()
a=QPointArray([8, x+r, y, right-r, y,
right, y + r, right, bottom-r,
right-r, bottom, x+r, bottom,
x, bottom-r, x, y+r])
region=QRegion(a)
d=r*2-1
region.unite(QRegion(x, y, r*2, r*2, QRegion.Ellipse))
region.unite(QRegion(right - d, y, r*2, r*2, QRegion.Ellipse))
region.unite(QRegion(x, bottom-d, r*2, r*2, QRegion.Ellipse))
region.unite(QRegion(right-d, bottom-d, r*2, r*2, QRegion.Ellipse))
return region
#
# Tab
#
def drawTab(self, painter, tabBar, tab, selected):
a=QPointArray(10)
a.setPoint(0, 0, -1)
a.setPoint(1, 0, 0)
# Nasty! r is a private member of QTab. We shouldn't access it.
y=tab.r.height()-2
x=y/2
x=x+1
a.setPoint(2, x, y-1)
x=x+1
a.setPoint(3, x, y)
x=x+1
y=y+1
a.setPoint(3, x, y)
a.setPoint(4, x, y)
right=tab.r.width()-1
for i in range(5):
a.setPoint(9-i, right - a.point(i)[0], a.point(i)[1])
for i in range(10):
a.setPoint(i, a.point(i)[0], tab.r.height() - 1 - a.point(i)[1])
a.translate(tab.r.left(), tab.r.top())
if selected:
painter.setBrush(tabBar.colorGroup().background())
else:
painter.setBrush(tabBar.colorGroup().light())
painter.setPen(tabBar.colorGroup().foreground())
painter.drawPolygon(a)
painter.setBrush(Qt.NoBrush)
def drawTabMask(self, painter, tabbar, tab, selected):
painter.drawRect(tab.r)
#
# Sliders
#
def drawSlider(self, painter, x, y, w, h, colorGroup,
orientation, tickAbove, tickBelow):
pass
def drawSliderMask(self, painter, x, y, w, h,
orientation, tickAbove, tickBelow):
painter.fillRect(x, y, w, h, Qt.color1)
def drawSliderGrooveMask(self, painter, x, y, w, h, coord, orientation):
colorGroup=QColorGroup(Qt.color1, Qt.color1, Qt.color1, Qt.color1,
Qt.color1, Qt.color1, Qt.color1, Qt.color1,
Qt.color0)
if orientation==Qt.Horizontal:
painter.fillRect(x, y, w, h, Qt.color1)
else:
painter.fillRect(x, y, w, h, Qt.color1)
#
# Buttons and pushbuttons
#
def drawButton(self, painter, x, y, w, h, colorGroup,
sunken=FALSE, fill=None):
oldBrush=painter.brush()
if fill != None:
painter.setBrush(fill)
self.drawroundrect(painter, x, y, w, h)
painter.setBrush(oldBrush)
def drawPushButtonlabel (self, button, painter):
QWindowsStyle.drawPushButonLabel(self, button, painter)
def drawPushButton(self, button, painter):
colorGroup=button.colorGroup()
(x1, y1, x2, y2)=button.rect().coords()
painter.setPen(colorGroup.foreground())
painter.setBrush(QBrush(colorGroup.button(),
Qt.NoBrush))
if button.isDown():
brush=QBrush()
brush.setColor(colorGroup.highlight())
brush.setStyle(QBrush.SolidPattern)
fill=brush
elif button.isOn():
brush=QBrush()
brush.setColor(colorGroup.mid())
brush.setStyle(QBrush.SolidPattern)
fill=brush
else:
fill=colorGroup.brush(colorGroup.Button)
if button.isDefault():
painter.setPen(QPen(Qt.black, 3))
self.drawroundrect(painter, x1, y1, x2-x1+1, y2-y1+1)
painter.setPen(QPen(Qt.black, 1))
x1=x1+4
y1=y1+4
x2=x2-4
y2=y2-4
if button.isOn() or button.isDown():
sunken=TRUE
else:
sunken=FALSE
self.drawButton(painter, x1, y1, x2-x1+1, y2-y1+1,
colorGroup, sunken, fill)
if button.isMenuButton():
dx=(y1-y2-4)/3
self.drawArrow(painter, Qt.DownArrow, FALSE,
x2-dx, dx, y1, y2-y1,
colorGroup, button.isEnabled())
if painter.brush().style != Qt.NoBrush:
painter.setBrush(Qt.NoBrush)
def drawPushButtonLabel(self, button, painter):
r=button.rect()
(x, y, w, h)=r.rect()
(x1, y1, x2, y2)=button.rect().coords()
dx=0
dy=0
if button.isMenuButton():
dx=(y2-y1)/3
if dx or dy:
p.translate(dx,dy)
x=x+2
y=y+2
w=w-4
h=h-4
g=button.colorGroup()
if button.isDown() or button.isOn():
pencolour=button.colorGroup().brightText()
else:
pencolour=button.colorGroup().buttonText()
self.drawItem(painter, x, y, w, h,
Qt.AlignCenter|Qt.ShowPrefix,
g, button.isEnabled(),
button.pixmap(), button.text(), -1,
pencolour)
if dx or dy:
painter.translate(-dx,-dy)
def drawBevelButton(self, painter, x, y, w, h, colorGroup,
sunken=FALSE, fill=None):
self.drawButton(painter, x, y, w, h, colorGroup, sunken, fill)
def buttonRect(self, x, y, w, h):
return QRect(x+3, y+2, w-6, h-4)
def drawButtonMask(self, p, x, y, w, h):
self.drawroundrect(p, x, y, w, h)
#
# Radio Button
#
def drawExclusiveIndicator(self, painter, x, y, w, h, colorGroup,
on, down, enabled):
painter.eraseRect(x, y, w, h)
painter.drawEllipse(x, y, w, h)
if on:
painter.setBrush(QBrush(colorGroup.foreground(), \
QBrush.SolidPattern))
painter.drawEllipse(x + 3, y + 3, w - 6, h -6)
def drawExclusiveIndicatorMask(self, painter, x, y, w, h, on):
painter.fillRect(x, y, w, h, QBrush(Qt.color1))
#
# Checkbox
#
def drawIndicator(self, painter, x, y, w, h, colorGroup,
state, down, enabled):
painter.save()
if enabled:
painter.setPen(QPen(colorGroup.foreground(), 1, \
QPen.SolidLine))
else:
painter.setPen(QPen(colorGroup.mid(), 1, QPen.SolidLine))
if state==QButton.Off:
painter.setBrush(QBrush(colorGroup.background(), \
QBrush.SolidPattern))
elif state==QButton.NoChange:
painter.setBrush(QBrush(colorGroup.dark(), \
QBrush.SolidPattern))
else:
painter.setBrush(QBrush(colorGroup.background(), \
QBrush.SolidPattern))
painter.drawRect(x, y, w, h)
if state==QButton.On:
painter.drawLine(x, y, x + w, y + h)
painter.drawLine(x, y + h - 1, x + w - 1, y)
painter.restore()
def drawIndicatorMask(self, painter, x, y, w, h, state):
painter.fillRect(x, y , w + 3, h, QBrush(Qt.color1))
#
# Menu bar
#
def drawMenuBarItem(self, painter, x, y, w, h, menuItem, \
colorGroup, enabled, active):
"""
Not subclassable?
"""
self.drawItem(painter, x, y, w, h,
Qt.AlignCenter | Qt.ShowPrefix | Qt.DontClip | \
Qt.SingleLine, colorGroup, menuItem.pixmap(), \
menuItem.text(), -1, QColorGroup.buttonText())
#
# These items are not (yet) implemented in PyQt
#
def drawPopupMenuItem (self, painter, checkable, maxpmw, tab,
menuItem, palette, act, enabled,
x, y, w, h):
"""
Not implemented in PyQt
"""
pass
def extraPopupMenuItemWidth (self, checkable, maxpmw,
menuItem, fontMetrics):
"""
Not implemented in PyQt
"""
pass
def popupMenuItemHeight (self, checkable, menuItem, fontMetrics):
"""
Not implemented in PyQt
"""
pass
Using styles from PyQt
Using these custom styles in your own application
is as simple as using the built-in styles. I have adapted the
small themes.py example that comes with
BlackAdder or
PyQt to show off the new custom
styles:
Example 22-5. Testing styles
#
# styletester.py - a testbed for styles.
# Based on Phil's adaption of my translation of the
# Qt themes example app.
#
FALSE=0
TRUE=1
import os, sys
from qt import *
from macstyle import MacStyle
class ButtonsGroups(QVBox):
def __init__(self, parent=None, name=None):
QVBox.__init__(self, parent, name)
# Create widgets which allow easy layouting
box1=QHBox(self)
box2=QHBox(self)
# first group
# Create an exclusive button group
grp1=QButtonGroup( 1
, QGroupBox.Horizontal
, "Button Group 1 (exclusive)"
, box1
)
grp1.setExclusive(TRUE)
# insert 3 radiobuttons
rb11=QRadioButton("&Radiobutton 1", grp1)
rb11.setChecked(TRUE)
QRadioButton("R&adiobutton 2", grp1)
QRadioButton("Ra&diobutton 3", grp1)
# second group
# Create a non-exclusive buttongroup
grp2=QButtonGroup( 1
, QGroupBox.Horizontal
, "Button Group 2 (non-exclusive)"
, box1
)
grp2.setExclusive(FALSE)
# insert 3 checkboxes
QCheckBox("&Checkbox 1", grp2)
cb12=QCheckBox("C&heckbox 2", grp2)
cb12.setChecked(TRUE)
cb13=QCheckBox("Triple &State Button", grp2)
cb13.setTristate(TRUE)
cb13.setChecked(TRUE)
# third group
# create a buttongroup which is exclusive for radiobuttons and
# non-exclusive for all other buttons
grp3=QButtonGroup( 1
, QGroupBox.Horizontal
, "Button Group 3 (Radiobutton-exclusive)"
, box2
)
grp3.setRadioButtonExclusive(TRUE)
# insert three radiobuttons
self.rb21=QRadioButton("Rad&iobutton 1", grp3)
self.rb22=QRadioButton("Radi&obutton 2", grp3)
self.rb23=QRadioButton("Radio&button 3", grp3)
self.rb23.setChecked(TRUE)
# insert a checkbox...
self.state=QCheckBox("E&nable Radiobuttons", grp3)
self.state.setChecked(TRUE)
# ...and connect its SIGNAL clicked() with the SLOT slotChangeGrp3State()
self.connect(self.state, SIGNAL('clicked()'),self.slotChangeGrp3State)
# fourth group
# create a groupbox which lays out its childs in a column
grp4=QGroupBox( 1
, QGroupBox.Horizontal
, "Groupbox with normal buttons"
, box2
)
# insert two pushbuttons...
QPushButton("&Push Button", grp4)
bn=QPushButton("&Default Button", grp4)
bn.setDefault(TRUE)
tb=QPushButton("&Toggle Button", grp4)
# ...and make the second one a toggle button
tb.setToggleButton(TRUE)
tb.setOn(TRUE)
def slotChangeGrp3State(self):
self.rb21.setEnabled(self.state.isChecked())
self.rb22.setEnabled(self.state.isChecked())
self.rb23.setEnabled(self.state.isChecked())
class LineEdits(QVBox):
def __init__(self, parent=None, name=None):
QVBox.__init__(self, parent, name)
self.setMargin(10)
# Widget for layouting
row1=QHBox(self)
row1.setMargin(5)
# Create a label
QLabel("Echo Mode: ", row1)
# Create a Combobox with three items...
self.combo1=QComboBox(FALSE, row1)
self.combo1.insertItem("Normal", -1)
self.combo1.insertItem("Password", -1)
self.combo1.insertItem("No Echo", -1)
self.connect(self.combo1, SIGNAL('activated(int)'), self.slotEchoChanged)
# insert the first LineEdit
self.lined1=QLineEdit(self)
# another widget which is used for layouting
row2=QHBox(self)
row2.setMargin(5)
# and the second label
QLabel("Validator: ", row2)
# A second Combobox with again three items...
self.combo2=QComboBox(FALSE, row2)
self.combo2.insertItem("No Validator", -1)
self.combo2.insertItem("Integer Validator", -1)
self.combo2.insertItem("Double Validator", -1)
self.connect(self.combo2, SIGNAL('activated(int)'),
self.slotValidatorChanged)
# and the second LineEdit
self.lined2=QLineEdit(self)
# yet another widget which is used for layouting
row3=QHBox(self)
row3.setMargin(5)
# we need a label for this too
QLabel("Alignment: ", row3)
# A combo box for setting alignment
self.combo3=QComboBox(FALSE, row3)
self.combo3.insertItem("Left", -1)
self.combo3.insertItem("Centered", -1)
self.combo3.insertItem("Right", -1)
self.connect(self.combo3, SIGNAL('activated(int)'),
self.slotAlignmentChanged)
# and the lineedit
self.lined3=QLineEdit(self)
# give the first LineEdit the focus at the beginning
self.lined1.setFocus()
def slotEchoChanged(self, i):
if i == 0:
self.lined1.setEchoMode(QLineEdit.EchoMode.Normal)
elif i == 1:
self.lined1.setEchoMode(QLineEdit.EchoMode.Password)
elif i == 2:
self.lined1.setEchoMode(QLineEdit.EchoMode.NoEcho)
self.lined1.setFocus()
def slotValidatorChanged(self, i):
if i == 0:
self.validator=None
self.lined2.setValidator(self.validator)
elif i == 1:
self.validator=QIntValidator(self.lined2)
self.lined2.setValidator(self.validator)
elif i == 2:
self.validator=QDoubleValidator(-999.0, 999.0, 2, self.lined2)
self.lined2.setValidator(self.validator)
self.lined2.setText("")
self.lined2.setFocus()
def slotAlignmentChanged(self, i):
if i == 0:
self.lined3.setAlignment(Qt.AlignLeft)
elif i == 1:
self.lined3.setAlignment(Qt.AlignCenter)
elif i == 2:
self.lined3.setAlignment(Qt.AlignRight)
self.lined3.setFocus()
class ProgressBar(QVBox):
def __init__(self, parent=None, name=None):
QVBox.__init__(self, parent, name)
self.timer=QTimer()
self.setMargin(10)
# Create a radiobutton-exclusive Buttongroup which aligns its childs
# in two columns
bg=QButtonGroup(2, QGroupBox.Horizontal, self)
bg.setRadioButtonExclusive(TRUE)
# insert three radiobuttons which the user can use to set the speed
# of the progress and two pushbuttons to start/pause/continue and
# reset the progress
self.slow=QRadioButton("&Slow", bg)
self.start=QPushButton("S&tart", bg)
self.normal=QRadioButton("&Normal", bg)
self.reset=QPushButton("&Reset", bg)
self.fast=QRadioButton("&Fast", bg)
# Create the progressbar
self.progress=QProgressBar(100, self)
# connect the clicked() SIGNALs of the pushbuttons to SLOTs
self.connect(self.start, SIGNAL('clicked()'), self.slotStart)
self.connect(self.reset, SIGNAL('clicked()'), self.slotReset)
# connect the timeout() SIGNAL of the progress-timer to a SLOT
self.connect(self.timer, SIGNAL('timeout()'), self.slotTimeout)
# Let's start with normal speed...
self.normal.setChecked(TRUE)
def slotStart(self):
# If the progress bar is at the beginning...
if self.progress.progress() == -1:
# ...set according to the checked speed-radionbutton the number of
# steps which are needed to complete the process
if self.slow.isChecked():
self.progress.setTotalSteps(10000)
elif self.normal.isChecked():
self.progress.setTotalSteps(1000)
else:
self.progress.setTotalSteps(50)
# disable the speed-radiobuttons:
self.slow.setEnabled(FALSE)
self.normal.setEnabled(FALSE)
self.fast.setEnabled(FALSE)
# If the progress is not running...
if not self.timer.isActive():
# ...start the time (and so the progress) with an interval fo 1ms...
self.timer.start(1)
# ...and rename the start/pause/continue button to Pause
self.start.setText("&Pause")
else:
# ...stop the timer (and so the progress)...
self.timer.stop()
# ...and rename the start/pause/continue button to Continue
self.start.setText("&Continue")
def slotReset(self):
# stop the timer and progress
self.timer.stop()
# rename the start/pause/continue button to Start...
self.start.setText("&Start")
# ...and enable this button
self.start.setEnabled(TRUE)
# enable the speed-radiobuttons
self.slow.setEnabled(TRUE)
self.normal.setEnabled(TRUE)
self.fast.setEnabled(TRUE)
# reset the progressbar
self.progress.reset()
def slotTimeout(self):
p = self.progress.progress()
# If the progress is complete...
if p == self.progress.totalSteps():
# ...rename the start/pause/continue button to Start...
self.start.setText("&Start")
# ...and disable it...
self.start.setEnabled(FALSE)
# ...and return
return
# If the progress is not complete increase it
self.progress.setProgress(p+1)
class ListBoxCombo(QVBox):
def __init__(self, parent=None, name=None):
QVBox.__init__(self, parent, name)
self.setMargin(5)
row1=QHBox(self)
row1.setMargin(5)
# Create a multi-selection ListBox...
self.lb1=QListBox(row1)
self.lb1.setMultiSelection(TRUE)
# ...insert a pixmap item...
self.lb1.insertItem(QPixmap("qtlogo.png"))
# ...and 100 text items
for i in range(100):
str=QString("Listbox Item %1").arg(i)
self.lb1.insertItem(str)
# Create a pushbutton...
self.arrow1=QPushButton(" -> ", row1)
# ...and connect the clicked SIGNAL with the SLOT slotLeft2Right
self.connect(self.arrow1, SIGNAL('clicked()'), self.slotLeft2Right)
# create an empty single-selection ListBox
self.lb2=QListBox(row1)
def slotLeft2Right(self):
# Go through all items of the first ListBox
for i in range(self.lb1.count()):
item=self.lb1.item(i)
# if the item is selected...
if item.selected():
# ...and it is a text item...
if not item.text().isEmpty():
# ...insert an item with the same text into the second ListBox
self.lb2.insertItem(QListBoxText(item.text()))
# ...and if it is a pixmap item...
elif item.pixmap():
# ...insert an item with the same pixmap into the second ListBox
self.lb2.insertItem(QListBoxPixmap(item.pixmap()))
class Themes(QMainWindow):
def __init__(self, parent=None, name=None, f=Qt.WType_TopLevel):
QMainWindow.__init__(self, parent, name, f)
self.appFont=QApplication.font()
self.tabwidget=QTabWidget(self)
self.buttonsgroups=ButtonsGroups(self.tabwidget)
self.tabwidget.addTab(self.buttonsgroups,"Buttons/Groups")
self.hbox=QHBox(self.tabwidget)
self.hbox.setMargin(5)
self.linedits=LineEdits(self.hbox)
self.progressbar=ProgressBar(self.hbox)
self.tabwidget.addTab(self.hbox, "Lineedits/Progressbar")
self.listboxcombo=ListBoxCombo(self.tabwidget)
self.tabwidget.addTab(self.listboxcombo, "Listboxes/Comboboxes")
self.setCentralWidget(self.tabwidget)
self.style=QPopupMenu(self)
self.style.setCheckable(TRUE)
self.menuBar().insertItem("&Style", self.style)
self.sMacStyle=self.style.insertItem("&Classic Mac", self.styleMac)
self.sPlatinum=self.style.insertItem("&Platinum", self.stylePlatinum)
self.sWindows=self.style.insertItem("&Windows", self.styleWindows)
self.sCDE=self.style.insertItem("&CDE", self.styleCDE)
self.sMotif=self.style.insertItem("M&otif", self.styleMotif)
self.sMotifPlus=self.style.insertItem("Motif P&lus", \
self.styleMotifPlus)
self.style.insertSeparator()
self.style.insertItem("&Quit", qApp.quit, Qt.CTRL | Qt.Key_Q)
self.help=QPopupMenu(self)
self.menuBar().insertSeparator()
self.menuBar().insertItem("&Help", self.help)
self.help.insertItem("&About", self.about, Qt.Key_F1)
self.help.insertItem("About &Qt", self.aboutQt)
self.style=MacStyle()
qApp.setStyle(self.style)
self.menuBar().setItemChecked(self.sMacStyle, TRUE)
# In the following we cannot simply set the new style as we can in C++.
# We need to keep the old style alive (if it is a Python one) so that it's
# unPolish methods can still be called when the new style is set.
def styleMac(self):
newstyle=MacStyle()
qApp.setStyle(newstyle)
self.style=newstyle
self.selectStyleMenu(self.sMacStyle)
def stylePlatinum(self):
newstyle=QPlatinumStyle()
qApp.setStyle(newstyle)
self.style=newstyle
p=QPalette(QColor(239, 239, 239))
qApp.setPalette(p, TRUE)
qApp.setFont(self.appFont, TRUE)
self.selectStyleMenu(self.sPlatinum)
def styleWindows(self):
newstyle=QWindowsStyle()
qApp.setStyle(newstyle)
self.style=newstyle
qApp.setFont(self.appFont, TRUE)
self.selectStyleMenu(self.sWindows)
def styleCDE(self):
newstyle=QCDEStyle(TRUE)
qApp.setStyle(newstyle)
self.style=newstyle
self.selectStyleMenu(self.sCDE)
p=QPalette(QColor(75, 123, 130))
p.setColor(QPalette.Active, QColorGroup.Base, QColor(55, 77, 78));
p.setColor(QPalette.Inactive, QColorGroup.Base, QColor(55, 77, 78));
p.setColor(QPalette.Disabled, QColorGroup.Base, QColor(55, 77, 78));
p.setColor(QPalette.Active, QColorGroup.Highlight, Qt.white);
p.setColor(QPalette.Active, QColorGroup.HighlightedText, \
QColor(55, 77, 78));
p.setColor(QPalette.Inactive, QColorGroup.Highlight, Qt.white);
p.setColor(QPalette.Inactive, QColorGroup.HighlightedText, \
QColor(55, 77, 78));
p.setColor(QPalette.Disabled, QColorGroup.Highlight, Qt.white);
p.setColor(QPalette.Disabled, QColorGroup.HighlightedText, \
QColor(55, 77, 78));
p.setColor(QPalette.Active, QColorGroup.Foreground, Qt.white);
p.setColor(QPalette.Active, QColorGroup.Text, Qt.white);
p.setColor(QPalette.Active, QColorGroup.ButtonText, Qt.white);
p.setColor(QPalette.Inactive, QColorGroup.Foreground, Qt.white);
p.setColor(QPalette.Inactive, QColorGroup.Text, Qt.white);
p.setColor(QPalette.Inactive, QColorGroup.ButtonText, Qt.white);
p.setColor(QPalette.Disabled, QColorGroup.Foreground, Qt.lightGray);
p.setColor(QPalette.Disabled, QColorGroup.Text, Qt.lightGray);
p.setColor(QPalette.Disabled, QColorGroup.ButtonText, Qt.lightGray);
qApp.setPalette(p, TRUE)
qApp.setFont(QFont("times", self.appFont.pointSize()), TRUE)
def styleMotif(self):
newstyle=QMotifStyle(TRUE)
qApp.setStyle(newstyle)
self.style=newstyle
p=QPalette(QColor(192, 192, 192))
qApp.setPalette(p, TRUE)
qApp.setFont(self.appFont, TRUE)
self.selectStyleMenu(self.sMotif)
def styleMotifPlus(self):
newstyle=QMotifPlusStyle(TRUE)
qApp.setStyle(newstyle)
self.style=newstyle
p=QPalette(QColor(192, 192, 192))
qApp.setPalette(p, TRUE)
qApp.setFont(self.appFont, TRUE)
self.selectStyleMenu(self.sMotifPlus)
def about(self):
QMessageBox.about(self, "Qt Themes Example",
"<p>This example demonstrates the concept of "
"<b>generalized GUI styles </b> first \
introduced "
" with the 2.0 release of Qt.</p>" )
def aboutQt(self):
QMessageBox.aboutQt(self, "Qt Themes Testbed")
def selectStyleMenu(self, s):
self.menuBar().setItemChecked(self.sMacStyle, FALSE)
self.menuBar().setItemChecked(self.sPlatinum, FALSE)
self.menuBar().setItemChecked(self.sCDE, FALSE)
self.menuBar().setItemChecked(self.sMotifPlus, FALSE)
self.menuBar().setItemChecked(self.sMotif, FALSE)
self.menuBar().setItemChecked(self.sWindows, FALSE)
self.menuBar().setItemChecked(s, TRUE)
def main(argv):
QApplication.setColorSpec(QApplication.CustomColor)
QApplication.setStyle(QWindowsStyle())
a=QApplication(sys.argv)
themes=Themes()
themes.setCaption('Theme (QStyle) example')
themes.resize(640,400)
a.setMainWidget(themes)
themes.show()
return a.exec_loop()
if __name__=="__main__":
main(sys.argv)
As you can see, it's a lot of work to
create a style from scratch, and in this case, the result is
not very impressive, but very retro, especially if we also use
the classic Chicago font: