Fix three PyChecker-detected gotchas.
[python/dscho.git] / Mac / Tools / IDE / Wbase.py
blob3c5ab602ec72a0cd254731b7e6479138982c98d8
1 import Qd
2 import Win
3 import QuickDraw
4 import Evt
5 import string
6 from types import *
7 import sys
9 WidgetsError = "WidgetsError"
11 DEBUG = 0
13 class Widget:
15 """Base class for all widgets."""
17 _selectable = 0
19 def __init__(self, possize):
20 self._widgets = []
21 self._widgetsdict = {}
22 self._possize = possize
23 self._bounds = None
24 self._visible = 1
25 self._enabled = 0
26 self._selected = 0
27 self._activated = 0
28 self._callback = None
29 self._parent = None
30 self._parentwindow = None
31 self._bindings = {}
32 self._backcolor = None
34 def show(self, onoff):
35 self._visible = onoff
36 for w in self._widgets:
37 w.show(onoff)
38 if self._parentwindow is not None and self._parentwindow.wid is not None:
39 self.SetPort()
40 if onoff:
41 self.draw()
42 else:
43 Qd.EraseRect(self._bounds)
45 def draw(self, visRgn = None):
46 if self._visible:
47 # draw your stuff here
48 pass
50 def getpossize(self):
51 return self._possize
53 def getbounds(self):
54 return self._bounds
56 def move(self, x, y = None):
57 """absolute move"""
58 if y == None:
59 x, y = x
60 if type(self._possize) <> TupleType:
61 raise WidgetsError, "can't move widget with bounds function"
62 l, t, r, b = self._possize
63 self.resize(x, y, r, b)
65 def rmove(self, x, y = None):
66 """relative move"""
67 if y == None:
68 x, y = x
69 if type(self._possize) <> TupleType:
70 raise WidgetsError, "can't move widget with bounds function"
71 l, t, r, b = self._possize
72 self.resize(l + x, t + y, r, b)
74 def resize(self, *args):
75 if len(args) == 1:
76 if type(args[0]) == FunctionType or type(args[0]) == MethodType:
77 self._possize = args[0]
78 else:
79 apply(self.resize, args[0])
80 elif len(args) == 2:
81 self._possize = (0, 0) + args
82 elif len(args) == 4:
83 self._possize = args
84 else:
85 raise TypeError, "wrong number of arguments"
86 self._calcbounds()
88 def open(self):
89 self._calcbounds()
91 def close(self):
92 del self._callback
93 del self._possize
94 del self._bindings
95 del self._parent
96 del self._parentwindow
98 def bind(self, key, callback):
99 """bind a key or an 'event' to a callback"""
100 if callback:
101 self._bindings[key] = callback
102 elif self._bindings.has_key(key):
103 del self._bindings[key]
105 def adjust(self, oldbounds):
106 self.SetPort()
107 self.GetWindow().InvalWindowRect(oldbounds)
108 self.GetWindow().InvalWindowRect(self._bounds)
110 def _calcbounds(self):
111 # calculate absolute bounds relative to the window origin from our
112 # abstract _possize attribute, which is either a 4-tuple or a callable object
113 oldbounds = self._bounds
114 pl, pt, pr, pb = self._parent._bounds
115 if callable(self._possize):
116 # _possize is callable, let it figure it out by itself: it should return
117 # the bounds relative to our parent widget.
118 width = pr - pl
119 height = pb - pt
120 self._bounds = Qd.OffsetRect(self._possize(width, height), pl, pt)
121 else:
122 # _possize must be a 4-tuple. This is where the algorithm by Peter Kriens and
123 # Petr van Blokland kicks in. (*** Parts of this algorithm are applied for
124 # patents by Ericsson, Sweden ***)
125 l, t, r, b = self._possize
126 # depending on the values of l(eft), t(op), r(right) and b(ottom),
127 # they mean different things:
128 if l < -1:
129 # l is less than -1, this mean it measures from the *right* of it's parent
130 l = pr + l
131 else:
132 # l is -1 or greater, this mean it measures from the *left* of it's parent
133 l = pl + l
134 if t < -1:
135 # t is less than -1, this mean it measures from the *bottom* of it's parent
136 t = pb + t
137 else:
138 # t is -1 or greater, this mean it measures from the *top* of it's parent
139 t = pt + t
140 if r > 1:
141 # r is greater than 1, this means r is the *width* of the widget
142 r = l + r
143 else:
144 # r is less than 1, this means it measures from the *right* of it's parent
145 r = pr + r
146 if b > 1:
147 # b is greater than 1, this means b is the *height* of the widget
148 b = t + b
149 else:
150 # b is less than 1, this means it measures from the *bottom* of it's parent
151 b = pb + b
152 self._bounds = (l, t, r, b)
153 if oldbounds and oldbounds <> self._bounds:
154 self.adjust(oldbounds)
155 for w in self._widgets:
156 w._calcbounds()
158 def test(self, point):
159 if Qd.PtInRect(point, self._bounds):
160 return 1
162 def click(self, point, modifiers):
163 pass
165 def findwidget(self, point, onlyenabled = 1):
166 if self.test(point):
167 for w in self._widgets:
168 widget = w.findwidget(point)
169 if widget is not None:
170 return widget
171 if self._enabled or not onlyenabled:
172 return self
174 def forall(self, methodname, *args):
175 for w in self._widgets:
176 rv = apply(w.forall, (methodname,) + args)
177 if rv:
178 return rv
179 if self._bindings.has_key("<" + methodname + ">"):
180 callback = self._bindings["<" + methodname + ">"]
181 rv = apply(callback, args)
182 if rv:
183 return rv
184 if hasattr(self, methodname):
185 method = getattr(self, methodname)
186 return apply(method, args)
188 def forall_butself(self, methodname, *args):
189 for w in self._widgets:
190 rv = apply(w.forall, (methodname,) + args)
191 if rv:
192 return rv
194 def forall_frombottom(self, methodname, *args):
195 if self._bindings.has_key("<" + methodname + ">"):
196 callback = self._bindings["<" + methodname + ">"]
197 rv = apply(callback, args)
198 if rv:
199 return rv
200 if hasattr(self, methodname):
201 method = getattr(self, methodname)
202 rv = apply(method, args)
203 if rv:
204 return rv
205 for w in self._widgets:
206 rv = apply(w.forall_frombottom, (methodname,) + args)
207 if rv:
208 return rv
210 def _addwidget(self, key, widget):
211 if widget in self._widgets:
212 raise ValueError, "duplicate widget"
213 if self._widgetsdict.has_key(key):
214 self._removewidget(key)
215 self._widgets.append(widget)
216 self._widgetsdict[key] = widget
217 widget._parent = self
218 self._setparentwindow(widget)
219 if self._parentwindow and self._parentwindow.wid:
220 widget.forall_frombottom("open")
221 self.GetWindow().InvalWindowRect(widget._bounds)
223 def _setparentwindow(self, widget):
224 widget._parentwindow = self._parentwindow
225 for w in widget._widgets:
226 self._setparentwindow(w)
228 def _removewidget(self, key):
229 if not self._widgetsdict.has_key(key):
230 raise KeyError, "no widget with key " + `key`
231 widget = self._widgetsdict[key]
232 for k in widget._widgetsdict.keys():
233 widget._removewidget(k)
234 if self._parentwindow._currentwidget == widget:
235 widget.select(0)
236 self._parentwindow._currentwidget = None
237 self.SetPort()
238 self.GetWindow().InvalWindowRect(widget._bounds)
239 widget.close()
240 del self._widgetsdict[key]
241 self._widgets.remove(widget)
243 def __setattr__(self, attr, value):
244 if type(value) == InstanceType and isinstance(value, Widget) and \
245 attr not in ("_currentwidget", "_lastrollover",
246 "_parent", "_parentwindow", "_defaultbutton"):
247 if hasattr(self, attr):
248 raise ValueError, "Can't replace existing attribute: " + attr
249 self._addwidget(attr, value)
250 self.__dict__[attr] = value
252 def __delattr__(self, attr):
253 if attr == "_widgetsdict":
254 raise AttributeError, "cannot delete attribute _widgetsdict"
255 if self._widgetsdict.has_key(attr):
256 self._removewidget(attr)
257 if self.__dict__.has_key(attr):
258 del self.__dict__[attr]
259 elif self.__dict__.has_key(attr):
260 del self.__dict__[attr]
261 else:
262 raise AttributeError, attr
264 def __setitem__(self, key, value):
265 self._addwidget(key, value)
267 def __getitem__(self, key):
268 if not self._widgetsdict.has_key(key):
269 raise KeyError, key
270 return self._widgetsdict[key]
272 def __delitem__(self, key):
273 self._removewidget(key)
275 def SetPort(self):
276 self._parentwindow.SetPort()
279 def GetWindow(self):
280 return self._parentwindow.GetWindow()
282 def __del__(self):
283 if DEBUG:
284 print "%s instance deleted" % self.__class__.__name__
286 def _drawbounds(self):
287 Qd.FrameRect(self._bounds)
290 class ClickableWidget(Widget):
292 """Base class for clickable widgets. (note: self._enabled must be true to receive click events.)"""
294 def click(self, point, modifiers):
295 pass
297 def enable(self, onoff):
298 self._enabled = onoff
299 self.SetPort()
300 self.draw()
302 def callback(self):
303 if self._callback:
304 return CallbackCall(self._callback, 1)
307 class SelectableWidget(ClickableWidget):
309 """Base class for selectable widgets."""
311 _selectable = 1
313 def select(self, onoff, isclick = 0):
314 if onoff == self._selected:
315 return 1
316 if self._bindings.has_key("<select>"):
317 callback = self._bindings["<select>"]
318 if callback(onoff):
319 return 1
320 self._selected = onoff
321 if onoff:
322 if self._parentwindow._currentwidget is not None:
323 self._parentwindow._currentwidget.select(0)
324 self._parentwindow._currentwidget = self
325 else:
326 self._parentwindow._currentwidget = None
328 def key(self, char, event):
329 pass
331 def drawselframe(self, onoff):
332 if not self._parentwindow._hasselframes:
333 return
334 thickrect = Qd.InsetRect(self._bounds, -3, -3)
335 state = Qd.GetPenState()
336 Qd.PenSize(2, 2)
337 if onoff:
338 Qd.PenPat(Qd.qd.black)
339 else:
340 Qd.PenPat(Qd.qd.white)
341 Qd.FrameRect(thickrect)
342 Qd.SetPenState(state)
344 def adjust(self, oldbounds):
345 self.SetPort()
346 if self._selected:
347 self.GetWindow().InvalWindowRect(Qd.InsetRect(oldbounds, -3, -3))
348 self.GetWindow().InvalWindowRect(Qd.InsetRect(self._bounds, -3, -3))
349 else:
350 self.GetWindow().InvalWindowRect(oldbounds)
351 self.GetWindow().InvalWindowRect(self._bounds)
354 class _Line(Widget):
356 def __init__(self, possize, thickness = 1):
357 Widget.__init__(self, possize)
358 self._thickness = thickness
360 def open(self):
361 self._calcbounds()
362 self.SetPort()
363 self.draw()
365 def draw(self, visRgn = None):
366 if self._visible:
367 Qd.PaintRect(self._bounds)
369 def _drawbounds(self):
370 pass
372 class HorizontalLine(_Line):
374 def _calcbounds(self):
375 Widget._calcbounds(self)
376 l, t, r, b = self._bounds
377 self._bounds = l, t, r, t + self._thickness
379 class VerticalLine(_Line):
381 def _calcbounds(self):
382 Widget._calcbounds(self)
383 l, t, r, b = self._bounds
384 self._bounds = l, t, l + self._thickness, b
387 class Frame(Widget):
389 def __init__(self, possize, pattern = Qd.qd.black, color = (0, 0, 0)):
390 Widget.__init__(self, possize)
391 self._framepattern = pattern
392 self._framecolor = color
394 def setcolor(self, color):
395 self._framecolor = color
396 self.SetPort()
397 self.draw()
399 def setpattern(self, pattern):
400 self._framepattern = pattern
401 self.SetPort()
402 self.draw()
404 def draw(self, visRgn = None):
405 if self._visible:
406 penstate = Qd.GetPenState()
407 Qd.PenPat(self._framepattern)
408 Qd.RGBForeColor(self._framecolor)
409 Qd.FrameRect(self._bounds)
410 Qd.RGBForeColor((0, 0, 0))
411 Qd.SetPenState(penstate)
413 def _darkencolor((r, g, b)):
414 return 0.75 * r, 0.75 * g, 0.75 * b
416 class BevelBox(Widget):
418 """'Platinum' beveled rectangle."""
420 def __init__(self, possize, color = (0xe000, 0xe000, 0xe000)):
421 Widget.__init__(self, possize)
422 self._color = color
423 self._darkercolor = _darkencolor(color)
425 def setcolor(self, color):
426 self._color = color
427 self.SetPort()
428 self.draw()
430 def draw(self, visRgn = None):
431 if self._visible:
432 l, t, r, b = Qd.InsetRect(self._bounds, 1, 1)
433 Qd.RGBForeColor(self._color)
434 Qd.PaintRect((l, t, r, b))
435 Qd.RGBForeColor(self._darkercolor)
436 Qd.MoveTo(l, b)
437 Qd.LineTo(r, b)
438 Qd.LineTo(r, t)
439 Qd.RGBForeColor((0, 0, 0))
442 class Group(Widget):
444 """A container for subwidgets"""
447 class HorizontalPanes(Widget):
449 """Panes, a.k.a. frames. Works a bit like a group. Devides the widget area into "panes",
450 which can be resized by the user by clicking and dragging between the subwidgets."""
452 _direction = 1
454 def __init__(self, possize, panesizes = None, gutter = 8):
455 """panesizes should be a tuple of numbers. The length of the tuple is the number of panes,
456 the items in the tuple are the relative sizes of these panes; these numbers should add up
457 to 1 (the total size of all panes)."""
458 ClickableWidget.__init__(self, possize)
459 self._panesizes = panesizes
460 self._gutter = gutter
461 self._enabled = 1
462 self.setuppanes()
464 #def open(self):
465 # self.installbounds()
466 # ClickableWidget.open(self)
468 def _calcbounds(self):
469 # hmmm. It should not neccesary be override _calcbounds :-(
470 self.installbounds()
471 Widget._calcbounds(self)
473 def setuppanes(self):
474 panesizes = self._panesizes
475 total = 0
476 if panesizes is not None:
477 #if len(self._widgets) <> len(panesizes):
478 # raise TypeError, 'number of widgets does not match number of panes'
479 for panesize in panesizes:
480 if not 0 < panesize < 1:
481 raise TypeError, 'pane sizes must be between 0 and 1, not including.'
482 total = total + panesize
483 if round(total, 4) <> 1.0:
484 raise TypeError, 'pane sizes must add up to 1'
485 else:
486 # XXX does not work!
487 step = 1.0 / len(self._widgets)
488 panesizes = []
489 for i in range(len(self._widgets)):
490 panesizes.append(step)
491 current = 0
492 self._panesizes = []
493 self._gutters = []
494 for panesize in panesizes:
495 if current:
496 self._gutters.append(current)
497 self._panesizes.append((current, current + panesize))
498 current = current + panesize
499 self.makepanebounds()
501 def getpanesizes(self):
502 return map(lambda (fr, to): to-fr, self._panesizes)
504 boundstemplate = "lambda width, height: (0, height * %s + %d, width, height * %s + %d)"
506 def makepanebounds(self):
507 halfgutter = self._gutter / 2
508 self._panebounds = []
509 for i in range(len(self._panesizes)):
510 panestart, paneend = self._panesizes[i]
511 boundsstring = self.boundstemplate % (`panestart`, panestart and halfgutter,
512 `paneend`, (paneend <> 1.0) and -halfgutter)
513 self._panebounds.append(eval(boundsstring))
515 def installbounds(self):
516 #self.setuppanes()
517 for i in range(len(self._widgets)):
518 w = self._widgets[i]
519 w._possize = self._panebounds[i]
520 #if hasattr(w, "setuppanes"):
521 # w.setuppanes()
522 if hasattr(w, "installbounds"):
523 w.installbounds()
525 def rollover(self, point, onoff):
526 if onoff:
527 orgmouse = point[self._direction]
528 halfgutter = self._gutter / 2
529 l, t, r, b = self._bounds
530 if self._direction:
531 begin, end = t, b
532 else:
533 begin, end = l, r
535 i = self.findgutter(orgmouse, begin, end)
536 if i is None:
537 SetCursor("arrow")
538 else:
539 SetCursor(self._direction and 'vmover' or 'hmover')
541 def findgutter(self, orgmouse, begin, end):
542 tolerance = max(4, self._gutter) / 2
543 for i in range(len(self._gutters)):
544 pos = begin + (end - begin) * self._gutters[i]
545 if abs(orgmouse - pos) <= tolerance:
546 break
547 else:
548 return
549 return i
551 def click(self, point, modifiers):
552 # what a mess...
553 orgmouse = point[self._direction]
554 halfgutter = self._gutter / 2
555 l, t, r, b = self._bounds
556 if self._direction:
557 begin, end = t, b
558 else:
559 begin, end = l, r
561 i = self.findgutter(orgmouse, begin, end)
562 if i is None:
563 return
565 pos = orgpos = begin + (end - begin) * self._gutters[i] # init pos too, for fast click on border, bug done by Petr
567 minpos = self._panesizes[i][0]
568 maxpos = self._panesizes[i+1][1]
569 minpos = begin + (end - begin) * minpos + 64
570 maxpos = begin + (end - begin) * maxpos - 64
571 if minpos > orgpos and maxpos < orgpos:
572 return
574 #SetCursor("fist")
575 self.SetPort()
576 if self._direction:
577 rect = l, orgpos - 1, r, orgpos
578 else:
579 rect = orgpos - 1, t, orgpos, b
581 # track mouse --- XXX move to separate method?
582 Qd.PenMode(QuickDraw.srcXor)
583 Qd.PenPat(Qd.qd.gray)
584 Qd.PaintRect(rect)
585 lastpos = None
586 while Evt.Button():
587 pos = orgpos - orgmouse + Evt.GetMouse()[self._direction]
588 pos = max(pos, minpos)
589 pos = min(pos, maxpos)
590 if pos == lastpos:
591 continue
592 Qd.PenPat(Qd.qd.gray)
593 Qd.PaintRect(rect)
594 if self._direction:
595 rect = l, pos - 1, r, pos
596 else:
597 rect = pos - 1, t, pos, b
598 Qd.PenPat(Qd.qd.gray)
599 Qd.PaintRect(rect)
600 lastpos = pos
601 Qd.PaintRect(rect)
602 Qd.PenNormal()
603 SetCursor("watch")
605 newpos = (pos - begin) / float(end - begin)
606 self._gutters[i] = newpos
607 self._panesizes[i] = self._panesizes[i][0], newpos
608 self._panesizes[i+1] = newpos, self._panesizes[i+1][1]
609 self.makepanebounds()
610 self.installbounds()
611 self._calcbounds()
614 class VerticalPanes(HorizontalPanes):
615 """see HorizontalPanes"""
616 _direction = 0
617 boundstemplate = "lambda width, height: (width * %s + %d, 0, width * %s + %d, height)"
620 class ColorPicker(ClickableWidget):
622 """Color picker widget. Allows the user to choose a color."""
624 def __init__(self, possize, color = (0, 0, 0), callback = None):
625 ClickableWidget.__init__(self, possize)
626 self._color = color
627 self._callback = callback
628 self._enabled = 1
630 def click(self, point, modifiers):
631 if not self._enabled:
632 return
633 import ColorPicker
634 newcolor, ok = ColorPicker.GetColor("", self._color)
635 if ok:
636 self._color = newcolor
637 self.SetPort()
638 self.draw()
639 if self._callback:
640 return CallbackCall(self._callback, 0, self._color)
642 def set(self, color):
643 self._color = color
644 self.SetPort()
645 self.draw()
647 def get(self):
648 return self._color
650 def draw(self, visRgn=None):
651 if self._visible:
652 if not visRgn:
653 visRgn = self._parentwindow.wid.GetWindowPort().visRgn
654 Qd.PenPat(Qd.qd.gray)
655 rect = self._bounds
656 Qd.FrameRect(rect)
657 rect = Qd.InsetRect(rect, 3, 3)
658 Qd.PenNormal()
659 Qd.RGBForeColor(self._color)
660 Qd.PaintRect(rect)
661 Qd.RGBForeColor((0, 0, 0))
664 # misc utils
666 def CallbackCall(callback, mustfit, *args):
667 """internal helper routine for W"""
668 # XXX this function should die.
669 if type(callback) == FunctionType:
670 func = callback
671 maxargs = func.func_code.co_argcount
672 elif type(callback) == MethodType:
673 func = callback.im_func
674 maxargs = func.func_code.co_argcount - 1
675 else:
676 if callable(callback):
677 return apply(callback, args)
678 else:
679 raise TypeError, "uncallable callback object"
681 if func.func_defaults:
682 minargs = maxargs - len(func.func_defaults)
683 else:
684 minargs = maxargs
685 if minargs <= len(args) <= maxargs:
686 return apply(callback, args)
687 elif not mustfit and minargs == 0:
688 return callback()
689 else:
690 if mustfit:
691 raise TypeError, "callback accepts wrong number of arguments: " + `len(args)`
692 else:
693 raise TypeError, "callback accepts wrong number of arguments: 0 or " + `len(args)`
696 def HasBaseClass(obj, class_):
697 try:
698 raise obj
699 except class_:
700 return 1
701 except:
702 pass
703 return 0
706 _cursors = {
707 "watch" : Qd.GetCursor(QuickDraw.watchCursor).data,
708 "arrow" : Qd.qd.arrow,
709 "iBeam" : Qd.GetCursor(QuickDraw.iBeamCursor).data,
710 "cross" : Qd.GetCursor(QuickDraw.crossCursor).data,
711 "plus" : Qd.GetCursor(QuickDraw.plusCursor).data,
712 "hand" : Qd.GetCursor(468).data,
713 "fist" : Qd.GetCursor(469).data,
714 "hmover" : Qd.GetCursor(470).data,
715 "vmover" : Qd.GetCursor(471).data,
716 "zoomin" : Qd.GetCursor(472).data,
717 "zoomout" : Qd.GetCursor(473).data,
718 "zoom" : Qd.GetCursor(474).data,
721 def SetCursor(what):
722 """Set the cursorshape to any of these: 'arrow', 'cross', 'fist', 'hand', 'hmover', 'iBeam',
723 'plus', 'vmover', 'watch', 'zoom', 'zoomin', 'zoomout'."""
724 Qd.SetCursor(_cursors[what])