From 0c89ec778d684a13a656b6b3462ae7dfd2837148 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 5 Nov 1990 19:44:31 +0000 Subject: *** empty log message *** --- Lib/lib-stdwin/Buttons.py | 143 ++++++++++++++-------------- Lib/lib-stdwin/Histogram.py | 37 ++++---- Lib/lib-stdwin/Sliders.py | 215 +++++++++++++++++++++---------------------- Lib/lib-stdwin/StripChart.py | 25 ++--- Lib/lib-stdwin/VUMeter.py | 7 +- Lib/stdwin/Buttons.py | 143 ++++++++++++++-------------- Lib/stdwin/Histogram.py | 37 ++++---- Lib/stdwin/Sliders.py | 215 +++++++++++++++++++++---------------------- Lib/stdwin/StripChart.py | 25 ++--- Lib/stdwin/VUMeter.py | 7 +- 10 files changed, 430 insertions(+), 424 deletions(-) diff --git a/Lib/lib-stdwin/Buttons.py b/Lib/lib-stdwin/Buttons.py index 5395f88..3ad5605 100644 --- a/Lib/lib-stdwin/Buttons.py +++ b/Lib/lib-stdwin/Buttons.py @@ -1,10 +1,8 @@ # Module 'Buttons' -from Resize import * - - -# Import module 'rect' renamed as '_rect' +# Import module 'rect' renamed as '_rect' to avoid exporting it on +# 'from Buttons import *' # import rect _rect = rect @@ -28,61 +26,69 @@ class LabelAppearance(): # # Initialization # - def init_appearance(self, (win, bounds)): - self.win = win - self.bounds = bounds + def init_appearance(self): + self.bounds = _rect.empty self.enabled = 1 self.hilited = 0 self.selected = 0 self.text = '' - self.limbo = 1 - self.recalc() - self.win.change(self.bounds) - # While the limbo flag is set, redraw calls are ignored. - # It is cleared by the first draw event. - # This is intended to avoid duplicate drawing during - # initialization. + # + # Size enquiry + # + def minsize(self, m): + try: + self.text = self.text + except NameError: + self.text = '' + return m.textwidth(self.text) + 6, m.lineheight() + 6 + # + def getbounds(self): + return self.bounds # # Changing the parameters # def settext(self, text): self.text = text - self.recalctextpos() - self.redraw() + if self.bounds <> _rect.empty: + self.recalctextpos() + self.redraw() # def setbounds(self, bounds): - # This delays drawing until after all buttons are moved - self.win.change(self.bounds) + if self.bounds <> _rect.empty: + self.parent.change(self.bounds) self.bounds = bounds - self.recalc() - self.win.change(bounds) + if self.bounds <> _rect.empty: + self.recalc() + self.parent.change(bounds) # # Changing the state bits # def enable(self, flag): if flag <> self.enabled: self.enabled = flag - if not self.limbo: - self.flipenable(self.win.begindrawing()) + if self.bounds <> _rect.empty: + self.flipenable(self.parent.begindrawing()) # def hilite(self, flag): if flag <> self.hilited: self.hilited = flag - if not self.limbo: - self.fliphilite(self.win.begindrawing()) + if self.bounds <> _rect.empty: + self.fliphilite(self.parent.begindrawing()) # def select(self, flag): if flag <> self.selected: self.selected = flag - self.redraw() + 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): - self.recalcbounds() - self.recalctextpos() + if self.bounds <> _rect.empty: + self.recalcbounds() + self.recalctextpos() # def recalcbounds(self): self.hilitebounds = _rect.inset(self.bounds, (3, 3)) @@ -90,20 +96,19 @@ class LabelAppearance(): # def recalctextpos(self): (left, top), (right, bottom) = self.bounds - d = self.win.begindrawing() - h = (left + right - d.textwidth(self.text)) / 2 - v = (top + bottom - d.lineheight()) / 2 + m = self.parent.beginmeasuring() + h = (left + right - m.textwidth(self.text)) / 2 + v = (top + bottom - m.lineheight()) / 2 self.textpos = h, v # - # Generic drawing mechanism. + # Generic drawing interface. # Do not override redraw() or draw() methods; override drawit() c.s. # def redraw(self): - if not self.limbo: - self.draw(self.win.begindrawing(), self.bounds) + if self.bounds <> _rect.empty: + self.draw(self.parent.begindrawing(), self.bounds) # def draw(self, (d, area)): - self.limbo = 0 area = _rect.intersect(area, self.bounds) if area = _rect.empty: return @@ -161,6 +166,10 @@ class ButtonAppearance() = LabelAppearance(): # class CheckAppearance() = LabelAppearance(): # + def minsize(self, m): + width, height = m.textwidth(self.text) + 6, m.lineheight() + 6 + return width + height + m.textwidth(' '), height + # def drawpict(self, d): d.box(self.boxbounds) if self.selected: _xorcross(d, self.boxbounds) @@ -173,10 +182,10 @@ class CheckAppearance() = LabelAppearance(): self.hilitebounds = self.boxbounds # def recalctextpos(self): - d = self.win.begindrawing() + m = self.parent.beginmeasuring() (left, top), (right, bottom) = self.boxbounds - h = right + d.textwidth(' ') - v = top + (self.size - d.lineheight()) / 2 + h = right + m.textwidth(' ') + v = top + (self.size - m.lineheight()) / 2 self.textpos = h, v # @@ -199,24 +208,31 @@ class RadioAppearance() = CheckAppearance(): # -# NoReactivity ignores mouse and timer events. +# 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 -# timer_hook called on timer events # 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 NoReactivity(): +class BaseReactivity(): # def init_reactivity(self): self.down_hook = self.move_hook = self.up_hook = \ - self.on_hook = self.off_hook = self.timer_hook = \ - self.hook = self.active = 0 + 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) @@ -230,9 +246,6 @@ class NoReactivity(): def mouse_up(self, detail): pass # - def timer(self): - pass - # def down_trigger(self): if self.down_hook: self.down_hook(self) # @@ -248,18 +261,14 @@ class NoReactivity(): def off_trigger(self): if self.off_hook: self.off_hook(self) # - def timer_trigger(self): - if self.timer_hook: self.timer_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. -# Its timer_trigger method is called for all timer events while hilited. # -class ToggleReactivity() = NoReactivity(): +class ToggleReactivity() = BaseReactivity(): # def mouse_down(self, detail): if self.enabled and self.mousetest(detail[_HV]): @@ -276,10 +285,6 @@ class ToggleReactivity() = NoReactivity(): self.up_trigger() self.active = 0 # - def timer(self): - if self.hilited: - self.timer_trigger() - # def down_trigger(self): if self.hilited: self.on_trigger() @@ -292,7 +297,7 @@ class ToggleReactivity() = NoReactivity(): # TriggerReactivity acts like a fancy pushbutton. # It hilites itself while the mouse is down within its bounds. # -class TriggerReactivity() = NoReactivity(): +class TriggerReactivity() = BaseReactivity(): # def mouse_down(self, detail): if self.enabled and self.mousetest(detail[_HV]): @@ -315,10 +320,6 @@ class TriggerReactivity() = NoReactivity(): self.active = 0 self.hilite(0) # - def timer(self): - if self.active and self.hilited: - self.timer_trigger() - # # CheckReactivity handles mouse events like TriggerReactivity, @@ -356,13 +357,22 @@ class RadioReactivity() = TriggerReactivity(): # Auxiliary class for 'define' method. +# Call the initializers in the right order. # -class Define() = NoResize(): +class Define(): # - def define(self, (win, bounds, text)): - self.init_appearance(win, bounds) + def define(self, parent): + self.parent = parent + parent.addchild(self) + self.init_appearance() self.init_reactivity() - self.init_resize() + return self + # + def destroy(self): + self.parent = 0 + # + def definetext(self, (parent, text)): + self = self.define(parent) self.settext(text) return self @@ -380,11 +390,10 @@ def _xorcross(d, bounds): d.xorline((left, bottom), (right, top)) -# Ready-made button classes +# Ready-made button classes. # -class BaseButton() = NoReactivity(), LabelAppearance(), Define(): pass class Label() = NoReactivity(), LabelAppearance(), Define(): pass -class ClassicButton() = TriggerReactivity(), ButtonAppearance(), Define(): pass +class PushButton() = TriggerReactivity(), ButtonAppearance(), Define(): pass class CheckButton() = CheckReactivity(), CheckAppearance(), Define(): pass class RadioButton() = RadioReactivity(), RadioAppearance(), Define(): pass -class Toggle() = ToggleReactivity(), ButtonAppearance(), Define(): pass +class ToggleButton() = ToggleReactivity(), ButtonAppearance(), Define(): pass diff --git a/Lib/lib-stdwin/Histogram.py b/Lib/lib-stdwin/Histogram.py index f469272..505bbaf 100644 --- a/Lib/lib-stdwin/Histogram.py +++ b/Lib/lib-stdwin/Histogram.py @@ -1,41 +1,36 @@ # Module 'Histogram' from Buttons import * -from Resize import Resize - # A Histogram displays a histogram of numeric data. -# It reacts to resize events by resizing itself, -# leaving the same amount of space around the borders. -# (This is geometry management, and should really be implemented -# by a different group of classes, but for now this hack is OK.) # -class HistogramAppearance() = Resize(), LabelAppearance(): +class HistogramAppearance() = LabelAppearance(), Define(): # - def define(self, (win, bounds, ydata, scale)): - self.init_appearance(win, bounds) - self.init_resize() - self.ydata = ydata - self.scale = scale # (min, max) + 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.win.change(self.bounds) + self.parent.change(self.bounds) # - def drawit(self, d): - ydata = self.ydata + def drawpict(self, d): (left, top), (right, bottom) = self.bounds min, max = self.scale size = max-min width, height = right-left, bottom-top - for i in range(len(ydata)): - h0 = left + i * width/len(ydata) - h1 = left + (i+1) * width/len(ydata) - v0 = top + height - (self.ydata[i]-min)*height/size - v1 = top + height + 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() = HistogramAppearance(), NoReactivity(): pass +class Histogram() = NoReactivity(), HistogramAppearance(): pass diff --git a/Lib/lib-stdwin/Sliders.py b/Lib/lib-stdwin/Sliders.py index b142ef4..f655e77 100644 --- a/Lib/lib-stdwin/Sliders.py +++ b/Lib/lib-stdwin/Sliders.py @@ -1,13 +1,11 @@ # Module 'Sliders' -# -# XXX Should split caller interface, appearance and reactivity better import stdwin from stdwinevents import * import rect from Buttons import * -from Resize import * +from HVSplit import HSplit # Field indices in event detail @@ -18,11 +16,13 @@ _BUTTON = 2 _MASK = 3 -# A dragslider is the simplest possible slider. +# 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() = NoReactivity(): +class DragSliderReactivity() = BaseReactivity(): # def mouse_down(self, detail): h, v = hv = detail[_HV] @@ -43,136 +43,133 @@ class DragSliderReactivity() = NoReactivity(): self.active = 0 # -class DragSliderAppearance() = NoResize(), ButtonAppearance(): - # - def define(self, (win, bounds)): - self.min = 0 - self.val = -1 # Changed by next setval call - self.max = 100 - self.setval_hook = 0 - self.pretext = self.postext = '' - self = ClassicButton.define(self, (win, bounds, '')) - self.setval(50) - return self +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 in Python ranges, the end point belongs + # (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.setval_trigger() - # (The trigger may change val, pretext and postext) - self.settext(self.pretext + `self.val` + self.postext) + self.recalctext() + self.trigger() + # + def trigger(self): + if self.hook: + self.hook(self) # - def setval_trigger(self): - if self.setval_hook: - self.setval_hook(self) + def recalctext(self): + self.settext(self.pretext + `self.val` + self.postext) # -class DragSlider() = DragSliderReactivity(), DragSliderAppearance(): pass +class DragSlider() = DragSliderReactivity(), DragSliderAppearance(), Define(): + def definetext(self, (parent, text)): + raise RuntimeError, 'DragSlider.definetext() not supported' -# Auxiliary class for DragSlider incorporated in ComplexSlider +# Auxiliary class for PushButton incorporated in ComplexSlider # -class _SubDragSlider() = DragSlider(): - def define(self, (win, bounds, parent)): - self.parent = parent - return DragSlider.define(self, (win, bounds)) - def setval_trigger(self): - self.parent.val = self.val - self.parent.setval_trigger() - -# Auxiliary class for ClassicButton incorporated in ComplexSlider -# -class _SubClassicButton() = ClassicButton(): - def define(self, (win, bounds, text, step, parent)): - self.parent = parent +class _StepButton() = PushButton(): + def define(self, parent): + self = PushButton.define(self, parent) + self.step = 0 + return self + def setstep(self, step): self.step = step - return ClassicButton.define(self, (win, bounds, text)) + 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.parent.setval(self.parent.val + self.step) - self.delay = 5 - self.win.settimer(self.delay) - def move_trigger(self): - self.win.settimer(self.delay) - def timer_trigger(self): - self.delay = 1 - self.parent.setval(self.parent.val + self.step) - self.win.settimer(self.delay) + 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 a wrapper around three buttons: -# One to step down, a dragslider, and one to step up. + +# A complex slider is an HSplit initialized to three buttons: +# one to step down, a dragslider, and one to step up. # -class ComplexSlider() = NoResize(), LabelAppearance(), NoReactivity(): +class ComplexSlider() = HSplit(): # - def define(self, (win, bounds)): - # - self.win = win - self.bounds = bounds - self.setval_hook = 0 - # - (left, top), (right, bottom) = bounds - size = bottom - top - # - downbox = (left, top), (left+size, bottom) - sliderbox = (left+size, top), (right-size, bottom) - upbox = (right-size, top), (right, bottom) - # - self.downbutton = \ - _SubClassicButton().define(win, downbox, '-', -1, self) - # - self.sliderbutton = \ - _SubDragSlider().define(win, sliderbox, self) - # - self.upbutton = \ - _SubClassicButton().define(win, upbox, '+', 1, self) - # - self.min = self.sliderbutton.min - self.val = self.sliderbutton.val - self.max = self.sliderbutton.max - self.pretext = self.sliderbutton.pretext - self.postext = self.sliderbutton.postext + # Override Slider define() method + # + def define(self, parent): + self = self.create(parent) # HSplit # - self.children = \ - [self.downbutton, self.sliderbutton, self.upbutton] + self.downbutton = _StepButton().definetextstep(self, '-', -1) + self.dragbutton = DragSlider().define(self) + self.upbutton = _StepButton().definetextstep(self, '+', 1) # return self # - def mouse_down(self, detail): - for b in self.children: - b.mouse_down(detail) + # Override HSplit methods # - def mouse_move(self, detail): - for b in self.children: - b.mouse_move(detail) - # - def mouse_up(self, detail): - for b in self.children: - b.mouse_up(detail) - # - def timer(self): - for b in self.children: - b.timer() - # - def draw(self, area): - for b in self.children: - b.draw(area) - # - def setval(self, val): - self.sliderbutton.min = self.min - self.sliderbutton.max = self.max - self.sliderbutton.pretext = self.pretext - self.sliderbutton.postext = self.postext - self.sliderbutton.setval(val) - # - def setval_trigger(self): - if self.setval_hook: - self.setval_hook(self) + def minsize(self, m): + w1, h1 = self.downbutton.minsize(m) + w2, h2 = self.dragbutton.minsize(m) + w3, h3 = self.upbutton.minsize(m) + height = max(h1, h2, h3) + w1 = max(w1, height) + w3 = max(w3, height) + return w1+w2+w3, height # + 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) diff --git a/Lib/lib-stdwin/StripChart.py b/Lib/lib-stdwin/StripChart.py index 2021cc4..ac767c3 100644 --- a/Lib/lib-stdwin/StripChart.py +++ b/Lib/lib-stdwin/StripChart.py @@ -1,17 +1,18 @@ # Module 'StripChart' - import rect -from Buttons import * -from Resize import * +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(), NoResize(): +class StripChart() = LabelAppearance(), NoReactivity(): # - def define(self, (win, bounds, scale)): - self.init_appearance(win, bounds) + def define(self, (parent, scale)): + self.parent = parent + parent.addchild(self) + self.init_appearance() self.init_reactivity() - self.init_resize() self.ydata = [] self.scale = scale self.resetbounds() @@ -37,18 +38,18 @@ class StripChart() = LabelAppearance(), NoReactivity(), NoResize(): excess = len(self.ydata) - self.width if excess > 0: del self.ydata[:excess] - if not self.limbo: - self.win.scroll(self.bounds, (-excess, 0)) - if not self.limbo: + 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.win.begindrawing(), area) + self.draw(self.parent.begindrawing(), area) # def draw(self, (d, area)): - self.limbo = 0 area = rect.intersect(area, self.bounds) if area = rect.empty: + print 'mt' return d.cliprect(area) d.erase(self.bounds) diff --git a/Lib/lib-stdwin/VUMeter.py b/Lib/lib-stdwin/VUMeter.py index 65c018b..59fc36f 100644 --- a/Lib/lib-stdwin/VUMeter.py +++ b/Lib/lib-stdwin/VUMeter.py @@ -10,8 +10,9 @@ class VUMeter() = StripChart(): # # Override define() and timer() methods # - def define(self, (win, bounds)): - self = StripChart.define(self, (win, bounds, 128)) + def define(self, parent): + self = StripChart.define(self, (parent, 128)) + self.parent.need_timer(self) self.sampling = 0 self.rate = 3 self.enable(0) @@ -31,7 +32,7 @@ class VUMeter() = StripChart(): audio.start_recording(size) self.sampling = 1 if self.sampling: - self.win.settimer(1) + self.parent.settimer(1) # # New methods: start() and stop() # diff --git a/Lib/stdwin/Buttons.py b/Lib/stdwin/Buttons.py index 5395f88..3ad5605 100755 --- a/Lib/stdwin/Buttons.py +++ b/Lib/stdwin/Buttons.py @@ -1,10 +1,8 @@ # Module 'Buttons' -from Resize import * - - -# Import module 'rect' renamed as '_rect' +# Import module 'rect' renamed as '_rect' to avoid exporting it on +# 'from Buttons import *' # import rect _rect = rect @@ -28,61 +26,69 @@ class LabelAppearance(): # # Initialization # - def init_appearance(self, (win, bounds)): - self.win = win - self.bounds = bounds + def init_appearance(self): + self.bounds = _rect.empty self.enabled = 1 self.hilited = 0 self.selected = 0 self.text = '' - self.limbo = 1 - self.recalc() - self.win.change(self.bounds) - # While the limbo flag is set, redraw calls are ignored. - # It is cleared by the first draw event. - # This is intended to avoid duplicate drawing during - # initialization. + # + # Size enquiry + # + def minsize(self, m): + try: + self.text = self.text + except NameError: + self.text = '' + return m.textwidth(self.text) + 6, m.lineheight() + 6 + # + def getbounds(self): + return self.bounds # # Changing the parameters # def settext(self, text): self.text = text - self.recalctextpos() - self.redraw() + if self.bounds <> _rect.empty: + self.recalctextpos() + self.redraw() # def setbounds(self, bounds): - # This delays drawing until after all buttons are moved - self.win.change(self.bounds) + if self.bounds <> _rect.empty: + self.parent.change(self.bounds) self.bounds = bounds - self.recalc() - self.win.change(bounds) + if self.bounds <> _rect.empty: + self.recalc() + self.parent.change(bounds) # # Changing the state bits # def enable(self, flag): if flag <> self.enabled: self.enabled = flag - if not self.limbo: - self.flipenable(self.win.begindrawing()) + if self.bounds <> _rect.empty: + self.flipenable(self.parent.begindrawing()) # def hilite(self, flag): if flag <> self.hilited: self.hilited = flag - if not self.limbo: - self.fliphilite(self.win.begindrawing()) + if self.bounds <> _rect.empty: + self.fliphilite(self.parent.begindrawing()) # def select(self, flag): if flag <> self.selected: self.selected = flag - self.redraw() + 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): - self.recalcbounds() - self.recalctextpos() + if self.bounds <> _rect.empty: + self.recalcbounds() + self.recalctextpos() # def recalcbounds(self): self.hilitebounds = _rect.inset(self.bounds, (3, 3)) @@ -90,20 +96,19 @@ class LabelAppearance(): # def recalctextpos(self): (left, top), (right, bottom) = self.bounds - d = self.win.begindrawing() - h = (left + right - d.textwidth(self.text)) / 2 - v = (top + bottom - d.lineheight()) / 2 + m = self.parent.beginmeasuring() + h = (left + right - m.textwidth(self.text)) / 2 + v = (top + bottom - m.lineheight()) / 2 self.textpos = h, v # - # Generic drawing mechanism. + # Generic drawing interface. # Do not override redraw() or draw() methods; override drawit() c.s. # def redraw(self): - if not self.limbo: - self.draw(self.win.begindrawing(), self.bounds) + if self.bounds <> _rect.empty: + self.draw(self.parent.begindrawing(), self.bounds) # def draw(self, (d, area)): - self.limbo = 0 area = _rect.intersect(area, self.bounds) if area = _rect.empty: return @@ -161,6 +166,10 @@ class ButtonAppearance() = LabelAppearance(): # class CheckAppearance() = LabelAppearance(): # + def minsize(self, m): + width, height = m.textwidth(self.text) + 6, m.lineheight() + 6 + return width + height + m.textwidth(' '), height + # def drawpict(self, d): d.box(self.boxbounds) if self.selected: _xorcross(d, self.boxbounds) @@ -173,10 +182,10 @@ class CheckAppearance() = LabelAppearance(): self.hilitebounds = self.boxbounds # def recalctextpos(self): - d = self.win.begindrawing() + m = self.parent.beginmeasuring() (left, top), (right, bottom) = self.boxbounds - h = right + d.textwidth(' ') - v = top + (self.size - d.lineheight()) / 2 + h = right + m.textwidth(' ') + v = top + (self.size - m.lineheight()) / 2 self.textpos = h, v # @@ -199,24 +208,31 @@ class RadioAppearance() = CheckAppearance(): # -# NoReactivity ignores mouse and timer events. +# 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 -# timer_hook called on timer events # 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 NoReactivity(): +class BaseReactivity(): # def init_reactivity(self): self.down_hook = self.move_hook = self.up_hook = \ - self.on_hook = self.off_hook = self.timer_hook = \ - self.hook = self.active = 0 + 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) @@ -230,9 +246,6 @@ class NoReactivity(): def mouse_up(self, detail): pass # - def timer(self): - pass - # def down_trigger(self): if self.down_hook: self.down_hook(self) # @@ -248,18 +261,14 @@ class NoReactivity(): def off_trigger(self): if self.off_hook: self.off_hook(self) # - def timer_trigger(self): - if self.timer_hook: self.timer_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. -# Its timer_trigger method is called for all timer events while hilited. # -class ToggleReactivity() = NoReactivity(): +class ToggleReactivity() = BaseReactivity(): # def mouse_down(self, detail): if self.enabled and self.mousetest(detail[_HV]): @@ -276,10 +285,6 @@ class ToggleReactivity() = NoReactivity(): self.up_trigger() self.active = 0 # - def timer(self): - if self.hilited: - self.timer_trigger() - # def down_trigger(self): if self.hilited: self.on_trigger() @@ -292,7 +297,7 @@ class ToggleReactivity() = NoReactivity(): # TriggerReactivity acts like a fancy pushbutton. # It hilites itself while the mouse is down within its bounds. # -class TriggerReactivity() = NoReactivity(): +class TriggerReactivity() = BaseReactivity(): # def mouse_down(self, detail): if self.enabled and self.mousetest(detail[_HV]): @@ -315,10 +320,6 @@ class TriggerReactivity() = NoReactivity(): self.active = 0 self.hilite(0) # - def timer(self): - if self.active and self.hilited: - self.timer_trigger() - # # CheckReactivity handles mouse events like TriggerReactivity, @@ -356,13 +357,22 @@ class RadioReactivity() = TriggerReactivity(): # Auxiliary class for 'define' method. +# Call the initializers in the right order. # -class Define() = NoResize(): +class Define(): # - def define(self, (win, bounds, text)): - self.init_appearance(win, bounds) + def define(self, parent): + self.parent = parent + parent.addchild(self) + self.init_appearance() self.init_reactivity() - self.init_resize() + return self + # + def destroy(self): + self.parent = 0 + # + def definetext(self, (parent, text)): + self = self.define(parent) self.settext(text) return self @@ -380,11 +390,10 @@ def _xorcross(d, bounds): d.xorline((left, bottom), (right, top)) -# Ready-made button classes +# Ready-made button classes. # -class BaseButton() = NoReactivity(), LabelAppearance(), Define(): pass class Label() = NoReactivity(), LabelAppearance(), Define(): pass -class ClassicButton() = TriggerReactivity(), ButtonAppearance(), Define(): pass +class PushButton() = TriggerReactivity(), ButtonAppearance(), Define(): pass class CheckButton() = CheckReactivity(), CheckAppearance(), Define(): pass class RadioButton() = RadioReactivity(), RadioAppearance(), Define(): pass -class Toggle() = ToggleReactivity(), ButtonAppearance(), Define(): pass +class ToggleButton() = ToggleReactivity(), ButtonAppearance(), Define(): pass diff --git a/Lib/stdwin/Histogram.py b/Lib/stdwin/Histogram.py index f469272..505bbaf 100755 --- a/Lib/stdwin/Histogram.py +++ b/Lib/stdwin/Histogram.py @@ -1,41 +1,36 @@ # Module 'Histogram' from Buttons import * -from Resize import Resize - # A Histogram displays a histogram of numeric data. -# It reacts to resize events by resizing itself, -# leaving the same amount of space around the borders. -# (This is geometry management, and should really be implemented -# by a different group of classes, but for now this hack is OK.) # -class HistogramAppearance() = Resize(), LabelAppearance(): +class HistogramAppearance() = LabelAppearance(), Define(): # - def define(self, (win, bounds, ydata, scale)): - self.init_appearance(win, bounds) - self.init_resize() - self.ydata = ydata - self.scale = scale # (min, max) + 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.win.change(self.bounds) + self.parent.change(self.bounds) # - def drawit(self, d): - ydata = self.ydata + def drawpict(self, d): (left, top), (right, bottom) = self.bounds min, max = self.scale size = max-min width, height = right-left, bottom-top - for i in range(len(ydata)): - h0 = left + i * width/len(ydata) - h1 = left + (i+1) * width/len(ydata) - v0 = top + height - (self.ydata[i]-min)*height/size - v1 = top + height + 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() = HistogramAppearance(), NoReactivity(): pass +class Histogram() = NoReactivity(), HistogramAppearance(): pass diff --git a/Lib/stdwin/Sliders.py b/Lib/stdwin/Sliders.py index b142ef4..f655e77 100755 --- a/Lib/stdwin/Sliders.py +++ b/Lib/stdwin/Sliders.py @@ -1,13 +1,11 @@ # Module 'Sliders' -# -# XXX Should split caller interface, appearance and reactivity better import stdwin from stdwinevents import * import rect from Buttons import * -from Resize import * +from HVSplit import HSplit # Field indices in event detail @@ -18,11 +16,13 @@ _BUTTON = 2 _MASK = 3 -# A dragslider is the simplest possible slider. +# 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() = NoReactivity(): +class DragSliderReactivity() = BaseReactivity(): # def mouse_down(self, detail): h, v = hv = detail[_HV] @@ -43,136 +43,133 @@ class DragSliderReactivity() = NoReactivity(): self.active = 0 # -class DragSliderAppearance() = NoResize(), ButtonAppearance(): - # - def define(self, (win, bounds)): - self.min = 0 - self.val = -1 # Changed by next setval call - self.max = 100 - self.setval_hook = 0 - self.pretext = self.postext = '' - self = ClassicButton.define(self, (win, bounds, '')) - self.setval(50) - return self +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 in Python ranges, the end point belongs + # (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.setval_trigger() - # (The trigger may change val, pretext and postext) - self.settext(self.pretext + `self.val` + self.postext) + self.recalctext() + self.trigger() + # + def trigger(self): + if self.hook: + self.hook(self) # - def setval_trigger(self): - if self.setval_hook: - self.setval_hook(self) + def recalctext(self): + self.settext(self.pretext + `self.val` + self.postext) # -class DragSlider() = DragSliderReactivity(), DragSliderAppearance(): pass +class DragSlider() = DragSliderReactivity(), DragSliderAppearance(), Define(): + def definetext(self, (parent, text)): + raise RuntimeError, 'DragSlider.definetext() not supported' -# Auxiliary class for DragSlider incorporated in ComplexSlider +# Auxiliary class for PushButton incorporated in ComplexSlider # -class _SubDragSlider() = DragSlider(): - def define(self, (win, bounds, parent)): - self.parent = parent - return DragSlider.define(self, (win, bounds)) - def setval_trigger(self): - self.parent.val = self.val - self.parent.setval_trigger() - -# Auxiliary class for ClassicButton incorporated in ComplexSlider -# -class _SubClassicButton() = ClassicButton(): - def define(self, (win, bounds, text, step, parent)): - self.parent = parent +class _StepButton() = PushButton(): + def define(self, parent): + self = PushButton.define(self, parent) + self.step = 0 + return self + def setstep(self, step): self.step = step - return ClassicButton.define(self, (win, bounds, text)) + 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.parent.setval(self.parent.val + self.step) - self.delay = 5 - self.win.settimer(self.delay) - def move_trigger(self): - self.win.settimer(self.delay) - def timer_trigger(self): - self.delay = 1 - self.parent.setval(self.parent.val + self.step) - self.win.settimer(self.delay) + 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 a wrapper around three buttons: -# One to step down, a dragslider, and one to step up. + +# A complex slider is an HSplit initialized to three buttons: +# one to step down, a dragslider, and one to step up. # -class ComplexSlider() = NoResize(), LabelAppearance(), NoReactivity(): +class ComplexSlider() = HSplit(): # - def define(self, (win, bounds)): - # - self.win = win - self.bounds = bounds - self.setval_hook = 0 - # - (left, top), (right, bottom) = bounds - size = bottom - top - # - downbox = (left, top), (left+size, bottom) - sliderbox = (left+size, top), (right-size, bottom) - upbox = (right-size, top), (right, bottom) - # - self.downbutton = \ - _SubClassicButton().define(win, downbox, '-', -1, self) - # - self.sliderbutton = \ - _SubDragSlider().define(win, sliderbox, self) - # - self.upbutton = \ - _SubClassicButton().define(win, upbox, '+', 1, self) - # - self.min = self.sliderbutton.min - self.val = self.sliderbutton.val - self.max = self.sliderbutton.max - self.pretext = self.sliderbutton.pretext - self.postext = self.sliderbutton.postext + # Override Slider define() method + # + def define(self, parent): + self = self.create(parent) # HSplit # - self.children = \ - [self.downbutton, self.sliderbutton, self.upbutton] + self.downbutton = _StepButton().definetextstep(self, '-', -1) + self.dragbutton = DragSlider().define(self) + self.upbutton = _StepButton().definetextstep(self, '+', 1) # return self # - def mouse_down(self, detail): - for b in self.children: - b.mouse_down(detail) + # Override HSplit methods # - def mouse_move(self, detail): - for b in self.children: - b.mouse_move(detail) - # - def mouse_up(self, detail): - for b in self.children: - b.mouse_up(detail) - # - def timer(self): - for b in self.children: - b.timer() - # - def draw(self, area): - for b in self.children: - b.draw(area) - # - def setval(self, val): - self.sliderbutton.min = self.min - self.sliderbutton.max = self.max - self.sliderbutton.pretext = self.pretext - self.sliderbutton.postext = self.postext - self.sliderbutton.setval(val) - # - def setval_trigger(self): - if self.setval_hook: - self.setval_hook(self) + def minsize(self, m): + w1, h1 = self.downbutton.minsize(m) + w2, h2 = self.dragbutton.minsize(m) + w3, h3 = self.upbutton.minsize(m) + height = max(h1, h2, h3) + w1 = max(w1, height) + w3 = max(w3, height) + return w1+w2+w3, height # + 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) diff --git a/Lib/stdwin/StripChart.py b/Lib/stdwin/StripChart.py index 2021cc4..ac767c3 100755 --- a/Lib/stdwin/StripChart.py +++ b/Lib/stdwin/StripChart.py @@ -1,17 +1,18 @@ # Module 'StripChart' - import rect -from Buttons import * -from Resize import * +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(), NoResize(): +class StripChart() = LabelAppearance(), NoReactivity(): # - def define(self, (win, bounds, scale)): - self.init_appearance(win, bounds) + def define(self, (parent, scale)): + self.parent = parent + parent.addchild(self) + self.init_appearance() self.init_reactivity() - self.init_resize() self.ydata = [] self.scale = scale self.resetbounds() @@ -37,18 +38,18 @@ class StripChart() = LabelAppearance(), NoReactivity(), NoResize(): excess = len(self.ydata) - self.width if excess > 0: del self.ydata[:excess] - if not self.limbo: - self.win.scroll(self.bounds, (-excess, 0)) - if not self.limbo: + 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.win.begindrawing(), area) + self.draw(self.parent.begindrawing(), area) # def draw(self, (d, area)): - self.limbo = 0 area = rect.intersect(area, self.bounds) if area = rect.empty: + print 'mt' return d.cliprect(area) d.erase(self.bounds) diff --git a/Lib/stdwin/VUMeter.py b/Lib/stdwin/VUMeter.py index 65c018b..59fc36f 100755 --- a/Lib/stdwin/VUMeter.py +++ b/Lib/stdwin/VUMeter.py @@ -10,8 +10,9 @@ class VUMeter() = StripChart(): # # Override define() and timer() methods # - def define(self, (win, bounds)): - self = StripChart.define(self, (win, bounds, 128)) + def define(self, parent): + self = StripChart.define(self, (parent, 128)) + self.parent.need_timer(self) self.sampling = 0 self.rate = 3 self.enable(0) @@ -31,7 +32,7 @@ class VUMeter() = StripChart(): audio.start_recording(size) self.sampling = 1 if self.sampling: - self.win.settimer(1) + self.parent.settimer(1) # # New methods: start() and stop() # -- cgit v0.12