blob: 9a9970789b6fa5adb09c9214b19b8fc1192d768d [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 Rossumce084481991-12-26 13:06:29 +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 #
Guido van Rossum89a78691992-12-14 12:57:56 +000038 def getminsize(self, m, (width, height)):
Guido van Rossum1e5ad4e1991-08-16 13:04:40 +000039 width = max(width, m.textwidth(self.text) + 6)
40 height = max(height, m.lineheight() + 6)
41 return width, height
Guido van Rossum0c89ec71990-11-05 19:44:31 +000042 #
43 def getbounds(self):
44 return self.bounds
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 Rossum0c89ec71990-11-05 19:44:31 +000050 if self.bounds <> _rect.empty:
51 self.recalctextpos()
52 self.redraw()
Guido van Rossum336f2811990-10-24 16:39:18 +000053 #
54 def setbounds(self, bounds):
Guido van Rossum336f2811990-10-24 16:39:18 +000055 self.bounds = bounds
Guido van Rossum0c89ec71990-11-05 19:44:31 +000056 if self.bounds <> _rect.empty:
57 self.recalc()
Guido van Rossum336f2811990-10-24 16:39:18 +000058 #
Guido van Rossumefba63b1991-04-07 13:32:19 +000059 def realize(self):
60 pass
61 #
Guido van Rossum336f2811990-10-24 16:39:18 +000062 # Changing the state bits
63 #
64 def enable(self, flag):
65 if flag <> self.enabled:
66 self.enabled = flag
Guido van Rossum0c89ec71990-11-05 19:44:31 +000067 if self.bounds <> _rect.empty:
68 self.flipenable(self.parent.begindrawing())
Guido van Rossum336f2811990-10-24 16:39:18 +000069 #
70 def hilite(self, flag):
71 if flag <> self.hilited:
72 self.hilited = flag
Guido van Rossum0c89ec71990-11-05 19:44:31 +000073 if self.bounds <> _rect.empty:
74 self.fliphilite(self.parent.begindrawing())
Guido van Rossum336f2811990-10-24 16:39:18 +000075 #
76 def select(self, flag):
77 if flag <> self.selected:
78 self.selected = flag
Guido van Rossum0c89ec71990-11-05 19:44:31 +000079 if self.bounds <> _rect.empty:
80 self.redraw()
Guido van Rossum336f2811990-10-24 16:39:18 +000081 #
Guido van Rossumceae5281990-10-25 18:50:27 +000082 # Recalculate the box bounds and text position.
83 # This can be overridden by buttons that draw different boxes
84 # or want their text in a different position.
85 #
86 def recalc(self):
Guido van Rossum0c89ec71990-11-05 19:44:31 +000087 if self.bounds <> _rect.empty:
88 self.recalcbounds()
89 self.recalctextpos()
Guido van Rossumceae5281990-10-25 18:50:27 +000090 #
91 def recalcbounds(self):
92 self.hilitebounds = _rect.inset(self.bounds, (3, 3))
93 self.crossbounds = self.bounds
94 #
95 def recalctextpos(self):
96 (left, top), (right, bottom) = self.bounds
Guido van Rossum0c89ec71990-11-05 19:44:31 +000097 m = self.parent.beginmeasuring()
98 h = (left + right - m.textwidth(self.text)) / 2
99 v = (top + bottom - m.lineheight()) / 2
Guido van Rossumceae5281990-10-25 18:50:27 +0000100 self.textpos = h, v
101 #
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000102 # Generic drawing interface.
Guido van Rossumceae5281990-10-25 18:50:27 +0000103 # Do not override redraw() or draw() methods; override drawit() c.s.
Guido van Rossum336f2811990-10-24 16:39:18 +0000104 #
105 def redraw(self):
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000106 if self.bounds <> _rect.empty:
Guido van Rossum17fca171991-05-14 12:13:40 +0000107 d = self.parent.begindrawing()
108 d.erase(self.bounds)
109 self.draw(d, self.bounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000110 #
Guido van Rossum89a78691992-12-14 12:57:56 +0000111 def draw(self, d, area):
112 area = _rect.intersect([area, self.bounds])
Guido van Rossumbdfcfcc1992-01-01 19:35:13 +0000113 if area == _rect.empty:
Guido van Rossum336f2811990-10-24 16:39:18 +0000114 return
115 d.cliprect(area)
Guido van Rossum336f2811990-10-24 16:39:18 +0000116 self.drawit(d)
117 d.noclip()
118 #
119 # The drawit() method is fairly generic but may be overridden.
120 #
121 def drawit(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000122 self.drawpict(d)
Guido van Rossum336f2811990-10-24 16:39:18 +0000123 if self.text:
Guido van Rossumceae5281990-10-25 18:50:27 +0000124 d.text(self.textpos, self.text)
Guido van Rossum336f2811990-10-24 16:39:18 +0000125 if not self.enabled:
126 self.flipenable(d)
127 if self.hilited:
128 self.fliphilite(d)
129 #
130 # Default drawing detail functions.
131 # Overriding these is normally sufficient to get different
132 # appearances.
Guido van Rossum336f2811990-10-24 16:39:18 +0000133 #
134 def drawpict(self, d):
135 pass
136 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000137 def flipenable(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000138 _xorcross(d, self.crossbounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000139 #
140 def fliphilite(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000141 d.invert(self.hilitebounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000142
143
Guido van Rossum1e5ad4e1991-08-16 13:04:40 +0000144# A Strut is a label with no width of its own.
145
Guido van Rossumce084481991-12-26 13:06:29 +0000146class StrutAppearance(LabelAppearance):
Guido van Rossum1e5ad4e1991-08-16 13:04:40 +0000147 #
Guido van Rossum89a78691992-12-14 12:57:56 +0000148 def getminsize(self, m, (width, height)):
Guido van Rossum1e5ad4e1991-08-16 13:04:40 +0000149 height = max(height, m.lineheight() + 6)
150 return width, height
151 #
152
153
Guido van Rossum336f2811990-10-24 16:39:18 +0000154# ButtonAppearance displays a centered string in a box.
155# selected --> bold border
156# disabled --> crossed out
157# hilited --> inverted
158#
Guido van Rossumce084481991-12-26 13:06:29 +0000159class ButtonAppearance(LabelAppearance):
Guido van Rossum336f2811990-10-24 16:39:18 +0000160 #
161 def drawpict(self, d):
162 d.box(_rect.inset(self.bounds, (1, 1)))
163 if self.selected:
164 # Make a thicker box
165 d.box(self.bounds)
166 d.box(_rect.inset(self.bounds, (2, 2)))
167 d.box(_rect.inset(self.bounds, (3, 3)))
168 #
169
170
171# CheckAppearance displays a small square box and a left-justified string.
172# selected --> a cross appears in the box
173# disabled --> whole button crossed out
174# hilited --> box is inverted
175#
Guido van Rossumce084481991-12-26 13:06:29 +0000176class CheckAppearance(LabelAppearance):
Guido van Rossum336f2811990-10-24 16:39:18 +0000177 #
Guido van Rossum89a78691992-12-14 12:57:56 +0000178 def getminsize(self, m, (width, height)):
Guido van Rossum1e5ad4e1991-08-16 13:04:40 +0000179 minwidth = m.textwidth(self.text) + 6
180 minheight = m.lineheight() + 6
181 width = max(width, minwidth + minheight + m.textwidth(' '))
182 height = max(height, minheight)
183 return width, height
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000184 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000185 def drawpict(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000186 d.box(self.boxbounds)
187 if self.selected: _xorcross(d, self.boxbounds)
Guido van Rossum336f2811990-10-24 16:39:18 +0000188 #
Guido van Rossumceae5281990-10-25 18:50:27 +0000189 def recalcbounds(self):
190 LabelAppearance.recalcbounds(self)
Guido van Rossum336f2811990-10-24 16:39:18 +0000191 (left, top), (right, bottom) = self.bounds
Guido van Rossumceae5281990-10-25 18:50:27 +0000192 self.size = bottom - top - 4
193 self.boxbounds = (left+2, top+2), (left+2+self.size, bottom-2)
194 self.hilitebounds = self.boxbounds
Guido van Rossum336f2811990-10-24 16:39:18 +0000195 #
Guido van Rossumceae5281990-10-25 18:50:27 +0000196 def recalctextpos(self):
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000197 m = self.parent.beginmeasuring()
Guido van Rossumceae5281990-10-25 18:50:27 +0000198 (left, top), (right, bottom) = self.boxbounds
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000199 h = right + m.textwidth(' ')
200 v = top + (self.size - m.lineheight()) / 2
Guido van Rossumceae5281990-10-25 18:50:27 +0000201 self.textpos = h, v
Guido van Rossum336f2811990-10-24 16:39:18 +0000202 #
203
204
205# RadioAppearance displays a round indicator and a left-justified string.
206# selected --> a dot appears in the indicator
207# disabled --> whole button crossed out
208# hilited --> indicator is inverted
209#
Guido van Rossumce084481991-12-26 13:06:29 +0000210class RadioAppearance(CheckAppearance):
Guido van Rossum336f2811990-10-24 16:39:18 +0000211 #
212 def drawpict(self, d):
Guido van Rossumceae5281990-10-25 18:50:27 +0000213 (left, top), (right, bottom) = self.boxbounds
214 radius = self.size / 2
Guido van Rossum17fca171991-05-14 12:13:40 +0000215 center = left + radius, top + radius
216 d.circle(center, radius)
Guido van Rossum336f2811990-10-24 16:39:18 +0000217 if self.selected:
Guido van Rossum17fca171991-05-14 12:13:40 +0000218 d.fillcircle(center, radius*3/5)
Guido van Rossum336f2811990-10-24 16:39:18 +0000219 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000220
221
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000222# NoReactivity ignores mouse events.
223#
Guido van Rossumce084481991-12-26 13:06:29 +0000224class NoReactivity:
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000225 def init_reactivity(self): pass
226
227
228# BaseReactivity defines hooks and asks for mouse events,
229# but provides only dummy mouse event handlers.
Guido van Rossum336f2811990-10-24 16:39:18 +0000230# The trigger methods call the corresponding hooks set by the user.
231# Hooks (and triggers) mean the following:
232# down_hook called on some mouse-down events
Guido van Rossumceae5281990-10-25 18:50:27 +0000233# move_hook called on some mouse-move events
Guido van Rossum336f2811990-10-24 16:39:18 +0000234# up_hook called on mouse-up events
235# on_hook called for buttons with on/off state, when it goes on
Guido van Rossum336f2811990-10-24 16:39:18 +0000236# hook called when a button 'fires' or a radiobutton goes on
237# There are usually extra conditions, e.g., hooks are only called
238# when the button is enabled, or active, or selected (on).
239#
Guido van Rossumce084481991-12-26 13:06:29 +0000240class BaseReactivity:
Guido van Rossum336f2811990-10-24 16:39:18 +0000241 #
242 def init_reactivity(self):
Guido van Rossumceae5281990-10-25 18:50:27 +0000243 self.down_hook = self.move_hook = self.up_hook = \
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000244 self.on_hook = self.off_hook = \
245 self.hook = self.active = 0
246 self.parent.need_mouse(self)
Guido van Rossum336f2811990-10-24 16:39:18 +0000247 #
248 def mousetest(self, hv):
249 return _rect.pointinrect(hv, self.bounds)
250 #
251 def mouse_down(self, detail):
252 pass
253 #
254 def mouse_move(self, detail):
255 pass
256 #
257 def mouse_up(self, detail):
258 pass
259 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000260 def down_trigger(self):
261 if self.down_hook: self.down_hook(self)
262 #
Guido van Rossumceae5281990-10-25 18:50:27 +0000263 def move_trigger(self):
264 if self.move_hook: self.move_hook(self)
Guido van Rossum336f2811990-10-24 16:39:18 +0000265 #
266 def up_trigger(self):
267 if self.up_hook: self.up_hook(self)
268 #
269 def on_trigger(self):
270 if self.on_hook: self.on_hook(self)
271 #
272 def off_trigger(self):
273 if self.off_hook: self.off_hook(self)
274 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000275 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.
Guido van Rossum336f2811990-10-24 16:39:18 +0000281#
Guido van Rossumce084481991-12-26 13:06:29 +0000282class ToggleReactivity(BaseReactivity):
Guido van Rossum336f2811990-10-24 16:39:18 +0000283 #
284 def mouse_down(self, detail):
285 if self.enabled and self.mousetest(detail[_HV]):
286 self.active = 1
287 self.hilite(not self.hilited)
288 self.down_trigger()
289 #
290 def mouse_move(self, detail):
291 if self.active:
Guido van Rossumceae5281990-10-25 18:50:27 +0000292 self.move_trigger()
Guido van Rossum336f2811990-10-24 16:39:18 +0000293 #
294 def mouse_up(self, detail):
295 if self.active:
296 self.up_trigger()
297 self.active = 0
298 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000299 def down_trigger(self):
300 if self.hilited:
301 self.on_trigger()
302 else:
303 self.off_trigger()
304 self.trigger()
305 #
306
307
308# TriggerReactivity acts like a fancy pushbutton.
309# It hilites itself while the mouse is down within its bounds.
310#
Guido van Rossumce084481991-12-26 13:06:29 +0000311class TriggerReactivity(BaseReactivity):
Guido van Rossum336f2811990-10-24 16:39:18 +0000312 #
313 def mouse_down(self, detail):
314 if self.enabled and self.mousetest(detail[_HV]):
315 self.active = 1
316 self.hilite(1)
317 self.down_trigger()
318 #
319 def mouse_move(self, detail):
320 if self.active:
321 self.hilite(self.mousetest(detail[_HV]))
322 if self.hilited:
Guido van Rossumceae5281990-10-25 18:50:27 +0000323 self.move_trigger()
Guido van Rossum336f2811990-10-24 16:39:18 +0000324 #
325 def mouse_up(self, detail):
326 if self.active:
327 self.hilite(self.mousetest(detail[_HV]))
328 if self.hilited:
329 self.up_trigger()
330 self.trigger()
331 self.active = 0
332 self.hilite(0)
333 #
Guido van Rossum336f2811990-10-24 16:39:18 +0000334
335
336# CheckReactivity handles mouse events like TriggerReactivity,
337# It overrides the up_trigger method to flip its selected state.
338#
Guido van Rossumce084481991-12-26 13:06:29 +0000339class CheckReactivity(TriggerReactivity):
Guido van Rossum336f2811990-10-24 16:39:18 +0000340 #
341 def up_trigger(self):
342 self.select(not self.selected)
343 if self.selected:
344 self.on_trigger()
345 else:
346 self.off_trigger()
347 self.trigger()
348
349
350# RadioReactivity turns itself on and the other buttons in its group
351# off when its up_trigger method is called.
352#
Guido van Rossumce084481991-12-26 13:06:29 +0000353class RadioReactivity(TriggerReactivity):
Guido van Rossum336f2811990-10-24 16:39:18 +0000354 #
355 def init_reactivity(self):
356 TriggerReactivity.init_reactivity(self)
357 self.group = []
358 #
359 def up_trigger(self):
360 for b in self.group:
361 if b <> self:
362 if b.selected:
363 b.select(0)
364 b.off_trigger()
365 self.select(1)
366 self.on_trigger()
367 self.trigger()
368
369
370# Auxiliary class for 'define' method.
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000371# Call the initializers in the right order.
Guido van Rossum336f2811990-10-24 16:39:18 +0000372#
Guido van Rossumce084481991-12-26 13:06:29 +0000373class Define:
Guido van Rossum336f2811990-10-24 16:39:18 +0000374 #
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000375 def define(self, parent):
376 self.parent = parent
377 parent.addchild(self)
378 self.init_appearance()
Guido van Rossum336f2811990-10-24 16:39:18 +0000379 self.init_reactivity()
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000380 return self
381 #
382 def destroy(self):
383 self.parent = 0
384 #
Guido van Rossum89a78691992-12-14 12:57:56 +0000385 def definetext(self, parent, text):
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000386 self = self.define(parent)
Guido van Rossumceae5281990-10-25 18:50:27 +0000387 self.settext(text)
Guido van Rossum336f2811990-10-24 16:39:18 +0000388 return self
389
390
Guido van Rossumceae5281990-10-25 18:50:27 +0000391# Subroutine to cross out a rectangle.
392#
393def _xorcross(d, bounds):
394 ((left, top), (right, bottom)) = bounds
395 # This is s bit funny to make it look better
396 left = left + 2
397 right = right - 2
398 top = top + 2
399 bottom = bottom - 3
400 d.xorline(((left, top), (right, bottom)))
401 d.xorline((left, bottom), (right, top))
402
403
Guido van Rossum0c89ec71990-11-05 19:44:31 +0000404# Ready-made button classes.
Guido van Rossum336f2811990-10-24 16:39:18 +0000405#
Guido van Rossumce084481991-12-26 13:06:29 +0000406class Label(NoReactivity, LabelAppearance, Define): pass
407class Strut(NoReactivity, StrutAppearance, Define): pass
408class PushButton(TriggerReactivity, ButtonAppearance, Define): pass
409class CheckButton(CheckReactivity, CheckAppearance, Define): pass
410class RadioButton(RadioReactivity, RadioAppearance, Define): pass
411class ToggleButton(ToggleReactivity, ButtonAppearance, Define): pass