Commit 813008e5 authored by Guido van Rossum's avatar Guido van Rossum

Deleting all stdwin library modules.

parent e298c301
# Abstract classes for parents and children.
#
# Do not use as base class -- this is for documentation only.
#
# Note that the tree must be built top down (create the parent,
# then add the children).
#
# Also note that the creation methods are not standardized --
# these have extra parameters dependent on the widget type.
# For historical reasons, button creation methods are called
# define() while split creation methods are called create().
class AbstractParent:
#
# Upcalls from child to parent
#
def addchild(self, child): unimpl()
def delchild(self, child): unimpl()
#
def need_mouse(self, child): unimpl()
def no_mouse(self, child): unimpl()
#
def need_timer(self, child): unimpl()
def no_timer(self, child): unimpl()
#
# XXX need_kbd, no_kbd; focus???
#
def begindrawing(self): return unimpl()
def beginmeasuring(self): return unimpl()
def getwindow(self): return unimpl() # Only for very special cases
#
def change(self, area): unimpl()
def scroll(self, area, (dh, dv)): unimpl()
def settimer(self, itimer): unimpl()
class AbstractChild:
#
# Downcalls from parent to child
#
def destroy(self): unimpl()
#
def realize(self): return unimpl()
def getminsize(self, m, (width, height)): return unimpl()
def getbounds(self): return unimpl()
def setbounds(self, bounds): unimpl()
def draw(self, d, area): unimpl()
#
# Downcalls only made after certain upcalls
#
def mouse_down(self, detail): unimpl()
def mouse_move(self, detail): unimpl()
def mouse_up(self, detail): unimpl()
#
def timer(self): unimpl()
# A "Split" is a child that manages one or more children.
# (This terminology is due to DEC SRC, except for CSplits.)
# A child of a split may be another split, a button, a slider, etc.
# Certain upcalls and downcalls can be handled transparently, but
# for others (e.g., all geometry related calls) this is not possible.
class AbstractSplit(AbstractChild, AbstractParent):
pass
from TransParent import TransParent
class BoxParent(TransParent):
#
def create(self, parent, (dh, dv)):
self = TransParent.create(self, parent)
self.dh = dh
self.dv = dv
return self
#
def getminsize(self, m, (width, height)):
width = max(0, width - 2*self.dh)
height = max(0, height - 2*self.dv)
width, height = self.child.getminsize(m, (width, height))
return width + 2*self.dh, height + 2*self.dv
#
def setbounds(self, bounds):
(left, top), (right, bottom) = bounds
self.bounds = bounds
left = min(right, left + self.dh)
top = min(bottom, top + self.dv)
right = max(left, right - self.dh)
bottom = max(top, bottom - self.dv)
self.innerbounds = (left, top), (right, bottom)
self.child.setbounds(self.innerbounds)
#
def getbounds(self):
return self.bounds
#
def draw(self, d, area):
(left, top), (right, bottom) = self.bounds
left = left + 1
top = top + 1
right = right - 1
bottom = bottom - 1
d.box((left, top), (right, bottom))
TransParent.draw(self, d, area) # XXX clip to innerbounds?
#
# XXX should scroll clip to innerbounds???
# XXX currently the only user restricts itself to child's bounds
# Module 'Buttons'
# Import module 'rect' renamed as '_rect' to avoid exporting it on
# 'from Buttons import *'
#
import rect
_rect = rect
del rect
# Field indices in mouse event detail
#
_HV = 0
_CLICKS = 1
_BUTTON = 2
_MASK = 3
# LabelAppearance provides defaults for all appearance methods.
# selected state not visible
# disabled --> crossed out
# hilited --> inverted
#
class LabelAppearance:
#
# Initialization
#
def init_appearance(self):
self.bounds = _rect.empty
self.enabled = 1
self.hilited = 0
self.selected = 0
self.text = ''
#
# Size enquiry
#
def getminsize(self, m, (width, height)):
width = max(width, m.textwidth(self.text) + 6)
height = max(height, m.lineheight() + 6)
return width, height
#
def getbounds(self):
return self.bounds
#
# Changing the parameters
#
def settext(self, text):
self.text = text
if self.bounds <> _rect.empty:
self.recalctextpos()
self.redraw()
#
def setbounds(self, bounds):
self.bounds = bounds
if self.bounds <> _rect.empty:
self.recalc()
#
def realize(self):
pass
#
# Changing the state bits
#
def enable(self, flag):
if flag <> self.enabled:
self.enabled = flag
if self.bounds <> _rect.empty:
self.flipenable(self.parent.begindrawing())
#
def hilite(self, flag):
if flag <> self.hilited:
self.hilited = flag
if self.bounds <> _rect.empty:
self.fliphilite(self.parent.begindrawing())
#
def select(self, flag):
if flag <> self.selected:
self.selected = flag
if self.bounds <> _rect.empty:
self.redraw()
#
# Recalculate the box bounds and text position.
# This can be overridden by buttons that draw different boxes
# or want their text in a different position.
#
def recalc(self):
if self.bounds <> _rect.empty:
self.recalcbounds()
self.recalctextpos()
#
def recalcbounds(self):
self.hilitebounds = _rect.inset(self.bounds, (3, 3))
self.crossbounds = self.bounds
#
def recalctextpos(self):
(left, top), (right, bottom) = self.bounds
m = self.parent.beginmeasuring()
h = (left + right - m.textwidth(self.text)) / 2
v = (top + bottom - m.lineheight()) / 2
self.textpos = h, v
#
# Generic drawing interface.
# Do not override redraw() or draw() methods; override drawit() c.s.
#
def redraw(self):
if self.bounds <> _rect.empty:
d = self.parent.begindrawing()
d.erase(self.bounds)
self.draw(d, self.bounds)
#
def draw(self, d, area):
area = _rect.intersect([area, self.bounds])
if area == _rect.empty:
return
d.cliprect(area)
self.drawit(d)
d.noclip()
#
# The drawit() method is fairly generic but may be overridden.
#
def drawit(self, d):
self.drawpict(d)
if self.text:
d.text(self.textpos, self.text)
if not self.enabled:
self.flipenable(d)
if self.hilited:
self.fliphilite(d)
#
# Default drawing detail functions.
# Overriding these is normally sufficient to get different
# appearances.
#
def drawpict(self, d):
pass
#
def flipenable(self, d):
_xorcross(d, self.crossbounds)
#
def fliphilite(self, d):
d.invert(self.hilitebounds)
# A Strut is a label with no width of its own.
class StrutAppearance(LabelAppearance):
#
def getminsize(self, m, (width, height)):
height = max(height, m.lineheight() + 6)
return width, height
#
# ButtonAppearance displays a centered string in a box.
# selected --> bold border
# disabled --> crossed out
# hilited --> inverted
#
class ButtonAppearance(LabelAppearance):
#
def drawpict(self, d):
d.box(_rect.inset(self.bounds, (1, 1)))
if self.selected:
# Make a thicker box
d.box(self.bounds)
d.box(_rect.inset(self.bounds, (2, 2)))
d.box(_rect.inset(self.bounds, (3, 3)))
#
# CheckAppearance displays a small square box and a left-justified string.
# selected --> a cross appears in the box
# disabled --> whole button crossed out
# hilited --> box is inverted
#
class CheckAppearance(LabelAppearance):
#
def getminsize(self, m, (width, height)):
minwidth = m.textwidth(self.text) + 6
minheight = m.lineheight() + 6
width = max(width, minwidth + minheight + m.textwidth(' '))
height = max(height, minheight)
return width, height
#
def drawpict(self, d):
d.box(self.boxbounds)
if self.selected: _xorcross(d, self.boxbounds)
#
def recalcbounds(self):
LabelAppearance.recalcbounds(self)
(left, top), (right, bottom) = self.bounds
self.size = bottom - top - 4
self.boxbounds = (left+2, top+2), (left+2+self.size, bottom-2)
self.hilitebounds = self.boxbounds
#
def recalctextpos(self):
m = self.parent.beginmeasuring()
(left, top), (right, bottom) = self.boxbounds
h = right + m.textwidth(' ')
v = top + (self.size - m.lineheight()) / 2
self.textpos = h, v
#
# RadioAppearance displays a round indicator and a left-justified string.
# selected --> a dot appears in the indicator
# disabled --> whole button crossed out
# hilited --> indicator is inverted
#
class RadioAppearance(CheckAppearance):
#
def drawpict(self, d):
(left, top), (right, bottom) = self.boxbounds
radius = self.size / 2
center = left + radius, top + radius
d.circle(center, radius)
if self.selected:
d.fillcircle(center, radius*3/5)
#
# NoReactivity ignores mouse events.
#
class NoReactivity:
def init_reactivity(self): pass
# BaseReactivity defines hooks and asks for mouse events,
# but provides only dummy mouse event handlers.
# The trigger methods call the corresponding hooks set by the user.
# Hooks (and triggers) mean the following:
# down_hook called on some mouse-down events
# move_hook called on some mouse-move events
# up_hook called on mouse-up events
# on_hook called for buttons with on/off state, when it goes on
# hook called when a button 'fires' or a radiobutton goes on
# There are usually extra conditions, e.g., hooks are only called
# when the button is enabled, or active, or selected (on).
#
class BaseReactivity:
#
def init_reactivity(self):
self.down_hook = self.move_hook = self.up_hook = \
self.on_hook = self.off_hook = \
self.hook = self.active = 0
self.parent.need_mouse(self)
#
def mousetest(self, hv):
return _rect.pointinrect(hv, self.bounds)
#
def mouse_down(self, detail):
pass
#
def mouse_move(self, detail):
pass
#
def mouse_up(self, detail):
pass
#
def down_trigger(self):
if self.down_hook: self.down_hook(self)
#
def move_trigger(self):
if self.move_hook: self.move_hook(self)
#
def up_trigger(self):
if self.up_hook: self.up_hook(self)
#
def on_trigger(self):
if self.on_hook: self.on_hook(self)
#
def off_trigger(self):
if self.off_hook: self.off_hook(self)
#
def trigger(self):
if self.hook: self.hook(self)
# ToggleReactivity acts like a simple pushbutton.
# It toggles its hilite state on mouse down events.
#
class ToggleReactivity(BaseReactivity):
#
def mouse_down(self, detail):
if self.enabled and self.mousetest(detail[_HV]):
self.active = 1
self.hilite(not self.hilited)
self.down_trigger()
#
def mouse_move(self, detail):
if self.active:
self.move_trigger()
#
def mouse_up(self, detail):
if self.active:
self.up_trigger()
self.active = 0
#
def down_trigger(self):
if self.hilited:
self.on_trigger()
else:
self.off_trigger()
self.trigger()
#
# TriggerReactivity acts like a fancy pushbutton.
# It hilites itself while the mouse is down within its bounds.
#
class TriggerReactivity(BaseReactivity):
#
def mouse_down(self, detail):
if self.enabled and self.mousetest(detail[_HV]):
self.active = 1
self.hilite(1)
self.down_trigger()
#
def mouse_move(self, detail):
if self.active:
self.hilite(self.mousetest(detail[_HV]))
if self.hilited:
self.move_trigger()
#
def mouse_up(self, detail):
if self.active:
self.hilite(self.mousetest(detail[_HV]))
if self.hilited:
self.up_trigger()
self.trigger()
self.active = 0
self.hilite(0)
#
# CheckReactivity handles mouse events like TriggerReactivity,
# It overrides the up_trigger method to flip its selected state.
#
class CheckReactivity(TriggerReactivity):
#
def up_trigger(self):
self.select(not self.selected)
if self.selected:
self.on_trigger()
else:
self.off_trigger()
self.trigger()
# RadioReactivity turns itself on and the other buttons in its group
# off when its up_trigger method is called.
#
class RadioReactivity(TriggerReactivity):
#
def init_reactivity(self):
TriggerReactivity.init_reactivity(self)
self.group = []
#
def up_trigger(self):
for b in self.group:
if b <> self:
if b.selected:
b.select(0)
b.off_trigger()
self.select(1)
self.on_trigger()
self.trigger()
# Auxiliary class for 'define' method.
# Call the initializers in the right order.
#
class Define:
#
def define(self, parent):
self.parent = parent
parent.addchild(self)
self.init_appearance()
self.init_reactivity()
return self
#
def destroy(self):
self.parent = 0
#
def definetext(self, parent, text):
self = self.define(parent)
self.settext(text)
return self
# Subroutine to cross out a rectangle.
#
def _xorcross(d, bounds):
((left, top), (right, bottom)) = bounds
# This is s bit funny to make it look better
left = left + 2
right = right - 2
top = top + 2
bottom = bottom - 3
d.xorline(((left, top), (right, bottom)))
d.xorline((left, bottom), (right, top))
# Ready-made button classes.
#
class Label(NoReactivity, LabelAppearance, Define): pass
class Strut(NoReactivity, StrutAppearance, Define): pass
class PushButton(TriggerReactivity, ButtonAppearance, Define): pass
class CheckButton(CheckReactivity, CheckAppearance, Define): pass
class RadioButton(RadioReactivity, RadioAppearance, Define): pass
class ToggleButton(ToggleReactivity, ButtonAppearance, Define): pass
# A CSplit is a Clock-shaped split: the children are grouped in a circle.
# The numbering is a little different from a real clock: the 12 o'clock
# position is called 0, not 12. This is a little easier since Python
# usually counts from zero. (BTW, there needn't be exactly 12 children.)
from math import pi, sin, cos
from Split import Split
class CSplit(Split):
#
def getminsize(self, m, (width, height)):
# Since things look best if the children are spaced evenly
# along the circle (and often all children have the same
# size anyway) we compute the max child size and assume
# this is each child's size.
for child in self.children:
wi, he = child.getminsize(m, (0, 0))
width = max(width, wi)
height = max(height, he)
# In approximation, the diameter of the circle we need is
# (diameter of box) * (#children) / pi.
# We approximate pi by 3 (so we slightly overestimate
# our minimal size requirements -- not so bad).
# Because the boxes stick out of the circle we add the
# box size to each dimension.
# Because we really deal with ellipses, do everything
# separate in each dimension.
n = len(self.children)
return width + (width*n + 2)/3, height + (height*n + 2)/3
#
def getbounds(self):
return self.bounds
#
def setbounds(self, bounds):
self.bounds = bounds
# Place the children. This involves some math.
# Compute center positions for children as if they were
# ellipses with a diameter about 1/N times the
# circumference of the big ellipse.
# (There is some rounding involved to make it look
# reasonable for small and large N alike.)
# XXX One day Python will have automatic conversions...
n = len(self.children)
fn = float(n)
if n == 0: return
(left, top), (right, bottom) = bounds
width, height = right-left, bottom-top
child_width, child_height = width*3/(n+4), height*3/(n+4)
half_width, half_height = \
float(width-child_width)/2.0, \
float(height-child_height)/2.0
center_h, center_v = center = (left+right)/2, (top+bottom)/2
fch, fcv = float(center_h), float(center_v)
alpha = 2.0 * pi / fn
for i in range(n):
child = self.children[i]
fi = float(i)
fh, fv = \
fch + half_width*sin(fi*alpha), \
fcv - half_height*cos(fi*alpha)
left, top = \
int(fh) - child_width/2, \
int(fv) - child_height/2
right, bottom = \
left + child_width, \
top + child_height
child.setbounds(((left, top), (right, bottom)))
#
# DirList -- Directory Listing widget
# XXX Displays messy paths when following '..'
import os
import stdwin, rect
from stdwinevents import *
from Buttons import PushButton
from WindowParent import WindowParent
from HVSplit import HSplit, VSplit
class DirList(VSplit):
#
def create(self, parent, dirname):
self = VSplit.create(self, parent)
names = os.listdir(dirname)
for name in names:
if os.path.isdir(os.path.join(dirname, name)):
fullname = os.path.join(dirname, name)
btn = SubdirButton().definetext(self, fullname)
elif name[-3:] == '.py':
btn = ModuleButton().definetext(self, name)
else:
btn = FileButton().definetext(self, name)
return self
#
class DirListWindow(WindowParent):
#
def create(self, dirname):
self = WindowParent.create(self, dirname, (0, 0))
child = DirList().create(self, dirname)
self.realize()
return self
#
class SubdirButton(PushButton):
#
def drawpict(self, d):
PushButton.drawpict(self, d)
d.box(rect.inset(self.bounds, (3, 1)))
#
def up_trigger(self):
window = DirListWindow().create(self.text)
#
class FileButton(PushButton):
#
def up_trigger(self):
stdwin.fleep()
#
class ModuleButton(FileButton):
#
def drawpict(self, d):
PushButton.drawpict(self, d)
d.box(rect.inset(self.bounds, (1, 3)))
#
# A FormSplit lets you place its children exactly where you want them
# (including silly places!).
# It does no explicit geometry management except moving its children
# when it is moved.
# The interface to place children is as follows.
# Before you add a child, you may specify its (left, top) position
# relative to the FormSplit. If you don't specify a position for
# a child, it goes right below the previous child; the first child
# goes to (0, 0) by default.
# NB: This places data attributes named form_* on its children.
# XXX Yes, I know, there should be options to do all sorts of relative
# placement, but for now this will do.
from Split import Split
class FormSplit(Split):
#
def create(self, parent):
self.next_left = self.next_top = 0
self.last_child = None
return Split.create(self, parent)
#
def getminsize(self, m, sugg_size):
max_width, max_height = 0, 0
for c in self.children:
c.form_width, c.form_height = c.getminsize(m, (0, 0))
max_width = max(max_width, c.form_width + c.form_left)
max_height = max(max_height, \
c.form_height + c.form_top)
return max_width, max_height
#
def getbounds(self):
return self.bounds
#
def setbounds(self, bounds):
self.bounds = bounds
fleft, ftop = bounds[0]
for c in self.children:
left, top = c.form_left + fleft, c.form_top + ftop
right, bottom = left + c.form_width, top + c.form_height
c.setbounds(((left, top), (right, bottom)))
#
def placenext(self, left, top):
self.next_left = left
self.next_top = top
self.last_child = None
#
def addchild(self, child):
if self.last_child:
width, height = \
self.last_child.getminsize(self.beginmeasuring(), \
(0, 0))
self.next_top = self.next_top + height
child.form_left = self.next_left
child.form_top = self.next_top
Split.addchild(self, child)
self.last_child = child
#
# HVSplit contains generic code for HSplit and VSplit.
# HSplit and VSplit are specializations to either dimension.
# XXX This does not yet stretch/shrink children if there is too much
# XXX or too little space in the split dimension.
# XXX (NB There is no interface to ask children for stretch preferences.)
from Split import Split
class HVSplit(Split):
#
def create(self, parent, hv):
# hv is 0 for HSplit, 1 for VSplit
self = Split.create(self, parent)
self.hv = hv
return self
#
def getminsize(self, m, sugg_size):
hv, vh = self.hv, 1 - self.hv
size = [0, 0]
sugg_size = [sugg_size[0], sugg_size[1]]
sugg_size[hv] = 0
sugg_size = sugg_size[0], sugg_size[1] # Make a tuple
for c in self.children:
csize = c.getminsize(m, sugg_size)
if csize[vh] > size[vh]: size[vh] = csize[vh]
size[hv] = size[hv] + csize[hv]
return size[0], size[1]
#
def getbounds(self):
return self.bounds
#
def setbounds(self, bounds):
self.bounds = bounds
hv, vh = self.hv, 1 - self.hv
mf = self.parent.beginmeasuring
begin, end = bounds
sugg_size = end[0] - begin[0], end[1] - begin[1]
size = self.getminsize(mf(), sugg_size)
origin = [begin[0], begin[1]]
sugg_size = [sugg_size[0], sugg_size[1]] # Make a list
sugg_size[hv] = 0
sugg_size = sugg_size[0], sugg_size[1] # Make a tuple
for c in self.children:
size = c.getminsize(mf(), sugg_size)
corner = [0, 0]
corner[vh] = end[vh]
corner[hv] = origin[hv] + size[hv]
c.setbounds(((origin[0], origin[1]), \
(corner[0], corner[1])))
origin[hv] = corner[hv]
# XXX stretch
# XXX too-small
#
class HSplit(HVSplit):
def create(self, parent):
return HVSplit.create(self, parent, 0)
class VSplit(HVSplit):
def create(self, parent):
return HVSplit.create(self, parent, 1)
# Module 'Histogram'
from Buttons import *
# A Histogram displays a histogram of numeric data.
#
class HistogramAppearance(LabelAppearance, Define):
#
def define(self, parent):
Define.define(self, (parent, ''))
self.ydata = []
self.scale = (0, 100)
return self
#
def setdata(self, ydata, scale):
self.ydata = ydata
self.scale = scale # (min, max)
self.parent.change(self.bounds)
#
def drawpict(self, d):
(left, top), (right, bottom) = self.bounds
min, max = self.scale
size = max-min
width, height = right-left, bottom-top
ydata = self.ydata
npoints = len(ydata)
v1 = top + height # constant
h1 = left # changed in loop
for i in range(npoints):
h0 = h1
v0 = top + height - (ydata[i]-min)*height/size
h1 = left + (i+1) * width/npoints
d.paint((h0, v0), (h1, v1))
#
class Histogram(NoReactivity, HistogramAppearance): pass
# Module 'Sliders'
import stdwin
from stdwinevents import *
import rect
from Buttons import *
from HVSplit import HSplit
# Field indices in event detail
#
_HV = 0
_CLICKS = 1
_BUTTON = 2
_MASK = 3
# DragSlider is the simplest possible slider.
# It looks like a button but dragging the mouse left or right
# changes the controlled value.
# It does not support any of the triggers or hooks defined by Buttons,
# but defines its own setval_trigger and setval_hook.
#
class DragSliderReactivity(BaseReactivity):
#
def mouse_down(self, detail):
h, v = hv = detail[_HV]
if self.enabled and self.mousetest(hv):
self.anchor = h
self.oldval = self.val
self.active = 1
#
def mouse_move(self, detail):
if self.active:
h, v = detail[_HV]
self.setval(self.oldval + (h - self.anchor))
#
def mouse_up(self, detail):
if self.active:
h, v = detail[_HV]
self.setval(self.oldval + (h - self.anchor))
self.active = 0
#
class DragSliderAppearance(ButtonAppearance):
#
# INVARIANTS maintained by the setval method:
#
# self.min <= self.val <= self.max
# self.text = self.pretext + `self.val` + self.postext
#
# (Notice that unlike Python ranges, the end point belongs
# to the range.)
#
def init_appearance(self):
ButtonAppearance.init_appearance(self)
self.min = 0
self.val = 0
self.max = 100
self.hook = 0
self.pretext = self.postext = ''
self.recalctext()
#
# The 'get*' and 'set*' methods belong to the generic slider interface
#
def getval(self): return self.val
#
def sethook(self, hook):
self.hook = hook
#
def setminvalmax(self, min, val, max):
self.min = min
self.max = max
self.setval(val)
#
def settexts(self, pretext, postext):
self.pretext = pretext
self.postext = postext
self.recalctext()
#
def setval(self, val):
val = min(self.max, max(self.min, val))
if val <> self.val:
self.val = val
self.recalctext()
self.trigger()
#
def trigger(self):
if self.hook:
self.hook(self)
#
def recalctext(self):
self.settext(self.pretext + `self.val` + self.postext)
#
class DragSlider(DragSliderReactivity, DragSliderAppearance, Define):
def definetext(self, parent, text):
raise RuntimeError, 'DragSlider.definetext() not supported'
# Auxiliary class for PushButton incorporated in ComplexSlider
#
class _StepButton(PushButton):
def define(self, parent):
self = PushButton.define(self, parent)
self.step = 0
return self
def setstep(self, step):
self.step = step
def definetextstep(self, parent, text, step):
self = self.definetext(parent, text)
self.setstep(step)
return self
def init_reactivity(self):
PushButton.init_reactivity(self)
self.parent.need_timer(self)
def step_trigger(self):
self.parent.setval(self.parent.getval() + self.step)
def down_trigger(self):
self.step_trigger()
self.parent.settimer(5)
def timer(self):
if self.hilited:
self.step_trigger()
if self.active:
self.parent.settimer(1)
# A complex slider is an HSplit initialized to three buttons:
# one to step down, a dragslider, and one to step up.
#
class ComplexSlider(HSplit):
#
# Override Slider define() method
#
def define(self, parent):
self = self.create(parent) # HSplit
#
self.downbutton = _StepButton().definetextstep(self, '-', -1)
self.dragbutton = DragSlider().define(self)
self.upbutton = _StepButton().definetextstep(self, '+', 1)
#
return self
#
# Override HSplit methods
#
def getminsize(self, m, (width, height)):
w1, h1 = self.downbutton.getminsize(m, (0, height))
w3, h3 = self.upbutton.getminsize(m, (0, height))
w1 = max(w1, h1)
w3 = max(w3, h3)
w2, h2 = self.dragbutton.getminsize(m, (width-w1-w3, height))
return w1+w2+w3, max(h1, h2, h3)
#
def setbounds(self, bounds):
(left, top), (right, bottom) = self.bounds = bounds
size = bottom - top
self.downbutton.setbounds(((left, top), (left+size, bottom)))
self.dragbutton.setbounds(((left+size, top), \
(right-size, bottom)))
self.upbutton.setbounds(((right-size, top), (right, bottom)))
#
# Pass other Slider methods on to dragbutton
#
def getval(self): return self.dragbutton.getval()
def sethook(self, hook): self.dragbutton.sethook(hook)
def setminvalmax(self, args): self.dragbutton.setminvalmax(args)
def settexts(self, args): self.dragbutton.settexts(args)
def setval(self, val): self.dragbutton.setval(val)
def enable(self, flag):
self.downbutton.enable(flag)
self.dragbutton.enable(flag)
self.upbutton.enable(flag)
# Module 'Soundogram'
import audio
from Histogram import Histogram
class Soundogram(Histogram):
#
def define(self, win, chunk):
width, height = corner = win.getwinsize()
bounds = (0, 0), corner
self.chunk = chunk
self.step = (len(chunk)-1)/(width/2+1) + 1
ydata = _make_ydata(chunk, self.step)
return Histogram.define(self, (win, bounds, ydata, (0, 128)))
#
def setchunk(self, chunk):
self.chunk = chunk
self.recompute()
#
def recompute(self):
(left, top), (right, bottom) = self.bounds
width = right - left
self.step = (len(chunk)-1)/width + 1
ydata = _make_ydata(chunk, self.step)
self.setdata(ydata, (0, 128))
#
def _make_ydata(chunk, step):
ydata = []
for i in range(0, len(chunk), step):
piece = audio.chr2num(chunk[i:i+step])
mi, ma = min(piece), max(piece)
y = max(abs(mi), abs(ma))
ydata.append(y)
return ydata
# Generic Split implementation.
# Use as a base class for other splits.
# Derived classes should at least implement the methods that call
# unimpl() below: getminsize(), getbounds() and setbounds().
Error = 'Split.Error' # Exception
import rect
from stdwinevents import *
class Split:
#
# Calls from creator
# NB derived classes may add parameters to create()
#
def create(self, parent):
parent.addchild(self)
self.parent = parent
self.children = []
self.mouse_interest = []
self.keybd_interest = []
self.timer_interest = []
self.altdraw_interest = []
self.mouse_focus = None
self.keybd_focus = None
return self
#
# Downcalls from parent to child
#
def destroy(self):
self.parent = None
for child in self.children:
child.destroy()
del self.children[:]
del self.mouse_interest[:]
del self.keybd_interest[:]
del self.timer_interest[:]
del self.altdraw_interest[:]
self.mouse_focus = None
self.keybd_focus = None
#
def getminsize(self, m, (width, height)):
return unimpl() # Should ask children
def getbounds(self):
return unimpl()
def setbounds(self, bounds):
unimpl() # Should tell children
#
def realize(self):
for child in self.children:
child.realize()
#
def draw(self, d, detail):
# (Could avoid calls to children outside the area)
for child in self.children:
child.draw(d, detail)
#
def altdraw(self, detail):
for child in self.altdraw_interest:
child.altdraw(detail)
#
# Keyboard focus handling (used internally)
# XXX This is not enough if two levels of splits
# XXX surround text fields!
#
def set_keybd_focus(self, child):
if self.keybd_focus <> child:
if self.keybd_focus:
self.keybd_focus.deactivate()
self.keybd_focus = None
if child:
child.activate()
self.keybd_focus = child
def next_keybd_focus(self):
if not self.keybd_interest:
self.set_keybd_focus(None)
return
if self.keybd_focus in self.keybd_interest:
i = self.keybd_interest.index(self.keybd_focus)
i = (i+1) % len(self.keybd_interest)
else:
i = 0
self.set_keybd_focus(self.keybd_interest[i])
#
# Downcalls only made after certain upcalls
#
def mouse_down(self, detail):
if self.mouse_focus:
self.mouse_focus.mouse_down(detail)
return
p = detail[0]
for child in self.mouse_interest:
if rect.pointinrect(p, child.getbounds()):
self.mouse_focus = child
if child in self.keybd_interest:
self.set_keybd_focus(child)
child.mouse_down(detail)
def mouse_move(self, detail):
if self.mouse_focus:
self.mouse_focus.mouse_move(detail)
def mouse_up(self, detail):
if self.mouse_focus:
self.mouse_focus.mouse_up(detail)
self.mouse_focus = None
#
def activate(self):
if self.keybd_focus:
self.keybd_focus.activate()
else:
self.next_keybd_focus()
def deactivate(self):
if self.keybd_focus:
self.keybd_focus.deactivate()
#
def keybd(self, type, detail):
if not self.keybd_focus:
self.set_keybd_focus(self.keybd_interest[0])
if type == WE_COMMAND and detail == WC_TAB and \
len(self.keybd_interest) > 1:
self.next_keybd_focus()
return
self.keybd_focus.keybd(type, detail)
#
def timer(self):
for child in self.timer_interest:
child.timer()
#
# Upcalls from child to parent
#
def addchild(self, child):
if child in self.children:
raise Error, 'addchild: child already inlist'
self.children.append(child)
def delchild(self, child):
if child not in self.children:
raise Error, 'delchild: child not in list'
self.children.remove(child)
if child in self.mouse_interest:
self.mouse_interest.remove(child)
if child in self.keybd_interest:
self.keybd_interest.remove(child)
if child in self.timer_interest:
self.timer_interest.remove(child)
if child in self.altdraw_interest:
self.altdraw_interest.remove(child)
if child == self.mouse_focus:
self.mouse_focus = None
if child == self.keybd_focus:
self.keybd_focus = None
#
def need_mouse(self, child):
if child not in self.mouse_interest:
self.mouse_interest.append(child)
self.parent.need_mouse(self)
def no_mouse(self, child):
if child == self.mouse_focus:
self.mouse_focus = None
if child in self.mouse_interest:
self.mouse_interest.remove(child)
if not self.mouse_interest:
self.parent.no_mouse(self)
#
def need_keybd(self, child):
if child not in self.keybd_interest:
self.keybd_interest.append(child)
self.parent.need_keybd(self)
if not self.keybd_focus:
self.set_keybd_focus(child)
def no_keybd(self, child):
if child == self.keybd_focus:
self.keybd_focus = None # Don't call child.deactivate()
if child in self.keybd_interest:
self.keybd_interest.remove(child)
if not self.keybd_interest:
self.parent.no_keybd(self)
#
def need_timer(self, child):
if child not in self.timer_interest:
self.timer_interest.append(child)
self.parent.need_timer(self)
def no_timer(self, child):
if child in self.timer_interest:
self.timer_interest.remove(child)
if not self.timer_interest:
self.parent.no_timer(self)
#
def need_altdraw(self, child):
if child not in self.altdraw_interest:
self.altdraw_interest.append(child)
self.parent.need_altdraw(self)
def no_altdraw(self, child):
if child in self.altdraw_interest:
self.altdraw_interest.remove(child)
if not self.altdraw_interest:
self.parent.no_altdraw(self)
#
# The rest are transparent:
#
def begindrawing(self):
return self.parent.begindrawing()
def beginmeasuring(self):
return self.parent.beginmeasuring()
def getwindow(self):
return self.parent.getwindow()
#
def change(self, area):
self.parent.change(area)
def scroll(self, area, vector):
self.parent.scroll(area, vector)
def settimer(self, itimer):
self.parent.settimer(itimer)
# Module 'StripChart'
import rect
from Buttons import LabelAppearance, NoReactivity
# A StripChart doesn't really look like a label but it needs a base class.
# LabelAppearance allows it to be disabled and hilited.
class StripChart(LabelAppearance, NoReactivity):
#
def define(self, parent, scale):
self.parent = parent
parent.addchild(self)
self.init_appearance()
self.init_reactivity()
self.ydata = []
self.scale = scale
self.resetbounds()
return self
#
def destroy(self):
self.parent = 0
#
def setbounds(self, bounds):
LabelAppearance.setbounds(self, bounds)
self.resetbounds()
#
def resetbounds(self):
(left, top), (right, bottom) = self.bounds
self.width = right-left
self.height = bottom-top
excess = len(self.ydata) - self.width
if excess > 0:
del self.ydata[:excess]
elif excess < 0:
while len(self.ydata) < self.width:
self.ydata.insert(0, 0)
#
def append(self, y):
self.ydata.append(y)
excess = len(self.ydata) - self.width
if excess > 0:
del self.ydata[:excess]
if self.bounds <> rect.empty:
self.parent.scroll(self.bounds, (-excess, 0))
if self.bounds <> rect.empty:
(left, top), (right, bottom) = self.bounds
i = len(self.ydata)
area = (left+i-1, top), (left+i, bottom)
self.draw(self.parent.begindrawing(), area)
#
def draw(self, d, area):
area = rect.intersect([area, self.bounds])
if area == rect.empty:
return
d.cliprect(area)
d.erase(self.bounds)
(a_left, a_top), (a_right, a_bottom) = area
(left, top), (right, bottom) = self.bounds
height = bottom - top
i1 = a_left - left
i2 = a_right - left
for i in range(max(0, i1), min(len(self.ydata), i2)):
split = bottom-self.ydata[i]*height/self.scale
d.paint((left+i, split), (left+i+1, bottom))
if not self.enabled:
self.flipenable(d)
if self.hilited:
self.fliphilite(d)
d.noclip()
# Text editing widget
# NB: this always assumes fixed bounds.
# For auto-growing TextEdit windows, different code would be needed.
from stdwinevents import *
class TextEdit:
#
def create(self, parent, (cols, rows)):
parent.addchild(self)
self.parent = parent
self.cols = cols
self.rows = rows
self.text = ''
# Creation of the editor is done in realize()
self.editor = None
self.dh = self.dv = 0
return self
#
def createboxed(self, parent, (cols, rows), (dh, dv)):
self = self.create(parent, (cols, rows))
self.dh = max(0, dh)
self.dv = max(0, dv)
return self
#
def settext(self, text):
self.editor.settext(text)
#
def gettext(self):
return self.editor.gettext(text)
#
# Downcalls from parent to child
#
def destroy(self):
del self.parent
del self.editor
del self.window
#
def getminsize(self, m, (width, height)):
width = max(0, width - 2*self.dh)
height = max(0, height - 2*self.dv)
if width > 0 and self.editor:
(left, top), (right, bottom) = self.editor.getrect()
act_width, act_height = right - left, bottom - top
if width >= act_width:
width = width + 2*self.dh
height = max(height, act_height) + 2*self.dv
return width, height
width = max(width, self.cols*m.textwidth('in')/2) + 2*self.dh
height = max(height, self.rows*m.lineheight()) + 2*self.dv
return width, height
#
def setbounds(self, bounds):
self.bounds = bounds
if self.editor:
(left, top), (right, bottom) = bounds
left = left + self.dh
top = top + self.dv
right = right - self.dh
bottom = bottom - self.dv
self.editor.move((left, top), (right, bottom))
if self.dh and self.dv:
(left, top), (right, bottom) = bounds
left = left + 1
top = top + 1
right = right - 1
bottom = bottom - 1
bounds = (left, top), (right, bottom)
self.editor.setview(bounds)
#
def getbounds(self):
return self.bounds
#
def realize(self):
self.window = self.parent.getwindow()
(left, top), (right, bottom) = self.bounds
left = left + self.dh
top = top + self.dv
right = right - self.dh
bottom = bottom - self.dv
self.editor = \
self.window.textcreate((left, top), (right, bottom))
self.editor.setactive(0)
bounds = self.bounds
if self.dh and self.dv:
(left, top), (right, bottom) = bounds
left = left + 1
top = top + 1
right = right - 1
bottom = bottom - 1
bounds = (left, top), (right, bottom)
self.editor.setview(bounds)
self.editor.settext(self.text)
self.parent.need_mouse(self)
self.parent.need_keybd(self)
self.parent.need_altdraw(self)
#
def draw(self, d, area):
if self.dh and self.dv:
d.box(self.bounds)
#
def altdraw(self, area):
self.editor.draw(area)
#
# Event downcalls
#
def mouse_down(self, detail):
x = self.editor.event(WE_MOUSE_DOWN, self.window, detail)
#
def mouse_move(self, detail):
x = self.editor.event(WE_MOUSE_MOVE, self.window, detail)
#
def mouse_up(self, detail):
x = self.editor.event(WE_MOUSE_UP, self.window, detail)
#
def keybd(self, type, detail):
x = self.editor.event(type, self.window, detail)
#
def activate(self):
self.editor.setfocus(0, 30000)
self.editor.setactive(1)
#
def deactivate(self):
self.editor.setactive(0)
#
# A class that sits transparently between a parent and one child.
# First create the parent, then this thing, then the child.
# Use this as a base class for objects that are almost transparent.
# Don't use as a base class for parents with multiple children.
Error = 'TransParent.Error' # Exception
class ManageOneChild:
#
# Upcalls shared with other single-child parents
#
def addchild(self, child):
if self.child:
raise Error, 'addchild: one child only'
if not child:
raise Error, 'addchild: bad child'
self.child = child
#
def delchild(self, child):
if not self.child:
raise Error, 'delchild: no child'
if child <> self.child:
raise Error, 'delchild: not my child'
self.child = 0
class TransParent(ManageOneChild):
#
# Calls from creator
# NB derived classes may add parameters to create()
#
def create(self, parent):
parent.addchild(self)
self.parent = parent
self.child = None # No child yet
return self
#
# Downcalls from parent to child
#
def destroy(self):
del self.parent
if self.child: self.child.destroy()
del self.child
#
def getminsize(self, args):
if not self.child:
m, size = args
return size
else:
return self.child.getminsize(args)
def getbounds(self, bounds):
if not self.child:
raise Error, 'getbounds w/o child'
else:
return self.child.getbounds()
def setbounds(self, bounds):
if not self.child:
raise Error, 'setbounds w/o child'
else:
self.child.setbounds(bounds)
def realize(self):
if self.child:
self.child.realize()
def draw(self, d, area):
if self.child:
self.child.draw(d, area)
def altdraw(self, area):
if self.child:
self.child.altdraw(area)
#
# Downcalls only made after certain upcalls
#
def mouse_down(self, detail):
if self.child: self.child.mouse_down(detail)
def mouse_move(self, detail):
if self.child: self.child.mouse_move(detail)
def mouse_up(self, detail):
if self.child: self.child.mouse_up(detail)
#
def keybd(self, type_detail):
self.child.keybd(type_detail)
def activate(self):
self.child.activate()
def deactivate(self):
self.child.deactivate()
#
def timer(self):
if self.child: self.child.timer()
#
# Upcalls from child to parent
#
def need_mouse(self, child):
self.parent.need_mouse(self)
def no_mouse(self, child):
self.parent.no_mouse(self)
#
def need_timer(self, child):
self.parent.need_timer(self)
def no_timer(self, child):
self.parent.no_timer(self)
#
def need_altdraw(self, child):
self.parent.need_altdraw(self)
def no_altdraw(self, child):
self.parent.no_altdraw(self)
#
def need_keybd(self, child):
self.parent.need_keybd(self)
def no_keybd(self, child):
self.parent.no_keybd(self)
#
def begindrawing(self):
return self.parent.begindrawing()
def beginmeasuring(self):
return self.parent.beginmeasuring()
def getwindow(self):
return self.parent.getwindow()
#
def change(self, area):
self.parent.change(area)
def scroll(self, area, vector):
self.parent.scroll(area, vector)
def settimer(self, itimer):
self.parent.settimer(itimer)
# Module 'VUMeter'
import audio
from StripChart import StripChart
K = 1024
Rates = [0, 32*K, 16*K, 8*K]
class VUMeter(StripChart):
#
# Override define() and timer() methods
#
def define(self, parent):
self = StripChart.define(self, (parent, 128))
self.parent.need_timer(self)
self.sampling = 0
self.rate = 3
self.enable(0)
return self
#
def timer(self):
if self.sampling:
chunk = audio.wait_recording()
self.sampling = 0
nums = audio.chr2num(chunk)
ampl = max(abs(min(nums)), abs(max(nums)))
self.append(ampl)
if self.enabled and not self.sampling:
audio.setrate(self.rate)
size = Rates[self.rate]/10
size = size/48*48
audio.start_recording(size)
self.sampling = 1
if self.sampling:
self.parent.settimer(1)
#
# New methods: start() and stop()
#
def stop(self):
if self.sampling:
chunk = audio.stop_recording()
self.sampling = 0
self.enable(0)
#
def start(self):
self.enable(1)
self.timer()
# A 'WindowParent' is the only module that uses real stdwin functionality.
# It is the root of the tree.
# It should have exactly one child when realized.
#
# There is also an alternative interface to "mainloop" here.
import stdwin
from stdwinevents import *
import mainloop
from TransParent import ManageOneChild
Error = 'WindowParent.Error' # Exception
class WindowParent(ManageOneChild):
#
def create(self, title, size):
self.title = title
self.size = size # (width, height)
self._reset()
self.close_hook = WindowParent.delayed_destroy
return self
#
def _reset(self):
self.child = None
self.win = None
self.itimer = 0
self.do_mouse = 0
self.do_keybd = 0
self.do_timer = 0
self.do_altdraw = 0
self.pending_destroy = 0
self.close_hook = None
self.menu_hook = None
#
def destroy(self):
mainloop.unregister(self.win)
if self.child: self.child.destroy()
self._reset()
#
def delayed_destroy(self):
# This interface to be used by 'Close' buttons etc.;
# destroying a window from within a button hook
# is not a good idea...
self.pending_destroy = 1
#
def close_trigger(self):
if self.close_hook: self.close_hook(self)
#
def menu_trigger(self, menu, item):
if self.menu_hook:
self.menu_hook(self, menu, item)
#
def need_mouse(self, child): self.do_mouse = 1
def no_mouse(self, child): self.do_mouse = 0
#
def need_keybd(self, child):
self.do_keybd = 1
self.child.activate()
def no_keybd(self, child):
self.do_keybd = 0
self.child.deactivate()
#
def need_timer(self, child): self.do_timer = 1
def no_timer(self, child): self.do_timer = 0
#
def need_altdraw(self, child): self.do_altdraw = 1
def no_altdraw(self, child): self.do_altdraw = 0
#
def realize(self):
if self.win:
raise Error, 'realize(): called twice'
if not self.child:
raise Error, 'realize(): no child'
# Compute suggested size
self.size = self.child.getminsize(self.beginmeasuring(), \
self.size)
save_defsize = stdwin.getdefwinsize()
scrwidth, scrheight = stdwin.getscrsize()
width, height = self.size
if width > scrwidth:
width = scrwidth * 2/3
if height > scrheight:
height = scrheight * 2/3
stdwin.setdefwinsize(width, height)
self.hbar, self.vbar = stdwin.getdefscrollbars()
self.win = stdwin.open(self.title)
stdwin.setdefwinsize(save_defsize)
self.win.setdocsize(self.size)
if self.itimer:
self.win.settimer(self.itimer)
width, height = self.win.getwinsize()
if self.hbar:
width = self.size[0]
if self.vbar:
height = self.size[1]
self.child.setbounds(((0, 0), (width, height)))
self.child.realize()
self.win.dispatch = self.dispatch
mainloop.register(self.win)
#
def fixup(self):
# XXX This could share code with realize() above
self.size = self.child.getminsize(self.beginmeasuring(), \
self.win.getwinsize())
self.win.setdocsize(self.size)
width, height = self.win.getwinsize()
if self.hbar:
width = self.size[0]
if self.vbar:
height = self.size[1]
self.child.setbounds(((0, 0), (width, height)))
# Force a redraw of the entire window:
self.win.change((0, 0), self.size)
#
def beginmeasuring(self):
# Return something with which a child can measure text
if self.win:
return self.win.begindrawing()
else:
return stdwin
#
def begindrawing(self):
if self.win:
return self.win.begindrawing()
else:
raise Error, 'begindrawing(): not realized yet'
#
def getwindow(self):
if self.win:
return self.win
else:
raise Error, 'getwindow(): not realized yet'
#
def change(self, area):
if self.win:
self.win.change(area)
#
def scroll(self, area, vector):
if self.win:
self.win.scroll(area, vector)
#
def settimer(self, itimer):
if self.win:
self.win.settimer(itimer)
else:
self.itimer = itimer
#
# Only call dispatch once we are realized
#
def dispatch(self, (type, win, detail)):
if type == WE_DRAW:
d = self.win.begindrawing()
self.child.draw(d, detail)
del d
if self.do_altdraw: self.child.altdraw(detail)
elif type == WE_MOUSE_DOWN:
if self.do_mouse: self.child.mouse_down(detail)
elif type == WE_MOUSE_MOVE:
if self.do_mouse: self.child.mouse_move(detail)
elif type == WE_MOUSE_UP:
if self.do_mouse: self.child.mouse_up(detail)
elif type in (WE_CHAR, WE_COMMAND):
if self.do_keybd: self.child.keybd(type, detail)
elif type == WE_TIMER:
if self.do_timer: self.child.timer()
elif type == WE_SIZE:
self.fixup()
elif type == WE_CLOSE:
self.close_trigger()
elif type == WE_MENU:
self.menu_trigger(detail)
if self.pending_destroy:
self.destroy()
#
def MainLoop():
mainloop.mainloop()
def Dispatch(event):
mainloop.dispatch(event)
# Interface used by WindowSched:
def CountWindows():
return mainloop.countwindows()
def AnyWindow():
return mainloop.anywindow()
# Combine a real-time scheduling queue and stdwin event handling.
# Keeps times in milliseconds.
import stdwin, stdwinq
from stdwinevents import WE_TIMER
import mainloop
import sched
import time
# Delay function called by the scheduler when it has nothing to do.
# Return immediately when something is done, or when the delay is up.
#
def delayfunc(msecs):
msecs = int(msecs)
#
# Check for immediate stdwin event
#
event = stdwinq.pollevent()
if event:
mainloop.dispatch(event)
return
#
# Use sleep for very short delays or if there are no windows
#
if msecs < 100 or mainloop.countwindows() == 0:
if msecs > 0:
time.sleep(msecs * 0.001)
return
#
# Post a timer event on an arbitrary window and wait for it
#
window = mainloop.anywindow()
window.settimer(msecs/100)
event = stdwinq.getevent()
window.settimer(0)
if event[0] <> WE_TIMER:
mainloop.dispatch(event)
def millitimer():
return time.time() * 1000
q = sched.scheduler(millitimer, delayfunc)
# Export functions enter, enterabs and cancel just like a scheduler
#
enter = q.enter
enterabs = q.enterabs
cancel = q.cancel
# Emptiness check must check both queues
#
def empty():
return q.empty() and mainloop.countwindows() == 0
# Run until there is nothing left to do
#
def run():
while not empty():
if q.empty():
mainloop.dispatch(stdwinq.getevent())
else:
q.run()
# Module 'anywin'
# Open a file or directory in a window
import dirwin
import filewin
import os
def open(name):
print 'opening', name, '...'
if os.path.isdir(name):
w = dirwin.open(name)
else:
w = filewin.open(name)
return w
# basewin.py
import stdwin
import mainloop
from stdwinevents import *
class BaseWindow:
def __init__(self, title):
self.win = stdwin.open(title)
self.win.dispatch = self.dispatch
mainloop.register(self.win)
# def reopen(self):
# title = self.win.gettitle()
# winpos = self.win.getwinpos()
# winsize = self.win.getwinsize()
# origin = self.win.getorigin()
# docsize = self.win.getdocsize()
# mainloop.unregister(self.win)
# del self.win.dispatch
# self.win.close()
# stdwin.setdefwinpos(winpos)
# stdwin.setdefwinsize(winsize)
# self.win = stdwin.open(title)
# stdwin.setdefwinpos(0, 0)
# stdwin.setdefwinsize(0, 0)
# self.win.setdocsize(docsize)
# self.win.setorigin(origin)
# self.win.dispatch = self.dispatch
# mainloop.register(self.win)
def popup(self):
if self.win is not stdwin.getactive():
self.win.setactive()
def close(self):
mainloop.unregister(self.win)
del self.win.dispatch
self.win.close()
def dispatch(self, event):
type, win, detail = event
if type == WE_CHAR:
self.char(detail)
elif type == WE_COMMAND:
self.command(detail)
elif type == WE_MOUSE_DOWN:
self.mouse_down(detail)
elif type == WE_MOUSE_MOVE:
self.mouse_move(detail)
elif type == WE_MOUSE_UP:
self.mouse_up(detail)
elif type == WE_DRAW:
self.draw(detail)
elif type == WE_CLOSE:
self.close()
def no_op(self, detail):
pass
char = command = mouse_down = mouse_move = mouse_up = draw = no_op
def refreshall(self):
self.win.change((-10, 0), (10000, 30000))
# Module 'dirwin'
# Directory windows, a subclass of listwin
import os
import gwin
import listwin
import anywin
import dircache
def action(w, string, i, detail):
(h, v), clicks, button, mask = detail
if clicks == 2:
name = os.path.join(w.name, string)
try:
w2 = anywin.open(name)
w2.parent = w
except os.error, why:
stdwin.message('Can\'t open ' + name + ': ' + why[1])
def open(name):
name = os.path.join(name, '')
list = dircache.opendir(name)[:]
list.sort()
dircache.annotate(name, list)
w = listwin.open(name, list)
w.name = name
w.action = action
return w
# Module 'filewin'
# File windows, a subclass of textwin (which is a subclass of gwin)
import textwin
import __builtin__
# FILE WINDOW
def open_readonly(fn): # Open a file window
fp = __builtin__.open(fn, 'r')
w = textwin.open_readonly(fn, fp.read())
w.fn = fn
return w
def open(fn): # Open a file window
fp = __builtin__.open(fn, 'r')
w = textwin.open(fn, fp.read())
w.fn = fn
return w
# A class to help applications that do fancy text formatting.
# You create an instance each time you must redraw the window.
# Set the initial left, top and right coordinates;
# then feed it words, font changes and vertical movements.
#
# This class should eventually be extended to support much fancier
# formatting, along the lines of TeX; for now, a very simple model
# is sufficient.
#
class formatter:
#
# Initialize a formatter instance.
# Pass the window's drawing object, and left, top, right
# coordinates of the drawing space as arguments.
#
def __init__(self, d, left, top, right):
self.d = d # Drawing object
self.left = left # Left margin
self.right = right # Right margin
self.v = top # Top of current line
self.center = 0
self.justify = 1
self.setfont('') # Default font
self._reset() # Prepare for new line
#
# Reset for start of fresh line.
#
def _reset(self):
self.boxes = [] # Boxes and glue still to be output
self.sum_width = 0 # Total width of boxes
self.sum_space = 0 # Total space between boxes
self.sum_stretch = 0 # Total stretch for space between boxes
self.max_ascent = 0 # Max ascent of current line
self.max_descent = 0 # Max descent of current line
self.avail_width = self.right - self.left
self.hang_indent = 0
#
# Set the current font, and compute some values from it.
#
def setfont(self, font):
self.font = font
self.d.setfont(font)
self.font_space = self.d.textwidth(' ')
self.font_ascent = self.d.baseline()
self.font_descent = self.d.lineheight() - self.font_ascent
#
# Add a word to the list of boxes; first flush if line is full.
# Space and stretch factors are expressed in fractions
# of the current font's space width.
# (Two variations: one without, one with explicit stretch factor.)
#
def addword(self, word, spacefactor):
self.addwordstretch(word, spacefactor, spacefactor)
#
def addwordstretch(self, word, spacefactor, stretchfactor):
width = self.d.textwidth(word)
if width > self.avail_width:
self._flush(1)
space = int(float(self.font_space) * float(spacefactor))
stretch = int(float(self.font_space) * float(stretchfactor))
box = (self.font, word, width, space, stretch)
self.boxes.append(box)
self.sum_width = self.sum_width + width
self.sum_space = self.sum_space + space
self.sum_stretch = self.sum_stretch + stretch
self.max_ascent = max(self.font_ascent, self.max_ascent)
self.max_descent = max(self.font_descent, self.max_descent)
self.avail_width = self.avail_width - width - space
#
# Flush current line and start a new one.
# Flushing twice is harmless (i.e. does not introduce a blank line).
# (Two versions: the internal one has a parameter for justification.)
#
def flush(self):
self._flush(0)
#
def _flush(self, justify):
if not self.boxes:
return
#
# Compute amount of stretch needed.
#
if justify and self.justify or self.center:
#
# Compute extra space to fill;
# this is avail_width plus glue from last box.
# Also compute available stretch.
#
last_box = self.boxes[len(self.boxes)-1]
font, word, width, space, stretch = last_box
tot_extra = self.avail_width + space
tot_stretch = self.sum_stretch - stretch
else:
tot_extra = tot_stretch = 0
#
# Output the boxes.
#
baseline = self.v + self.max_ascent
h = self.left + self.hang_indent
if self.center:
h = h + tot_extra / 2
tot_extra = tot_stretch = 0
for font, word, width, space, stretch in self.boxes:
self.d.setfont(font)
v = baseline - self.d.baseline()
self.d.text((h, v), word)
h = h + width + space
if tot_extra > 0 and tot_stretch > 0:
extra = stretch * tot_extra / tot_stretch
h = h + extra
tot_extra = tot_extra - extra
tot_stretch = tot_stretch - stretch
#
# Prepare for next line.
#
self.v = baseline + self.max_descent
self.d.setfont(self.font)
self._reset()
#
# Add vertical space; first flush.
# Vertical space is expressed in fractions of the current
# font's line height.
#
def vspace(self, lines):
self.vspacepixels(int(lines * self.d.lineheight()))
#
# Add vertical space given in pixels.
#
def vspacepixels(self, dv):
self.flush()
self.v = self.v + dv
#
# Set temporary (hanging) indent, for paragraph start.
# First flush.
#
def tempindent(self, space):
self.flush()
hang = int(float(self.font_space) * float(space))
self.hang_indent = hang
self.avail_width = self.avail_width - hang
#
# Add (permanent) left indentation. First flush.
#
def addleftindent(self, space):
self.flush()
self.left = self.left \
+ int(float(self.font_space) * float(space))
self._reset()
#
# Test procedure
#
def test():
import stdwin, stdwinq
from stdwinevents import *
try:
import mac
# Mac font assignments:
font1 = 'times', '', 12
font2 = 'times', 'b', 14
except ImportError:
# X11R4 font assignments
font1 = '*times-medium-r-*-120-*'
font2 = '*times-bold-r-*-140-*'
words = \
['The','quick','brown','fox','jumps','over','the','lazy','dog.']
words = words * 2
stage = 0
stages = [(0,0,'ragged'), (1,0,'justified'), (0,1,'centered')]
justify, center, title = stages[stage]
stdwin.setdefwinsize(300,200)
w = stdwin.open(title)
winsize = w.getwinsize()
while 1:
type, window, detail = stdwinq.getevent()
if type == WE_CLOSE:
break
elif type == WE_SIZE:
newsize = w.getwinsize()
if newsize <> winsize:
w.change((0,0), winsize)
winsize = newsize
w.change((0,0), winsize)
elif type == WE_MOUSE_DOWN:
stage = (stage + 1) % len(stages)
justify, center, title = stages[stage]
w.settitle(title)
w.change((0, 0), (1000, 1000))
elif type == WE_DRAW:
width, height = winsize
f = formatter(w.begindrawing(), 0, 0, width)
f.center = center
f.justify = justify
if not center:
f.tempindent(5)
for font in font1, font2, font1:
f.setfont(font)
for word in words:
space = 1 + (word[-1:] == '.')
f.addword(word, space)
if center and space > 1:
f.flush()
f.flush()
height = f.v
del f
w.setdocsize(0, height)
# Module 'gwin'
# Generic stdwin windows
# This is used as a base class from which to derive other window types.
# XXX DON'T USE THIS CODE ANY MORE! It is ages old!
import stdwin, stdwinq
from stdwinevents import *
from mainloop import mainloop, register, unregister, windows
# Open a window
def open(title): # Open a generic window
w = stdwin.open(title)
stdwin.setdefwinsize(0, 0)
# Set default event handlers
w.draw = nop
w.char = nop
w.mdown = nop
w.mmove = nop
w.mup = nop
w.m2down = m2down
w.m2up = m2up
w.size = nop
w.move = nop
w.activate = w.deactivate = nop
w.timer = nop
# default command handlers
w.close = close
w.tab = tab
w.enter = enter
w.backspace = backspace
w.arrow = arrow
w.kleft = w.kup = w.kright = w.kdown = nop
w.dispatch = treatevent
register(w)
return w
def treatevent(e): # Handle a stdwin event
type, w, detail = e
if type == WE_DRAW:
w.draw(w, detail)
elif type == WE_MENU:
m, item = detail
m.action[item](w, m, item)
elif type == WE_COMMAND:
treatcommand(w, detail)
elif type == WE_CHAR:
w.char(w, detail)
elif type == WE_MOUSE_DOWN:
if detail[1] > 1: w.m2down(w, detail)
else: w.mdown(w, detail)
elif type == WE_MOUSE_MOVE:
w.mmove(w, detail)
elif type == WE_MOUSE_UP:
if detail[1] > 1: w.m2up(w, detail)
else: w.mup(w, detail)
elif type == WE_SIZE:
w.size(w, w.getwinsize())
elif type == WE_ACTIVATE:
w.activate(w)
elif type == WE_DEACTIVATE:
w.deactivate(w)
elif type == WE_MOVE:
w.move(w)
elif type == WE_TIMER:
w.timer(w)
elif type == WE_CLOSE:
w.close(w)
def treatcommand(w, type): # Handle a we_command event
if type == WC_CLOSE:
w.close(w)
elif type == WC_RETURN:
w.enter(w)
elif type == WC_TAB:
w.tab(w)
elif type == WC_BACKSPACE:
w.backspace(w)
elif type in (WC_LEFT, WC_UP, WC_RIGHT, WC_DOWN):
w.arrow(w, type)
# Methods
def close(w): # Close method
unregister(w)
del w.close # Delete our close function
w.close() # Call the close method
def arrow(w, detail): # Arrow key method
if detail == WC_LEFT:
w.kleft(w)
elif detail == WC_UP:
w.kup(w)
elif detail == WC_RIGHT:
w.kright(w)
elif detail == WC_DOWN:
w.kdown(w)
# Trivial methods
def tab(w): w.char(w, '\t')
def enter(w): w.char(w, '\n') # 'return' is a Python reserved word
def backspace(w): w.char(w, '\b')
def m2down(w, detail): w.mdown(w, detail)
def m2up(w, detail): w.mup(w, detail)
def nop(*args): pass
# Module 'listwin'
# List windows, a subclass of gwin
import gwin
import stdwin
def maxlinewidth(a): # Compute maximum textwidth of lines in a sequence
max = 0
for line in a:
width = stdwin.textwidth(line)
if width > max: max = width
return max
def action(w, string, i, detail): # Default item selection method
pass
def mup(w, detail): # Mouse up method
(h, v), clicks, button, mask = detail
i = divmod(v, w.lineheight)[0]
if 0 <= i < len(w.data):
w.action(w, w.data[i], i, detail)
def draw(w, ((left, top), (right, bottom))): # Text window draw method
data = w.data
d = w.begindrawing()
lh = w.lineheight
itop = top/lh
ibot = (bottom-1)/lh + 1
if itop < 0: itop = 0
if ibot > len(data): ibot = len(data)
for i in range(itop, ibot): d.text((0, i*lh), data[i])
def open(title, data): # Display a list of texts in a window
lineheight = stdwin.lineheight()
h, v = maxlinewidth(data), len(data)*lineheight
h0, v0 = h + stdwin.textwidth(' '), v + lineheight
if h0 > stdwin.textwidth(' ')*80: h0 = 0
if v0 > stdwin.lineheight()*24: v0 = 0
stdwin.setdefwinsize(h0, v0)
w = gwin.open(title)
w.setdocsize(h, v)
w.lineheight = lineheight
w.data = data
w.draw = draw
w.action = action
w.mup = mup
return w
# Standard main loop for *all* STDWIN applications.
# This requires that applications:
# - register their windows on creation and unregister them when closed
# - have a 'dispatch' function as a window member
import stdwin, stdwinq
from stdwinevents import *
# List of windows known to the main loop.
#
windows = []
# Last window that ever received an event
#
last_window = None
# Function to register a window.
#
def register(win):
# First test the dispatch function by passing it a null event --
# this catches registration of unconforming windows.
win.dispatch((WE_NULL, win, None))
if win not in windows:
windows.append(win)
# Function to unregister a window.
# It is not an error to unregister an already unregistered window
# (this is useful for cleanup actions).
#
def unregister(win):
global last_window
if win == last_window:
last_window = None
if win in windows:
windows.remove(win) # Not in 0.9.1
# 0.9.1 solution:
#for i in range(len(windows)):
# if windows[i] = win:
# del windows[i]
# break
# Interfaces used by WindowSched.
#
def countwindows():
return len(windows)
#
def anywindow():
if windows:
return windows[0]
else:
return None
# NEW: register any number of file descriptors
#
fdlist = []
select_args = None
select_handlers = None
#
def registerfd(fd, mode, handler):
if mode not in ('r', 'w', 'x'):
raise ValueError, 'mode must be r, w or x'
if type(fd) <> type(0):
fd = fd.fileno() # If this fails it's not a proper select arg
for i in range(len(fdlist)):
if fdlist[i][:2] == (fd, mode):
raise ValueError, \
'(fd, mode) combination already registered'
fdlist.append((fd, mode, handler))
make_select_args()
#
def unregisterfd(fd, *args):
if type(fd) <> type(0):
fd = fd.fileno() # If this fails it's not a proper select arg
args = (fd,) + args
n = len(args)
for i in range(len(fdlist)):
if fdlist[i][:n] == args:
del fdlist[i]
make_select_args()
#
def make_select_args():
global select_args, select_handlers
rlist, wlist, xlist = [], [], []
rhandlers, whandlers, xhandlers = {}, {}, {}
for fd, mode, handler in fdlist:
if mode == 'r':
rlist.append(fd)
rhandlers[`fd`] = handler
if mode == 'w':
wlist.append(fd)
whandlers[`fd`] = handler
if mode == 'x':
xlist.append(fd)
xhandlers[`fd`] = handler
if rlist or wlist or xlist:
select_args = rlist, wlist, xlist
select_handlers = rhandlers, whandlers, xhandlers
else:
select_args = None
select_handlers = None
#
def do_select():
import select
reply = apply(select.select, select_args)
for mode in 0, 1, 2:
list = reply[mode]
for fd in list:
handler = select_handlers[mode][`fd`]
handler(fd, 'rwx'[mode])
# Event processing main loop.
# Return when there are no windows left, or when an unhandled
# exception occurs. (It is safe to restart the main loop after
# an unsuccessful exit.)
# Python's stdwin.getevent() turns WE_COMMAND/WC_CANCEL events
# into KeyboardInterrupt exceptions; these are turned back in events.
#
recursion_level = 0 # Hack to make it reentrant
def mainloop():
global recursion_level
recursion_level = recursion_level + 1
try:
stdwin_select_handler() # Process events already in queue
while 1:
if windows and not fdlist:
while windows and not fdlist:
try:
event = stdwinq.getevent()
except KeyboardInterrupt:
event = (WE_COMMAND, \
None, WC_CANCEL)
dispatch(event)
elif windows and fdlist:
fd = stdwin.fileno()
if recursion_level == 1:
registerfd(fd, 'r', stdwin_select_handler)
try:
while windows:
do_select()
stdwin_select_handler()
finally:
if recursion_level == 1:
unregisterfd(fd)
elif fdlist:
while fdlist and not windows:
do_select()
else:
break
finally:
recursion_level = recursion_level - 1
# Check for events without ever blocking
#
def check():
stdwin_select_handler()
# XXX Should check for socket stuff as well
# Handle stdwin events until none are left
#
def stdwin_select_handler(*args):
while 1:
try:
event = stdwinq.pollevent()
except KeyboardInterrupt:
event = (WE_COMMAND, None, WC_CANCEL)
if event is None:
break
dispatch(event)
# Run a modal dialog loop for a window. The dialog window must have
# been registered first. This prohibits most events (except size/draw
# events) to other windows. The modal dialog loop ends when the
# dialog window unregisters itself.
#
passthrough = WE_SIZE, WE_DRAW
beeping = WE_MOUSE_DOWN, WE_COMMAND, WE_CHAR, WE_KEY, WE_CLOSE, WE_MENU
#
def modaldialog(window):
if window not in windows:
raise ValueError, 'modaldialog window not registered'
while window in windows:
try:
event = stdwinq.getevent()
except KeyboardInterrupt:
event = WE_COMMAND, None, WC_CANCEL
etype, ewindow, edetail = event
if etype not in passthrough and ewindow <> window:
if etype in beeping:
stdwin.fleep()
continue
dispatch(event)
# Dispatch a single event.
# Events for the no window in particular are sent to the active window
# or to the last window that received an event (these hacks are for the
# WE_LOST_SEL event, which is directed to no particular window).
# Windows not in the windows list don't get their events:
# events for such windows are silently ignored.
#
def dispatch(event):
global last_window
if event[1] == None:
active = stdwin.getactive()
if active: last_window = active
else:
last_window = event[1]
if last_window in windows:
last_window.dispatch(event)
# Dialog base class
#
class Dialog:
#
def __init__(self, title):
self.window = stdwin.open(title)
self.window.dispatch = self.dispatch
register(self.window)
#
def close(self):
unregister(self.window)
del self.window.dispatch
self.window.close()
#
def dispatch(self, event):
etype, ewindow, edetail = event
if etype == WE_CLOSE:
self.close()
# Standard modal dialogs
# XXX implemented using stdwin dialogs for now
#
def askstr(prompt, default):
return stdwin.askstr(prompt, default)
#
def askync(prompt, yesorno):
return stdwin.askync(prompt, yesorno)
#
def askfile(prompt, default, new):
return stdwin.askfile(prompt, default, new)
#
def message(msg):
stdwin.message(msg)
# Module 'rect'.
#
# Operations on rectangles.
# There is some normalization: all results return the object 'empty'
# if their result would contain no points.
# Exception.
#
error = 'rect.error'
# The empty rectangle.
#
empty = (0, 0), (0, 0)
# Check if a rectangle is empty.
#
def is_empty(r):
(left, top), (right, bottom) = r
return left >= right or top >= bottom
# Compute the intersection or two or more rectangles.
# This works with a list or tuple argument.
#
def intersect(list):
if not list: raise error, 'intersect called with empty list'
if is_empty(list[0]): return empty
(left, top), (right, bottom) = list[0]
for rect in list[1:]:
if is_empty(rect):
return empty
(l, t), (r, b) = rect
if left < l: left = l
if top < t: top = t
if right > r: right = r
if bottom > b: bottom = b
if is_empty(((left, top), (right, bottom))):
return empty
return (left, top), (right, bottom)
# Compute the smallest rectangle containing all given rectangles.
# This works with a list or tuple argument.
#
def union(list):
(left, top), (right, bottom) = list[0]
for (l, t), (r, b) in list[1:]:
if not is_empty(((l, t), (r, b))):
if l < left: left = l
if t < top: top = t
if r > right: right = r
if b > bottom: bottom = b
res = (left, top), (right, bottom)
if is_empty(res):
return empty
return res
# Check if a point is in a rectangle.
#
def pointinrect((h, v), ((left, top), (right, bottom))):
return left <= h < right and top <= v < bottom
# Return a rectangle that is dh, dv inside another
#
def inset(((left, top), (right, bottom)), (dh, dv)):
left = left + dh
top = top + dv
right = right - dh
bottom = bottom - dv
r = (left, top), (right, bottom)
if is_empty(r):
return empty
else:
return r
# Conversions between rectangles and 'geometry tuples',
# given as origin (h, v) and dimensions (width, height).
#
def rect2geom((left, top), (right, bottom)):
return (left, top), (right-left, bottom-top)
def geom2rect((h, v), (width, height)):
return (h, v), (h+width, v+height)
# srcwin.py -- a source listing window
import stdwin
from stdwinevents import *
import basewin
WIDTH = 40
MAXHEIGHT = 24
class TextWindow(basewin.BaseWindow):
def __init__(self, title, contents):
self.contents = contents
self.linecount = countlines(self.contents)
#
self.lineheight = lh = stdwin.lineheight()
self.leftmargin = self.getmargin()
self.top = 0
self.rightmargin = 30000 # Infinity
self.bottom = lh * self.linecount
#
width = WIDTH*stdwin.textwidth('0')
height = lh*min(MAXHEIGHT, self.linecount)
stdwin.setdefwinsize(width, height)
basewin.BaseWindow.__init__(self, title)
#
self.win.setdocsize(0, self.bottom)
self.initeditor()
def initeditor(self):
r = (self.leftmargin, self.top), (self.rightmargin, self.bottom)
self.editor = self.win.textcreate(r)
self.editor.settext(self.contents)
def closeeditor(self):
self.editor.close()
# def reopen(self):
# self.closeeditor()
# basewin.BaseWindow.reopen(self)
# self.initeditor()
# Override the following two methods to format line numbers differently
def getmark(self, lineno):
return `lineno`
def getmargin(self):
return stdwin.textwidth(`self.linecount + 1` + ' ')
# Event dispatcher, called from mainloop.mainloop()
def dispatch(self, event):
if event[0] == WE_NULL: return # Dummy tested by mainloop
if event[0] == WE_DRAW or not self.editor.event(event):
basewin.BaseWindow.dispatch(self, event)
# Event handlers
def close(self):
self.closeeditor()
basewin.BaseWindow.close(self)
def draw(self, detail):
dummy = self.editor.draw(detail)
# Draw line numbers
(left, top), (right, bottom) = detail
topline = top/self.lineheight
botline = bottom/self.lineheight + 1
botline = min(self.linecount, botline)
d = self.win.begindrawing()
try:
h, v = 0, self.lineheight * topline
for lineno in range(topline+1, botline+1):
d.text((h, v), self.getmark(lineno))
v = v + self.lineheight
finally:
d.close()
# Calls from outside
def changemark(self, lineno): # redraw the mark for a line
left = 0
top = (lineno-1) * self.lineheight
right = self.leftmargin
bottom = lineno * self.lineheight
d = self.win.begindrawing()
try:
d.erase((left, top), (right, bottom))
d.text((left, top), self.getmark(lineno))
finally:
d.close()
def showline(self, lineno): # scroll to make a line visible
left = 0
top = (lineno-1) * self.lineheight
right = self.leftmargin
bottom = lineno * self.lineheight
self.win.show((left, top), (right, bottom))
# Subroutine to count the number of lines in a string
def countlines(text):
n = 0
for c in text:
if c == '\n': n = n+1
if text and text[-1] != '\n': n = n+1 # Partial last line
return n
class SourceWindow(TextWindow):
def __init__(self, filename):
self.filename = filename
f = open(self.filename, 'r')
contents = f.read()
f.close()
TextWindow.__init__(self, self.filename, contents)
# ------------------------------ testing ------------------------------
TESTFILE = 'srcwin.py'
def test():
import mainloop
sw = SourceWindow(TESTFILE)
mainloop.mainloop()
# Module 'stdwinevents' -- Constants for stdwin event types
#
# Suggested usage:
# from stdwinevents import *
# The function stdwin.getevent() returns a tuple containing:
# (type, window, detail)
# where detail may be <no value> or a value depending on type, see below:
# Values for type:
WE_NULL = 0 # not reported -- means 'no event' internally
WE_ACTIVATE = 1 # detail is None
WE_CHAR = 2 # detail is the character
WE_COMMAND = 3 # detail is one of the WC_* constants below
WE_MOUSE_DOWN = 4 # detail is ((h, v), clicks, button, mask)
WE_MOUSE_MOVE = 5 # ditto
WE_MOUSE_UP = 6 # ditto
WE_MENU = 7 # detail is (menu, item)
WE_SIZE = 8 # detail is (width, height)
WE_MOVE = 9 # not reported -- reserved for future use
WE_DRAW = 10 # detail is ((left, top), (right, bottom))
WE_TIMER = 11 # detail is None
WE_DEACTIVATE = 12 # detail is None
WE_EXTERN = 13 # detail is None
WE_KEY = 14 # detail is ???
WE_LOST_SEL = 15 # detail is selection number
WE_CLOSE = 16 # detail is None
# Values for detail when type is WE_COMMAND:
WC_CLOSE = 1 # obsolete; now reported as WE_CLOSE
WC_LEFT = 2 # left arrow key
WC_RIGHT = 3 # right arrow key
WC_UP = 4 # up arrow key
WC_DOWN = 5 # down arrow key
WC_CANCEL = 6 # not reported -- turned into KeyboardInterrupt
WC_BACKSPACE = 7 # backspace key
WC_TAB = 8 # tab key
WC_RETURN = 9 # return or enter key
# Selection numbers
WS_CLIPBOARD = 0
WS_PRIMARY = 1
WS_SECONDARY = 2
# Modifier masks in key and mouse events
WM_SHIFT = (1 << 0)
WM_LOCK = (1 << 1)
WM_CONTROL = (1 << 2)
WM_META = (1 << 3)
WM_OPTION = (1 << 4)
WM_NUM = (1 << 5)
WM_BUTTON1 = (1 << 8)
WM_BUTTON2 = (1 << 9)
WM_BUTTON3 = (1 << 10)
WM_BUTTON4 = (1 << 11)
WM_BUTTON5 = (1 << 12)
# Replacements for getevent() and pollevent(),
# and new functions ungetevent() and sync().
# Every library module should ideally use this instead of
# stdwin.{get,poll}event(), so applications can use the services
# of ungetevent() and sync().
import stdwin
# Events read ahead are stored in this queue.
#
queue = []
# Replacement for getevent().
#
def getevent():
if queue:
event = queue[0]
del queue[0]
return event
else:
return stdwin.getevent()
# Replacement for pollevent().
#
def pollevent():
if queue:
return getevent()
else:
return stdwin.pollevent()
# Push an event back in the queue.
#
def ungetevent(event):
queue.insert(0, event)
# Synchronize the display. It turns out that this is the way to
# force STDWIN to call XSync(), which some (esoteric) applications need.
# (This is stronger than just flushing -- it actually waits for a
# positive response from the X server on the last command issued.)
#
def sync():
while 1:
event = stdwin.pollevent()
if not event: break
queue.append(event)
# Module 'tablewin'
# Display a table, with per-item actions:
# A1 | A2 | A3 | .... | AN
# B1 | B2 | B3 | .... | BN
# C1 | C2 | C3 | .... | CN
# .. | .. | .. | .... | ..
# Z1 | Z2 | Z3 | .... | ZN
# Not all columns need to have the same length.
# The data structure is a list of columns;
# each column is a list of items.
# Each item is a pair of a string and an action procedure.
# The first item may be a column title.
import stdwin
import gwin
from stdwinevents import *
def open(title, data): # Public function to open a table window
#
# Set geometry parameters (one day, these may be changeable)
#
margin = stdwin.textwidth(' ')
lineheight = stdwin.lineheight()
#
# Geometry calculations
#
colstarts = [0]
totwidth = 0
maxrows = 0
for coldata in data:
# Height calculations
rows = len(coldata)
if rows > maxrows: maxrows = rows
# Width calculations
width = colwidth(coldata) + margin
totwidth = totwidth + width
colstarts.append(totwidth)
#
# Calculate document and window height
#
docwidth, docheight = totwidth, maxrows*lineheight
winwidth, winheight = docwidth, docheight
if winwidth > stdwin.textwidth('n')*100: winwidth = 0
if winheight > stdwin.lineheight()*30: winheight = 0
#
# Create the window
#
stdwin.setdefwinsize(winwidth, winheight)
w = gwin.open(title)
#
# Set properties and override methods
#
w.data = data
w.margin = margin
w.lineheight = lineheight
w.colstarts = colstarts
w.totwidth = totwidth
w.maxrows = maxrows
w.selection = (-1, -1)
w.lastselection = (-1, -1)
w.selshown = 0
w.setdocsize(docwidth, docheight)
w.draw = draw
w.mup = mup
w.arrow = arrow
#
# Return
#
return w
def update(w, data): # Change the data
#
# Hide selection
#
hidesel(w, w.begindrawing())
#
# Get old geometry parameters
#
margin = w.margin
lineheight = w.lineheight
#
# Geometry calculations
#
colstarts = [0]
totwidth = 0
maxrows = 0
for coldata in data:
# Height calculations
rows = len(coldata)
if rows > maxrows: maxrows = rows
# Width calculations
width = colwidth(coldata) + margin
totwidth = totwidth + width
colstarts.append(totwidth)
#
# Calculate document and window height
#
docwidth, docheight = totwidth, maxrows*lineheight
#
# Set changed properties and change window size
#
w.data = data
w.colstarts = colstarts
w.totwidth = totwidth
w.maxrows = maxrows
w.change((0, 0), (10000, 10000))
w.setdocsize(docwidth, docheight)
w.change((0, 0), (docwidth, docheight))
#
# Show selection, or forget it if out of range
#
showsel(w, w.begindrawing())
if not w.selshown: w.selection = (-1, -1)
def colwidth(coldata): # Subroutine to calculate column width
maxwidth = 0
for string, action in coldata:
width = stdwin.textwidth(string)
if width > maxwidth: maxwidth = width
return maxwidth
def draw(w, ((left, top), (right, bottom))): # Draw method
ileft = whichcol(w, left)
iright = whichcol(w, right-1) + 1
if iright > len(w.data): iright = len(w.data)
itop = divmod(top, w.lineheight)[0]
if itop < 0: itop = 0
ibottom, remainder = divmod(bottom, w.lineheight)
if remainder: ibottom = ibottom + 1
d = w.begindrawing()
if ileft <= w.selection[0] < iright:
if itop <= w.selection[1] < ibottom:
hidesel(w, d)
d.erase((left, top), (right, bottom))
for i in range(ileft, iright):
col = w.data[i]
jbottom = len(col)
if ibottom < jbottom: jbottom = ibottom
h = w.colstarts[i]
v = itop * w.lineheight
for j in range(itop, jbottom):
string, action = col[j]
d.text((h, v), string)
v = v + w.lineheight
showsel(w, d)
def mup(w, detail): # Mouse up method
(h, v), nclicks, button, mask = detail
icol = whichcol(w, h)
if 0 <= icol < len(w.data):
irow = divmod(v, w.lineheight)[0]
col = w.data[icol]
if 0 <= irow < len(col):
string, action = col[irow]
action(w, string, (icol, irow), detail)
def whichcol(w, h): # Return column number (may be >= len(w.data))
for icol in range(0, len(w.data)):
if h < w.colstarts[icol+1]:
return icol
return len(w.data)
def arrow(w, type):
if type == WC_LEFT:
incr = -1, 0
elif type == WC_UP:
incr = 0, -1
elif type == WC_RIGHT:
incr = 1, 0
elif type == WC_DOWN:
incr = 0, 1
else:
return
icol, irow = w.lastselection
icol = icol + incr[0]
if icol < 0: icol = len(w.data)-1
if icol >= len(w.data): icol = 0
if 0 <= icol < len(w.data):
irow = irow + incr[1]
if irow < 0: irow = len(w.data[icol]) - 1
if irow >= len(w.data[icol]): irow = 0
else:
irow = 0
if 0 <= icol < len(w.data) and 0 <= irow < len(w.data[icol]):
w.lastselection = icol, irow
string, action = w.data[icol][irow]
detail = (0, 0), 1, 1, 1
action(w, string, (icol, irow), detail)
# Selection management
# TO DO: allow multiple selected entries
def select(w, selection): # Public function to set the item selection
d = w.begindrawing()
hidesel(w, d)
w.selection = selection
showsel(w, d)
if w.selshown: lastselection = selection
def hidesel(w, d): # Hide the selection, if shown
if w.selshown: invertsel(w, d)
def showsel(w, d): # Show the selection, if hidden
if not w.selshown: invertsel(w, d)
def invertsel(w, d): # Invert the selection, if valid
icol, irow = w.selection
if 0 <= icol < len(w.data) and 0 <= irow < len(w.data[icol]):
left = w.colstarts[icol]
right = w.colstarts[icol+1]
top = irow * w.lineheight
bottom = (irow+1) * w.lineheight
d.invert((left, top), (right, bottom))
w.selshown = (not w.selshown)
# Demonstration
def demo_action(w, string, (icol, irow), detail): # Action function for demo
select(w, (irow, icol))
def demo(): # Demonstration
da = demo_action # shorthand
col0 = [('a1', da), ('bbb1', da), ('c1', da)]
col1 = [('a2', da), ('bbb2', da)]
col2 = [('a3', da), ('b3', da), ('c3', da), ('d4', da), ('d5', da)]
col3 = []
for i in range(1, 31): col3.append(('xxx' + `i`, da))
data = [col0, col1, col2, col3]
w = open('tablewin.demo', data)
gwin.mainloop()
return w
# Module 'textwin'
# Text windows, a subclass of gwin
import stdwin
import gwin
from stdwinevents import *
def fixsize(w):
docwidth, docheight = w.text.getrect()[1]
winheight = w.getwinsize()[1]
if winheight > docheight: docheight = winheight
w.setdocsize(0, docheight)
fixeditmenu(w)
def cut(w, m, id):
s = w.text.getfocustext()
if s:
stdwin.setcutbuffer(0, s)
w.text.replace('')
fixsize(w)
def copy(w, m, id):
s = w.text.getfocustext()
if s:
stdwin.setcutbuffer(0, s)
fixeditmenu(w)
def paste(w, m, id):
w.text.replace(stdwin.getcutbuffer(0))
fixsize(w)
def addeditmenu(w):
m = w.editmenu = w.menucreate('Edit')
m.action = []
m.additem('Cut', 'X')
m.action.append(cut)
m.additem('Copy', 'C')
m.action.append(copy)
m.additem('Paste', 'V')
m.action.append(paste)
def fixeditmenu(w):
m = w.editmenu
f = w.text.getfocus()
can_copy = (f[0] < f[1])
m.enable(1, can_copy)
if not w.readonly:
m.enable(0, can_copy)
m.enable(2, (stdwin.getcutbuffer(0) <> ''))
def draw(w, area): # Draw method
w.text.draw(area)
def size(w, newsize): # Size method
w.text.move((0, 0), newsize)
fixsize(w)
def close(w): # Close method
del w.text # Break circular ref
gwin.close(w)
def char(w, c): # Char method
w.text.replace(c)
fixsize(w)
def backspace(w): # Backspace method
void = w.text.event(WE_COMMAND, w, WC_BACKSPACE)
fixsize(w)
def arrow(w, detail): # Arrow method
w.text.arrow(detail)
fixeditmenu(w)
def mdown(w, detail): # Mouse down method
void = w.text.event(WE_MOUSE_DOWN, w, detail)
fixeditmenu(w)
def mmove(w, detail): # Mouse move method
void = w.text.event(WE_MOUSE_MOVE, w, detail)
def mup(w, detail): # Mouse up method
void = w.text.event(WE_MOUSE_UP, w, detail)
fixeditmenu(w)
def activate(w): # Activate method
fixeditmenu(w)
def open(title, str): # Display a string in a window
w = gwin.open(title)
w.readonly = 0
w.text = w.textcreate((0, 0), w.getwinsize())
w.text.replace(str)
w.text.setfocus(0, 0)
addeditmenu(w)
fixsize(w)
w.draw = draw
w.size = size
w.close = close
w.mdown = mdown
w.mmove = mmove
w.mup = mup
w.char = char
w.backspace = backspace
w.arrow = arrow
w.activate = activate
return w
def open_readonly(title, str): # Same with char input disabled
w = open(title, str)
w.readonly = 1
w.char = w.backspace = gwin.nop
# Disable Cut and Paste menu item; leave Copy alone
w.editmenu.enable(0, 0)
w.editmenu.enable(2, 0)
return w
# wdb.py -- a window-based Python debugger
# XXX To do:
# - don't fall out of bottom frame
import stdwin
from stdwinevents import *
import sys
import basewin
import bdb
import repr
WIDTH = 40
HEIGHT = 8
WdbDone = 'wdb.WdbDone' # Exception to continue execution
class Wdb(bdb.Bdb, basewin.BaseWindow): # Window debugger
def __init__(self):
self.sourcewindows = {}
self.framewindows = {}
bdb.Bdb.__init__(self)
width = WIDTH*stdwin.textwidth('0')
height = HEIGHT*stdwin.lineheight()
stdwin.setdefwinsize(width, height)
basewin.BaseWindow.__init__(self, '--Stack--')
self.closed = 0
def reset(self):
if self.closed: raise RuntimeError, 'already closed'
bdb.Bdb.reset(self)
self.forget()
def forget(self):
self.lineno = None
self.stack = []
self.curindex = 0
self.curframe = None
for fn in self.sourcewindows.keys():
self.sourcewindows[fn].resetlineno()
def setup(self, f, t):
self.forget()
self.stack, self.curindex = self.get_stack(f, t)
self.curframe = self.stack[self.curindex][0]
# Build a list of current frames
cfl = []
for f, i in self.stack: cfl.append(f)
# Remove deactivated frame windows
for name in self.framewindows.keys():
fw = self.framewindows[name]
if fw.frame not in cfl: fw.close()
else: fw.refreshframe()
# Refresh the stack window
self.refreshstack()
# Override Bdb methods (except user_call, for now)
def user_line(self, frame):
# This function is called when we stop or break at this line
self.interaction(frame, None)
def user_return(self, frame, return_value):
# This function is called when a return trap is set here
frame.f_locals['__return__'] = return_value
self.settitle('--Return--')
self.interaction(frame, None)
if not self.closed:
self.settitle('--Stack--')
def user_exception(self, frame, (exc_type, exc_value, exc_traceback)):
# This function is called if an exception occurs,
# but only if we are to stop at or just below this level
frame.f_locals['__exception__'] = exc_type, exc_value
if type(exc_type) == type(''):
exc_type_name = exc_type
else: exc_type_name = exc_type.__name__
self.settitle(exc_type_name + ': ' + repr.repr(exc_value))
stdwin.fleep()
self.interaction(frame, exc_traceback)
if not self.closed:
self.settitle('--Stack--')
# Change the title
def settitle(self, title):
self.savetitle = self.win.gettitle()
self.win.settitle(title)
# General interaction function
def interaction(self, frame, traceback):
import mainloop
self.popup()
self.setup(frame, traceback)
try:
mainloop.mainloop()
except WdbDone:
pass
self.forget()
# Functions whose name is do_X for some character X
# are callable directly from the keyboard.
def do_up(self):
if self.curindex == 0:
stdwin.fleep()
else:
self.curindex = self.curindex - 1
self.curframe = self.stack[self.curindex][0]
self.refreshstack()
do_u = do_up
def do_down(self):
if self.curindex + 1 == len(self.stack):
stdwin.fleep()
else:
self.curindex = self.curindex + 1
self.curframe = self.stack[self.curindex][0]
self.refreshstack()
do_d = do_down
def do_step(self):
self.set_step()
raise WdbDone
do_s = do_step
def do_next(self):
self.set_next(self.curframe)
raise WdbDone
do_n = do_next
def do_return(self):
self.set_return(self.curframe)
raise WdbDone
do_r = do_return
def do_continue(self):
self.set_continue()
raise WdbDone
do_c = do_cont = do_continue
def do_quit(self):
self.close()
raise WdbDone
do_q = do_quit
def do_list(self):
fn = self.curframe.f_code.co_filename
if not self.sourcewindows.has_key(fn):
import wdbsrcwin
try:
self.sourcewindows[fn] = wdbsrcwin. \
DebuggerSourceWindow(self, fn)
except IOError:
stdwin.fleep()
return
w = self.sourcewindows[fn]
lineno = self.stack[self.curindex][1]
w.setlineno(lineno)
w.popup()
do_l = do_list
def do_frame(self):
name = 'locals' + `self.curframe`[16:-1]
if self.framewindows.has_key(name):
self.framewindows[name].popup()
else:
import wdbframewin
self.framewindows[name] = \
wdbframewin.FrameWindow(self, \
self.curframe, \
self.curframe.f_locals, name)
do_f = do_frame
def do_globalframe(self):
name = 'globals' + `self.curframe`[16:-1]
if self.framewindows.has_key(name):
self.framewindows[name].popup()
else:
import wdbframewin
self.framewindows[name] = \
wdbframewin.FrameWindow(self, \
self.curframe, \
self.curframe.f_globals, name)
do_g = do_globalframe
# Link between the debugger and the window
def refreshstack(self):
height = stdwin.lineheight() * (1 + len(self.stack))
self.win.setdocsize((0, height))
self.refreshall() # XXX be more subtle later
# Also pass the information on to the source windows
filename = self.curframe.f_code.co_filename
lineno = self.curframe.f_lineno
for fn in self.sourcewindows.keys():
w = self.sourcewindows[fn]
if fn == filename:
w.setlineno(lineno)
else:
w.resetlineno()
# The remaining methods override BaseWindow methods
def close(self):
if not self.closed:
basewin.BaseWindow.close(self)
self.closed = 1
for key in self.sourcewindows.keys():
self.sourcewindows[key].close()
for key in self.framewindows.keys():
self.framewindows[key].close()
self.set_quit()
def char(self, detail):
try:
func = eval('self.do_' + detail)
except (AttributeError, SyntaxError):
stdwin.fleep()
return
func()
def command(self, detail):
if detail == WC_UP:
self.do_up()
elif detail == WC_DOWN:
self.do_down()
def mouse_down(self, detail):
(h, v), clicks, button, mask = detail
i = v / stdwin.lineheight()
if 0 <= i < len(self.stack):
if i != self.curindex:
self.curindex = i
self.curframe = self.stack[self.curindex][0]
self.refreshstack()
elif clicks == 2:
self.do_frame()
else:
stdwin.fleep()
def draw(self, detail):
import linecache, string
d = self.win.begindrawing()
try:
h, v = 0, 0
for f, lineno in self.stack:
fn = f.f_code.co_filename
if f is self.curframe:
s = '> '
else:
s = ' '
s = s + fn + '(' + `lineno` + ')'
s = s + f.f_code.co_name
if f.f_locals.has_key('__args__'):
args = f.f_locals['__args__']
if args is not None:
s = s + repr.repr(args)
if f.f_locals.has_key('__return__'):
rv = f.f_locals['__return__']
s = s + '->'
s = s + repr.repr(rv)
line = linecache.getline(fn, lineno)
if line: s = s + ': ' + string.strip(line)
d.text((h, v), s)
v = v + d.lineheight()
finally:
d.close()
# Simplified interface
def run(statement, globals=None, locals=None):
x = Wdb()
try: x.run(statement, globals, locals)
finally: x.close()
def runeval(expression, globals=None, locals=None):
x = Wdb()
try: return x.runeval(expression, globals, locals)
finally: x.close()
def runctx(statement, globals, locals):
# B/W compatibility
run(statement, globals, locals)
def runcall(*args):
x = Wdb()
try: return apply(x.runcall, args)
finally: x.close()
def set_trace():
Wdb().set_trace()
# Post-Mortem interface
def post_mortem(traceback):
x = Wdb()
x.reset()
x.interaction(None, traceback)
def pm():
import sys
post_mortem(sys.last_traceback)
# Main program for testing
TESTCMD = 'import x; x.main()'
def test():
run(TESTCMD)
# wdbframewin.py -- frame window for wdb.py
# XXX To do:
# - display function name in window title
# - execute arbitrary statements instead of just evaluating expressions
# - allow setting variables by editing their values
import stdwin
from stdwinevents import *
import basewin
import sys
WIDTH = 40
MINHEIGHT = 8
MAXHEIGHT = 16
class FrameWindow(basewin.BaseWindow):
def __init__(self, debugger, frame, dict, name):
self.debugger = debugger
self.frame = frame # Not used except for identity tests
self.dict = dict
self.name = name
nl = max(MINHEIGHT, len(self.dict) + 5)
nl = min(nl, MAXHEIGHT)
width = WIDTH*stdwin.textwidth('0')
height = nl*stdwin.lineheight()
stdwin.setdefwinsize(width, height)
basewin.BaseWindow.__init__(
self, '--Frame ' + name + '--')
# XXX Should use current function name
self.initeditor()
self.displaylist = ['>>>', '', '-'*WIDTH]
self.refreshframe()
def initeditor(self):
r = (stdwin.textwidth('>>> '), 0), (30000, stdwin.lineheight())
self.editor = self.win.textcreate(r)
def closeeditor(self):
self.editor.close()
def dispatch(self, event):
type, win, detail = event
if type == WE_NULL: return # Dummy tested by mainloop
if type in (WE_DRAW, WE_COMMAND) \
or not self.editor.event(event):
basewin.BaseWindow.dispatch(self, event)
def close(self):
del self.debugger.framewindows[self.name]
del self.debugger, self.dict
self.closeeditor()
basewin.BaseWindow.close(self)
def command(self, detail):
if detail == WC_RETURN:
self.re_eval()
else:
dummy = self.editor.event(WE_COMMAND, \
self.win, detail)
def mouse_down(self, detail):
(h, v), clicks, button, mask = detail
if clicks != 2:
return
i = v / stdwin.lineheight()
if 5 <= i < len(self.displaylist):
import string
name = string.splitfields(self.displaylist[i],' = ')[0]
if not self.dict.has_key(name):
stdwin.fleep()
return
value = self.dict[name]
if not hasattr(value, '__dict__'):
stdwin.fleep()
return
name = 'instance ' + `value`
if self.debugger.framewindows.has_key(name):
self.debugger.framewindows[name].popup()
else:
self.debugger.framewindows[name] = \
FrameWindow(self.debugger,
self.frame, value.__dict__,
name)
return
stdwin.fleep()
def re_eval(self):
import string, repr
expr = string.strip(self.editor.gettext())
if expr == '':
output = ''
else:
globals = self.frame.f_globals
globals['__privileged__'] = 1
locals = self.dict
try:
value = eval(expr, globals, locals)
output = repr.repr(value)
except:
if type(sys.exc_type) == type(''):
exc_type_name = sys.exc_type
else: exc_type_name = sys.exc_type.__name__
output = exc_type_name + ': ' + `sys.exc_value`
self.displaylist[1] = output
lh = stdwin.lineheight()
r = (-10, 0), (30000, 2*lh)
self.win.change(r)
self.editor.setfocus(0, len(expr))
def draw(self, detail):
(left, top), (right, bottom) = detail
dummy = self.editor.draw(detail)
d = self.win.begindrawing()
try:
lh = d.lineheight()
h, v = 0, 0
for line in self.displaylist:
if v+lh > top and v < bottom:
d.text((h, v), line)
v = v + lh
finally:
d.close()
def refreshframe(self):
import repr
del self.displaylist[3:]
self.re_eval()
names = self.dict.keys()
for key, label in ('__args__', 'Args: '), \
('__return__', 'Return: '):
if self.dict.has_key(key):
names.remove(key)
value = self.dict[key]
label = label + repr.repr(value)
self.displaylist.append(label)
names.sort()
for name in names:
value = self.dict[name]
line = name + ' = ' + repr.repr(value)
self.displaylist.append(line)
self.win.setdocsize(0, \
stdwin.lineheight() * len(self.displaylist))
self.refreshall() # XXX Be more subtle later
# wdbsrcwin.py -- source window for wdb
import stdwin
from stdwinevents import *
import srcwin
class DebuggerSourceWindow(srcwin.SourceWindow):
def __init__(self, debugger, filename):
self.debugger = debugger
self.curlineno = 0
self.focus = 0
srcwin.SourceWindow.__init__(self, filename)
def close(self):
del self.debugger.sourcewindows[self.filename]
del self.debugger
srcwin.SourceWindow.close(self)
def dispatch(self, event):
type, win, detail = event
if type == WE_CHAR:
self.char(detail)
elif type == WE_COMMAND:
self.command(detail)
elif type == WE_MOUSE_DOWN:
self.mouse_down(detail)
else:
srcwin.SourceWindow.dispatch(self, event)
def char(self, detail):
self.debugger.char(detail)
def command(self, detail):
self.debugger.command(detail)
def mouse_down(self, detail):
(h, v), clicks, button, mask = detail
if h >= self.leftmargin:
srcwin.SourceWindow.dispatch(self, \
(WE_MOUSE_DOWN, self.win, detail))
return
lineno = v/self.lineheight + 1
if 1 <= lineno <= self.linecount:
if self.debugger.get_break(self.filename, lineno):
f = self.debugger.clear_break
else:
f = self.debugger.set_break
err = f(self.filename, lineno)
if err: stdwin.message(err)
else: self.changemark(lineno)
else:
stdwin.fleep()
def getmark(self, lineno):
s = `lineno`
if lineno == self.focus:
s = '[' + s + ']'
else:
s = ' ' + s + ' '
if lineno == self.curlineno:
s = s + '->'
else:
s = s + ' '
br = self.debugger.breaks
if br.has_key(self.filename) and lineno in br[self.filename]:
s = s + 'B'
else:
s = s + ' '
return s
def getmargin(self):
return stdwin.textwidth('[' + `self.linecount+1` + ']->B ')
def setlineno(self, newlineno):
if newlineno != self.curlineno:
oldlineno = self.curlineno
self.curlineno = newlineno
self.changemark(oldlineno)
self.changemark(newlineno)
if newlineno != 0:
self.showline(newlineno)
def resetlineno(self):
self.setlineno(0)
def setfocus(self, newfocus):
if newfocus != self.focus:
oldfocus = self.focus
self.focus = newfocus
self.changemark(oldfocus)
self.changemark(newfocus)
if newfocus != 0:
self.showline(newfocus)
def resetfocus(self):
self.setfocus(0)
# XXX Should get rid of focus stuff again
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment