]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Initial revision
authorGuido van Rossum <guido@python.org>
Wed, 24 Oct 1990 16:39:18 +0000 (16:39 +0000)
committerGuido van Rossum <guido@python.org>
Wed, 24 Oct 1990 16:39:18 +0000 (16:39 +0000)
Lib/lib-stdwin/Buttons.py [new file with mode: 0644]
Lib/lib-stdwin/Sliders.py [new file with mode: 0644]
Lib/stdwin/Buttons.py [new file with mode: 0755]
Lib/stdwin/Sliders.py [new file with mode: 0755]

diff --git a/Lib/lib-stdwin/Buttons.py b/Lib/lib-stdwin/Buttons.py
new file mode 100644 (file)
index 0000000..d6ad6e3
--- /dev/null
@@ -0,0 +1,397 @@
+# Module 'Buttons' -- see README
+#
+# Module functionality is now split in two parts:
+# - 'appearance' defines what it looks like
+# - 'reactivity' defines how it acts to mouse events
+
+
+# Import module 'rect' renamed as '_rect'
+#
+import rect
+_rect = rect
+del rect
+
+
+# Field indices in mouse event detail
+#
+_HV = 0
+_CLICKS = 1
+_BUTTON = 2
+_MASK = 3
+
+
+# BaseAppearance provides defaults for all appearance methods.
+# In fact it looks like a label.
+#
+class BaseAppearance():
+       #
+       # Initialization
+       #
+       def init_appearance(self, (win, bounds)):
+               win.change(bounds)
+               self.win = win
+               self.bounds = bounds
+               self.enabled = 1
+               self.hilited = 0
+               self.selected = 0
+               self.text = ''
+       #
+       # Changing the parameters
+       #
+       def settext(self, text):
+               self.text = text
+               self.redraw()
+       #
+       def setbounds(self, bounds):
+               # This elays drawing until after all buttons are moved
+               self.win.change(self.bounds)
+               self.bounds = bounds
+               self.win.change(bounds)
+       #
+       # Changing the state bits
+       #
+       def enable(self, flag):
+               if flag <> self.enabled:
+                       self.enabled = flag
+                       self.flipenable(self.win.begindrawing())
+       #
+       def hilite(self, flag):
+               if flag <> self.hilited:
+                       self.hilited = flag
+                       self.fliphilite(self.win.begindrawing())
+       #
+       def select(self, flag):
+               if flag <> self.selected:
+                       self.selected = flag
+                       self.redraw()
+       #
+       # Generic drawing mechanism.
+       # There should be no reason to override redraw() or draw() methods.
+       #
+       def redraw(self):
+               self.draw(self.win.begindrawing(), self.bounds)
+       #
+       def draw(self, (d, area)):
+               area = _rect.intersect(area, self.bounds)
+               if area = _rect.empty:
+                       return
+               d.cliprect(area)
+               d.erase(self.bounds)
+               self.drawit(d)
+               d.noclip()
+       #
+       # The drawit() method is fairly generic but may be overridden.
+       #
+       def drawit(self, d):
+               self.drawpict(d)        # Box, circle etc.; also 'selected'
+               if self.text:
+                       hv = self.textpos(d)
+                       d.text(hv, 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.
+       # No picture; centered text; enable crosses out; hilite inverts.
+       #
+       def drawpict(self, d):
+               pass
+       #
+       def textpos(self, d):
+               # XXX shouldn't this be done once by init/settext()?
+               (left, top), (right, bottom) = self.bounds
+               h = (left + right - d.textwidth(self.text)) / 2
+               v = (top + bottom - d.lineheight()) / 2
+               return h, v
+       #
+       def flipenable(self, d):
+               _xorcross(d, self.bounds)
+       #
+       def fliphilite(self, d):
+               d.invert(_rect.inset(self.bounds, (3, 3)))
+
+
+# Subroutine to cross out a rectangle.
+#
+def _xorcross(d, bounds):
+       ((left, top), (right, bottom)) = bounds
+       left = left + 2
+       right = right - 2
+       top = top + 2
+       bottom = bottom - 3
+       d.xorline(((left, top), (right, bottom)))
+       d.xorline((left, bottom), (right, top))
+
+
+# LabelAppearance displays a centered string.
+# selected --> underlined
+# disabled --> crossed out
+# hilited  --> inverted
+#
+class LabelAppearance() = BaseAppearance():
+       #
+       def drawpict(self, d):
+               if self.selected:
+                       # Underline it
+                       d.line((left+1, bottom-1), (right-1, bottom-1))
+               #
+               if not self.enabled: self._crossout(d)
+               if self.hilited: self._invert(d)
+       #
+
+
+# ButtonAppearance displays a centered string in a box.
+# selected --> bold border
+# disabled --> crossed out
+# hilited  --> inverted
+#
+class ButtonAppearance() = BaseAppearance():
+       #
+       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() = BaseAppearance():
+       #
+       def drawpict(self, d):
+               (left, top), (right, bottom) = self.bounds
+               size = bottom - top
+               boxbounds = (left, top), (left+size, bottom)
+               d.box(boxbounds)
+               if self.selected: _xorcross(d, boxbounds)
+       #
+       def textpos(self, d):
+               (left, top), (right, bottom) = self.bounds
+               size = bottom - top
+               h = left + size + d.textwidth(' ')
+               v = top + (size - d.lineheight()) / 2
+               return h, v
+       #
+       def fliphilite(self, d):
+               (left, top), (right, bottom) = self.bounds
+               size = bottom - top
+               boxbounds = (left, top), (left+size, bottom)
+               d.invert(boxbounds)
+       #
+
+
+# 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() = BaseAppearance():
+       #
+       def drawpict(self, d):
+               (left, top), (right, bottom) = self.bounds
+               size = bottom - top
+               radius = size / 2
+               h, v = left + radius, top + radius
+               d.circle((h, v), radius - 1)
+               if self.selected:
+                       some = radius/3
+                       d.paint((h-some, v-some), (h+some, v+some))
+       #
+       def textpos(self, d):
+               (left, top), (right, bottom) = self.bounds
+               size = bottom - top
+               h = left + size + d.textwidth(' ')
+               v = top + (size - d.lineheight()) / 2
+               return h, v
+       #
+       def fliphilite(self, d):
+               (left, top), (right, bottom) = self.bounds
+               size = bottom - top
+               d.invert((left, top), (left + size, bottom))
+       #
+
+
+# NoReactivity ignores mouse and timer events.
+# 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
+# active_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():
+       #
+       def init_reactivity(self):
+               self.down_hook = self.active_hook = self.up_hook = \
+                 self.on_hook = self.off_hook = self.timer_hook = \
+                 self.hook = self.active = 0
+       #
+       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 timer(self):
+               pass
+       #
+       def down_trigger(self):
+               if self.down_hook: self.down_hook(self)
+       #
+       def active_trigger(self):
+               if self.active_hook: self.active_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 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():
+       #
+       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.active_trigger()
+       #
+       def mouse_up(self, detail):
+               if self.active:
+                       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()
+               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() = NoReactivity():
+       #
+       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.active_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)
+       #
+       def timer(self):
+               if self.active and self.hilited:
+                       self.active_trigger()
+       #
+
+
+# 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.
+#
+class Define():
+       #
+       def define(self, (win, bounds, text)):
+               self.init_appearance(win, bounds)
+               self.text = text
+               self.init_reactivity()
+               return self
+
+
+# Ready-made button classes
+#
+class BaseButton() = NoReactivity(), BaseAppearance(), Define(): pass
+class Label() = NoReactivity(), LabelAppearance(), Define(): pass
+class ClassicButton() = TriggerReactivity(), ButtonAppearance(), Define(): pass
+class CheckButton() = CheckReactivity(), CheckAppearance(), Define(): pass
+class RadioButton() = RadioReactivity(), RadioAppearance(), Define(): pass
+class Toggle() = ToggleReactivity(), ButtonAppearance(), Define(): pass
diff --git a/Lib/lib-stdwin/Sliders.py b/Lib/lib-stdwin/Sliders.py
new file mode 100644 (file)
index 0000000..8953efd
--- /dev/null
@@ -0,0 +1,74 @@
+# Module 'Sliders'
+#
+# Sliders are somewhat like buttons but have an extra hook that is
+# called whenever their value is changed.
+
+import stdwin
+from stdwinevents import *
+import rect
+from minmax import min, max
+from Buttons import ClassicButton
+
+
+# Field indices in event detail
+#
+_HV = 0
+_CLICKS = 1
+_BUTTON = 2
+_MASK = 3
+
+
+# A dragslider is the simplest possible slider.
+# It looks like a button but dragging the mouse left or right
+# changes the controlled value.
+#
+class DragSlider() = ClassicButton():
+       #
+       # INVARIANTS maintained by the define and setval methods:
+       #
+       #       self.min <= self.val <= self.max
+       #       self.text = `self.val`
+       #
+       # (Notice that unlike in Python ranges, the end point belongs
+       # to the range.)
+       #
+       def define(self, (win, bounds)):
+               self.min = 0
+               self.val = 50
+               self.max = 100
+               self.setval_hook = 0
+               self.pretext = self.postext = ''
+               self.text = self.pretext + `self.val` + self.postext
+               self = ClassicButton.define(self, (win, bounds, self.text))
+               return self
+       #
+       def setval(self, val):
+               val = min(self.max, max(self.min, val))
+               if val <> self.val:
+                       self.val = val
+                       self.text = self.pretext + `self.val` + self.postext
+                       if self.setval_hook:
+                               self.setval_hook(self)
+                       self.redraw()
+       #
+       def settext(self, text):
+               pass # shouldn't be called at all
+       #
+       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
+       #
diff --git a/Lib/stdwin/Buttons.py b/Lib/stdwin/Buttons.py
new file mode 100755 (executable)
index 0000000..d6ad6e3
--- /dev/null
@@ -0,0 +1,397 @@
+# Module 'Buttons' -- see README
+#
+# Module functionality is now split in two parts:
+# - 'appearance' defines what it looks like
+# - 'reactivity' defines how it acts to mouse events
+
+
+# Import module 'rect' renamed as '_rect'
+#
+import rect
+_rect = rect
+del rect
+
+
+# Field indices in mouse event detail
+#
+_HV = 0
+_CLICKS = 1
+_BUTTON = 2
+_MASK = 3
+
+
+# BaseAppearance provides defaults for all appearance methods.
+# In fact it looks like a label.
+#
+class BaseAppearance():
+       #
+       # Initialization
+       #
+       def init_appearance(self, (win, bounds)):
+               win.change(bounds)
+               self.win = win
+               self.bounds = bounds
+               self.enabled = 1
+               self.hilited = 0
+               self.selected = 0
+               self.text = ''
+       #
+       # Changing the parameters
+       #
+       def settext(self, text):
+               self.text = text
+               self.redraw()
+       #
+       def setbounds(self, bounds):
+               # This elays drawing until after all buttons are moved
+               self.win.change(self.bounds)
+               self.bounds = bounds
+               self.win.change(bounds)
+       #
+       # Changing the state bits
+       #
+       def enable(self, flag):
+               if flag <> self.enabled:
+                       self.enabled = flag
+                       self.flipenable(self.win.begindrawing())
+       #
+       def hilite(self, flag):
+               if flag <> self.hilited:
+                       self.hilited = flag
+                       self.fliphilite(self.win.begindrawing())
+       #
+       def select(self, flag):
+               if flag <> self.selected:
+                       self.selected = flag
+                       self.redraw()
+       #
+       # Generic drawing mechanism.
+       # There should be no reason to override redraw() or draw() methods.
+       #
+       def redraw(self):
+               self.draw(self.win.begindrawing(), self.bounds)
+       #
+       def draw(self, (d, area)):
+               area = _rect.intersect(area, self.bounds)
+               if area = _rect.empty:
+                       return
+               d.cliprect(area)
+               d.erase(self.bounds)
+               self.drawit(d)
+               d.noclip()
+       #
+       # The drawit() method is fairly generic but may be overridden.
+       #
+       def drawit(self, d):
+               self.drawpict(d)        # Box, circle etc.; also 'selected'
+               if self.text:
+                       hv = self.textpos(d)
+                       d.text(hv, 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.
+       # No picture; centered text; enable crosses out; hilite inverts.
+       #
+       def drawpict(self, d):
+               pass
+       #
+       def textpos(self, d):
+               # XXX shouldn't this be done once by init/settext()?
+               (left, top), (right, bottom) = self.bounds
+               h = (left + right - d.textwidth(self.text)) / 2
+               v = (top + bottom - d.lineheight()) / 2
+               return h, v
+       #
+       def flipenable(self, d):
+               _xorcross(d, self.bounds)
+       #
+       def fliphilite(self, d):
+               d.invert(_rect.inset(self.bounds, (3, 3)))
+
+
+# Subroutine to cross out a rectangle.
+#
+def _xorcross(d, bounds):
+       ((left, top), (right, bottom)) = bounds
+       left = left + 2
+       right = right - 2
+       top = top + 2
+       bottom = bottom - 3
+       d.xorline(((left, top), (right, bottom)))
+       d.xorline((left, bottom), (right, top))
+
+
+# LabelAppearance displays a centered string.
+# selected --> underlined
+# disabled --> crossed out
+# hilited  --> inverted
+#
+class LabelAppearance() = BaseAppearance():
+       #
+       def drawpict(self, d):
+               if self.selected:
+                       # Underline it
+                       d.line((left+1, bottom-1), (right-1, bottom-1))
+               #
+               if not self.enabled: self._crossout(d)
+               if self.hilited: self._invert(d)
+       #
+
+
+# ButtonAppearance displays a centered string in a box.
+# selected --> bold border
+# disabled --> crossed out
+# hilited  --> inverted
+#
+class ButtonAppearance() = BaseAppearance():
+       #
+       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() = BaseAppearance():
+       #
+       def drawpict(self, d):
+               (left, top), (right, bottom) = self.bounds
+               size = bottom - top
+               boxbounds = (left, top), (left+size, bottom)
+               d.box(boxbounds)
+               if self.selected: _xorcross(d, boxbounds)
+       #
+       def textpos(self, d):
+               (left, top), (right, bottom) = self.bounds
+               size = bottom - top
+               h = left + size + d.textwidth(' ')
+               v = top + (size - d.lineheight()) / 2
+               return h, v
+       #
+       def fliphilite(self, d):
+               (left, top), (right, bottom) = self.bounds
+               size = bottom - top
+               boxbounds = (left, top), (left+size, bottom)
+               d.invert(boxbounds)
+       #
+
+
+# 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() = BaseAppearance():
+       #
+       def drawpict(self, d):
+               (left, top), (right, bottom) = self.bounds
+               size = bottom - top
+               radius = size / 2
+               h, v = left + radius, top + radius
+               d.circle((h, v), radius - 1)
+               if self.selected:
+                       some = radius/3
+                       d.paint((h-some, v-some), (h+some, v+some))
+       #
+       def textpos(self, d):
+               (left, top), (right, bottom) = self.bounds
+               size = bottom - top
+               h = left + size + d.textwidth(' ')
+               v = top + (size - d.lineheight()) / 2
+               return h, v
+       #
+       def fliphilite(self, d):
+               (left, top), (right, bottom) = self.bounds
+               size = bottom - top
+               d.invert((left, top), (left + size, bottom))
+       #
+
+
+# NoReactivity ignores mouse and timer events.
+# 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
+# active_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():
+       #
+       def init_reactivity(self):
+               self.down_hook = self.active_hook = self.up_hook = \
+                 self.on_hook = self.off_hook = self.timer_hook = \
+                 self.hook = self.active = 0
+       #
+       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 timer(self):
+               pass
+       #
+       def down_trigger(self):
+               if self.down_hook: self.down_hook(self)
+       #
+       def active_trigger(self):
+               if self.active_hook: self.active_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 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():
+       #
+       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.active_trigger()
+       #
+       def mouse_up(self, detail):
+               if self.active:
+                       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()
+               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() = NoReactivity():
+       #
+       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.active_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)
+       #
+       def timer(self):
+               if self.active and self.hilited:
+                       self.active_trigger()
+       #
+
+
+# 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.
+#
+class Define():
+       #
+       def define(self, (win, bounds, text)):
+               self.init_appearance(win, bounds)
+               self.text = text
+               self.init_reactivity()
+               return self
+
+
+# Ready-made button classes
+#
+class BaseButton() = NoReactivity(), BaseAppearance(), Define(): pass
+class Label() = NoReactivity(), LabelAppearance(), Define(): pass
+class ClassicButton() = TriggerReactivity(), ButtonAppearance(), Define(): pass
+class CheckButton() = CheckReactivity(), CheckAppearance(), Define(): pass
+class RadioButton() = RadioReactivity(), RadioAppearance(), Define(): pass
+class Toggle() = ToggleReactivity(), ButtonAppearance(), Define(): pass
diff --git a/Lib/stdwin/Sliders.py b/Lib/stdwin/Sliders.py
new file mode 100755 (executable)
index 0000000..8953efd
--- /dev/null
@@ -0,0 +1,74 @@
+# Module 'Sliders'
+#
+# Sliders are somewhat like buttons but have an extra hook that is
+# called whenever their value is changed.
+
+import stdwin
+from stdwinevents import *
+import rect
+from minmax import min, max
+from Buttons import ClassicButton
+
+
+# Field indices in event detail
+#
+_HV = 0
+_CLICKS = 1
+_BUTTON = 2
+_MASK = 3
+
+
+# A dragslider is the simplest possible slider.
+# It looks like a button but dragging the mouse left or right
+# changes the controlled value.
+#
+class DragSlider() = ClassicButton():
+       #
+       # INVARIANTS maintained by the define and setval methods:
+       #
+       #       self.min <= self.val <= self.max
+       #       self.text = `self.val`
+       #
+       # (Notice that unlike in Python ranges, the end point belongs
+       # to the range.)
+       #
+       def define(self, (win, bounds)):
+               self.min = 0
+               self.val = 50
+               self.max = 100
+               self.setval_hook = 0
+               self.pretext = self.postext = ''
+               self.text = self.pretext + `self.val` + self.postext
+               self = ClassicButton.define(self, (win, bounds, self.text))
+               return self
+       #
+       def setval(self, val):
+               val = min(self.max, max(self.min, val))
+               if val <> self.val:
+                       self.val = val
+                       self.text = self.pretext + `self.val` + self.postext
+                       if self.setval_hook:
+                               self.setval_hook(self)
+                       self.redraw()
+       #
+       def settext(self, text):
+               pass # shouldn't be called at all
+       #
+       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
+       #