[Skencil-commits] r524 - in skencil/trunk: . Sketch/Editor test

scm-commit@wald.intevation.org scm-commit at wald.intevation.org
Sun Feb 26 23:10:50 CET 2006


Author: bh
Date: 2006-02-26 23:10:50 +0100 (Sun, 26 Feb 2006)
New Revision: 524

Added:
   skencil/trunk/test/test_tools.py
Modified:
   skencil/trunk/ChangeLog
   skencil/trunk/Sketch/Editor/doceditor.py
   skencil/trunk/Sketch/Editor/tools.py
   skencil/trunk/test/ChangeLog
Log:

Savannah Patch #4516 (temptool2.diff) by Valentin Ungureanu:

* Sketch/Editor/tools.py (TemporaryToolInstance): New. A class
that implements a temporary editor tool.

* Sketch/Editor/doceditor.py (__init__): New instance variable
tool_stack. When a temporary tools is activated the current tool
is added to this list instead of being ended. The list is emptied
when a non-temporary tool is activated.
(EditorWithSelection.SetTool): Adapt to temporary tools. Also make
it a noop if the name of the current tool is the same as the name
of the tool to be activated.
(TemporaryToolFinished): New. Switch to the previous tool in the
stack.  This method is called by the temporary tools when they are
done.

* test_tools.py: New.  Tests for temporary tools.  Tests for other
tools might stay here as well.


Modified: skencil/trunk/ChangeLog
===================================================================
--- skencil/trunk/ChangeLog	2006-02-12 00:19:46 UTC (rev 523)
+++ skencil/trunk/ChangeLog	2006-02-26 22:10:50 UTC (rev 524)
@@ -1,3 +1,21 @@
+2006-02-26  Bernhard Herzog  <bh at intevation.de>
+
+	Savannah Patch #4516 by Valentin Ungureanu:
+
+	* Sketch/Editor/tools.py (TemporaryToolInstance): New. A class
+	that implements a temporary editor tool.
+
+	* Sketch/Editor/doceditor.py (__init__): New instance variable
+	tool_stack. When a temporary tools is activated the current tool
+	is added to this list instead of being ended. The list is emptied
+	when a non-temporary tool is activated.
+	(EditorWithSelection.SetTool): Adapt to temporary tools. Also make
+	it a noop if the name of the current tool is the same as the name
+	of the tool to be activated.
+	(TemporaryToolFinished): New. Switch to the previous tool in the
+	stack.  This method is called by the temporary tools when they are
+	done.
+
 2006-01-29  Bernhard Herzog  <bh at intevation.de>
 
 	* Sketch/Modules/skreadmodule.c (sklex): Detect NUL-bytes after a

Modified: skencil/trunk/Sketch/Editor/doceditor.py
===================================================================
--- skencil/trunk/Sketch/Editor/doceditor.py	2006-02-12 00:19:46 UTC (rev 523)
+++ skencil/trunk/Sketch/Editor/doceditor.py	2006-02-26 22:10:50 UTC (rev 524)
@@ -1,5 +1,5 @@
 # Sketch - A Python-based interactive drawing program
-# Copyright (C) 1996 -- 2005 by Bernhard Herzog
+# Copyright (C) 1996 -- 2006 by Bernhard Herzog
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Library General Public
@@ -29,6 +29,7 @@
 from Sketch.Editor import AddEditorBuiltin
 
 from selection import Selection
+from tools import TemporaryToolInstance
 
 
 class EditorWithSelection(Sketch.Base.Publisher):
@@ -188,6 +189,7 @@
         # the name of the active tool
         self.tool = None
         self.toolname = ''
+        self.tool_stack = []
 
         # At first we're using the selection tool. Given that the tool
         # is determined by the toolbox this default will usually be
@@ -440,25 +442,47 @@
     def SetTool(self, toolname):
         """Switch to the tool named toolname.
 
-        If there already is a tool call its End method. Then look up the
-        tool in Sketch.Editor.toolmap and instantiate the tool for self.
-        Finally, update the handles.
+        First deal with the active tool.  If the new tool is a temporary
+        one then add the current tool to self.tool_stack so that it can
+        be retrieved later when the temporary tool is finished.
+        Otherwise call the End method of the current tool and also empty
+        self.tool_stack and end all the tools kept there, if any.
+        Then look up the tool in Sketch.Editor.toolmap and instantiate the
+        tool for self.
+        Finally, update the handles and send a TOOL message with the new tool
+        name.
         """
+        if self.toolname == toolname:
+            return
         tool = Sketch.Editor.toolmap.get(toolname)
-        old_toolname = self.toolname
         if tool is not None:
-            if self.toolname != toolname:
-                if self.tool is not None:
+            if self.tool is not None:
+                if issubclass(tool.editor_tool, TemporaryToolInstance):
+                    self.tool_stack.append((self.tool, self.toolname))
+                else:
                     self.tool.End()
+                    while self.tool_stack:
+                        self.tool_stack.pop()[0].End()
             self.tool = tool.Instantiate(self)
             self.toolname = toolname
         else:
             self.tool = None
             self.toolname = ''
-        if self.toolname != old_toolname:
-            self.issue(TOOL, self.toolname)
+        self.issue(TOOL, self.toolname)
         self.update_handles()
 
+    def TemporaryToolFinished(self):
+        """Switch to the previous tool in the stack.
+
+        This method should be called by temporary tools when they are
+        done.  After restoring the previous tool, send a TOOL message
+        with the name of tool just activated.
+        """
+        self.tool.End()
+        self.tool, self.toolname = self.tool_stack.pop()
+        self.issue(TOOL, self.toolname)
+        self.update_handles()
+
     def Tool(self):
         """Return the currently active tool"""
         return self.tool

Modified: skencil/trunk/Sketch/Editor/tools.py
===================================================================
--- skencil/trunk/Sketch/Editor/tools.py	2006-02-12 00:19:46 UTC (rev 523)
+++ skencil/trunk/Sketch/Editor/tools.py	2006-02-26 22:10:50 UTC (rev 524)
@@ -1,5 +1,5 @@
 # Sketch - A Python-based interactive drawing program
-# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2004 by Bernhard Herzog
+# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2004, 2006 by Bernhard Herzog
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Library General Public
@@ -168,6 +168,23 @@
         pass
 
 
+class TemporaryToolInstance(ToolInstance):
+
+    """Base class for temporary tools.
+
+    Derived classes should call self.finished() when they are done. This
+    instructs the editor to switch to the previous tool in its tool
+    stack.
+    """
+
+    def finished(self):
+        self.editor.TemporaryToolFinished()
+
+    def Cancel(self, context):
+        ToolInstance.Cancel(self, context)
+        self.finished()
+
+
 class ToolInfo:
 
     def __init__(self, name, editor_tool, cursor = 'CurStd',

Modified: skencil/trunk/test/ChangeLog
===================================================================
--- skencil/trunk/test/ChangeLog	2006-02-12 00:19:46 UTC (rev 523)
+++ skencil/trunk/test/ChangeLog	2006-02-26 22:10:50 UTC (rev 524)
@@ -1,3 +1,10 @@
+2006-02-26  Bernhard Herzog  <bh at intevation.de>
+
+	Savannah Patch #4516 by Valentin Ungureanu:
+
+	* test_tools.py: New.  Tests for temporary tools.  Tests for other
+	tools might stay here as well.
+
 2006-01-29  Bernhard Herzog  <bh at intevation.de>
 
 	* test_skread.py (SKReadTest.test_tokenize_line_error_reporting)

Added: skencil/trunk/test/test_tools.py
===================================================================
--- skencil/trunk/test/test_tools.py	2006-02-12 00:19:46 UTC (rev 523)
+++ skencil/trunk/test/test_tools.py	2006-02-26 22:10:50 UTC (rev 524)
@@ -0,0 +1,350 @@
+# Skencil - A Python-based interactive drawing program
+# Copyright (C) 2006 by Bernhard Herzog
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+"""Tests for Tool objects"""
+
+__version__ = "$Revision$"
+# $Id$
+
+
+import unittest
+
+import support
+support.add_sketch_dir_to_path()
+
+import Sketch
+from Sketch import Point, CreatePath
+from Sketch.Base.const import TOOL
+from Sketch.Graphics import PolyBezier
+from Sketch.Editor import EditorWithSelection, InteractiveDocument, Context, \
+     Button1Mask, toolmap, Button1
+from Sketch.Editor.selectiontool import SelectionRectangle
+from Sketch.Editor.tools import ToolInfo, TemporaryToolInstance
+
+from test_doceditor import MockCanvas, MockToolInstance
+
+
+class MockTemporaryToolInstance(MockToolInstance, TemporaryToolInstance):
+
+    """TemporaryToolInstance that records some method calls"""
+
+    def Cancel(self, context):
+        self.called_methods.append(("Cancel", context))
+        TemporaryToolInstance.Cancel(self, context)
+
+#
+# Two temporary tools.
+#
+
+class TempToolA(MockTemporaryToolInstance):
+
+    """A temporary tool that handles a press-drag-release cycle"""
+
+    def ButtonPress(self, canvas, p, snapped, button, state, handle = None):
+        MockTemporaryToolInstance.ButtonPress(self, canvas, p, snapped, button,
+                                              state, handle = None)
+        self.begin_edit_object(canvas, SelectionRectangle(p), p, button, state)
+
+    def ButtonRelease(self, canvas, p, snapped, button, state):
+        MockTemporaryToolInstance.ButtonRelease(self, canvas, p, snapped,
+                                                button, state)
+        obj = self.end_edit_object(canvas, p, button, state)
+
+        # obj can be None if the tool receives a ButtonRelease without first
+        # receiveing a ButtonPress (this can happen if the tool is activated
+        # with a keyboard shortcut while the mouse button is pressed.) Here,
+        # if that is the case, we just ignore the button release.
+        if obj is not None:
+            self.finished()
+
+Tool_A = ToolInfo("Tool_A", TempToolA)
+
+
+class TempToolB(MockTemporaryToolInstance):
+
+    """A temporary tool that handles a mouse click"""
+
+    def ButtonClick(self, canvas, p, snapped, button, state, handle = None):
+        MockTemporaryToolInstance.ButtonClick(self, canvas, p, snapped,
+                                              button, state, handle = None)
+        self.finished()
+
+Tool_B = ToolInfo("Tool_B", TempToolB)
+
+
+class TestTools(unittest.TestCase):
+
+    def setUp(self):
+        self.old_toolmap = toolmap.copy()
+        toolmap["Tool_B"] = Tool_B
+        toolmap['Tool_A'] = Tool_A
+        self.messages = []
+
+        self.document = InteractiveDocument(create_layer = 1)
+        self.editor = EditorWithSelection(self.document)
+        self.editor.Subscribe(TOOL, self.tool_changed)
+        self.context = Context(None)
+        self.context.set_editor(self.editor)
+        self.canvas = MockCanvas()
+
+        # Make sure snapping is off
+        self.failIf(self.editor.IsSnappingToGrid())
+        self.failIf(self.editor.IsSnappingToGuides())
+        self.failIf(self.editor.IsSnappingToObjects())
+
+        # SelectionTool must be active
+        self.assertToolActive('SelectionTool')
+
+    def tearDown(self):
+        if self.editor is not None:
+            self.editor.Destroy()
+        self.editor = self.document = self.canvas = self.context = None
+        toolmap.clear()
+        toolmap.update(self.old_toolmap)
+
+    def clear_messages(self):
+        del self.messages[:]
+
+    def tool_changed(self, *args):
+        self.messages.append(args)
+
+    def assertToolActive(self, toolname):
+        """Check if a specific editor tool is active."""
+        toolinstance = toolmap[toolname].editor_tool
+        self.failUnless(isinstance(self.editor.Tool(), toolinstance))
+
+    def assertToolMessages(self, messages):
+        self.assertEquals(self.messages, messages)
+        self.clear_messages()
+
+    def test_edittool_with_temporary(self):
+        """Test switching to a temporary tool while EditTool is active.
+
+        Create a polyline and select its first node.
+        Activate a temporary tool.
+        After exiting the temporary tool check if the node is still selected.
+        """
+        editor = self.editor
+        canvas = self.canvas
+        # insert a polyline into the editor
+        path = CreatePath()
+        path.AppendLine(0, 0)
+        path.AppendLine(10, 10)
+        poly = PolyBezier(paths = (path,))
+        self.editor.Insert(poly)
+        self.editor.SelectObject(poly)
+        self.context.SetTool('EditTool')
+        self.assertToolActive('EditTool')
+        self.assertToolMessages([('EditTool',)])
+        # remember the tool instance
+        tool_edit = editor.Tool()
+
+        # Get a handle so that we can click on it
+        handle = editor.Handles()[0]
+        editor.ButtonPress(canvas, Point(0, 0), Button1, Button1Mask,
+                           handle=handle)
+
+        # Check if the first node was indeed selected
+        self.failUnless(editor.CurrentObject().Paths()[0].SegmentSelected(0))
+
+        # Switch to a temporary tool
+        self.context.SetTool('Tool_B')
+        self.assertToolActive('Tool_B')
+        self.assertToolMessages([('Tool_B',)])
+        tool_b = editor.Tool()
+        # This shoul end tool_b:
+        editor.ButtonClick(self.canvas, Point(5,5), Button1, 0)
+        # check if ButtonClick and End were called
+        self.assertEquals(tool_b.called_methods, [('ButtonClick', canvas,
+                          Point(5, 5), Point(5, 5), 1, 0, None), ('End',)])
+
+        # check if EditTool was reativated
+        self.assertToolActive('EditTool')
+        self.assertToolMessages([('EditTool',)])
+        # check that we have the same instance
+        self.assert_(editor.Tool() is tool_edit)
+        # check if the first node is still selected
+        self.failUnless(editor.CurrentObject().Paths()[0].SegmentSelected(0))
+
+    def test_polyline_tool_with_temporaries_chain(self):
+        """Test chaining two temporary tools while creating a polyline.
+
+        A polyline is started but before finishing we also start two temporary
+        tools, one 'embedded' into the other.
+
+        After each button press/release we check if the editor sent the event
+        to the proper tool by examining the last item in the called_methods
+        attribute of the tool.
+
+        The (rather improbable) chain of events is:
+            Activate CreatePoly
+            Press
+            Move
+            Release (this creates a first segment)
+            Activate Tool_A
+            Press
+            Move
+            Activate Tool_B
+            Release
+            Click (this ends Tool_B and reactivates Tool_A)
+            Press
+            Release (this end Tool_A and reactivates CreatePoly)
+            Click (this creates a second segment)
+            Activate SelectionTool and check the created object
+        """
+        self.context.SetTool('CreatePoly')
+        self.assertToolMessages([('CreatePoly',)])
+        editor = self.editor
+        canvas = self.canvas
+        # Remember the tool
+        tool_poly = editor.Tool()
+
+        # Start creating a polyline.
+        editor.ButtonPress(canvas, Point(0, 0), Button1, 0)
+        editor.PointerMotion(canvas, Point(100, 100), Button1Mask)
+        editor.ButtonRelease(canvas, Point(200, 200), Button1, Button1Mask)
+
+        # Switch to the temporary tool A
+        self.context.SetTool('Tool_A')
+        self.assertToolActive('Tool_A')
+        self.assertToolMessages([('Tool_A',)])
+        tool_a = editor.Tool()
+
+        editor.ButtonPress(canvas, Point(10, 10), Button1, 0)
+        # Check that the event was sent to tool_a. The editor adds to the
+        # arguments a "snapped" point and the handle over which the button
+        # was pressed (None in our case)
+        self.assertEquals(tool_a.called_methods[-1], ('ButtonPress', canvas,
+                          Point(10, 10), Point(10, 10), Button1, 0, None))
+
+        editor.PointerMotion(canvas, Point(20, 20), Button1Mask)
+        self.assertEquals(tool_a.called_methods[-1], ('PointerMotion', canvas,
+                          Point(20, 20), Point(20, 20), Button1Mask))
+
+        # Switch to the temporary tool B
+        self.context.SetTool('Tool_B')
+        self.assertToolActive('Tool_B')
+        self.assertToolMessages([('Tool_B',)])
+        tool_b = editor.Tool()
+
+        # Release the mouse as it remained pressed
+        editor.ButtonRelease(canvas, Point(20, 20), Button1, Button1Mask)
+        self.assertEquals(tool_b.called_methods[-1], ('ButtonRelease', canvas,
+                          Point(20, 20), Point(20, 20), Button1, Button1Mask))
+
+        editor.ButtonClick(canvas, Point(30, 30), Button1, 0)
+        # This ButtonClick should have ended tool_b
+        self.assertEquals(tool_b.called_methods[-1], ('End',))
+
+        #
+        # Now we should have reverted to temporary tool A
+        #
+        self.assertToolActive('Tool_A')
+        self.assertToolMessages([('Tool_A',)])
+        # Check if the tool instance is the same
+        self.assert_(editor.Tool() is tool_a)
+
+        editor.ButtonPress(canvas, Point(40, 40), Button1, 0)
+        # This press should have been received by tool_a
+        self.assertEquals(tool_a.called_methods[-1], ('ButtonPress', canvas,
+                          Point(40, 40), Point(40, 40), Button1, 0, None))
+        editor.ButtonRelease(canvas, Point(40, 40), Button1, Button1Mask)
+        # This ButtonRelease ended tool_a
+        self.assertEquals(tool_a.called_methods[-1], ('End',))
+
+        #
+        # Now we should have been reverted to the initial polyline tool
+        #
+        self.assertToolActive('CreatePoly')
+        self.assertToolMessages([('CreatePoly',)])
+        # Check if the tool instance is the same
+        self.assert_(editor.Tool() is tool_poly)
+        editor.ButtonClick(canvas, Point(200, 0), Button1, Button1Mask)
+
+        # Switch to SelectionTool
+        self.context.SetTool('SelectionTool')
+        self.assertToolMessages([('SelectionTool',)])
+        obj = self.editor.CurrentObject()
+        self.failIf(obj is None)
+        self.failUnless(obj.is_Bezier)
+        self.assertEquals(len(obj.Paths()), 1)
+        self.assertEquals(obj.Paths()[0].NodeList(), [Point(0, 0),
+                                                      Point(200, 200),
+                                                      Point(200, 0)])
+
+
+    def test_clear_chain(self):
+        """Test switching tool while temporary tools are active.
+
+        This must cause the editor to call the End() method on all
+        temporary tools it kept in its stack.
+        """
+        self.context.SetTool('SelectionTool')
+        self.assertToolActive('SelectionTool')
+        # the SelectionTool is active by default. There shouldn't have
+        # been a TOOL message yet.
+        self.assertToolMessages([])
+
+        # start Tool_A
+        self.context.SetTool('Tool_A')
+        self.assertToolActive('Tool_A')
+        self.assertToolMessages([('Tool_A',)])
+        tool_a = self.editor.Tool()
+
+        # start Tool_B
+        self.context.SetTool('Tool_B')
+        self.assertToolActive('Tool_B')
+        self.assertToolMessages([('Tool_B',)])
+        tool_b = self.editor.Tool()
+
+        # swith to a non-temporary tool
+        self.context.SetTool('EditTool')
+        self.assertToolActive('EditTool')
+        self.assertToolMessages([('EditTool',)])
+
+        # Check if the tools were properly ended (i.e. their End() method was
+        # called):
+        self.assertEquals(tool_a.called_methods, [('End',)])
+        self.assertEquals(tool_b.called_methods, [('End',)])
+
+
+    def test_cancel_temporary(self):
+        """Test canceling a temporary tool."""
+        canvas = self.canvas
+        editor = self.editor
+        self.context.SetTool('Tool_A')
+        self.assertToolActive('Tool_A')
+        self.assertToolMessages([('Tool_A',)])
+        tool_a = editor.Tool()
+        editor.ButtonPress(canvas, Point(0, 0), Button1, 0)
+        editor.PointerMotion(canvas, Point(100, 100), Button1Mask)
+        editor.Cancel(canvas)
+        # NOTE: Canceling a temporary tool makes the editor to also end it.
+        self.assertEquals(tool_a.called_methods,
+                          [('ButtonPress', canvas, Point(0, 0), Point(0, 0),
+                            1, 0, None),
+                           ('PointerMotion', canvas, Point(100, 100),
+                            Point(100, 100), Button1Mask),
+                           ('Cancel', canvas),
+                           ('End',)])
+        self.assertToolActive('SelectionTool')
+        self.assertToolMessages([('SelectionTool',)])
+
+
+if __name__ == "__main__":
+    unittest.main()
+


Property changes on: skencil/trunk/test/test_tools.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native



More information about the Skencil-commits mailing list