blob: 5395f881907ccbce4f2402b4ea7541350c40f3ab [file] [log] [blame]
Guido van Rossumceae5281990-10-25 18:50:27 +00001# Module 'Buttons'
Guido van Rossum336f2811990-10-24 16:39:18 +00002
3
Guido van Rossumfc9af021990-10-31 11:16:43 +00004from Resize import *
5
6
Guido van Rossum336f2811990-10-24 16:39:18 +00007# Import module 'rect' renamed as '_rect'
8#
9import rect
10_rect = rect
11del rect
12
13
14# Field indices in mouse event detail
15#
16_HV = 0
17_CLICKS = 1
18_BUTTON = 2
19_MASK = 3
20
21
Guido van Rossumceae5281990-10-25 18:50:27 +000022# LabelAppearance provides defaults for all appearance methods.
23# selected state not visible
24# disabled --> crossed out
25# hilited --> inverted
Guido van Rossum336f2811990-10-24 16:39:18 +000026#
Guido van Rossumceae5281990-10-25 18:50:27 +000027class LabelAppearance():
Guido van Rossum336f2811990-10-24 16:39:18 +000028 #
29 # Initialization
30 #
31 def init_appearance(self, (win, bounds)):
Guido van Rossum336f2811990-10-24 16:39:18 +000032 self.win = win
33 self.bounds = bounds
34 self.enabled = 1
35 self.hilited = 0
36 self.selected = 0
37 self.text = ''
Guido van Rossumceae5281990-10-25 18:50:27 +000038 self.limbo = 1
39 self.recalc()
40 self.win.change(self.bounds)
41 # While the limbo flag is set, redraw calls are ignored.
42 # It is cleared by the first draw event.
43 # This is intended to avoid duplicate drawing during
44 # initialization.
Guido van Rossum336f2811990-10-24 16:39:18 +000045 #
46 # Changing the parameters
47 #
48 def settext(self, text):
49 self.text = text
Guido van Rossumceae5281990-10-25 18:50:27 +000050 self.recalctextpos()
Guido van Rossum336f2811990-10-24 16:39:18 +000051 self.redraw()
52 #
53 def setbounds(self, bounds):
Guido van Rossumceae5281990-10-25 18:50:27 +000054 # This delays drawing until after all buttons are moved
Guido van Rossum336f2811990-10-24 16:39:18 +000055 self.win.change(self.bounds)
56 self.bounds = bounds
Guido van Rossumceae5281990-10-25 18:50:27 +000057 self.recalc()
Guido van Rossum336f2811990-10-24 16:39:18 +000058 self.win.change(bounds)
59 #
60 # Changing the state bits
61 #
62 def enable(self, flag):
63 if flag <> self.enabled:
64 self.enabled = flag
Guido van Rossumceae5281990-10-25 18:50:27 +000065 if not self.limbo:
66 self.flipenable(self.win.begindrawing())
Guido van Rossum336f2811990-10-24 16:39:18 +000067 #
68 def hilite(self, flag):
69 if flag <> self.hilited:
70 self.hilited = flag
Guido van Rossumceae5281990-10-25 18:50:27 +000071 if not self.limbo:
72 self.fliphilite(self.win.begindrawing())
Guido van Rossum336f2811990-10-24 16:39:18 +000073 #
74 def select(self, flag):
75 if flag <> self.selected:
76 self.selected = flag
77 self.redraw()
78 #
Guido van Rossumceae5281990-10-25 18:50:27 +000079 # Recalculate the box bounds and text position.
80 # This can be overridden by buttons that draw different boxes
81 # or want their text in a different position.
82 #
83 def recalc(self):
84 self.recalcbounds()
85 self.recalctextpos()
86 #
87 def recalcbounds(self):
88 self.hilitebounds = _rect.inset(self.bounds, (3, 3))
89 self.crossbounds = self.bounds
90 #
91 def recalctextpos(self):
92 (left, top), (right, bottom) = self.bounds
93 d = self.win.begindrawing()
94 h = (left + right - d.textwidth(self.text)) / 2
95 v = (top + bottom - d.lineheight()) / 2
96 self.textpos = h, v
97 #
Guido van Rossum336f2811990-10-24 16:39:18 +000098 # Generic drawing mechanism.
Guido van Rossumceae5281990-10-25 18:50:27 +000099 # Do not override redraw() or draw() methods; override drawit() c.s.
Guido van Rossum336f2811990-10-24 16:39:18 +0000100 #
101 def redraw(self):
Guido van Rossumceae5281990-10-25 18:50:27 +0000102 if not self.limbo:
103 self.draw(self.win.begindrawing(), self.bounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000104 #
105 def draw(self, (d, area)):
Guido van Rossumceae5281990-10-25 18:50:27 +0000106 self.limbo = 0
Guido van Rossum336f2811990-10-24 16:39:18 +0000107 area = _rect.intersect(area, self.bounds)
108 if area = _rect.empty:
109 return
110 d.cliprect(area)
111 d.erase(self.bounds)
112 self.drawit(d)
113 d.noclip()
114 #
115 # The drawit() method is fairly generic but may be overridden.
116 #
117 def drawit(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000118 self.drawpict(d)
Guido van Rossum336f2811990-10-24 16:39:18 +0000119 if self.text:
Guido van Rossumceae5281990-10-25 18:50:27 +0000120 d.text(self.textpos, self.text)
Guido van Rossum336f2811990-10-24 16:39:18 +0000121 if not self.enabled:
122 self.flipenable(d)
123 if self.hilited:
124 self.fliphilite(d)
125 #
126 # Default drawing detail functions.
127 # Overriding these is normally sufficient to get different
128 # appearances.
Guido van Rossum336f2811990-10-24 16:39:18 +0000129 #
130 def drawpict(self, d):
131 pass
132 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000133 def flipenable(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000134 _xorcross(d, self.crossbounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000135 #
136 def fliphilite(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000137 d.invert(self.hilitebounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000138
139
140# ButtonAppearance displays a centered string in a box.
141# selected --> bold border
142# disabled --> crossed out
143# hilited --> inverted
144#
Guido van Rossumceae5281990-10-25 18:50:27 +0000145class ButtonAppearance() = LabelAppearance():
Guido van Rossum336f2811990-10-24 16:39:18 +0000146 #
147 def drawpict(self, d):
148 d.box(_rect.inset(self.bounds, (1, 1)))
149 if self.selected:
150 # Make a thicker box
151 d.box(self.bounds)
152 d.box(_rect.inset(self.bounds, (2, 2)))
153 d.box(_rect.inset(self.bounds, (3, 3)))
154 #
155
156
157# CheckAppearance displays a small square box and a left-justified string.
158# selected --> a cross appears in the box
159# disabled --> whole button crossed out
160# hilited --> box is inverted
161#
Guido van Rossumceae5281990-10-25 18:50:27 +0000162class CheckAppearance() = LabelAppearance():
Guido van Rossum336f2811990-10-24 16:39:18 +0000163 #
164 def drawpict(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000165 d.box(self.boxbounds)
166 if self.selected: _xorcross(d, self.boxbounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000167 #
Guido van Rossumceae5281990-10-25 18:50:27 +0000168 def recalcbounds(self):
169 LabelAppearance.recalcbounds(self)
Guido van Rossum336f2811990-10-24 16:39:18 +0000170 (left, top), (right, bottom) = self.bounds
Guido van Rossumceae5281990-10-25 18:50:27 +0000171 self.size = bottom - top - 4
172 self.boxbounds = (left+2, top+2), (left+2+self.size, bottom-2)
173 self.hilitebounds = self.boxbounds
Guido van Rossum336f2811990-10-24 16:39:18 +0000174 #
Guido van Rossumceae5281990-10-25 18:50:27 +0000175 def recalctextpos(self):
176 d = self.win.begindrawing()
177 (left, top), (right, bottom) = self.boxbounds
178 h = right + d.textwidth(' ')
179 v = top + (self.size - d.lineheight()) / 2
180 self.textpos = h, v
Guido van Rossum336f2811990-10-24 16:39:18 +0000181 #
182
183
184# RadioAppearance displays a round indicator and a left-justified string.
185# selected --> a dot appears in the indicator
186# disabled --> whole button crossed out
187# hilited --> indicator is inverted
188#
Guido van Rossumceae5281990-10-25 18:50:27 +0000189class RadioAppearance() = CheckAppearance():
Guido van Rossum336f2811990-10-24 16:39:18 +0000190 #
191 def drawpict(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000192 (left, top), (right, bottom) = self.boxbounds
193 radius = self.size / 2
Guido van Rossum336f2811990-10-24 16:39:18 +0000194 h, v = left + radius, top + radius
Guido van Rossumceae5281990-10-25 18:50:27 +0000195 d.circle((h, v), radius)
Guido van Rossum336f2811990-10-24 16:39:18 +0000196 if self.selected:
197 some = radius/3
198 d.paint((h-some, v-some), (h+some, v+some))
199 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000200
201
202# NoReactivity ignores mouse and timer events.
203# The trigger methods call the corresponding hooks set by the user.
204# Hooks (and triggers) mean the following:
205# down_hook called on some mouse-down events
Guido van Rossumceae5281990-10-25 18:50:27 +0000206# move_hook called on some mouse-move events
Guido van Rossum336f2811990-10-24 16:39:18 +0000207# up_hook called on mouse-up events
208# on_hook called for buttons with on/off state, when it goes on
209# timer_hook called on timer events
210# hook called when a button 'fires' or a radiobutton goes on
211# There are usually extra conditions, e.g., hooks are only called
212# when the button is enabled, or active, or selected (on).
213#
214class NoReactivity():
215 #
216 def init_reactivity(self):
Guido van Rossumceae5281990-10-25 18:50:27 +0000217 self.down_hook = self.move_hook = self.up_hook = \
Guido van Rossum336f2811990-10-24 16:39:18 +0000218 self.on_hook = self.off_hook = self.timer_hook = \
219 self.hook = self.active = 0
220 #
221 def mousetest(self, hv):
222 return _rect.pointinrect(hv, self.bounds)
223 #
224 def mouse_down(self, detail):
225 pass
226 #
227 def mouse_move(self, detail):
228 pass
229 #
230 def mouse_up(self, detail):
231 pass
232 #
233 def timer(self):
234 pass
235 #
236 def down_trigger(self):
237 if self.down_hook: self.down_hook(self)
238 #
Guido van Rossumceae5281990-10-25 18:50:27 +0000239 def move_trigger(self):
240 if self.move_hook: self.move_hook(self)
Guido van Rossum336f2811990-10-24 16:39:18 +0000241 #
242 def up_trigger(self):
243 if self.up_hook: self.up_hook(self)
244 #
245 def on_trigger(self):
246 if self.on_hook: self.on_hook(self)
247 #
248 def off_trigger(self):
249 if self.off_hook: self.off_hook(self)
250 #
251 def timer_trigger(self):
252 if self.timer_hook: self.timer_hook(self)
253 #
254 def trigger(self):
255 if self.hook: self.hook(self)
256
257
258# ToggleReactivity acts like a simple pushbutton.
259# It toggles its hilite state on mouse down events.
260# Its timer_trigger method is called for all timer events while hilited.
261#
262class ToggleReactivity() = NoReactivity():
263 #
264 def mouse_down(self, detail):
265 if self.enabled and self.mousetest(detail[_HV]):
266 self.active = 1
267 self.hilite(not self.hilited)
268 self.down_trigger()
269 #
270 def mouse_move(self, detail):
271 if self.active:
Guido van Rossumceae5281990-10-25 18:50:27 +0000272 self.move_trigger()
Guido van Rossum336f2811990-10-24 16:39:18 +0000273 #
274 def mouse_up(self, detail):
275 if self.active:
276 self.up_trigger()
277 self.active = 0
278 #
279 def timer(self):
280 if self.hilited:
281 self.timer_trigger()
282 #
283 def down_trigger(self):
284 if self.hilited:
285 self.on_trigger()
286 else:
287 self.off_trigger()
288 self.trigger()
289 #
290
291
292# TriggerReactivity acts like a fancy pushbutton.
293# It hilites itself while the mouse is down within its bounds.
294#
295class TriggerReactivity() = NoReactivity():
296 #
297 def mouse_down(self, detail):
298 if self.enabled and self.mousetest(detail[_HV]):
299 self.active = 1
300 self.hilite(1)
301 self.down_trigger()
302 #
303 def mouse_move(self, detail):
304 if self.active:
305 self.hilite(self.mousetest(detail[_HV]))
306 if self.hilited:
Guido van Rossumceae5281990-10-25 18:50:27 +0000307 self.move_trigger()
Guido van Rossum336f2811990-10-24 16:39:18 +0000308 #
309 def mouse_up(self, detail):
310 if self.active:
311 self.hilite(self.mousetest(detail[_HV]))
312 if self.hilited:
313 self.up_trigger()
314 self.trigger()
315 self.active = 0
316 self.hilite(0)
317 #
318 def timer(self):
319 if self.active and self.hilited:
Guido van Rossumceae5281990-10-25 18:50:27 +0000320 self.timer_trigger()
Guido van Rossum336f2811990-10-24 16:39:18 +0000321 #
322
323
324# CheckReactivity handles mouse events like TriggerReactivity,
325# It overrides the up_trigger method to flip its selected state.
326#
327class CheckReactivity() = TriggerReactivity():
328 #
329 def up_trigger(self):
330 self.select(not self.selected)
331 if self.selected:
332 self.on_trigger()
333 else:
334 self.off_trigger()
335 self.trigger()
336
337
338# RadioReactivity turns itself on and the other buttons in its group
339# off when its up_trigger method is called.
340#
341class RadioReactivity() = TriggerReactivity():
342 #
343 def init_reactivity(self):
344 TriggerReactivity.init_reactivity(self)
345 self.group = []
346 #
347 def up_trigger(self):
348 for b in self.group:
349 if b <> self:
350 if b.selected:
351 b.select(0)
352 b.off_trigger()
353 self.select(1)
354 self.on_trigger()
355 self.trigger()
356
357
358# Auxiliary class for 'define' method.
359#
Guido van Rossumfc9af021990-10-31 11:16:43 +0000360class Define() = NoResize():
Guido van Rossum336f2811990-10-24 16:39:18 +0000361 #
362 def define(self, (win, bounds, text)):
363 self.init_appearance(win, bounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000364 self.init_reactivity()
Guido van Rossumfc9af021990-10-31 11:16:43 +0000365 self.init_resize()
Guido van Rossumceae5281990-10-25 18:50:27 +0000366 self.settext(text)
Guido van Rossum336f2811990-10-24 16:39:18 +0000367 return self
368
369
Guido van Rossumceae5281990-10-25 18:50:27 +0000370# Subroutine to cross out a rectangle.
371#
372def _xorcross(d, bounds):
373 ((left, top), (right, bottom)) = bounds
374 # This is s bit funny to make it look better
375 left = left + 2
376 right = right - 2
377 top = top + 2
378 bottom = bottom - 3
379 d.xorline(((left, top), (right, bottom)))
380 d.xorline((left, bottom), (right, top))
381
382
Guido van Rossum336f2811990-10-24 16:39:18 +0000383# Ready-made button classes
384#
Guido van Rossumceae5281990-10-25 18:50:27 +0000385class BaseButton() = NoReactivity(), LabelAppearance(), Define(): pass
Guido van Rossum336f2811990-10-24 16:39:18 +0000386class Label() = NoReactivity(), LabelAppearance(), Define(): pass
387class ClassicButton() = TriggerReactivity(), ButtonAppearance(), Define(): pass
388class CheckButton() = CheckReactivity(), CheckAppearance(), Define(): pass
389class RadioButton() = RadioReactivity(), RadioAppearance(), Define(): pass
390class Toggle() = ToggleReactivity(), ButtonAppearance(), Define(): pass