blob: d6ad6e3111002c4fa89fa7153ac692ff20240651 [file] [log] [blame]
Guido van Rossum336f2811990-10-24 16:39:18 +00001# Module 'Buttons' -- see README
2#
3# Module functionality is now split in two parts:
4# - 'appearance' defines what it looks like
5# - 'reactivity' defines how it acts to mouse events
6
7
8# Import module 'rect' renamed as '_rect'
9#
10import rect
11_rect = rect
12del rect
13
14
15# Field indices in mouse event detail
16#
17_HV = 0
18_CLICKS = 1
19_BUTTON = 2
20_MASK = 3
21
22
23# BaseAppearance provides defaults for all appearance methods.
24# In fact it looks like a label.
25#
26class BaseAppearance():
27 #
28 # Initialization
29 #
30 def init_appearance(self, (win, bounds)):
31 win.change(bounds)
32 self.win = win
33 self.bounds = bounds
34 self.enabled = 1
35 self.hilited = 0
36 self.selected = 0
37 self.text = ''
38 #
39 # Changing the parameters
40 #
41 def settext(self, text):
42 self.text = text
43 self.redraw()
44 #
45 def setbounds(self, bounds):
46 # This elays drawing until after all buttons are moved
47 self.win.change(self.bounds)
48 self.bounds = bounds
49 self.win.change(bounds)
50 #
51 # Changing the state bits
52 #
53 def enable(self, flag):
54 if flag <> self.enabled:
55 self.enabled = flag
56 self.flipenable(self.win.begindrawing())
57 #
58 def hilite(self, flag):
59 if flag <> self.hilited:
60 self.hilited = flag
61 self.fliphilite(self.win.begindrawing())
62 #
63 def select(self, flag):
64 if flag <> self.selected:
65 self.selected = flag
66 self.redraw()
67 #
68 # Generic drawing mechanism.
69 # There should be no reason to override redraw() or draw() methods.
70 #
71 def redraw(self):
72 self.draw(self.win.begindrawing(), self.bounds)
73 #
74 def draw(self, (d, area)):
75 area = _rect.intersect(area, self.bounds)
76 if area = _rect.empty:
77 return
78 d.cliprect(area)
79 d.erase(self.bounds)
80 self.drawit(d)
81 d.noclip()
82 #
83 # The drawit() method is fairly generic but may be overridden.
84 #
85 def drawit(self, d):
86 self.drawpict(d) # Box, circle etc.; also 'selected'
87 if self.text:
88 hv = self.textpos(d)
89 d.text(hv, self.text)
90 if not self.enabled:
91 self.flipenable(d)
92 if self.hilited:
93 self.fliphilite(d)
94 #
95 # Default drawing detail functions.
96 # Overriding these is normally sufficient to get different
97 # appearances.
98 # No picture; centered text; enable crosses out; hilite inverts.
99 #
100 def drawpict(self, d):
101 pass
102 #
103 def textpos(self, d):
104 # XXX shouldn't this be done once by init/settext()?
105 (left, top), (right, bottom) = self.bounds
106 h = (left + right - d.textwidth(self.text)) / 2
107 v = (top + bottom - d.lineheight()) / 2
108 return h, v
109 #
110 def flipenable(self, d):
111 _xorcross(d, self.bounds)
112 #
113 def fliphilite(self, d):
114 d.invert(_rect.inset(self.bounds, (3, 3)))
115
116
117# Subroutine to cross out a rectangle.
118#
119def _xorcross(d, bounds):
120 ((left, top), (right, bottom)) = bounds
121 left = left + 2
122 right = right - 2
123 top = top + 2
124 bottom = bottom - 3
125 d.xorline(((left, top), (right, bottom)))
126 d.xorline((left, bottom), (right, top))
127
128
129# LabelAppearance displays a centered string.
130# selected --> underlined
131# disabled --> crossed out
132# hilited --> inverted
133#
134class LabelAppearance() = BaseAppearance():
135 #
136 def drawpict(self, d):
137 if self.selected:
138 # Underline it
139 d.line((left+1, bottom-1), (right-1, bottom-1))
140 #
141 if not self.enabled: self._crossout(d)
142 if self.hilited: self._invert(d)
143 #
144
145
146# ButtonAppearance displays a centered string in a box.
147# selected --> bold border
148# disabled --> crossed out
149# hilited --> inverted
150#
151class ButtonAppearance() = BaseAppearance():
152 #
153 def drawpict(self, d):
154 d.box(_rect.inset(self.bounds, (1, 1)))
155 if self.selected:
156 # Make a thicker box
157 d.box(self.bounds)
158 d.box(_rect.inset(self.bounds, (2, 2)))
159 d.box(_rect.inset(self.bounds, (3, 3)))
160 #
161
162
163# CheckAppearance displays a small square box and a left-justified string.
164# selected --> a cross appears in the box
165# disabled --> whole button crossed out
166# hilited --> box is inverted
167#
168class CheckAppearance() = BaseAppearance():
169 #
170 def drawpict(self, d):
171 (left, top), (right, bottom) = self.bounds
172 size = bottom - top
173 boxbounds = (left, top), (left+size, bottom)
174 d.box(boxbounds)
175 if self.selected: _xorcross(d, boxbounds)
176 #
177 def textpos(self, d):
178 (left, top), (right, bottom) = self.bounds
179 size = bottom - top
180 h = left + size + d.textwidth(' ')
181 v = top + (size - d.lineheight()) / 2
182 return h, v
183 #
184 def fliphilite(self, d):
185 (left, top), (right, bottom) = self.bounds
186 size = bottom - top
187 boxbounds = (left, top), (left+size, bottom)
188 d.invert(boxbounds)
189 #
190
191
192# RadioAppearance displays a round indicator and a left-justified string.
193# selected --> a dot appears in the indicator
194# disabled --> whole button crossed out
195# hilited --> indicator is inverted
196#
197class RadioAppearance() = BaseAppearance():
198 #
199 def drawpict(self, d):
200 (left, top), (right, bottom) = self.bounds
201 size = bottom - top
202 radius = size / 2
203 h, v = left + radius, top + radius
204 d.circle((h, v), radius - 1)
205 if self.selected:
206 some = radius/3
207 d.paint((h-some, v-some), (h+some, v+some))
208 #
209 def textpos(self, d):
210 (left, top), (right, bottom) = self.bounds
211 size = bottom - top
212 h = left + size + d.textwidth(' ')
213 v = top + (size - d.lineheight()) / 2
214 return h, v
215 #
216 def fliphilite(self, d):
217 (left, top), (right, bottom) = self.bounds
218 size = bottom - top
219 d.invert((left, top), (left + size, bottom))
220 #
221
222
223# NoReactivity ignores mouse and timer events.
224# The trigger methods call the corresponding hooks set by the user.
225# Hooks (and triggers) mean the following:
226# down_hook called on some mouse-down events
227# active_hook called on some mouse-move events
228# up_hook called on mouse-up events
229# on_hook called for buttons with on/off state, when it goes on
230# timer_hook called on timer events
231# hook called when a button 'fires' or a radiobutton goes on
232# There are usually extra conditions, e.g., hooks are only called
233# when the button is enabled, or active, or selected (on).
234#
235class NoReactivity():
236 #
237 def init_reactivity(self):
238 self.down_hook = self.active_hook = self.up_hook = \
239 self.on_hook = self.off_hook = self.timer_hook = \
240 self.hook = self.active = 0
241 #
242 def mousetest(self, hv):
243 return _rect.pointinrect(hv, self.bounds)
244 #
245 def mouse_down(self, detail):
246 pass
247 #
248 def mouse_move(self, detail):
249 pass
250 #
251 def mouse_up(self, detail):
252 pass
253 #
254 def timer(self):
255 pass
256 #
257 def down_trigger(self):
258 if self.down_hook: self.down_hook(self)
259 #
260 def active_trigger(self):
261 if self.active_hook: self.active_hook(self)
262 #
263 def up_trigger(self):
264 if self.up_hook: self.up_hook(self)
265 #
266 def on_trigger(self):
267 if self.on_hook: self.on_hook(self)
268 #
269 def off_trigger(self):
270 if self.off_hook: self.off_hook(self)
271 #
272 def timer_trigger(self):
273 if self.timer_hook: self.timer_hook(self)
274 #
275 def trigger(self):
276 if self.hook: self.hook(self)
277
278
279# ToggleReactivity acts like a simple pushbutton.
280# It toggles its hilite state on mouse down events.
281# Its timer_trigger method is called for all timer events while hilited.
282#
283class ToggleReactivity() = NoReactivity():
284 #
285 def mouse_down(self, detail):
286 if self.enabled and self.mousetest(detail[_HV]):
287 self.active = 1
288 self.hilite(not self.hilited)
289 self.down_trigger()
290 #
291 def mouse_move(self, detail):
292 if self.active:
293 self.active_trigger()
294 #
295 def mouse_up(self, detail):
296 if self.active:
297 self.up_trigger()
298 self.active = 0
299 #
300 def timer(self):
301 if self.hilited:
302 self.timer_trigger()
303 #
304 def down_trigger(self):
305 if self.hilited:
306 self.on_trigger()
307 else:
308 self.off_trigger()
309 self.trigger()
310 #
311
312
313# TriggerReactivity acts like a fancy pushbutton.
314# It hilites itself while the mouse is down within its bounds.
315#
316class TriggerReactivity() = NoReactivity():
317 #
318 def mouse_down(self, detail):
319 if self.enabled and self.mousetest(detail[_HV]):
320 self.active = 1
321 self.hilite(1)
322 self.down_trigger()
323 #
324 def mouse_move(self, detail):
325 if self.active:
326 self.hilite(self.mousetest(detail[_HV]))
327 if self.hilited:
328 self.active_trigger()
329 #
330 def mouse_up(self, detail):
331 if self.active:
332 self.hilite(self.mousetest(detail[_HV]))
333 if self.hilited:
334 self.up_trigger()
335 self.trigger()
336 self.active = 0
337 self.hilite(0)
338 #
339 def timer(self):
340 if self.active and self.hilited:
341 self.active_trigger()
342 #
343
344
345# CheckReactivity handles mouse events like TriggerReactivity,
346# It overrides the up_trigger method to flip its selected state.
347#
348class CheckReactivity() = TriggerReactivity():
349 #
350 def up_trigger(self):
351 self.select(not self.selected)
352 if self.selected:
353 self.on_trigger()
354 else:
355 self.off_trigger()
356 self.trigger()
357
358
359# RadioReactivity turns itself on and the other buttons in its group
360# off when its up_trigger method is called.
361#
362class RadioReactivity() = TriggerReactivity():
363 #
364 def init_reactivity(self):
365 TriggerReactivity.init_reactivity(self)
366 self.group = []
367 #
368 def up_trigger(self):
369 for b in self.group:
370 if b <> self:
371 if b.selected:
372 b.select(0)
373 b.off_trigger()
374 self.select(1)
375 self.on_trigger()
376 self.trigger()
377
378
379# Auxiliary class for 'define' method.
380#
381class Define():
382 #
383 def define(self, (win, bounds, text)):
384 self.init_appearance(win, bounds)
385 self.text = text
386 self.init_reactivity()
387 return self
388
389
390# Ready-made button classes
391#
392class BaseButton() = NoReactivity(), BaseAppearance(), Define(): pass
393class Label() = NoReactivity(), LabelAppearance(), Define(): pass
394class ClassicButton() = TriggerReactivity(), ButtonAppearance(), Define(): pass
395class CheckButton() = CheckReactivity(), CheckAppearance(), Define(): pass
396class RadioButton() = RadioReactivity(), RadioAppearance(), Define(): pass
397class Toggle() = ToggleReactivity(), ButtonAppearance(), Define(): pass