blob: 8e012def97fc7820c8cc2fce08df5bc949f57ed4 [file] [log] [blame]
from Carbon import Qd
from Carbon import Win
from Carbon import QuickDraw
from Carbon import Evt
import string
from types import *
import sys
WidgetsError = "WidgetsError"
DEBUG = 0
class Widget:
"""Base class for all widgets."""
_selectable = 0
def __init__(self, possize):
self._widgets = []
self._widgetsdict = {}
self._possize = possize
self._bounds = None
self._visible = 1
self._enabled = 0
self._selected = 0
self._activated = 0
self._callback = None
self._parent = None
self._parentwindow = None
self._bindings = {}
self._backcolor = None
def show(self, onoff):
self._visible = onoff
for w in self._widgets:
w.show(onoff)
if self._parentwindow is not None and self._parentwindow.wid is not None:
self.SetPort()
if onoff:
self.draw()
else:
Qd.EraseRect(self._bounds)
def draw(self, visRgn = None):
if self._visible:
# draw your stuff here
pass
def getpossize(self):
return self._possize
def getbounds(self):
return self._bounds
def move(self, x, y = None):
"""absolute move"""
if y == None:
x, y = x
if type(self._possize) <> TupleType:
raise WidgetsError, "can't move widget with bounds function"
l, t, r, b = self._possize
self.resize(x, y, r, b)
def rmove(self, x, y = None):
"""relative move"""
if y == None:
x, y = x
if type(self._possize) <> TupleType:
raise WidgetsError, "can't move widget with bounds function"
l, t, r, b = self._possize
self.resize(l + x, t + y, r, b)
def resize(self, *args):
if len(args) == 1:
if type(args[0]) == FunctionType or type(args[0]) == MethodType:
self._possize = args[0]
else:
apply(self.resize, args[0])
elif len(args) == 2:
self._possize = (0, 0) + args
elif len(args) == 4:
self._possize = args
else:
raise TypeError, "wrong number of arguments"
self._calcbounds()
def open(self):
self._calcbounds()
def close(self):
del self._callback
del self._possize
del self._bindings
del self._parent
del self._parentwindow
def bind(self, key, callback):
"""bind a key or an 'event' to a callback"""
if callback:
self._bindings[key] = callback
elif self._bindings.has_key(key):
del self._bindings[key]
def adjust(self, oldbounds):
self.SetPort()
self.GetWindow().InvalWindowRect(oldbounds)
self.GetWindow().InvalWindowRect(self._bounds)
def _calcbounds(self):
# calculate absolute bounds relative to the window origin from our
# abstract _possize attribute, which is either a 4-tuple or a callable object
oldbounds = self._bounds
pl, pt, pr, pb = self._parent._bounds
if callable(self._possize):
# _possize is callable, let it figure it out by itself: it should return
# the bounds relative to our parent widget.
width = pr - pl
height = pb - pt
self._bounds = Qd.OffsetRect(self._possize(width, height), pl, pt)
else:
# _possize must be a 4-tuple. This is where the algorithm by Peter Kriens and
# Petr van Blokland kicks in. (*** Parts of this algorithm are applied for
# patents by Ericsson, Sweden ***)
l, t, r, b = self._possize
# depending on the values of l(eft), t(op), r(right) and b(ottom),
# they mean different things:
if l < -1:
# l is less than -1, this mean it measures from the *right* of it's parent
l = pr + l
else:
# l is -1 or greater, this mean it measures from the *left* of it's parent
l = pl + l
if t < -1:
# t is less than -1, this mean it measures from the *bottom* of it's parent
t = pb + t
else:
# t is -1 or greater, this mean it measures from the *top* of it's parent
t = pt + t
if r > 1:
# r is greater than 1, this means r is the *width* of the widget
r = l + r
else:
# r is less than 1, this means it measures from the *right* of it's parent
r = pr + r
if b > 1:
# b is greater than 1, this means b is the *height* of the widget
b = t + b
else:
# b is less than 1, this means it measures from the *bottom* of it's parent
b = pb + b
self._bounds = (l, t, r, b)
if oldbounds and oldbounds <> self._bounds:
self.adjust(oldbounds)
for w in self._widgets:
w._calcbounds()
def test(self, point):
if Qd.PtInRect(point, self._bounds):
return 1
def click(self, point, modifiers):
pass
def findwidget(self, point, onlyenabled = 1):
if self.test(point):
for w in self._widgets:
widget = w.findwidget(point)
if widget is not None:
return widget
if self._enabled or not onlyenabled:
return self
def forall(self, methodname, *args):
for w in self._widgets:
rv = apply(w.forall, (methodname,) + args)
if rv:
return rv
if self._bindings.has_key("<" + methodname + ">"):
callback = self._bindings["<" + methodname + ">"]
rv = apply(callback, args)
if rv:
return rv
if hasattr(self, methodname):
method = getattr(self, methodname)
return apply(method, args)
def forall_butself(self, methodname, *args):
for w in self._widgets:
rv = apply(w.forall, (methodname,) + args)
if rv:
return rv
def forall_frombottom(self, methodname, *args):
if self._bindings.has_key("<" + methodname + ">"):
callback = self._bindings["<" + methodname + ">"]
rv = apply(callback, args)
if rv:
return rv
if hasattr(self, methodname):
method = getattr(self, methodname)
rv = apply(method, args)
if rv:
return rv
for w in self._widgets:
rv = apply(w.forall_frombottom, (methodname,) + args)
if rv:
return rv
def _addwidget(self, key, widget):
if widget in self._widgets:
raise ValueError, "duplicate widget"
if self._widgetsdict.has_key(key):
self._removewidget(key)
self._widgets.append(widget)
self._widgetsdict[key] = widget
widget._parent = self
self._setparentwindow(widget)
if self._parentwindow and self._parentwindow.wid:
widget.forall_frombottom("open")
self.GetWindow().InvalWindowRect(widget._bounds)
def _setparentwindow(self, widget):
widget._parentwindow = self._parentwindow
for w in widget._widgets:
self._setparentwindow(w)
def _removewidget(self, key):
if not self._widgetsdict.has_key(key):
raise KeyError, "no widget with key " + `key`
widget = self._widgetsdict[key]
for k in widget._widgetsdict.keys():
widget._removewidget(k)
if self._parentwindow._currentwidget == widget:
widget.select(0)
self._parentwindow._currentwidget = None
self.SetPort()
self.GetWindow().InvalWindowRect(widget._bounds)
widget.close()
del self._widgetsdict[key]
self._widgets.remove(widget)
def __setattr__(self, attr, value):
if type(value) == InstanceType and isinstance(value, Widget) and \
attr not in ("_currentwidget", "_lastrollover",
"_parent", "_parentwindow", "_defaultbutton"):
if hasattr(self, attr):
raise ValueError, "Can't replace existing attribute: " + attr
self._addwidget(attr, value)
self.__dict__[attr] = value
def __delattr__(self, attr):
if attr == "_widgetsdict":
raise AttributeError, "cannot delete attribute _widgetsdict"
if self._widgetsdict.has_key(attr):
self._removewidget(attr)
if self.__dict__.has_key(attr):
del self.__dict__[attr]
elif self.__dict__.has_key(attr):
del self.__dict__[attr]
else:
raise AttributeError, attr
def __setitem__(self, key, value):
self._addwidget(key, value)
def __getitem__(self, key):
if not self._widgetsdict.has_key(key):
raise KeyError, key
return self._widgetsdict[key]
def __delitem__(self, key):
self._removewidget(key)
def SetPort(self):
self._parentwindow.SetPort()
def GetWindow(self):
return self._parentwindow.GetWindow()
def __del__(self):
if DEBUG:
print "%s instance deleted" % self.__class__.__name__
def _drawbounds(self):
Qd.FrameRect(self._bounds)
class ClickableWidget(Widget):
"""Base class for clickable widgets. (note: self._enabled must be true to receive click events.)"""
def click(self, point, modifiers):
pass
def enable(self, onoff):
self._enabled = onoff
self.SetPort()
self.draw()
def callback(self):
if self._callback:
return CallbackCall(self._callback, 1)
class SelectableWidget(ClickableWidget):
"""Base class for selectable widgets."""
_selectable = 1
def select(self, onoff, isclick = 0):
if onoff == self._selected:
return 1
if self._bindings.has_key("<select>"):
callback = self._bindings["<select>"]
if callback(onoff):
return 1
self._selected = onoff
if onoff:
if self._parentwindow._currentwidget is not None:
self._parentwindow._currentwidget.select(0)
self._parentwindow._currentwidget = self
else:
self._parentwindow._currentwidget = None
def key(self, char, event):
pass
def drawselframe(self, onoff):
if not self._parentwindow._hasselframes:
return
thickrect = Qd.InsetRect(self._bounds, -3, -3)
state = Qd.GetPenState()
Qd.PenSize(2, 2)
if onoff:
Qd.PenPat(Qd.qd.black)
else:
Qd.PenPat(Qd.qd.white)
Qd.FrameRect(thickrect)
Qd.SetPenState(state)
def adjust(self, oldbounds):
self.SetPort()
if self._selected:
self.GetWindow().InvalWindowRect(Qd.InsetRect(oldbounds, -3, -3))
self.GetWindow().InvalWindowRect(Qd.InsetRect(self._bounds, -3, -3))
else:
self.GetWindow().InvalWindowRect(oldbounds)
self.GetWindow().InvalWindowRect(self._bounds)
class _Line(Widget):
def __init__(self, possize, thickness = 1):
Widget.__init__(self, possize)
self._thickness = thickness
def open(self):
self._calcbounds()
self.SetPort()
self.draw()
def draw(self, visRgn = None):
if self._visible:
Qd.PaintRect(self._bounds)
def _drawbounds(self):
pass
class HorizontalLine(_Line):
def _calcbounds(self):
Widget._calcbounds(self)
l, t, r, b = self._bounds
self._bounds = l, t, r, t + self._thickness
class VerticalLine(_Line):
def _calcbounds(self):
Widget._calcbounds(self)
l, t, r, b = self._bounds
self._bounds = l, t, l + self._thickness, b
class Frame(Widget):
def __init__(self, possize, pattern = Qd.qd.black, color = (0, 0, 0)):
Widget.__init__(self, possize)
self._framepattern = pattern
self._framecolor = color
def setcolor(self, color):
self._framecolor = color
self.SetPort()
self.draw()
def setpattern(self, pattern):
self._framepattern = pattern
self.SetPort()
self.draw()
def draw(self, visRgn = None):
if self._visible:
penstate = Qd.GetPenState()
Qd.PenPat(self._framepattern)
Qd.RGBForeColor(self._framecolor)
Qd.FrameRect(self._bounds)
Qd.RGBForeColor((0, 0, 0))
Qd.SetPenState(penstate)
def _darkencolor((r, g, b)):
return 0.75 * r, 0.75 * g, 0.75 * b
class BevelBox(Widget):
"""'Platinum' beveled rectangle."""
def __init__(self, possize, color = (0xe000, 0xe000, 0xe000)):
Widget.__init__(self, possize)
self._color = color
self._darkercolor = _darkencolor(color)
def setcolor(self, color):
self._color = color
self.SetPort()
self.draw()
def draw(self, visRgn = None):
if self._visible:
l, t, r, b = Qd.InsetRect(self._bounds, 1, 1)
Qd.RGBForeColor(self._color)
Qd.PaintRect((l, t, r, b))
Qd.RGBForeColor(self._darkercolor)
Qd.MoveTo(l, b)
Qd.LineTo(r, b)
Qd.LineTo(r, t)
Qd.RGBForeColor((0, 0, 0))
class Group(Widget):
"""A container for subwidgets"""
class HorizontalPanes(Widget):
"""Panes, a.k.a. frames. Works a bit like a group. Devides the widget area into "panes",
which can be resized by the user by clicking and dragging between the subwidgets."""
_direction = 1
def __init__(self, possize, panesizes = None, gutter = 8):
"""panesizes should be a tuple of numbers. The length of the tuple is the number of panes,
the items in the tuple are the relative sizes of these panes; these numbers should add up
to 1 (the total size of all panes)."""
ClickableWidget.__init__(self, possize)
self._panesizes = panesizes
self._gutter = gutter
self._enabled = 1
self.setuppanes()
#def open(self):
# self.installbounds()
# ClickableWidget.open(self)
def _calcbounds(self):
# hmmm. It should not neccesary be override _calcbounds :-(
self.installbounds()
Widget._calcbounds(self)
def setuppanes(self):
panesizes = self._panesizes
total = 0
if panesizes is not None:
#if len(self._widgets) <> len(panesizes):
# raise TypeError, 'number of widgets does not match number of panes'
for panesize in panesizes:
if not 0 < panesize < 1:
raise TypeError, 'pane sizes must be between 0 and 1, not including.'
total = total + panesize
if round(total, 4) <> 1.0:
raise TypeError, 'pane sizes must add up to 1'
else:
# XXX does not work!
step = 1.0 / len(self._widgets)
panesizes = []
for i in range(len(self._widgets)):
panesizes.append(step)
current = 0
self._panesizes = []
self._gutters = []
for panesize in panesizes:
if current:
self._gutters.append(current)
self._panesizes.append((current, current + panesize))
current = current + panesize
self.makepanebounds()
def getpanesizes(self):
return map(lambda (fr, to): to-fr, self._panesizes)
boundstemplate = "lambda width, height: (0, height * %s + %d, width, height * %s + %d)"
def makepanebounds(self):
halfgutter = self._gutter / 2
self._panebounds = []
for i in range(len(self._panesizes)):
panestart, paneend = self._panesizes[i]
boundsstring = self.boundstemplate % (`panestart`, panestart and halfgutter,
`paneend`, (paneend <> 1.0) and -halfgutter)
self._panebounds.append(eval(boundsstring))
def installbounds(self):
#self.setuppanes()
for i in range(len(self._widgets)):
w = self._widgets[i]
w._possize = self._panebounds[i]
#if hasattr(w, "setuppanes"):
# w.setuppanes()
if hasattr(w, "installbounds"):
w.installbounds()
def rollover(self, point, onoff):
if onoff:
orgmouse = point[self._direction]
halfgutter = self._gutter / 2
l, t, r, b = self._bounds
if self._direction:
begin, end = t, b
else:
begin, end = l, r
i = self.findgutter(orgmouse, begin, end)
if i is None:
SetCursor("arrow")
else:
SetCursor(self._direction and 'vmover' or 'hmover')
def findgutter(self, orgmouse, begin, end):
tolerance = max(4, self._gutter) / 2
for i in range(len(self._gutters)):
pos = begin + (end - begin) * self._gutters[i]
if abs(orgmouse - pos) <= tolerance:
break
else:
return
return i
def click(self, point, modifiers):
# what a mess...
orgmouse = point[self._direction]
halfgutter = self._gutter / 2
l, t, r, b = self._bounds
if self._direction:
begin, end = t, b
else:
begin, end = l, r
i = self.findgutter(orgmouse, begin, end)
if i is None:
return
pos = orgpos = begin + (end - begin) * self._gutters[i] # init pos too, for fast click on border, bug done by Petr
minpos = self._panesizes[i][0]
maxpos = self._panesizes[i+1][1]
minpos = begin + (end - begin) * minpos + 64
maxpos = begin + (end - begin) * maxpos - 64
if minpos > orgpos and maxpos < orgpos:
return
#SetCursor("fist")
self.SetPort()
if self._direction:
rect = l, orgpos - 1, r, orgpos
else:
rect = orgpos - 1, t, orgpos, b
# track mouse --- XXX move to separate method?
Qd.PenMode(QuickDraw.srcXor)
Qd.PenPat(Qd.qd.gray)
Qd.PaintRect(rect)
lastpos = None
while Evt.Button():
pos = orgpos - orgmouse + Evt.GetMouse()[self._direction]
pos = max(pos, minpos)
pos = min(pos, maxpos)
if pos == lastpos:
continue
Qd.PenPat(Qd.qd.gray)
Qd.PaintRect(rect)
if self._direction:
rect = l, pos - 1, r, pos
else:
rect = pos - 1, t, pos, b
Qd.PenPat(Qd.qd.gray)
Qd.PaintRect(rect)
lastpos = pos
Qd.PaintRect(rect)
Qd.PenNormal()
SetCursor("watch")
newpos = (pos - begin) / float(end - begin)
self._gutters[i] = newpos
self._panesizes[i] = self._panesizes[i][0], newpos
self._panesizes[i+1] = newpos, self._panesizes[i+1][1]
self.makepanebounds()
self.installbounds()
self._calcbounds()
class VerticalPanes(HorizontalPanes):
"""see HorizontalPanes"""
_direction = 0
boundstemplate = "lambda width, height: (width * %s + %d, 0, width * %s + %d, height)"
class ColorPicker(ClickableWidget):
"""Color picker widget. Allows the user to choose a color."""
def __init__(self, possize, color = (0, 0, 0), callback = None):
ClickableWidget.__init__(self, possize)
self._color = color
self._callback = callback
self._enabled = 1
def click(self, point, modifiers):
if not self._enabled:
return
import ColorPicker
newcolor, ok = ColorPicker.GetColor("", self._color)
if ok:
self._color = newcolor
self.SetPort()
self.draw()
if self._callback:
return CallbackCall(self._callback, 0, self._color)
def set(self, color):
self._color = color
self.SetPort()
self.draw()
def get(self):
return self._color
def draw(self, visRgn=None):
if self._visible:
if not visRgn:
visRgn = self._parentwindow.wid.GetWindowPort().visRgn
Qd.PenPat(Qd.qd.gray)
rect = self._bounds
Qd.FrameRect(rect)
rect = Qd.InsetRect(rect, 3, 3)
Qd.PenNormal()
Qd.RGBForeColor(self._color)
Qd.PaintRect(rect)
Qd.RGBForeColor((0, 0, 0))
# misc utils
def CallbackCall(callback, mustfit, *args):
"""internal helper routine for W"""
# XXX this function should die.
if type(callback) == FunctionType:
func = callback
maxargs = func.func_code.co_argcount
elif type(callback) == MethodType:
func = callback.im_func
maxargs = func.func_code.co_argcount - 1
else:
if callable(callback):
return apply(callback, args)
else:
raise TypeError, "uncallable callback object"
if func.func_defaults:
minargs = maxargs - len(func.func_defaults)
else:
minargs = maxargs
if minargs <= len(args) <= maxargs:
return apply(callback, args)
elif not mustfit and minargs == 0:
return callback()
else:
if mustfit:
raise TypeError, "callback accepts wrong number of arguments: " + `len(args)`
else:
raise TypeError, "callback accepts wrong number of arguments: 0 or " + `len(args)`
def HasBaseClass(obj, class_):
try:
raise obj
except class_:
return 1
except:
pass
return 0
_cursors = {
"watch" : Qd.GetCursor(QuickDraw.watchCursor).data,
"arrow" : Qd.qd.arrow,
"iBeam" : Qd.GetCursor(QuickDraw.iBeamCursor).data,
"cross" : Qd.GetCursor(QuickDraw.crossCursor).data,
"plus" : Qd.GetCursor(QuickDraw.plusCursor).data,
"hand" : Qd.GetCursor(468).data,
"fist" : Qd.GetCursor(469).data,
"hmover" : Qd.GetCursor(470).data,
"vmover" : Qd.GetCursor(471).data,
"zoomin" : Qd.GetCursor(472).data,
"zoomout" : Qd.GetCursor(473).data,
"zoom" : Qd.GetCursor(474).data,
}
def SetCursor(what):
"""Set the cursorshape to any of these: 'arrow', 'cross', 'fist', 'hand', 'hmover', 'iBeam',
'plus', 'vmover', 'watch', 'zoom', 'zoomin', 'zoomout'."""
Qd.SetCursor(_cursors[what])