blob: b864c566a84189d5600a4d641a56d65066470311 [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 Rossum0c89ec71990-11-05 19:44:31 +00004# Import module 'rect' renamed as '_rect' to avoid exporting it on
5# 'from Buttons import *'
Guido van Rossum336f2811990-10-24 16:39:18 +00006#
7import rect
8_rect = rect
9del rect
10
11
12# Field indices in mouse event detail
13#
14_HV = 0
15_CLICKS = 1
16_BUTTON = 2
17_MASK = 3
18
19
Guido van Rossumceae5281990-10-25 18:50:27 +000020# LabelAppearance provides defaults for all appearance methods.
21# selected state not visible
22# disabled --> crossed out
23# hilited --> inverted
Guido van Rossum336f2811990-10-24 16:39:18 +000024#
Guido van Rossumceae5281990-10-25 18:50:27 +000025class LabelAppearance():
Guido van Rossum336f2811990-10-24 16:39:18 +000026 #
27 # Initialization
28 #
Guido van Rossum0c89ec71990-11-05 19:44:31 +000029 def init_appearance(self):
30 self.bounds = _rect.empty
Guido van Rossum336f2811990-10-24 16:39:18 +000031 self.enabled = 1
32 self.hilited = 0
33 self.selected = 0
34 self.text = ''
Guido van Rossum0c89ec71990-11-05 19:44:31 +000035 #
36 # Size enquiry
37 #
38 def minsize(self, m):
39 try:
40 self.text = self.text
41 except NameError:
42 self.text = ''
43 return m.textwidth(self.text) + 6, m.lineheight() + 6
44 #
45 def getbounds(self):
46 return self.bounds
Guido van Rossum336f2811990-10-24 16:39:18 +000047 #
48 # Changing the parameters
49 #
50 def settext(self, text):
51 self.text = text
Guido van Rossum0c89ec71990-11-05 19:44:31 +000052 if self.bounds <> _rect.empty:
53 self.recalctextpos()
54 self.redraw()
Guido van Rossum336f2811990-10-24 16:39:18 +000055 #
56 def setbounds(self, bounds):
Guido van Rossum0c89ec71990-11-05 19:44:31 +000057 if self.bounds <> _rect.empty:
58 self.parent.change(self.bounds)
Guido van Rossum336f2811990-10-24 16:39:18 +000059 self.bounds = bounds
Guido van Rossum0c89ec71990-11-05 19:44:31 +000060 if self.bounds <> _rect.empty:
61 self.recalc()
62 self.parent.change(bounds)
Guido van Rossum336f2811990-10-24 16:39:18 +000063 #
Guido van Rossumefba63b1991-04-07 13:32:19 +000064 def realize(self):
65 pass
66 #
Guido van Rossum336f2811990-10-24 16:39:18 +000067 # Changing the state bits
68 #
69 def enable(self, flag):
70 if flag <> self.enabled:
71 self.enabled = flag
Guido van Rossum0c89ec71990-11-05 19:44:31 +000072 if self.bounds <> _rect.empty:
73 self.flipenable(self.parent.begindrawing())
Guido van Rossum336f2811990-10-24 16:39:18 +000074 #
75 def hilite(self, flag):
76 if flag <> self.hilited:
77 self.hilited = flag
Guido van Rossum0c89ec71990-11-05 19:44:31 +000078 if self.bounds <> _rect.empty:
79 self.fliphilite(self.parent.begindrawing())
Guido van Rossum336f2811990-10-24 16:39:18 +000080 #
81 def select(self, flag):
82 if flag <> self.selected:
83 self.selected = flag
Guido van Rossum0c89ec71990-11-05 19:44:31 +000084 if self.bounds <> _rect.empty:
85 self.redraw()
Guido van Rossum336f2811990-10-24 16:39:18 +000086 #
Guido van Rossumceae5281990-10-25 18:50:27 +000087 # Recalculate the box bounds and text position.
88 # This can be overridden by buttons that draw different boxes
89 # or want their text in a different position.
90 #
91 def recalc(self):
Guido van Rossum0c89ec71990-11-05 19:44:31 +000092 if self.bounds <> _rect.empty:
93 self.recalcbounds()
94 self.recalctextpos()
Guido van Rossumceae5281990-10-25 18:50:27 +000095 #
96 def recalcbounds(self):
97 self.hilitebounds = _rect.inset(self.bounds, (3, 3))
98 self.crossbounds = self.bounds
99 #
100 def recalctextpos(self):
101 (left, top), (right, bottom) = self.bounds
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000102 m = self.parent.beginmeasuring()
103 h = (left + right - m.textwidth(self.text)) / 2
104 v = (top + bottom - m.lineheight()) / 2
Guido van Rossumceae5281990-10-25 18:50:27 +0000105 self.textpos = h, v
106 #
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000107 # Generic drawing interface.
Guido van Rossumceae5281990-10-25 18:50:27 +0000108 # Do not override redraw() or draw() methods; override drawit() c.s.
Guido van Rossum336f2811990-10-24 16:39:18 +0000109 #
110 def redraw(self):
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000111 if self.bounds <> _rect.empty:
112 self.draw(self.parent.begindrawing(), self.bounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000113 #
114 def draw(self, (d, area)):
115 area = _rect.intersect(area, self.bounds)
116 if area = _rect.empty:
117 return
118 d.cliprect(area)
119 d.erase(self.bounds)
120 self.drawit(d)
121 d.noclip()
122 #
123 # The drawit() method is fairly generic but may be overridden.
124 #
125 def drawit(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000126 self.drawpict(d)
Guido van Rossum336f2811990-10-24 16:39:18 +0000127 if self.text:
Guido van Rossumceae5281990-10-25 18:50:27 +0000128 d.text(self.textpos, self.text)
Guido van Rossum336f2811990-10-24 16:39:18 +0000129 if not self.enabled:
130 self.flipenable(d)
131 if self.hilited:
132 self.fliphilite(d)
133 #
134 # Default drawing detail functions.
135 # Overriding these is normally sufficient to get different
136 # appearances.
Guido van Rossum336f2811990-10-24 16:39:18 +0000137 #
138 def drawpict(self, d):
139 pass
140 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000141 def flipenable(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000142 _xorcross(d, self.crossbounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000143 #
144 def fliphilite(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000145 d.invert(self.hilitebounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000146
147
148# ButtonAppearance displays a centered string in a box.
149# selected --> bold border
150# disabled --> crossed out
151# hilited --> inverted
152#
Guido van Rossumceae5281990-10-25 18:50:27 +0000153class ButtonAppearance() = LabelAppearance():
Guido van Rossum336f2811990-10-24 16:39:18 +0000154 #
155 def drawpict(self, d):
156 d.box(_rect.inset(self.bounds, (1, 1)))
157 if self.selected:
158 # Make a thicker box
159 d.box(self.bounds)
160 d.box(_rect.inset(self.bounds, (2, 2)))
161 d.box(_rect.inset(self.bounds, (3, 3)))
162 #
163
164
165# CheckAppearance displays a small square box and a left-justified string.
166# selected --> a cross appears in the box
167# disabled --> whole button crossed out
168# hilited --> box is inverted
169#
Guido van Rossumceae5281990-10-25 18:50:27 +0000170class CheckAppearance() = LabelAppearance():
Guido van Rossum336f2811990-10-24 16:39:18 +0000171 #
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000172 def minsize(self, m):
173 width, height = m.textwidth(self.text) + 6, m.lineheight() + 6
174 return width + height + m.textwidth(' '), height
175 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000176 def drawpict(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000177 d.box(self.boxbounds)
178 if self.selected: _xorcross(d, self.boxbounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000179 #
Guido van Rossumceae5281990-10-25 18:50:27 +0000180 def recalcbounds(self):
181 LabelAppearance.recalcbounds(self)
Guido van Rossum336f2811990-10-24 16:39:18 +0000182 (left, top), (right, bottom) = self.bounds
Guido van Rossumceae5281990-10-25 18:50:27 +0000183 self.size = bottom - top - 4
184 self.boxbounds = (left+2, top+2), (left+2+self.size, bottom-2)
185 self.hilitebounds = self.boxbounds
Guido van Rossum336f2811990-10-24 16:39:18 +0000186 #
Guido van Rossumceae5281990-10-25 18:50:27 +0000187 def recalctextpos(self):
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000188 m = self.parent.beginmeasuring()
Guido van Rossumceae5281990-10-25 18:50:27 +0000189 (left, top), (right, bottom) = self.boxbounds
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000190 h = right + m.textwidth(' ')
191 v = top + (self.size - m.lineheight()) / 2
Guido van Rossumceae5281990-10-25 18:50:27 +0000192 self.textpos = h, v
Guido van Rossum336f2811990-10-24 16:39:18 +0000193 #
194
195
196# RadioAppearance displays a round indicator and a left-justified string.
197# selected --> a dot appears in the indicator
198# disabled --> whole button crossed out
199# hilited --> indicator is inverted
200#
Guido van Rossumceae5281990-10-25 18:50:27 +0000201class RadioAppearance() = CheckAppearance():
Guido van Rossum336f2811990-10-24 16:39:18 +0000202 #
203 def drawpict(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000204 (left, top), (right, bottom) = self.boxbounds
205 radius = self.size / 2
Guido van Rossum336f2811990-10-24 16:39:18 +0000206 h, v = left + radius, top + radius
Guido van Rossumceae5281990-10-25 18:50:27 +0000207 d.circle((h, v), radius)
Guido van Rossum336f2811990-10-24 16:39:18 +0000208 if self.selected:
209 some = radius/3
210 d.paint((h-some, v-some), (h+some, v+some))
211 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000212
213
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000214# NoReactivity ignores mouse events.
215#
216class NoReactivity():
217 def init_reactivity(self): pass
218
219
220# BaseReactivity defines hooks and asks for mouse events,
221# but provides only dummy mouse event handlers.
Guido van Rossum336f2811990-10-24 16:39:18 +0000222# The trigger methods call the corresponding hooks set by the user.
223# Hooks (and triggers) mean the following:
224# down_hook called on some mouse-down events
Guido van Rossumceae5281990-10-25 18:50:27 +0000225# move_hook called on some mouse-move events
Guido van Rossum336f2811990-10-24 16:39:18 +0000226# up_hook called on mouse-up events
227# on_hook called for buttons with on/off state, when it goes on
Guido van Rossum336f2811990-10-24 16:39:18 +0000228# hook called when a button 'fires' or a radiobutton goes on
229# There are usually extra conditions, e.g., hooks are only called
230# when the button is enabled, or active, or selected (on).
231#
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000232class BaseReactivity():
Guido van Rossum336f2811990-10-24 16:39:18 +0000233 #
234 def init_reactivity(self):
Guido van Rossumceae5281990-10-25 18:50:27 +0000235 self.down_hook = self.move_hook = self.up_hook = \
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000236 self.on_hook = self.off_hook = \
237 self.hook = self.active = 0
238 self.parent.need_mouse(self)
Guido van Rossum336f2811990-10-24 16:39:18 +0000239 #
240 def mousetest(self, hv):
241 return _rect.pointinrect(hv, self.bounds)
242 #
243 def mouse_down(self, detail):
244 pass
245 #
246 def mouse_move(self, detail):
247 pass
248 #
249 def mouse_up(self, detail):
250 pass
251 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000252 def down_trigger(self):
253 if self.down_hook: self.down_hook(self)
254 #
Guido van Rossumceae5281990-10-25 18:50:27 +0000255 def move_trigger(self):
256 if self.move_hook: self.move_hook(self)
Guido van Rossum336f2811990-10-24 16:39:18 +0000257 #
258 def up_trigger(self):
259 if self.up_hook: self.up_hook(self)
260 #
261 def on_trigger(self):
262 if self.on_hook: self.on_hook(self)
263 #
264 def off_trigger(self):
265 if self.off_hook: self.off_hook(self)
266 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000267 def trigger(self):
268 if self.hook: self.hook(self)
269
270
271# ToggleReactivity acts like a simple pushbutton.
272# It toggles its hilite state on mouse down events.
Guido van Rossum336f2811990-10-24 16:39:18 +0000273#
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000274class ToggleReactivity() = BaseReactivity():
Guido van Rossum336f2811990-10-24 16:39:18 +0000275 #
276 def mouse_down(self, detail):
277 if self.enabled and self.mousetest(detail[_HV]):
278 self.active = 1
279 self.hilite(not self.hilited)
280 self.down_trigger()
281 #
282 def mouse_move(self, detail):
283 if self.active:
Guido van Rossumceae5281990-10-25 18:50:27 +0000284 self.move_trigger()
Guido van Rossum336f2811990-10-24 16:39:18 +0000285 #
286 def mouse_up(self, detail):
287 if self.active:
288 self.up_trigger()
289 self.active = 0
290 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000291 def down_trigger(self):
292 if self.hilited:
293 self.on_trigger()
294 else:
295 self.off_trigger()
296 self.trigger()
297 #
298
299
300# TriggerReactivity acts like a fancy pushbutton.
301# It hilites itself while the mouse is down within its bounds.
302#
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000303class TriggerReactivity() = BaseReactivity():
Guido van Rossum336f2811990-10-24 16:39:18 +0000304 #
305 def mouse_down(self, detail):
306 if self.enabled and self.mousetest(detail[_HV]):
307 self.active = 1
308 self.hilite(1)
309 self.down_trigger()
310 #
311 def mouse_move(self, detail):
312 if self.active:
313 self.hilite(self.mousetest(detail[_HV]))
314 if self.hilited:
Guido van Rossumceae5281990-10-25 18:50:27 +0000315 self.move_trigger()
Guido van Rossum336f2811990-10-24 16:39:18 +0000316 #
317 def mouse_up(self, detail):
318 if self.active:
319 self.hilite(self.mousetest(detail[_HV]))
320 if self.hilited:
321 self.up_trigger()
322 self.trigger()
323 self.active = 0
324 self.hilite(0)
325 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000326
327
328# CheckReactivity handles mouse events like TriggerReactivity,
329# It overrides the up_trigger method to flip its selected state.
330#
331class CheckReactivity() = TriggerReactivity():
332 #
333 def up_trigger(self):
334 self.select(not self.selected)
335 if self.selected:
336 self.on_trigger()
337 else:
338 self.off_trigger()
339 self.trigger()
340
341
342# RadioReactivity turns itself on and the other buttons in its group
343# off when its up_trigger method is called.
344#
345class RadioReactivity() = TriggerReactivity():
346 #
347 def init_reactivity(self):
348 TriggerReactivity.init_reactivity(self)
349 self.group = []
350 #
351 def up_trigger(self):
352 for b in self.group:
353 if b <> self:
354 if b.selected:
355 b.select(0)
356 b.off_trigger()
357 self.select(1)
358 self.on_trigger()
359 self.trigger()
360
361
362# Auxiliary class for 'define' method.
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000363# Call the initializers in the right order.
Guido van Rossum336f2811990-10-24 16:39:18 +0000364#
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000365class Define():
Guido van Rossum336f2811990-10-24 16:39:18 +0000366 #
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000367 def define(self, parent):
368 self.parent = parent
369 parent.addchild(self)
370 self.init_appearance()
Guido van Rossum336f2811990-10-24 16:39:18 +0000371 self.init_reactivity()
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000372 return self
373 #
374 def destroy(self):
375 self.parent = 0
376 #
377 def definetext(self, (parent, text)):
378 self = self.define(parent)
Guido van Rossumceae5281990-10-25 18:50:27 +0000379 self.settext(text)
Guido van Rossum336f2811990-10-24 16:39:18 +0000380 return self
381
382
Guido van Rossumceae5281990-10-25 18:50:27 +0000383# Subroutine to cross out a rectangle.
384#
385def _xorcross(d, bounds):
386 ((left, top), (right, bottom)) = bounds
387 # This is s bit funny to make it look better
388 left = left + 2
389 right = right - 2
390 top = top + 2
391 bottom = bottom - 3
392 d.xorline(((left, top), (right, bottom)))
393 d.xorline((left, bottom), (right, top))
394
395
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000396# Ready-made button classes.
Guido van Rossum336f2811990-10-24 16:39:18 +0000397#
Guido van Rossum336f2811990-10-24 16:39:18 +0000398class Label() = NoReactivity(), LabelAppearance(), Define(): pass
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000399class PushButton() = TriggerReactivity(), ButtonAppearance(), Define(): pass
Guido van Rossum336f2811990-10-24 16:39:18 +0000400class CheckButton() = CheckReactivity(), CheckAppearance(), Define(): pass
401class RadioButton() = RadioReactivity(), RadioAppearance(), Define(): pass
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000402class ToggleButton() = ToggleReactivity(), ButtonAppearance(), Define(): pass