blob: 3ad56050545ffacadaad72d636f0a45fb998b124 [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 #
64 # Changing the state bits
65 #
66 def enable(self, flag):
67 if flag <> self.enabled:
68 self.enabled = flag
Guido van Rossum0c89ec71990-11-05 19:44:31 +000069 if self.bounds <> _rect.empty:
70 self.flipenable(self.parent.begindrawing())
Guido van Rossum336f2811990-10-24 16:39:18 +000071 #
72 def hilite(self, flag):
73 if flag <> self.hilited:
74 self.hilited = flag
Guido van Rossum0c89ec71990-11-05 19:44:31 +000075 if self.bounds <> _rect.empty:
76 self.fliphilite(self.parent.begindrawing())
Guido van Rossum336f2811990-10-24 16:39:18 +000077 #
78 def select(self, flag):
79 if flag <> self.selected:
80 self.selected = flag
Guido van Rossum0c89ec71990-11-05 19:44:31 +000081 if self.bounds <> _rect.empty:
82 self.redraw()
Guido van Rossum336f2811990-10-24 16:39:18 +000083 #
Guido van Rossumceae5281990-10-25 18:50:27 +000084 # Recalculate the box bounds and text position.
85 # This can be overridden by buttons that draw different boxes
86 # or want their text in a different position.
87 #
88 def recalc(self):
Guido van Rossum0c89ec71990-11-05 19:44:31 +000089 if self.bounds <> _rect.empty:
90 self.recalcbounds()
91 self.recalctextpos()
Guido van Rossumceae5281990-10-25 18:50:27 +000092 #
93 def recalcbounds(self):
94 self.hilitebounds = _rect.inset(self.bounds, (3, 3))
95 self.crossbounds = self.bounds
96 #
97 def recalctextpos(self):
98 (left, top), (right, bottom) = self.bounds
Guido van Rossum0c89ec71990-11-05 19:44:31 +000099 m = self.parent.beginmeasuring()
100 h = (left + right - m.textwidth(self.text)) / 2
101 v = (top + bottom - m.lineheight()) / 2
Guido van Rossumceae5281990-10-25 18:50:27 +0000102 self.textpos = h, v
103 #
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000104 # Generic drawing interface.
Guido van Rossumceae5281990-10-25 18:50:27 +0000105 # Do not override redraw() or draw() methods; override drawit() c.s.
Guido van Rossum336f2811990-10-24 16:39:18 +0000106 #
107 def redraw(self):
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000108 if self.bounds <> _rect.empty:
109 self.draw(self.parent.begindrawing(), self.bounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000110 #
111 def draw(self, (d, area)):
112 area = _rect.intersect(area, self.bounds)
113 if area = _rect.empty:
114 return
115 d.cliprect(area)
116 d.erase(self.bounds)
117 self.drawit(d)
118 d.noclip()
119 #
120 # The drawit() method is fairly generic but may be overridden.
121 #
122 def drawit(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000123 self.drawpict(d)
Guido van Rossum336f2811990-10-24 16:39:18 +0000124 if self.text:
Guido van Rossumceae5281990-10-25 18:50:27 +0000125 d.text(self.textpos, self.text)
Guido van Rossum336f2811990-10-24 16:39:18 +0000126 if not self.enabled:
127 self.flipenable(d)
128 if self.hilited:
129 self.fliphilite(d)
130 #
131 # Default drawing detail functions.
132 # Overriding these is normally sufficient to get different
133 # appearances.
Guido van Rossum336f2811990-10-24 16:39:18 +0000134 #
135 def drawpict(self, d):
136 pass
137 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000138 def flipenable(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000139 _xorcross(d, self.crossbounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000140 #
141 def fliphilite(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000142 d.invert(self.hilitebounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000143
144
145# ButtonAppearance displays a centered string in a box.
146# selected --> bold border
147# disabled --> crossed out
148# hilited --> inverted
149#
Guido van Rossumceae5281990-10-25 18:50:27 +0000150class ButtonAppearance() = LabelAppearance():
Guido van Rossum336f2811990-10-24 16:39:18 +0000151 #
152 def drawpict(self, d):
153 d.box(_rect.inset(self.bounds, (1, 1)))
154 if self.selected:
155 # Make a thicker box
156 d.box(self.bounds)
157 d.box(_rect.inset(self.bounds, (2, 2)))
158 d.box(_rect.inset(self.bounds, (3, 3)))
159 #
160
161
162# CheckAppearance displays a small square box and a left-justified string.
163# selected --> a cross appears in the box
164# disabled --> whole button crossed out
165# hilited --> box is inverted
166#
Guido van Rossumceae5281990-10-25 18:50:27 +0000167class CheckAppearance() = LabelAppearance():
Guido van Rossum336f2811990-10-24 16:39:18 +0000168 #
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000169 def minsize(self, m):
170 width, height = m.textwidth(self.text) + 6, m.lineheight() + 6
171 return width + height + m.textwidth(' '), height
172 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000173 def drawpict(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000174 d.box(self.boxbounds)
175 if self.selected: _xorcross(d, self.boxbounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000176 #
Guido van Rossumceae5281990-10-25 18:50:27 +0000177 def recalcbounds(self):
178 LabelAppearance.recalcbounds(self)
Guido van Rossum336f2811990-10-24 16:39:18 +0000179 (left, top), (right, bottom) = self.bounds
Guido van Rossumceae5281990-10-25 18:50:27 +0000180 self.size = bottom - top - 4
181 self.boxbounds = (left+2, top+2), (left+2+self.size, bottom-2)
182 self.hilitebounds = self.boxbounds
Guido van Rossum336f2811990-10-24 16:39:18 +0000183 #
Guido van Rossumceae5281990-10-25 18:50:27 +0000184 def recalctextpos(self):
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000185 m = self.parent.beginmeasuring()
Guido van Rossumceae5281990-10-25 18:50:27 +0000186 (left, top), (right, bottom) = self.boxbounds
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000187 h = right + m.textwidth(' ')
188 v = top + (self.size - m.lineheight()) / 2
Guido van Rossumceae5281990-10-25 18:50:27 +0000189 self.textpos = h, v
Guido van Rossum336f2811990-10-24 16:39:18 +0000190 #
191
192
193# RadioAppearance displays a round indicator and a left-justified string.
194# selected --> a dot appears in the indicator
195# disabled --> whole button crossed out
196# hilited --> indicator is inverted
197#
Guido van Rossumceae5281990-10-25 18:50:27 +0000198class RadioAppearance() = CheckAppearance():
Guido van Rossum336f2811990-10-24 16:39:18 +0000199 #
200 def drawpict(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000201 (left, top), (right, bottom) = self.boxbounds
202 radius = self.size / 2
Guido van Rossum336f2811990-10-24 16:39:18 +0000203 h, v = left + radius, top + radius
Guido van Rossumceae5281990-10-25 18:50:27 +0000204 d.circle((h, v), radius)
Guido van Rossum336f2811990-10-24 16:39:18 +0000205 if self.selected:
206 some = radius/3
207 d.paint((h-some, v-some), (h+some, v+some))
208 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000209
210
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000211# NoReactivity ignores mouse events.
212#
213class NoReactivity():
214 def init_reactivity(self): pass
215
216
217# BaseReactivity defines hooks and asks for mouse events,
218# but provides only dummy mouse event handlers.
Guido van Rossum336f2811990-10-24 16:39:18 +0000219# The trigger methods call the corresponding hooks set by the user.
220# Hooks (and triggers) mean the following:
221# down_hook called on some mouse-down events
Guido van Rossumceae5281990-10-25 18:50:27 +0000222# move_hook called on some mouse-move events
Guido van Rossum336f2811990-10-24 16:39:18 +0000223# up_hook called on mouse-up events
224# on_hook called for buttons with on/off state, when it goes on
Guido van Rossum336f2811990-10-24 16:39:18 +0000225# hook called when a button 'fires' or a radiobutton goes on
226# There are usually extra conditions, e.g., hooks are only called
227# when the button is enabled, or active, or selected (on).
228#
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000229class BaseReactivity():
Guido van Rossum336f2811990-10-24 16:39:18 +0000230 #
231 def init_reactivity(self):
Guido van Rossumceae5281990-10-25 18:50:27 +0000232 self.down_hook = self.move_hook = self.up_hook = \
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000233 self.on_hook = self.off_hook = \
234 self.hook = self.active = 0
235 self.parent.need_mouse(self)
Guido van Rossum336f2811990-10-24 16:39:18 +0000236 #
237 def mousetest(self, hv):
238 return _rect.pointinrect(hv, self.bounds)
239 #
240 def mouse_down(self, detail):
241 pass
242 #
243 def mouse_move(self, detail):
244 pass
245 #
246 def mouse_up(self, detail):
247 pass
248 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000249 def down_trigger(self):
250 if self.down_hook: self.down_hook(self)
251 #
Guido van Rossumceae5281990-10-25 18:50:27 +0000252 def move_trigger(self):
253 if self.move_hook: self.move_hook(self)
Guido van Rossum336f2811990-10-24 16:39:18 +0000254 #
255 def up_trigger(self):
256 if self.up_hook: self.up_hook(self)
257 #
258 def on_trigger(self):
259 if self.on_hook: self.on_hook(self)
260 #
261 def off_trigger(self):
262 if self.off_hook: self.off_hook(self)
263 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000264 def trigger(self):
265 if self.hook: self.hook(self)
266
267
268# ToggleReactivity acts like a simple pushbutton.
269# It toggles its hilite state on mouse down events.
Guido van Rossum336f2811990-10-24 16:39:18 +0000270#
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000271class ToggleReactivity() = BaseReactivity():
Guido van Rossum336f2811990-10-24 16:39:18 +0000272 #
273 def mouse_down(self, detail):
274 if self.enabled and self.mousetest(detail[_HV]):
275 self.active = 1
276 self.hilite(not self.hilited)
277 self.down_trigger()
278 #
279 def mouse_move(self, detail):
280 if self.active:
Guido van Rossumceae5281990-10-25 18:50:27 +0000281 self.move_trigger()
Guido van Rossum336f2811990-10-24 16:39:18 +0000282 #
283 def mouse_up(self, detail):
284 if self.active:
285 self.up_trigger()
286 self.active = 0
287 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000288 def down_trigger(self):
289 if self.hilited:
290 self.on_trigger()
291 else:
292 self.off_trigger()
293 self.trigger()
294 #
295
296
297# TriggerReactivity acts like a fancy pushbutton.
298# It hilites itself while the mouse is down within its bounds.
299#
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000300class TriggerReactivity() = BaseReactivity():
Guido van Rossum336f2811990-10-24 16:39:18 +0000301 #
302 def mouse_down(self, detail):
303 if self.enabled and self.mousetest(detail[_HV]):
304 self.active = 1
305 self.hilite(1)
306 self.down_trigger()
307 #
308 def mouse_move(self, detail):
309 if self.active:
310 self.hilite(self.mousetest(detail[_HV]))
311 if self.hilited:
Guido van Rossumceae5281990-10-25 18:50:27 +0000312 self.move_trigger()
Guido van Rossum336f2811990-10-24 16:39:18 +0000313 #
314 def mouse_up(self, detail):
315 if self.active:
316 self.hilite(self.mousetest(detail[_HV]))
317 if self.hilited:
318 self.up_trigger()
319 self.trigger()
320 self.active = 0
321 self.hilite(0)
322 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000323
324
325# CheckReactivity handles mouse events like TriggerReactivity,
326# It overrides the up_trigger method to flip its selected state.
327#
328class CheckReactivity() = TriggerReactivity():
329 #
330 def up_trigger(self):
331 self.select(not self.selected)
332 if self.selected:
333 self.on_trigger()
334 else:
335 self.off_trigger()
336 self.trigger()
337
338
339# RadioReactivity turns itself on and the other buttons in its group
340# off when its up_trigger method is called.
341#
342class RadioReactivity() = TriggerReactivity():
343 #
344 def init_reactivity(self):
345 TriggerReactivity.init_reactivity(self)
346 self.group = []
347 #
348 def up_trigger(self):
349 for b in self.group:
350 if b <> self:
351 if b.selected:
352 b.select(0)
353 b.off_trigger()
354 self.select(1)
355 self.on_trigger()
356 self.trigger()
357
358
359# Auxiliary class for 'define' method.
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000360# Call the initializers in the right order.
Guido van Rossum336f2811990-10-24 16:39:18 +0000361#
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000362class Define():
Guido van Rossum336f2811990-10-24 16:39:18 +0000363 #
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000364 def define(self, parent):
365 self.parent = parent
366 parent.addchild(self)
367 self.init_appearance()
Guido van Rossum336f2811990-10-24 16:39:18 +0000368 self.init_reactivity()
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000369 return self
370 #
371 def destroy(self):
372 self.parent = 0
373 #
374 def definetext(self, (parent, text)):
375 self = self.define(parent)
Guido van Rossumceae5281990-10-25 18:50:27 +0000376 self.settext(text)
Guido van Rossum336f2811990-10-24 16:39:18 +0000377 return self
378
379
Guido van Rossumceae5281990-10-25 18:50:27 +0000380# Subroutine to cross out a rectangle.
381#
382def _xorcross(d, bounds):
383 ((left, top), (right, bottom)) = bounds
384 # This is s bit funny to make it look better
385 left = left + 2
386 right = right - 2
387 top = top + 2
388 bottom = bottom - 3
389 d.xorline(((left, top), (right, bottom)))
390 d.xorline((left, bottom), (right, top))
391
392
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000393# Ready-made button classes.
Guido van Rossum336f2811990-10-24 16:39:18 +0000394#
Guido van Rossum336f2811990-10-24 16:39:18 +0000395class Label() = NoReactivity(), LabelAppearance(), Define(): pass
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000396class PushButton() = TriggerReactivity(), ButtonAppearance(), Define(): pass
Guido van Rossum336f2811990-10-24 16:39:18 +0000397class CheckButton() = CheckReactivity(), CheckAppearance(), Define(): pass
398class RadioButton() = RadioReactivity(), RadioAppearance(), Define(): pass
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000399class ToggleButton() = ToggleReactivity(), ButtonAppearance(), Define(): pass