[Python-checkins] r66033 - in sandbox/trunk/ttk-gsoc/src: 2.x/test/test_extensions.py 2.x/ttk.py 3.x/test/test_extensions.py 3.x/ttk.py

guilherme.polo python-checkins at python.org
Mon Aug 25 22:23:58 CEST 2008


Author: guilherme.polo
Date: Mon Aug 25 22:23:57 2008
New Revision: 66033

Log:
ttk.Scale now emits a <<RangedChanged>> event when its range is changed;
Assorted fixes in LabeledScale, tests added.


Modified:
   sandbox/trunk/ttk-gsoc/src/2.x/test/test_extensions.py
   sandbox/trunk/ttk-gsoc/src/2.x/ttk.py
   sandbox/trunk/ttk-gsoc/src/3.x/test/test_extensions.py
   sandbox/trunk/ttk-gsoc/src/3.x/ttk.py

Modified: sandbox/trunk/ttk-gsoc/src/2.x/test/test_extensions.py
==============================================================================
--- sandbox/trunk/ttk-gsoc/src/2.x/test/test_extensions.py	(original)
+++ sandbox/trunk/ttk-gsoc/src/2.x/test/test_extensions.py	Mon Aug 25 22:23:57 2008
@@ -6,14 +6,6 @@
 import support
 
 class LabeledScaleTest(unittest.TestCase):
-    # XXX
-
-#    def setUp(self):
-#        self.lscale = ttk.LabeledScale()
-
-#    def tearDown(self):
-#        self.lscale.destroy()
-
 
     def test_widget_destroy(self):
         # automatically created variable
@@ -59,18 +51,99 @@
         self.failUnlessEqual(x.value, 0.5)
         self.failUnlessEqual(x._variable._name, myvar._name)
 
+        # widget positionment
+        def check_positions(scale, scale_pos, label, label_pos):
+            self.failUnlessEqual(scale.pack_info()['side'], scale_pos)
+            self.failUnlessEqual(label.place_info()['anchor'], label_pos)
+        x = ttk.LabeledScale(compound='top')
+        check_positions(x.scale, 'bottom', x.label, 'n')
+        x = ttk.LabeledScale(compound='bottom')
+        check_positions(x.scale, 'top', x.label, 's')
+        x = ttk.LabeledScale(compound='unknown') # invert default positions
+        check_positions(x.scale, 'top', x.label, 's')
+        x = ttk.LabeledScale() # take default positions
+        check_positions(x.scale, 'bottom', x.label, 'n')
+
         # extra, and invalid, kwargs
         self.failUnlessRaises(Tkinter.TclError, ttk.LabeledScale, a='b')
 
+        x.destroy()
+
+
+    def test_range(self):
+        lscale = ttk.LabeledScale(from_=0, to=10)
+        lscale.pack()
+        lscale.wait_visibility()
+        lscale.update()
+
+        linfo_1 = lscale.label.place_info()
+        prev_xcoord = lscale.scale.coords()[0]
+        self.failUnlessEqual(prev_xcoord, int(linfo_1['x']))
+        # change range to: from -5 to 5. This should change the x coord of
+        # the scale widget, since 0 is at the middle of the new
+        # range.
+        lscale.scale.configure(from_=-5, to=5)
+        # The following update is needed since the test doesn't use mainloop,
+        # at the same time this shouldn't affect test outcome
+        lscale.update()
+        curr_xcoord = lscale.scale.coords()[0]
+        self.failUnless(prev_xcoord != curr_xcoord)
+        # the label widget should have been repositioned too
+        linfo_2 = lscale.label.place_info()
+        self.failUnlessEqual(lscale.label['text'], 0)
+        self.failUnlessEqual(curr_xcoord, int(linfo_2['x']))
+        # change the range back
+        lscale.scale.configure(from_=0, to=10)
+        self.failUnless(prev_xcoord != curr_xcoord)
+        self.failUnlessEqual(prev_xcoord, int(linfo_1['x']))
+
+        lscale.destroy()
+
+
+    def test_variable_change(self):
+        x = ttk.LabeledScale()
+        x.pack()
+        x.wait_visibility()
+        x.update()
+
+        curr_xcoord = x.scale.coords()[0]
+        newval = x.value + 1
+        x.value = newval
+        # The following update is needed since the test doesn't use mainloop,
+        # at the same time this shouldn't affect test outcome
+        x.update()
+        self.failUnlessEqual(x.label['text'], newval)
+        self.failUnless(x.scale.coords()[0] > curr_xcoord)
+        self.failUnlessEqual(x.scale.coords()[0],
+            int(x.label.place_info()['x']))
+
+        # value outside range
+        x.value = x.scale['to'] + 1 # no changes shouldn't happen
+        x.update()
+        self.failUnlessEqual(x.label['text'], newval)
+        self.failUnlessEqual(x.scale.coords()[0],
+            int(x.label.place_info()['x']))
+
+        x.destroy()
+
+
+    def test_resize(self):
+        x = ttk.LabeledScale()
+        x.pack(expand=True, fill='both')
+        x.wait_visibility()
+        x.update()
+
+        width, height = x.master.winfo_width(), x.master.winfo_height()
+        width, height = width * 2, height * 2
+
+        x.value = 3
+        x.update()
+        x.master.wm_geometry("%dx%d" % (width, height))
+        self.failUnlessEqual(int(x.label.place_info()['x']),
+            x.scale.coords()[0])
 
-#    def test_ranges(self): # XXX finish this
-#        self.lscale.pack()
-#        self.lscale.wait_visibility()
-#        self.lscale.update()
-
-#        # test decreasing range
-#        self.lscale.scale.configure(from_=1, to=-1)
-#        self.failUnlessEqual(self.lscale.label['text'], 1)
+        x.master.wm_geometry("%dx%d" % (width, height))
+        x.destroy()
 
 
 class OptionMenuTest(unittest.TestCase):
@@ -104,6 +177,8 @@
         self.failUnless(optmenu['menu'])
         self.failUnless(optmenu['textvariable'])
 
+        optmenu.destroy()
+
 
     def test_menu(self):
         items = ('a', 'b', 'c')
@@ -152,6 +227,7 @@
         if not success:
             self.fail("Menu callback not invoked")
 
+        optmenu.destroy()
 
 
 def test_main():

Modified: sandbox/trunk/ttk-gsoc/src/2.x/ttk.py
==============================================================================
--- sandbox/trunk/ttk-gsoc/src/2.x/ttk.py	(original)
+++ sandbox/trunk/ttk-gsoc/src/2.x/ttk.py	Mon Aug 25 22:23:57 2008
@@ -1053,6 +1053,16 @@
         Widget.__init__(self, master, "ttk::scale", kw)
 
 
+    def configure(self, **kw):
+        """Modify or query scale options.
+
+        Changing "from", "from_" or "to" options generates a
+        <<RangeChanged>> event."""
+        Widget.configure(self, **kw)
+        if any(['from' in kw, 'from_' in kw, 'to' in kw]):
+            self.event_generate('<<RangeChanged>>')
+
+
     def get(self, x=None, y=None):
         """Get the current value of the value option, or the value
         corresponding to the coordinates x, y if they are specified.
@@ -1436,9 +1446,9 @@
     can be accessed through instance.label"""
 
     def __init__(self, master=None, variable=None, from_=0, to=10, **kw):
-        """Construct a LabeledScale with parent master, a variable to be
-        associated with the Ttk Scale widget and its range. If variable is
-        not specified, a Tkinter.IntVar is created.
+        """Construct an horizontal LabeledScale with parent master, a
+        variable to be associated with the Ttk Scale widget and its range.
+        If variable is not specified, a Tkinter.IntVar is created.
 
         WIDGET-SPECIFIC OPTIONS
 
@@ -1450,9 +1460,11 @@
         Frame.__init__(self, master, **kw)
         self._variable = variable or Tkinter.IntVar(master)
         self._variable.set(from_)
+        self._last_valid = from_
 
         self.label = Label(self)
         self.scale = Scale(self, variable=self._variable, from_=from_, to=to)
+        self.scale.bind('<<RangeChanged>>', self._adjust)
 
         # position scale and label according to the compound option
         scale_side = 'bottom' if self._label_top else 'top'
@@ -1463,8 +1475,8 @@
 
         # update the label as scale or variable changes
         self._variable.trace_variable('w', self._adjust)
-        self.scale.bind('<Configure>', self._adjust)
-        self.scale.bind('<Map>', self._adjust)
+        self.bind('<Configure>', self._adjust)
+        self.bind('<Map>', self._adjust)
 
 
     def destroy(self):
@@ -1475,24 +1487,29 @@
 
     def _adjust(self, *args):
         """Adjust the label position according to the scale."""
+        def adjust_label():
+            self.update_idletasks() # "force" scale redraw
+
+            x, y = self.scale.coords()
+            if self._label_top:
+                y = self.scale.winfo_y() - self.label.winfo_reqheight()
+            else:
+                y = self.scale.winfo_reqheight() + self.label.winfo_reqheight()
+
+            self.label.place_configure(x=x, y=y)
+
         from_, to = self.scale['from'], self.scale['to']
         if to < from_:
             from_, to = to, from_
         newval = self._variable.get()
         if not from_ <= newval <= to:
+            # value outside range, set value back to the last valid one
+            self.value = self._last_valid
             return
 
-        self.update()
+        self._last_valid = newval
         self.label['text'] = newval
-
-        x, y = self.scale.coords()
-        if self._label_top:
-            y = self.scale.winfo_y() - self.label.winfo_reqheight()
-        else:
-            y = self.scale.winfo_reqheight() + self.label.winfo_reqheight()
-        x = x + self.scale.winfo_x()
-
-        self.label.place_configure(x=x, y=y)
+        self.after_idle(adjust_label)
 
 
     def _get_value(self):
@@ -1516,7 +1533,9 @@
         """Construct a themed OptionMenu widget with master as the parent,
         the resource textvariable set to variable, the initially selected
         value specified by the default parameter, the menu values given by
-        *values and additional keywords:
+        *values and additional keywords.
+
+        WIDGET-SPECIFIC OPTIONS
 
             style: stylename
                 Menubutton style.

Modified: sandbox/trunk/ttk-gsoc/src/3.x/test/test_extensions.py
==============================================================================
--- sandbox/trunk/ttk-gsoc/src/3.x/test/test_extensions.py	(original)
+++ sandbox/trunk/ttk-gsoc/src/3.x/test/test_extensions.py	Mon Aug 25 22:23:57 2008
@@ -6,14 +6,6 @@
 import support
 
 class LabeledScaleTest(unittest.TestCase):
-    # XXX
-
-#    def setUp(self):
-#        self.lscale = ttk.LabeledScale()
-
-#    def tearDown(self):
-#        self.lscale.destroy()
-
 
     def test_widget_destroy(self):
         # automatically created variable
@@ -59,18 +51,99 @@
         self.failUnlessEqual(x.value, 0.5)
         self.failUnlessEqual(x._variable._name, myvar._name)
 
+        # widget positionment
+        def check_positions(scale, scale_pos, label, label_pos):
+            self.failUnlessEqual(scale.pack_info()['side'], scale_pos)
+            self.failUnlessEqual(label.place_info()['anchor'], label_pos)
+        x = ttk.LabeledScale(compound='top')
+        check_positions(x.scale, 'bottom', x.label, 'n')
+        x = ttk.LabeledScale(compound='bottom')
+        check_positions(x.scale, 'top', x.label, 's')
+        x = ttk.LabeledScale(compound='unknown') # invert default positions
+        check_positions(x.scale, 'top', x.label, 's')
+        x = ttk.LabeledScale() # take default positions
+        check_positions(x.scale, 'bottom', x.label, 'n')
+
         # extra, and invalid, kwargs
         self.failUnlessRaises(tkinter.TclError, ttk.LabeledScale, a='b')
 
+        x.destroy()
+
+
+    def test_range(self):
+        lscale = ttk.LabeledScale(from_=0, to=10)
+        lscale.pack()
+        lscale.wait_visibility()
+        lscale.update()
+
+        linfo_1 = lscale.label.place_info()
+        prev_xcoord = lscale.scale.coords()[0]
+        self.failUnlessEqual(prev_xcoord, int(linfo_1['x']))
+        # change range to: from -5 to 5. This should change the x coord of
+        # the scale widget, since 0 is at the middle of the new
+        # range.
+        lscale.scale.configure(from_=-5, to=5)
+        # The following update is needed since the test doesn't use mainloop,
+        # at the same time this shouldn't affect test outcome
+        lscale.update()
+        curr_xcoord = lscale.scale.coords()[0]
+        self.failUnless(prev_xcoord != curr_xcoord)
+        # the label widget should have been repositioned too
+        linfo_2 = lscale.label.place_info()
+        self.failUnlessEqual(lscale.label['text'], 0)
+        self.failUnlessEqual(curr_xcoord, int(linfo_2['x']))
+        # change the range back
+        lscale.scale.configure(from_=0, to=10)
+        self.failUnless(prev_xcoord != curr_xcoord)
+        self.failUnlessEqual(prev_xcoord, int(linfo_1['x']))
+
+        lscale.destroy()
+
+
+    def test_variable_change(self):
+        x = ttk.LabeledScale()
+        x.pack()
+        x.wait_visibility()
+        x.update()
+
+        curr_xcoord = x.scale.coords()[0]
+        newval = x.value + 1
+        x.value = newval
+        # The following update is needed since the test doesn't use mainloop,
+        # at the same time this shouldn't affect test outcome
+        x.update()
+        self.failUnlessEqual(x.label['text'], newval)
+        self.failUnless(x.scale.coords()[0] > curr_xcoord)
+        self.failUnlessEqual(x.scale.coords()[0],
+            int(x.label.place_info()['x']))
+
+        # value outside range
+        x.value = x.scale['to'] + 1 # no changes shouldn't happen
+        x.update()
+        self.failUnlessEqual(x.label['text'], newval)
+        self.failUnlessEqual(x.scale.coords()[0],
+            int(x.label.place_info()['x']))
+
+        x.destroy()
+
+
+    def test_resize(self):
+        x = ttk.LabeledScale()
+        x.pack(expand=True, fill='both')
+        x.wait_visibility()
+        x.update()
+
+        width, height = x.master.winfo_width(), x.master.winfo_height()
+        width, height = width * 2, height * 2
+
+        x.value = 3
+        x.update()
+        x.master.wm_geometry("%dx%d" % (width, height))
+        self.failUnlessEqual(int(x.label.place_info()['x']),
+            x.scale.coords()[0])
 
-#    def test_ranges(self): # XXX finish this
-#        self.lscale.pack()
-#        self.lscale.wait_visibility()
-#        self.lscale.update()
-
-#        # test decreasing range
-#        self.lscale.scale.configure(from_=1, to=-1)
-#        self.failUnlessEqual(self.lscale.label['text'], 1)
+        x.master.wm_geometry("%dx%d" % (width, height))
+        x.destroy()
 
 
 class OptionMenuTest(unittest.TestCase):
@@ -104,6 +177,8 @@
         self.failUnless(optmenu['menu'])
         self.failUnless(optmenu['textvariable'])
 
+        optmenu.destroy()
+
 
     def test_menu(self):
         items = ('a', 'b', 'c')
@@ -152,6 +227,7 @@
         if not success:
             self.fail("Menu callback not invoked")
 
+        optmenu.destroy()
 
 
 def test_main():

Modified: sandbox/trunk/ttk-gsoc/src/3.x/ttk.py
==============================================================================
--- sandbox/trunk/ttk-gsoc/src/3.x/ttk.py	(original)
+++ sandbox/trunk/ttk-gsoc/src/3.x/ttk.py	Mon Aug 25 22:23:57 2008
@@ -1053,6 +1053,16 @@
         Widget.__init__(self, master, "ttk::scale", kw)
 
 
+    def configure(self, **kw):
+        """Modify or query scale options.
+
+        Changing "from", "from_" or "to" options generates a
+        <<RangeChanged>> event."""
+        Widget.configure(self, **kw)
+        if any(['from' in kw, 'from_' in kw, 'to' in kw]):
+            self.event_generate('<<RangeChanged>>')
+
+
     def get(self, x=None, y=None):
         """Get the current value of the value option, or the value
         corresponding to the coordinates x, y if they are specified.
@@ -1436,9 +1446,9 @@
     can be accessed through instance.label"""
 
     def __init__(self, master=None, variable=None, from_=0, to=10, **kw):
-        """Construct a LabeledScale with parent master, a variable to be
-        associated with the Ttk Scale widget and its range. If variable is
-        not specified, a tkinter.IntVar is created.
+        """Construct an horizontal LabeledScale with parent master, a
+        variable to be associated with the Ttk Scale widget and its range.
+        If variable is not specified, a tkinter.IntVar is created.
 
         WIDGET-SPECIFIC OPTIONS
 
@@ -1450,9 +1460,11 @@
         Frame.__init__(self, master, **kw)
         self._variable = variable or tkinter.IntVar(master)
         self._variable.set(from_)
+        self._last_valid = from_
 
         self.label = Label(self)
         self.scale = Scale(self, variable=self._variable, from_=from_, to=to)
+        self.scale.bind('<<RangeChanged>>', self._adjust)
 
         # position scale and label according to the compound option
         scale_side = 'bottom' if self._label_top else 'top'
@@ -1463,8 +1475,8 @@
 
         # update the label as scale or variable changes
         self._variable.trace_variable('w', self._adjust)
-        self.scale.bind('<Configure>', self._adjust)
-        self.scale.bind('<Map>', self._adjust)
+        self.bind('<Configure>', self._adjust)
+        self.bind('<Map>', self._adjust)
 
 
     def destroy(self):
@@ -1475,24 +1487,29 @@
 
     def _adjust(self, *args):
         """Adjust the label position according to the scale."""
+        def adjust_label():
+            self.update_idletasks() # "force" scale redraw
+
+            x, y = self.scale.coords()
+            if self._label_top:
+                y = self.scale.winfo_y() - self.label.winfo_reqheight()
+            else:
+                y = self.scale.winfo_reqheight() + self.label.winfo_reqheight()
+
+            self.label.place_configure(x=x, y=y)
+
         from_, to = self.scale['from'], self.scale['to']
         if to < from_:
             from_, to = to, from_
         newval = self._variable.get()
         if not from_ <= newval <= to:
+            # value outside range, set value back to the last valid one
+            self.value = self._last_valid
             return
 
-        self.update()
+        self._last_valid = newval
         self.label['text'] = newval
-
-        x, y = self.scale.coords()
-        if self._label_top:
-            y = self.scale.winfo_y() - self.label.winfo_reqheight()
-        else:
-            y = self.scale.winfo_reqheight() + self.label.winfo_reqheight()
-        x = x + self.scale.winfo_x()
-
-        self.label.place_configure(x=x, y=y)
+        self.after_idle(adjust_label)
 
 
     def _get_value(self):
@@ -1516,7 +1533,9 @@
         """Construct a themed OptionMenu widget with master as the parent,
         the resource textvariable set to variable, the initially selected
         value specified by the default parameter, the menu values given by
-        *values and additional keywords:
+        *values and additional keywords.
+
+        WIDGET-SPECIFIC OPTIONS
 
             style: stylename
                 Menubutton style.


More information about the Python-checkins mailing list