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 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.
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.
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 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:
Some Qt widgets with an ancient Mac look.