With the introduction of System 8, a number of additional standard controls
were introduced. One of these new controls was the beveled button; they
behave like regular buttons, but have a rectangular 3D appearance, rather
than the usual rounded rectangle. They come in three types with varying
levels of apparent relief. We'll subclass the W.Button
class to display
these new controls, instead of the standard push button.
Firstly we need to import the W
module to gain access to W.Button
class, the Ctl
module to access the Control Manager toolbox
calls, and the Controls
module to access the Control Manager
constants. We also need Qd
for drawing the default ring, and
a utility routine from Wbase
which helps with the beveled
appearance.
import W import Ctl import Controls import Qd from Wbase import _darkencolor
The first class we define will be a standard bevel button, which subclasses
the Button
class:
class NormalBevelButton(W.Button):
Each of the the bevel buttons is created by a different control definition procedure. To make it easier to create the other types of bevel button, we'll define the control procedure as a class variable:
procID = Controls.kControlBevelButtonNormalBevelProc
Most of Button
's methods will work just
fine with our new button class, but Button.__init__
assumes
that the control definition procedure is pushButProc
. We need
to override that to use the correct control definition:
def __init__(self, possize, title = "BevelButton", callback = None): W.ControlWidget.__init__(self, possize, title, self.procID, callback, 0, 0, 1) self._isdefault = 0
The change of shape means that we also need to change what happens if the
button is the default button of a window. Normally the Appearance Manager
would take care of this automatically, but the interface is not yet
implemented in the Ctl
module. Instead we will have to draw
the default ring by hand around the button.
The method which takes care of this is drawfatframe()
. Again,
the change is fairly straightforward (although it looks complex because of
the drawing commands):
def drawfatframe(self, onoff): # draw a platinum appearance default box color = (0xe000, 0xe000, 0xe000) (l, t, r, b) = Qd.InsetRect(self._bounds, -4, -4) if onoff: # Draw the frame Qd.FrameRect((l, t, r, b)) Qd.RGBForeColor(color) Qd.PaintRect((l+1, t+1, r-1, b-1)) Qd.RGBForeColor(_darkencolor(color)) Qd.MoveTo(l+2, b-2) Qd.LineTo(r-2, b-2) Qd.LineTo(r-2, t+2) Qd.RGBForeColor(_darkencolor(color)) (l, t, r, b) = Qd.InsetRect(self._bounds, -1, -1) Qd.PaintRect((l, t, r, b)) Qd.RGBForeColor((0, 0, 0)) Qd.RGBForeColor(_darkencolor(color)) Qd.MoveTo(l, b-1) Qd.LineTo(r-1, b-1) Qd.LineTo(r-1, t) else: # Erase the frame Qd.RGBForeColor((0xffff, 0xffff, 0xffff)) Qd.PaintRect((l, t, r, b)) Qd.RGBForeColor((0, 0, 0))
However, because we were lazy and used Qd.PaintRect
rather
than Qd.FrameRect
to draw the fat frame, the frame draws over
the top of the button. Fixing this is simple, but requires rewriting a few
routines to make sure the button control is drawn after the
default ring.
def enable(self, onoff): if self._control and self._enabled != onoff: self._enabled = onoff if self._isdefault and self._visible: self.SetPort() self.drawfatframe(onoff) self._control.HiliteControl((not onoff) and 255) def activate(self, onoff): self._activated = onoff if self._enabled: if self._isdefault and self._visible: self.SetPort() self.drawfatframe(onoff) self._control.HiliteControl((not onoff) and 255) def draw(self, visRgn = None): if self._visible: if self._isdefault and self._activated: self.drawfatframe(self._enabled) self._control.Draw1Control() def _setdefault(self, onoff): self._isdefault = onoff if self._control and self._enabled: self.SetPort() self.drawfatframe(onoff) self.draw()
We've now done all the hard work. Defining classes for the other two types of bevel button is as simple as:
class SmallBevelButton(NormalBevelButton): ProcID = Controls.kControlBevelButtonSmallBevelProc class LargeBevelButton(NormalBevelButton): ProcID = Controls.kControlBevelButtonLargeBevelProc
You can view the code put together into a complete module. Run it as main in the IDE to see it in action.
These classes could be improved in a number of ways - to take the overall theme into account when drawing the default frame, for example - but they do illustrate the process involved in writing your own widget classes.
(XXX SmallBevelButton
and LargeBevelButton
look
the same as NormalBevelButton
, despite the correct
ProcID
being passed, on my computer. Is Appearance Manager
somehow not set up correctly?)
ControlWidget.__init__()
to
kControlBehaviorToggles
or
kControlBehaviorSticky
, a bevel button can act like a check
box or a radio button, respectively. Subclass the RadioButton
and CheckBox
widgets to use bevel
buttons.drawfatframe()
routine, so that the other routines
do not need to be changed.