4 # Import module 'rect' renamed as '_rect' to avoid exporting it on
5 # 'from Buttons import *'
12 # Field indices in mouse event detail
20 # LabelAppearance provides defaults for all appearance methods.
21 # selected state not visible
22 # disabled --> crossed out
23 # hilited --> inverted
25 class LabelAppearance
:
29 def init_appearance(self
):
30 self
.bounds
= _rect
.empty
38 def getminsize(self
, m
, (width
, height
)):
39 width
= max(width
, m
.textwidth(self
.text
) + 6)
40 height
= max(height
, m
.lineheight() + 6)
46 # Changing the parameters
48 def settext(self
, text
):
50 if self
.bounds
<> _rect
.empty
:
54 def setbounds(self
, bounds
):
56 if self
.bounds
<> _rect
.empty
:
62 # Changing the state bits
64 def enable(self
, flag
):
65 if flag
<> self
.enabled
:
67 if self
.bounds
<> _rect
.empty
:
68 self
.flipenable(self
.parent
.begindrawing())
70 def hilite(self
, flag
):
71 if flag
<> self
.hilited
:
73 if self
.bounds
<> _rect
.empty
:
74 self
.fliphilite(self
.parent
.begindrawing())
76 def select(self
, flag
):
77 if flag
<> self
.selected
:
79 if self
.bounds
<> _rect
.empty
:
82 # Recalculate the box bounds and text position.
83 # This can be overridden by buttons that draw different boxes
84 # or want their text in a different position.
87 if self
.bounds
<> _rect
.empty
:
91 def recalcbounds(self
):
92 self
.hilitebounds
= _rect
.inset(self
.bounds
, (3, 3))
93 self
.crossbounds
= self
.bounds
95 def recalctextpos(self
):
96 (left
, top
), (right
, bottom
) = self
.bounds
97 m
= self
.parent
.beginmeasuring()
98 h
= (left
+ right
- m
.textwidth(self
.text
)) / 2
99 v
= (top
+ bottom
- m
.lineheight()) / 2
102 # Generic drawing interface.
103 # Do not override redraw() or draw() methods; override drawit() c.s.
106 if self
.bounds
<> _rect
.empty
:
107 d
= self
.parent
.begindrawing()
109 self
.draw(d
, self
.bounds
)
111 def draw(self
, d
, area
):
112 area
= _rect
.intersect([area
, self
.bounds
])
113 if area
== _rect
.empty
:
119 # The drawit() method is fairly generic but may be overridden.
124 d
.text(self
.textpos
, self
.text
)
130 # Default drawing detail functions.
131 # Overriding these is normally sufficient to get different
134 def drawpict(self
, d
):
137 def flipenable(self
, d
):
138 _xorcross(d
, self
.crossbounds
)
140 def fliphilite(self
, d
):
141 d
.invert(self
.hilitebounds
)
144 # A Strut is a label with no width of its own.
146 class StrutAppearance(LabelAppearance
):
148 def getminsize(self
, m
, (width
, height
)):
149 height
= max(height
, m
.lineheight() + 6)
154 # ButtonAppearance displays a centered string in a box.
155 # selected --> bold border
156 # disabled --> crossed out
157 # hilited --> inverted
159 class ButtonAppearance(LabelAppearance
):
161 def drawpict(self
, d
):
162 d
.box(_rect
.inset(self
.bounds
, (1, 1)))
166 d
.box(_rect
.inset(self
.bounds
, (2, 2)))
167 d
.box(_rect
.inset(self
.bounds
, (3, 3)))
171 # CheckAppearance displays a small square box and a left-justified string.
172 # selected --> a cross appears in the box
173 # disabled --> whole button crossed out
174 # hilited --> box is inverted
176 class CheckAppearance(LabelAppearance
):
178 def getminsize(self
, m
, (width
, height
)):
179 minwidth
= m
.textwidth(self
.text
) + 6
180 minheight
= m
.lineheight() + 6
181 width
= max(width
, minwidth
+ minheight
+ m
.textwidth(' '))
182 height
= max(height
, minheight
)
185 def drawpict(self
, d
):
186 d
.box(self
.boxbounds
)
187 if self
.selected
: _xorcross(d
, self
.boxbounds
)
189 def recalcbounds(self
):
190 LabelAppearance
.recalcbounds(self
)
191 (left
, top
), (right
, bottom
) = self
.bounds
192 self
.size
= bottom
- top
- 4
193 self
.boxbounds
= (left
+2, top
+2), (left
+2+self
.size
, bottom
-2)
194 self
.hilitebounds
= self
.boxbounds
196 def recalctextpos(self
):
197 m
= self
.parent
.beginmeasuring()
198 (left
, top
), (right
, bottom
) = self
.boxbounds
199 h
= right
+ m
.textwidth(' ')
200 v
= top
+ (self
.size
- m
.lineheight()) / 2
205 # RadioAppearance displays a round indicator and a left-justified string.
206 # selected --> a dot appears in the indicator
207 # disabled --> whole button crossed out
208 # hilited --> indicator is inverted
210 class RadioAppearance(CheckAppearance
):
212 def drawpict(self
, d
):
213 (left
, top
), (right
, bottom
) = self
.boxbounds
214 radius
= self
.size
/ 2
215 center
= left
+ radius
, top
+ radius
216 d
.circle(center
, radius
)
218 d
.fillcircle(center
, radius
*3/5)
222 # NoReactivity ignores mouse events.
225 def init_reactivity(self
): pass
228 # BaseReactivity defines hooks and asks for mouse events,
229 # but provides only dummy mouse event handlers.
230 # The trigger methods call the corresponding hooks set by the user.
231 # Hooks (and triggers) mean the following:
232 # down_hook called on some mouse-down events
233 # move_hook called on some mouse-move events
234 # up_hook called on mouse-up events
235 # on_hook called for buttons with on/off state, when it goes on
236 # hook called when a button 'fires' or a radiobutton goes on
237 # There are usually extra conditions, e.g., hooks are only called
238 # when the button is enabled, or active, or selected (on).
240 class BaseReactivity
:
242 def init_reactivity(self
):
243 self
.down_hook
= self
.move_hook
= self
.up_hook
= \
244 self
.on_hook
= self
.off_hook
= \
245 self
.hook
= self
.active
= 0
246 self
.parent
.need_mouse(self
)
248 def mousetest(self
, hv
):
249 return _rect
.pointinrect(hv
, self
.bounds
)
251 def mouse_down(self
, detail
):
254 def mouse_move(self
, detail
):
257 def mouse_up(self
, detail
):
260 def down_trigger(self
):
261 if self
.down_hook
: self
.down_hook(self
)
263 def move_trigger(self
):
264 if self
.move_hook
: self
.move_hook(self
)
266 def up_trigger(self
):
267 if self
.up_hook
: self
.up_hook(self
)
269 def on_trigger(self
):
270 if self
.on_hook
: self
.on_hook(self
)
272 def off_trigger(self
):
273 if self
.off_hook
: self
.off_hook(self
)
276 if self
.hook
: self
.hook(self
)
279 # ToggleReactivity acts like a simple pushbutton.
280 # It toggles its hilite state on mouse down events.
282 class ToggleReactivity(BaseReactivity
):
284 def mouse_down(self
, detail
):
285 if self
.enabled
and self
.mousetest(detail
[_HV
]):
287 self
.hilite(not self
.hilited
)
290 def mouse_move(self
, detail
):
294 def mouse_up(self
, detail
):
299 def down_trigger(self
):
308 # TriggerReactivity acts like a fancy pushbutton.
309 # It hilites itself while the mouse is down within its bounds.
311 class TriggerReactivity(BaseReactivity
):
313 def mouse_down(self
, detail
):
314 if self
.enabled
and self
.mousetest(detail
[_HV
]):
319 def mouse_move(self
, detail
):
321 self
.hilite(self
.mousetest(detail
[_HV
]))
325 def mouse_up(self
, detail
):
327 self
.hilite(self
.mousetest(detail
[_HV
]))
336 # CheckReactivity handles mouse events like TriggerReactivity,
337 # It overrides the up_trigger method to flip its selected state.
339 class CheckReactivity(TriggerReactivity
):
341 def up_trigger(self
):
342 self
.select(not self
.selected
)
350 # RadioReactivity turns itself on and the other buttons in its group
351 # off when its up_trigger method is called.
353 class RadioReactivity(TriggerReactivity
):
355 def init_reactivity(self
):
356 TriggerReactivity
.init_reactivity(self
)
359 def up_trigger(self
):
370 # Auxiliary class for 'define' method.
371 # Call the initializers in the right order.
375 def define(self
, parent
):
377 parent
.addchild(self
)
378 self
.init_appearance()
379 self
.init_reactivity()
385 def definetext(self
, parent
, text
):
386 self
= self
.define(parent
)
391 # Subroutine to cross out a rectangle.
393 def _xorcross(d
, bounds
):
394 ((left
, top
), (right
, bottom
)) = bounds
395 # This is s bit funny to make it look better
400 d
.xorline(((left
, top
), (right
, bottom
)))
401 d
.xorline((left
, bottom
), (right
, top
))
404 # Ready-made button classes.
406 class Label(NoReactivity
, LabelAppearance
, Define
): pass
407 class Strut(NoReactivity
, StrutAppearance
, Define
): pass
408 class PushButton(TriggerReactivity
, ButtonAppearance
, Define
): pass
409 class CheckButton(CheckReactivity
, CheckAppearance
, Define
): pass
410 class RadioButton(RadioReactivity
, RadioAppearance
, Define
): pass
411 class ToggleButton(ToggleReactivity
, ButtonAppearance
, Define
): pass