[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