blob: 739d7bb16c55097077f94782378cae1dc6cbb495 [file] [log] [blame]
Guido van Rossumceae5281990-10-25 18:50:27 +00001# Module 'Buttons'
Guido van Rossum336f2811990-10-24 16:39:18 +00002
3
4# Import module 'rect' renamed as '_rect'
5#
6import rect
7_rect = rect
8del rect
9
10
11# Field indices in mouse event detail
12#
13_HV = 0
14_CLICKS = 1
15_BUTTON = 2
16_MASK = 3
17
18
Guido van Rossumceae5281990-10-25 18:50:27 +000019# LabelAppearance provides defaults for all appearance methods.
20# selected state not visible
21# disabled --> crossed out
22# hilited --> inverted
Guido van Rossum336f2811990-10-24 16:39:18 +000023#
Guido van Rossumceae5281990-10-25 18:50:27 +000024class LabelAppearance():
Guido van Rossum336f2811990-10-24 16:39:18 +000025 #
26 # Initialization
27 #
28 def init_appearance(self, (win, bounds)):
Guido van Rossum336f2811990-10-24 16:39:18 +000029 self.win = win
30 self.bounds = bounds
31 self.enabled = 1
32 self.hilited = 0
33 self.selected = 0
34 self.text = ''
Guido van Rossumceae5281990-10-25 18:50:27 +000035 self.limbo = 1
36 self.recalc()
37 self.win.change(self.bounds)
38 # While the limbo flag is set, redraw calls are ignored.
39 # It is cleared by the first draw event.
40 # This is intended to avoid duplicate drawing during
41 # initialization.
Guido van Rossum336f2811990-10-24 16:39:18 +000042 #
43 # Changing the parameters
44 #
45 def settext(self, text):
46 self.text = text
Guido van Rossumceae5281990-10-25 18:50:27 +000047 self.recalctextpos()
Guido van Rossum336f2811990-10-24 16:39:18 +000048 self.redraw()
49 #
50 def setbounds(self, bounds):
Guido van Rossumceae5281990-10-25 18:50:27 +000051 # This delays drawing until after all buttons are moved
Guido van Rossum336f2811990-10-24 16:39:18 +000052 self.win.change(self.bounds)
53 self.bounds = bounds
Guido van Rossumceae5281990-10-25 18:50:27 +000054 self.recalc()
Guido van Rossum336f2811990-10-24 16:39:18 +000055 self.win.change(bounds)
56 #
57 # Changing the state bits
58 #
59 def enable(self, flag):
60 if flag <> self.enabled:
61 self.enabled = flag
Guido van Rossumceae5281990-10-25 18:50:27 +000062 if not self.limbo:
63 self.flipenable(self.win.begindrawing())
Guido van Rossum336f2811990-10-24 16:39:18 +000064 #
65 def hilite(self, flag):
66 if flag <> self.hilited:
67 self.hilited = flag
Guido van Rossumceae5281990-10-25 18:50:27 +000068 if not self.limbo:
69 self.fliphilite(self.win.begindrawing())
Guido van Rossum336f2811990-10-24 16:39:18 +000070 #
71 def select(self, flag):
72 if flag <> self.selected:
73 self.selected = flag
74 self.redraw()
75 #
Guido van Rossumceae5281990-10-25 18:50:27 +000076 # Recalculate the box bounds and text position.
77 # This can be overridden by buttons that draw different boxes
78 # or want their text in a different position.
79 #
80 def recalc(self):
81 self.recalcbounds()
82 self.recalctextpos()
83 #
84 def recalcbounds(self):
85 self.hilitebounds = _rect.inset(self.bounds, (3, 3))
86 self.crossbounds = self.bounds
87 #
88 def recalctextpos(self):
89 (left, top), (right, bottom) = self.bounds
90 d = self.win.begindrawing()
91 h = (left + right - d.textwidth(self.text)) / 2
92 v = (top + bottom - d.lineheight()) / 2
93 self.textpos = h, v
94 #
95 # Resize method.
96 # Override for widgets that take over window geomtry management.
97 #
98 def resize(self):
99 pass
100 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000101 # Generic drawing mechanism.
Guido van Rossumceae5281990-10-25 18:50:27 +0000102 # Do not override redraw() or draw() methods; override drawit() c.s.
Guido van Rossum336f2811990-10-24 16:39:18 +0000103 #
104 def redraw(self):
Guido van Rossumceae5281990-10-25 18:50:27 +0000105 if not self.limbo:
106 self.draw(self.win.begindrawing(), self.bounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000107 #
108 def draw(self, (d, area)):
Guido van Rossumceae5281990-10-25 18:50:27 +0000109 self.limbo = 0
Guido van Rossum336f2811990-10-24 16:39:18 +0000110 area = _rect.intersect(area, self.bounds)
111 if area = _rect.empty:
112 return
113 d.cliprect(area)
114 d.erase(self.bounds)
115 self.drawit(d)
116 d.noclip()
117 #
118 # The drawit() method is fairly generic but may be overridden.
119 #
120 def drawit(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000121 self.drawpict(d)
Guido van Rossum336f2811990-10-24 16:39:18 +0000122 if self.text:
Guido van Rossumceae5281990-10-25 18:50:27 +0000123 d.text(self.textpos, self.text)
Guido van Rossum336f2811990-10-24 16:39:18 +0000124 if not self.enabled:
125 self.flipenable(d)
126 if self.hilited:
127 self.fliphilite(d)
128 #
129 # Default drawing detail functions.
130 # Overriding these is normally sufficient to get different
131 # appearances.
Guido van Rossum336f2811990-10-24 16:39:18 +0000132 #
133 def drawpict(self, d):
134 pass
135 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000136 def flipenable(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000137 _xorcross(d, self.crossbounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000138 #
139 def fliphilite(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000140 d.invert(self.hilitebounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000141
142
143# ButtonAppearance displays a centered string in a box.
144# selected --> bold border
145# disabled --> crossed out
146# hilited --> inverted
147#
Guido van Rossumceae5281990-10-25 18:50:27 +0000148class ButtonAppearance() = LabelAppearance():
Guido van Rossum336f2811990-10-24 16:39:18 +0000149 #
150 def drawpict(self, d):
151 d.box(_rect.inset(self.bounds, (1, 1)))
152 if self.selected:
153 # Make a thicker box
154 d.box(self.bounds)
155 d.box(_rect.inset(self.bounds, (2, 2)))
156 d.box(_rect.inset(self.bounds, (3, 3)))
157 #
158
159
160# CheckAppearance displays a small square box and a left-justified string.
161# selected --> a cross appears in the box
162# disabled --> whole button crossed out
163# hilited --> box is inverted
164#
Guido van Rossumceae5281990-10-25 18:50:27 +0000165class CheckAppearance() = LabelAppearance():
Guido van Rossum336f2811990-10-24 16:39:18 +0000166 #
167 def drawpict(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000168 d.box(self.boxbounds)
169 if self.selected: _xorcross(d, self.boxbounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000170 #
Guido van Rossumceae5281990-10-25 18:50:27 +0000171 def recalcbounds(self):
172 LabelAppearance.recalcbounds(self)
Guido van Rossum336f2811990-10-24 16:39:18 +0000173 (left, top), (right, bottom) = self.bounds
Guido van Rossumceae5281990-10-25 18:50:27 +0000174 self.size = bottom - top - 4
175 self.boxbounds = (left+2, top+2), (left+2+self.size, bottom-2)
176 self.hilitebounds = self.boxbounds
Guido van Rossum336f2811990-10-24 16:39:18 +0000177 #
Guido van Rossumceae5281990-10-25 18:50:27 +0000178 def recalctextpos(self):
179 d = self.win.begindrawing()
180 (left, top), (right, bottom) = self.boxbounds
181 h = right + d.textwidth(' ')
182 v = top + (self.size - d.lineheight()) / 2
183 self.textpos = h, v
Guido van Rossum336f2811990-10-24 16:39:18 +0000184 #
185
186
187# RadioAppearance displays a round indicator and a left-justified string.
188# selected --> a dot appears in the indicator
189# disabled --> whole button crossed out
190# hilited --> indicator is inverted
191#
Guido van Rossumceae5281990-10-25 18:50:27 +0000192class RadioAppearance() = CheckAppearance():
Guido van Rossum336f2811990-10-24 16:39:18 +0000193 #
194 def drawpict(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000195 (left, top), (right, bottom) = self.boxbounds
196 radius = self.size / 2
Guido van Rossum336f2811990-10-24 16:39:18 +0000197 h, v = left + radius, top + radius
Guido van Rossumceae5281990-10-25 18:50:27 +0000198 d.circle((h, v), radius)
Guido van Rossum336f2811990-10-24 16:39:18 +0000199 if self.selected:
200 some = radius/3
201 d.paint((h-some, v-some), (h+some, v+some))
202 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000203
204
205# NoReactivity ignores mouse and timer events.
206# The trigger methods call the corresponding hooks set by the user.
207# Hooks (and triggers) mean the following:
208# down_hook called on some mouse-down events
Guido van Rossumceae5281990-10-25 18:50:27 +0000209# move_hook called on some mouse-move events
Guido van Rossum336f2811990-10-24 16:39:18 +0000210# up_hook called on mouse-up events
211# on_hook called for buttons with on/off state, when it goes on
212# timer_hook called on timer events
213# hook called when a button 'fires' or a radiobutton goes on
214# There are usually extra conditions, e.g., hooks are only called
215# when the button is enabled, or active, or selected (on).
216#
217class NoReactivity():
218 #
219 def init_reactivity(self):
Guido van Rossumceae5281990-10-25 18:50:27 +0000220 self.down_hook = self.move_hook = self.up_hook = \
Guido van Rossum336f2811990-10-24 16:39:18 +0000221 self.on_hook = self.off_hook = self.timer_hook = \
222 self.hook = self.active = 0
223 #
224 def mousetest(self, hv):
225 return _rect.pointinrect(hv, self.bounds)
226 #
227 def mouse_down(self, detail):
228 pass
229 #
230 def mouse_move(self, detail):
231 pass
232 #
233 def mouse_up(self, detail):
234 pass
235 #
236 def timer(self):
237 pass
238 #
239 def down_trigger(self):
240 if self.down_hook: self.down_hook(self)
241 #
Guido van Rossumceae5281990-10-25 18:50:27 +0000242 def move_trigger(self):
243 if self.move_hook: self.move_hook(self)
Guido van Rossum336f2811990-10-24 16:39:18 +0000244 #
245 def up_trigger(self):
246 if self.up_hook: self.up_hook(self)
247 #
248 def on_trigger(self):
249 if self.on_hook: self.on_hook(self)
250 #
251 def off_trigger(self):
252 if self.off_hook: self.off_hook(self)
253 #
254 def timer_trigger(self):
255 if self.timer_hook: self.timer_hook(self)
256 #
257 def trigger(self):
258 if self.hook: self.hook(self)
259
260
261# ToggleReactivity acts like a simple pushbutton.
262# It toggles its hilite state on mouse down events.
263# Its timer_trigger method is called for all timer events while hilited.
264#
265class ToggleReactivity() = NoReactivity():
266 #
267 def mouse_down(self, detail):
268 if self.enabled and self.mousetest(detail[_HV]):
269 self.active = 1
270 self.hilite(not self.hilited)
271 self.down_trigger()
272 #
273 def mouse_move(self, detail):
274 if self.active:
Guido van Rossumceae5281990-10-25 18:50:27 +0000275 self.move_trigger()
Guido van Rossum336f2811990-10-24 16:39:18 +0000276 #
277 def mouse_up(self, detail):
278 if self.active:
279 self.up_trigger()
280 self.active = 0
281 #
282 def timer(self):
283 if self.hilited:
284 self.timer_trigger()
285 #
286 def down_trigger(self):
287 if self.hilited:
288 self.on_trigger()
289 else:
290 self.off_trigger()
291 self.trigger()
292 #
293
294
295# TriggerReactivity acts like a fancy pushbutton.
296# It hilites itself while the mouse is down within its bounds.
297#
298class TriggerReactivity() = NoReactivity():
299 #
300 def mouse_down(self, detail):
301 if self.enabled and self.mousetest(detail[_HV]):
302 self.active = 1
303 self.hilite(1)
304 self.down_trigger()
305 #
306 def mouse_move(self, detail):
307 if self.active:
308 self.hilite(self.mousetest(detail[_HV]))
309 if self.hilited:
Guido van Rossumceae5281990-10-25 18:50:27 +0000310 self.move_trigger()
Guido van Rossum336f2811990-10-24 16:39:18 +0000311 #
312 def mouse_up(self, detail):
313 if self.active:
314 self.hilite(self.mousetest(detail[_HV]))
315 if self.hilited:
316 self.up_trigger()
317 self.trigger()
318 self.active = 0
319 self.hilite(0)
320 #
321 def timer(self):
322 if self.active and self.hilited:
Guido van Rossumceae5281990-10-25 18:50:27 +0000323 self.timer_trigger()
Guido van Rossum336f2811990-10-24 16:39:18 +0000324 #
325
326
327# CheckReactivity handles mouse events like TriggerReactivity,
328# It overrides the up_trigger method to flip its selected state.
329#
330class CheckReactivity() = TriggerReactivity():
331 #
332 def up_trigger(self):
333 self.select(not self.selected)
334 if self.selected:
335 self.on_trigger()
336 else:
337 self.off_trigger()
338 self.trigger()
339
340
341# RadioReactivity turns itself on and the other buttons in its group
342# off when its up_trigger method is called.
343#
344class RadioReactivity() = TriggerReactivity():
345 #
346 def init_reactivity(self):
347 TriggerReactivity.init_reactivity(self)
348 self.group = []
349 #
350 def up_trigger(self):
351 for b in self.group:
352 if b <> self:
353 if b.selected:
354 b.select(0)
355 b.off_trigger()
356 self.select(1)
357 self.on_trigger()
358 self.trigger()
359
360
361# Auxiliary class for 'define' method.
362#
363class Define():
364 #
365 def define(self, (win, bounds, text)):
366 self.init_appearance(win, bounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000367 self.init_reactivity()
Guido van Rossumceae5281990-10-25 18:50:27 +0000368 self.settext(text)
Guido van Rossum336f2811990-10-24 16:39:18 +0000369 return self
370
371
Guido van Rossumceae5281990-10-25 18:50:27 +0000372# Subroutine to cross out a rectangle.
373#
374def _xorcross(d, bounds):
375 ((left, top), (right, bottom)) = bounds
376 # This is s bit funny to make it look better
377 left = left + 2
378 right = right - 2
379 top = top + 2
380 bottom = bottom - 3
381 d.xorline(((left, top), (right, bottom)))
382 d.xorline((left, bottom), (right, top))
383
384
Guido van Rossum336f2811990-10-24 16:39:18 +0000385# Ready-made button classes
386#
Guido van Rossumceae5281990-10-25 18:50:27 +0000387class BaseButton() = NoReactivity(), LabelAppearance(), Define(): pass
Guido van Rossum336f2811990-10-24 16:39:18 +0000388class Label() = NoReactivity(), LabelAppearance(), Define(): pass
389class ClassicButton() = TriggerReactivity(), ButtonAppearance(), Define(): pass
390class CheckButton() = CheckReactivity(), CheckAppearance(), Define(): pass
391class RadioButton() = RadioReactivity(), RadioAppearance(), Define(): pass
392class Toggle() = ToggleReactivity(), ButtonAppearance(), Define(): pass