9 WidgetsError
= "WidgetsError"
15 """Base class for all widgets."""
19 def __init__(self
, possize
):
21 self
._widgetsdict
= {}
22 self
._possize
= possize
30 self
._parentwindow
= None
32 self
._backcolor
= None
34 def show(self
, onoff
):
36 for w
in self
._widgets
:
38 if self
._parentwindow
is not None and self
._parentwindow
.wid
is not None:
43 Qd
.EraseRect(self
._bounds
)
45 def draw(self
, visRgn
= None):
47 # draw your stuff here
56 def move(self
, x
, y
= None):
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):
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
):
76 if type(args
[0]) == FunctionType
or type(args
[0]) == MethodType
:
77 self
._possize
= args
[0]
79 apply(self
.resize
, args
[0])
81 self
._possize
= (0, 0) + args
85 raise TypeError, "wrong number of arguments"
96 del self
._parentwindow
98 def bind(self
, key
, callback
):
99 """bind a key or an 'event' to a callback"""
101 self
._bindings
[key
] = callback
102 elif self
._bindings
.has_key(key
):
103 del self
._bindings
[key
]
105 def adjust(self
, oldbounds
):
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.
120 self
._bounds
= Qd
.OffsetRect(self
._possize
(width
, height
), pl
, pt
)
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:
129 # l is less than -1, this mean it measures from the *right* of it's parent
132 # l is -1 or greater, this mean it measures from the *left* of it's parent
135 # t is less than -1, this mean it measures from the *bottom* of it's parent
138 # t is -1 or greater, this mean it measures from the *top* of it's parent
141 # r is greater than 1, this means r is the *width* of the widget
144 # r is less than 1, this means it measures from the *right* of it's parent
147 # b is greater than 1, this means b is the *height* of the widget
150 # b is less than 1, this means it measures from the *bottom* of it's parent
152 self
._bounds
= (l
, t
, r
, b
)
153 if oldbounds
and oldbounds
<> self
._bounds
:
154 self
.adjust(oldbounds
)
155 for w
in self
._widgets
:
158 def test(self
, point
):
159 if Qd
.PtInRect(point
, self
._bounds
):
162 def click(self
, point
, modifiers
):
165 def findwidget(self
, point
, onlyenabled
= 1):
167 for w
in self
._widgets
:
168 widget
= w
.findwidget(point
)
169 if widget
is not None:
171 if self
._enabled
or not onlyenabled
:
174 def forall(self
, methodname
, *args
):
175 for w
in self
._widgets
:
176 rv
= apply(w
.forall
, (methodname
,) + args
)
179 if self
._bindings
.has_key("<" + methodname
+ ">"):
180 callback
= self
._bindings
["<" + methodname
+ ">"]
181 rv
= apply(callback
, args
)
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
)
194 def forall_frombottom(self
, methodname
, *args
):
195 if self
._bindings
.has_key("<" + methodname
+ ">"):
196 callback
= self
._bindings
["<" + methodname
+ ">"]
197 rv
= apply(callback
, args
)
200 if hasattr(self
, methodname
):
201 method
= getattr(self
, methodname
)
202 rv
= apply(method
, args
)
205 for w
in self
._widgets
:
206 rv
= apply(w
.forall_frombottom
, (methodname
,) + args
)
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
:
236 self
._parentwindow
._currentwidget
= None
238 self
.GetWindow().InvalWindowRect(widget
._bounds
)
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
]
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
):
270 return self
._widgetsdict
[key
]
272 def __delitem__(self
, key
):
273 self
._removewidget
(key
)
276 self
._parentwindow
.SetPort()
280 return self
._parentwindow
.GetWindow()
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
):
297 def enable(self
, onoff
):
298 self
._enabled
= onoff
304 return CallbackCall(self
._callback
, 1)
307 class SelectableWidget(ClickableWidget
):
309 """Base class for selectable widgets."""
313 def select(self
, onoff
, isclick
= 0):
314 if onoff
== self
._selected
:
316 if self
._bindings
.has_key("<select>"):
317 callback
= self
._bindings
["<select>"]
320 self
._selected
= onoff
322 if self
._parentwindow
._currentwidget
is not None:
323 self
._parentwindow
._currentwidget
.select(0)
324 self
._parentwindow
._currentwidget
= self
326 self
._parentwindow
._currentwidget
= None
328 def key(self
, char
, event
):
331 def drawselframe(self
, onoff
):
332 if not self
._parentwindow
._hasselframes
:
334 thickrect
= Qd
.InsetRect(self
._bounds
, -3, -3)
335 state
= Qd
.GetPenState()
338 Qd
.PenPat(Qd
.qd
.black
)
340 Qd
.PenPat(Qd
.qd
.white
)
341 Qd
.FrameRect(thickrect
)
342 Qd
.SetPenState(state
)
344 def adjust(self
, oldbounds
):
347 self
.GetWindow().InvalWindowRect(Qd
.InsetRect(oldbounds
, -3, -3))
348 self
.GetWindow().InvalWindowRect(Qd
.InsetRect(self
._bounds
, -3, -3))
350 self
.GetWindow().InvalWindowRect(oldbounds
)
351 self
.GetWindow().InvalWindowRect(self
._bounds
)
356 def __init__(self
, possize
, thickness
= 1):
357 Widget
.__init
__(self
, possize
)
358 self
._thickness
= thickness
365 def draw(self
, visRgn
= None):
367 Qd
.PaintRect(self
._bounds
)
369 def _drawbounds(self
):
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
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
399 def setpattern(self
, pattern
):
400 self
._framepattern
= pattern
404 def draw(self
, visRgn
= None):
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
)
423 self
._darkercolor
= _darkencolor(color
)
425 def setcolor(self
, color
):
430 def draw(self
, visRgn
= None):
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
)
439 Qd
.RGBForeColor((0, 0, 0))
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."""
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
465 # self.installbounds()
466 # ClickableWidget.open(self)
468 def _calcbounds(self
):
469 # hmmm. It should not neccesary be override _calcbounds :-(
471 Widget
._calcbounds
(self
)
473 def setuppanes(self
):
474 panesizes
= self
._panesizes
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'
487 step
= 1.0 / len(self
._widgets
)
489 for i
in range(len(self
._widgets
)):
490 panesizes
.append(step
)
494 for panesize
in panesizes
:
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
):
517 for i
in range(len(self
._widgets
)):
519 w
._possize
= self
._panebounds
[i
]
520 #if hasattr(w, "setuppanes"):
522 if hasattr(w
, "installbounds"):
525 def rollover(self
, point
, onoff
):
527 orgmouse
= point
[self
._direction
]
528 halfgutter
= self
._gutter
/ 2
529 l
, t
, r
, b
= self
._bounds
535 i
= self
.findgutter(orgmouse
, begin
, end
)
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
:
551 def click(self
, point
, modifiers
):
553 orgmouse
= point
[self
._direction
]
554 halfgutter
= self
._gutter
/ 2
555 l
, t
, r
, b
= self
._bounds
561 i
= self
.findgutter(orgmouse
, begin
, end
)
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
:
577 rect
= l
, orgpos
- 1, r
, orgpos
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
)
587 pos
= orgpos
- orgmouse
+ Evt
.GetMouse()[self
._direction
]
588 pos
= max(pos
, minpos
)
589 pos
= min(pos
, maxpos
)
592 Qd
.PenPat(Qd
.qd
.gray
)
595 rect
= l
, pos
- 1, r
, pos
597 rect
= pos
- 1, t
, pos
, b
598 Qd
.PenPat(Qd
.qd
.gray
)
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()
614 class VerticalPanes(HorizontalPanes
):
615 """see HorizontalPanes"""
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
)
627 self
._callback
= callback
630 def click(self
, point
, modifiers
):
631 if not self
._enabled
:
634 newcolor
, ok
= ColorPicker
.GetColor("", self
._color
)
636 self
._color
= newcolor
640 return CallbackCall(self
._callback
, 0, self
._color
)
642 def set(self
, color
):
650 def draw(self
, visRgn
=None):
653 visRgn
= self
._parentwindow
.wid
.GetWindowPort().visRgn
654 Qd
.PenPat(Qd
.qd
.gray
)
657 rect
= Qd
.InsetRect(rect
, 3, 3)
659 Qd
.RGBForeColor(self
._color
)
661 Qd
.RGBForeColor((0, 0, 0))
666 def CallbackCall(callback
, mustfit
, *args
):
667 """internal helper routine for W"""
668 # XXX this function should die.
669 if type(callback
) == FunctionType
:
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
676 if callable(callback
):
677 return apply(callback
, args
)
679 raise TypeError, "uncallable callback object"
681 if func
.func_defaults
:
682 minargs
= maxargs
- len(func
.func_defaults
)
685 if minargs
<= len(args
) <= maxargs
:
686 return apply(callback
, args
)
687 elif not mustfit
and minargs
== 0:
691 raise TypeError, "callback accepts wrong number of arguments: " + `
len(args
)`
693 raise TypeError, "callback accepts wrong number of arguments: 0 or " + `
len(args
)`
696 def HasBaseClass(obj
, class_
):
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
,
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
])