blob: d991c96a7b56afc22f5ecf663f43329be586a5b3 [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 Rossum336f2811990-10-24 16:39:18 +000057 self.bounds = bounds
Guido van Rossum0c89ec71990-11-05 19:44:31 +000058 if self.bounds <> _rect.empty:
59 self.recalc()
Guido van Rossum336f2811990-10-24 16:39:18 +000060 #
Guido van Rossumefba63b1991-04-07 13:32:19 +000061 def realize(self):
62 pass
63 #
Guido van Rossum336f2811990-10-24 16:39:18 +000064 # 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:
Guido van Rossum17fca171991-05-14 12:13:40 +0000109 d = self.parent.begindrawing()
110 d.erase(self.bounds)
111 self.draw(d, self.bounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000112 #
113 def draw(self, (d, area)):
114 area = _rect.intersect(area, self.bounds)
115 if area = _rect.empty:
116 return
117 d.cliprect(area)
Guido van Rossum336f2811990-10-24 16:39:18 +0000118 self.drawit(d)
119 d.noclip()
120 #
121 # The drawit() method is fairly generic but may be overridden.
122 #
123 def drawit(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000124 self.drawpict(d)
Guido van Rossum336f2811990-10-24 16:39:18 +0000125 if self.text:
Guido van Rossumceae5281990-10-25 18:50:27 +0000126 d.text(self.textpos, self.text)
Guido van Rossum336f2811990-10-24 16:39:18 +0000127 if not self.enabled:
128 self.flipenable(d)
129 if self.hilited:
130 self.fliphilite(d)
131 #
132 # Default drawing detail functions.
133 # Overriding these is normally sufficient to get different
134 # appearances.
Guido van Rossum336f2811990-10-24 16:39:18 +0000135 #
136 def drawpict(self, d):
137 pass
138 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000139 def flipenable(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000140 _xorcross(d, self.crossbounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000141 #
142 def fliphilite(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000143 d.invert(self.hilitebounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000144
145
146# ButtonAppearance displays a centered string in a box.
147# selected --> bold border
148# disabled --> crossed out
149# hilited --> inverted
150#
Guido van Rossumceae5281990-10-25 18:50:27 +0000151class ButtonAppearance() = LabelAppearance():
Guido van Rossum336f2811990-10-24 16:39:18 +0000152 #
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#
Guido van Rossumceae5281990-10-25 18:50:27 +0000168class CheckAppearance() = LabelAppearance():
Guido van Rossum336f2811990-10-24 16:39:18 +0000169 #
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000170 def minsize(self, m):
171 width, height = m.textwidth(self.text) + 6, m.lineheight() + 6
172 return width + height + m.textwidth(' '), height
173 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000174 def drawpict(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000175 d.box(self.boxbounds)
176 if self.selected: _xorcross(d, self.boxbounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000177 #
Guido van Rossumceae5281990-10-25 18:50:27 +0000178 def recalcbounds(self):
179 LabelAppearance.recalcbounds(self)
Guido van Rossum336f2811990-10-24 16:39:18 +0000180 (left, top), (right, bottom) = self.bounds
Guido van Rossumceae5281990-10-25 18:50:27 +0000181 self.size = bottom - top - 4
182 self.boxbounds = (left+2, top+2), (left+2+self.size, bottom-2)
183 self.hilitebounds = self.boxbounds
Guido van Rossum336f2811990-10-24 16:39:18 +0000184 #
Guido van Rossumceae5281990-10-25 18:50:27 +0000185 def recalctextpos(self):
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000186 m = self.parent.beginmeasuring()
Guido van Rossumceae5281990-10-25 18:50:27 +0000187 (left, top), (right, bottom) = self.boxbounds
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000188 h = right + m.textwidth(' ')
189 v = top + (self.size - m.lineheight()) / 2
Guido van Rossumceae5281990-10-25 18:50:27 +0000190 self.textpos = h, v
Guido van Rossum336f2811990-10-24 16:39:18 +0000191 #
192
193
194# RadioAppearance displays a round indicator and a left-justified string.
195# selected --> a dot appears in the indicator
196# disabled --> whole button crossed out
197# hilited --> indicator is inverted
198#
Guido van Rossumceae5281990-10-25 18:50:27 +0000199class RadioAppearance() = CheckAppearance():
Guido van Rossum336f2811990-10-24 16:39:18 +0000200 #
201 def drawpict(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000202 (left, top), (right, bottom) = self.boxbounds
203 radius = self.size / 2
Guido van Rossum17fca171991-05-14 12:13:40 +0000204 center = left + radius, top + radius
205 d.circle(center, radius)
Guido van Rossum336f2811990-10-24 16:39:18 +0000206 if self.selected:
Guido van Rossum17fca171991-05-14 12:13:40 +0000207 d.fillcircle(center, radius*3/5)
Guido van Rossum336f2811990-10-24 16:39:18 +0000208 #
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