Clarify portability and main program.
[python/dscho.git] / Mac / Contrib / PyIDE-src / IDELib / Widgets / Wbase.py
blobbcd778999070e5b5bb7011eafadf435cfc739a15
1 import Qd
2 import Win
3 import QuickDraw
4 import Evt
5 import string
6 from types import *
7 from SpecialKeys import *
8 import sys
10 WidgetsError = "WidgetsError"
12 DEBUG = 0
14 class Widget:
16 _selectable = 0
18 def __init__(self, possize):
19 self._widgets = []
20 self._widgetsdict = {}
21 self._possize = possize
22 self._bounds = None
23 self._visible = 1
24 self._enabled = 0
25 self._selected = 0
26 self._activated = 0
27 self._callback = None
28 self._parent = None
29 self._parentwindow = None
30 self._bindings = {}
31 self._backcolor = None
33 def show(self, onoff):
34 self.SetPort()
35 self._visible = onoff
36 print 'Background'
37 if self._visible and self._backcolor:
38 penstate = Qd.GetPenState()
39 Qd.RGBForeColor(self._backcolor)
40 Qd.FrameRect(self._bounds)
41 Qd.RGBForeColor((0, 0, 0))
42 Qd.SetPenState(penstate)
44 for w in self._widgets:
45 w.show(onoff)
46 if onoff:
47 self.draw()
48 else:
49 Qd.EraseRect(self._bounds)
51 def draw(self, visRgn = None):
52 if self._visible:
53 # draw your stuff here
54 pass
56 def getpossize(self):
57 return self._possize
59 def getbounds(self):
60 return self._bounds
62 def move(self, x, y = None):
63 """absolute move"""
64 if y == None:
65 x, y = x
66 if type(self._possize) <> TupleType:
67 raise WidgetsError, "can't move widget with bounds function"
68 l, t, r, b = self._possize
69 self.resize(x, y, r, b)
71 def rmove(self, x, y = None):
72 """relative move"""
73 if y == None:
74 x, y = x
75 if type(self._possize) <> TupleType:
76 raise WidgetsError, "can't move widget with bounds function"
77 l, t, r, b = self._possize
78 self.resize(l + x, t + y, r, b)
80 def resize(self, *args):
81 #print "yep.", args
82 if len(args) == 1:
83 if type(args[0]) == FunctionType or type(args[0]) == MethodType:
84 self._possize = args[0]
85 else:
86 apply(self.resize, args[0])
87 elif len(args) == 2:
88 self._possize = (0, 0) + args
89 elif len(args) == 4:
90 self._possize = args
91 else:
92 raise TypeError, "wrong number of arguments"
93 self._calcbounds()
95 def open(self):
96 self._calcbounds()
98 def close(self):
99 #print "xxx Closing Widget"
100 del self._callback
101 del self._possize
102 del self._bindings
103 del self._parent
104 del self._parentwindow
106 def bind(self, key, callback):
107 """bind a key or an 'event' to a callback"""
108 if callback:
109 self._bindings[key] = callback
110 elif self._bindings.has_key(key):
111 del self._bindings[key]
113 def adjust(self, oldbounds):
114 self.SetPort()
115 Win.InvalRect(oldbounds)
116 Win.InvalRect(self._bounds)
118 def _calcbounds(self):
119 oldbounds = self._bounds
120 pl, pt, pr, pb = self._parent._bounds
121 if callable(self._possize):
122 width = pr - pl
123 height = pb - pt
124 self._bounds = Qd.OffsetRect(self._possize(width, height), pl, pt)
125 else:
126 l, t, r, b = self._possize
127 if l < -1:
128 l = pr + l
129 else:
130 l = pl + l
131 if t < -1:
132 t = pb + t
133 else:
134 t = pt + t
135 if r > 1:
136 r = l + r
137 else:
138 r = pr + r
139 if b > 1:
140 b = t + b
141 else:
142 b = pb + b
143 self._bounds = (l, t, r, b)
144 if oldbounds and oldbounds <> self._bounds:
145 self.adjust(oldbounds)
146 for w in self._widgets:
147 w._calcbounds()
149 def test(self, point):
150 if Qd.PtInRect(point, self._bounds):
151 return 1
153 def click(self, point, modifiers):
154 pass
156 def findwidget(self, point, onlyenabled = 1):
157 if self.test(point):
158 for w in self._widgets:
159 widget = w.findwidget(point)
160 if widget is not None:
161 return widget
162 if self._enabled or not onlyenabled:
163 return self
165 def forall(self, methodname, *args):
166 for w in self._widgets:
167 rv = apply(w.forall, (methodname,) + args)
168 if rv:
169 return rv
170 if self._bindings.has_key("<" + methodname + ">"):
171 callback = self._bindings["<" + methodname + ">"]
172 rv = apply(callback, args)
173 if rv:
174 return rv
175 if hasattr(self, methodname):
176 method = getattr(self, methodname)
177 return apply(method, args)
179 def forall_butself(self, methodname, *args):
180 for w in self._widgets:
181 rv = apply(w.forall, (methodname,) + args)
182 if rv:
183 return rv
185 def forall_frombottom(self, methodname, *args):
186 if self._bindings.has_key("<" + methodname + ">"):
187 callback = self._bindings["<" + methodname + ">"]
188 rv = apply(callback, args)
189 if rv:
190 return rv
191 if hasattr(self, methodname):
192 method = getattr(self, methodname)
193 rv = apply(method, args)
194 if rv:
195 return rv
196 for w in self._widgets:
197 rv = apply(w.forall_frombottom, (methodname,) + args)
198 if rv:
199 return rv
201 def _addwidget(self, key, widget):
202 if widget in self._widgets:
203 raise ValueError, "duplicate widget"
204 if self._widgetsdict.has_key(key):
205 self._removewidget(key)
206 self._widgets.append(widget)
207 self._widgetsdict[key] = widget
208 widget._parent = self
209 self._setparentwindow(widget)
210 if self._parentwindow and self._parentwindow.wid:
211 widget.forall_frombottom("open")
212 Win.InvalRect(widget._bounds)
214 def _setparentwindow(self, widget):
215 widget._parentwindow = self._parentwindow
216 for w in widget._widgets:
217 self._setparentwindow(w)
219 def _removewidget(self, key):
220 if not self._widgetsdict.has_key(key):
221 raise KeyError, "no widget with key " + `key`
222 widget = self._widgetsdict[key]
223 for k in widget._widgetsdict.keys():
224 widget._removewidget(k)
225 if self._parentwindow._currentwidget == widget:
226 widget.select(0)
227 self._parentwindow._currentwidget = None
228 self.SetPort()
229 Win.InvalRect(widget._bounds)
230 widget.close()
231 del self._widgetsdict[key]
232 self._widgets.remove(widget)
234 def __setattr__(self, attr, value):
235 if type(value) == InstanceType and HasBaseClass(value, Widget) and \
236 attr not in ("_currentwidget", "_lastrollover",
237 "_parent", "_parentwindow", "_defaultbutton"):
238 if hasattr(self, attr):
239 raise ValueError, "Can't replace existing attribute: " + attr
240 self._addwidget(attr, value)
241 self.__dict__[attr] = value
243 def __delattr__(self, attr):
244 if attr == "_widgetsdict":
245 raise AttributeError, "cannot delete attribute _widgetsdict"
246 if self._widgetsdict.has_key(attr):
247 self._removewidget(attr)
248 if self.__dict__.has_key(attr):
249 del self.__dict__[attr]
250 elif self.__dict__.has_key(attr):
251 del self.__dict__[attr]
252 else:
253 raise AttributeError, attr
255 def __setitem__(self, key, value):
256 self._addwidget(key, value)
258 def __getitem__(self, key):
259 if not self._widgetsdict.has_key(key):
260 raise KeyError, key
261 return self._widgetsdict[key]
263 def __delitem__(self, key):
264 self._removewidget(key)
266 def SetPort(self):
267 self._parentwindow.SetPort()
269 def __del__(self):
270 if DEBUG:
271 print "%s instance deleted" % self.__class__.__name__
273 def _drawbounds(self):
274 Qd.FrameRect(self._bounds)
277 class ClickableWidget(Widget):
279 def click(self, point, modifiers):
280 pass
282 def enable(self, onoff):
283 self._enabled = onoff
284 self.SetPort()
285 self.draw()
287 def callback(self):
288 if self._callback:
289 return CallbackCall(self._callback, 1)
292 class SelectableWidget(ClickableWidget):
294 _selectable = 1
296 def select(self, onoff, isclick = 0):
297 if onoff == self._selected:
298 return 1
299 if self._bindings.has_key("<select>"):
300 callback = self._bindings["<select>"]
301 if callback(onoff):
302 return 1
303 self._selected = onoff
304 if onoff:
305 if self._parentwindow._currentwidget is not None:
306 self._parentwindow._currentwidget.select(0)
307 self._parentwindow._currentwidget = self
308 else:
309 self._parentwindow._currentwidget = None
311 def key(self, char, event):
312 pass
314 def drawselframe(self, onoff):
315 if not self._parentwindow._hasselframes:
316 return
317 thickrect = Qd.InsetRect(self._bounds, -3, -3)
318 state = Qd.GetPenState()
319 Qd.PenSize(2, 2)
320 if onoff:
321 Qd.PenPat(Qd.qd.black)
322 else:
323 Qd.PenPat(Qd.qd.white)
324 Qd.FrameRect(thickrect)
325 Qd.SetPenState(state)
327 def adjust(self, oldbounds):
328 self.SetPort()
329 if self._selected:
330 Win.InvalRect(Qd.InsetRect(oldbounds, -3, -3))
331 Win.InvalRect(Qd.InsetRect(self._bounds, -3, -3))
332 else:
333 Win.InvalRect(oldbounds)
334 Win.InvalRect(self._bounds)
337 class _Line(Widget):
339 def __init__(self, possize, thickness = 1):
340 Widget.__init__(self, possize)
341 self._thickness = thickness
343 def open(self):
344 self._calcbounds()
345 self.SetPort()
346 self.draw()
348 def draw(self, visRgn = None):
349 if self._visible:
350 Qd.PaintRect(self._bounds)
352 def _drawbounds(self):
353 pass
355 class HorizontalLine(_Line):
357 def _calcbounds(self):
358 Widget._calcbounds(self)
359 l, t, r, b = self._bounds
360 self._bounds = l, t, r, t + self._thickness
362 class VerticalLine(_Line):
364 def _calcbounds(self):
365 Widget._calcbounds(self)
366 l, t, r, b = self._bounds
367 self._bounds = l, t, l + self._thickness, b
370 class Frame(Widget):
372 def __init__(self, possize, pattern = Qd.qd.black, color = (0, 0, 0)):
373 Widget.__init__(self, possize)
374 self._framepattern = pattern
375 self._framecolor = color
377 def setcolor(self, color):
378 self._framecolor = color
379 self.draw()
381 def setpattern(self, pattern):
382 self._framepattern = pattern
383 self.draw()
385 def draw(self, visRgn = None):
386 if self._visible:
387 penstate = Qd.GetPenState()
388 Qd.PenPat(self._framepattern)
389 Qd.RGBForeColor(self._framecolor)
390 Qd.FrameRect(self._bounds)
391 Qd.RGBForeColor((0, 0, 0))
392 Qd.SetPenState(penstate)
395 class Group(Widget): pass
398 class HorizontalPanes(Widget):
400 _direction = 1
402 def __init__(self, possize, panesizes = None, gutter = 8):
403 ClickableWidget.__init__(self, possize)
404 self._panesizes = panesizes
405 self._gutter = gutter
406 self._enabled = 1
407 self.setuppanes()
409 def open(self):
410 self.installbounds()
411 ClickableWidget.open(self)
413 def setuppanes(self):
414 panesizes = self._panesizes
415 total = 0
416 if panesizes is not None:
417 #if len(self._widgets) <> len(panesizes):
418 # raise TypeError, 'number of widgets does not match number of panes'
419 for panesize in panesizes:
420 if not 0 < panesize < 1:
421 raise TypeError, 'pane sizes must be between 0 and 1, not including.'
422 total = total + panesize
423 if round(total, 4) <> 1.0:
424 raise TypeError, 'pane sizes must add up to 1'
425 else:
426 step = 1.0 / len(self._widgets)
427 panesizes = []
428 for i in range(len(self._widgets)):
429 panesizes.append(step)
430 current = 0
431 self._panesizes = []
432 self._gutters = []
433 for panesize in panesizes:
434 if current:
435 self._gutters.append(current)
436 self._panesizes.append(current, current + panesize)
437 current = current + panesize
438 self.makepanebounds()
440 def getpanesizes(self):
441 return map(lambda (fr, to): to-fr, self._panesizes)
443 boundstemplate = "lambda width, height: (0, height * %s + %d, width, height * %s + %d)"
445 def makepanebounds(self):
446 halfgutter = self._gutter / 2
447 self._panebounds = []
448 for i in range(len(self._panesizes)):
449 panestart, paneend = self._panesizes[i]
450 boundsstring = self.boundstemplate % (`panestart`, panestart and halfgutter,
451 `paneend`, (paneend <> 1.0) and -halfgutter)
452 self._panebounds.append(eval(boundsstring))
454 def installbounds(self):
455 #self.setuppanes()
456 for i in range(len(self._widgets)):
457 w = self._widgets[i]
458 w._possize = self._panebounds[i]
459 #if hasattr(w, "setuppanes"):
460 # w.setuppanes()
461 if hasattr(w, "installbounds"):
462 w.installbounds()
464 def rollover(self, point, onoff):
465 if onoff:
466 orgmouse = point[self._direction]
467 halfgutter = self._gutter / 2
468 l, t, r, b = self._bounds
469 if self._direction:
470 begin, end = t, b
471 else:
472 begin, end = l, r
474 i = self.findgutter(orgmouse, begin, end)
475 if i is None:
476 SetCursor("arrow")
477 else:
478 SetCursor(self._direction and 'vmover' or 'hmover')
480 def findgutter(self, orgmouse, begin, end):
481 tolerance = max(4, self._gutter) / 2
482 for i in range(len(self._gutters)):
483 pos = begin + (end - begin) * self._gutters[i]
484 if abs(orgmouse - pos) <= tolerance:
485 break
486 else:
487 return
488 return i
490 def click(self, point, modifiers):
491 # what a mess...
492 orgmouse = point[self._direction]
493 halfgutter = self._gutter / 2
494 l, t, r, b = self._bounds
495 if self._direction:
496 begin, end = t, b
497 else:
498 begin, end = l, r
500 i = self.findgutter(orgmouse, begin, end)
501 if i is None:
502 return
504 pos = orgpos = begin + (end - begin) * self._gutters[i] # init pos too, for fast click on border, bug done by Petr
506 minpos = self._panesizes[i][0]
507 maxpos = self._panesizes[i+1][1]
508 minpos = begin + (end - begin) * minpos + 64
509 maxpos = begin + (end - begin) * maxpos - 64
510 if minpos > orgpos and maxpos < orgpos:
511 return
513 #SetCursor("fist")
514 self.SetPort()
515 if self._direction:
516 rect = l, orgpos - 1, r, orgpos
517 else:
518 rect = orgpos - 1, t, orgpos, b
520 # track mouse --- XXX move to separate method?
521 Qd.PenMode(QuickDraw.srcXor)
522 Qd.PenPat(Qd.qd.gray)
523 Qd.PaintRect(rect)
524 lastpos = None
525 while Evt.Button():
526 pos = orgpos - orgmouse + Evt.GetMouse()[self._direction]
527 pos = max(pos, minpos)
528 pos = min(pos, maxpos)
529 if pos == lastpos:
530 continue
531 Qd.PenPat(Qd.qd.gray)
532 Qd.PaintRect(rect)
533 if self._direction:
534 rect = l, pos - 1, r, pos
535 else:
536 rect = pos - 1, t, pos, b
537 Qd.PenPat(Qd.qd.gray)
538 Qd.PaintRect(rect)
539 lastpos = pos
540 Qd.PaintRect(rect)
541 Qd.PenNormal()
542 SetCursor("watch")
544 newpos = (pos - begin) / float(end - begin)
545 self._gutters[i] = newpos
546 self._panesizes[i] = self._panesizes[i][0], newpos
547 self._panesizes[i+1] = newpos, self._panesizes[i+1][1]
548 self.makepanebounds()
549 self.installbounds()
550 self._calcbounds()
553 class VerticalPanes(HorizontalPanes):
555 _direction = 0
556 boundstemplate = "lambda width, height: (width * %s + %d, 0, width * %s + %d, height)"
559 # misc utils
561 def CallbackCall(callback, mustfit, *args):
562 if type(callback) == FunctionType:
563 func = callback
564 maxargs = func.func_code.co_argcount
565 elif type(callback) == MethodType:
566 func = callback.im_func
567 maxargs = func.func_code.co_argcount - 1
568 else:
569 if callable(callback):
570 return apply(callback, args)
571 else:
572 raise TypeError, "uncallable callback object"
574 if func.func_defaults:
575 minargs = maxargs - len(func.func_defaults)
576 else:
577 minargs = maxargs
578 if minargs <= len(args) <= maxargs:
579 return apply(callback, args)
580 elif not mustfit and minargs == 0:
581 return callback()
582 else:
583 if mustfit:
584 raise TypeError, "callback accepts wrong number of arguments: " + `len(args)`
585 else:
586 raise TypeError, "callback accepts wrong number of arguments: 0 or " + `len(args)`
589 def HasBaseClass(obj, class_):
590 try:
591 raise obj
592 except class_:
593 return 1
594 except:
595 pass
596 return 0
599 _cursors = {
600 "watch" : Qd.GetCursor(QuickDraw.watchCursor).data,
601 "arrow" : Qd.qd.arrow,
602 "iBeam" : Qd.GetCursor(QuickDraw.iBeamCursor).data,
603 "cross" : Qd.GetCursor(QuickDraw.crossCursor).data,
604 "plus" : Qd.GetCursor(QuickDraw.plusCursor).data,
605 "hand" : Qd.GetCursor(468).data,
606 "fist" : Qd.GetCursor(469).data,
607 "hmover" : Qd.GetCursor(470).data,
608 "vmover" : Qd.GetCursor(471).data
611 def SetCursor(what):
612 Qd.SetCursor(_cursors[what])