diff -urN sketch.orig/ChangeLog sketch/ChangeLog --- sketch.orig/ChangeLog 2006-03-07 17:06:39.000000000 +0200 +++ sketch/ChangeLog 2006-03-07 17:06:39.000000000 +0200 @@ -1,3 +1,39 @@ +2006-03-07 + + Create guidelines by dragging them off the rulers. + Based on a patch posted on sketch-devel by Ralf Engels: + http://lists.gnu.org/archive/html/sketch-devel/2003-01/msg00005.html + + * Sketch/Editor/drawguide.py (GuideCreator): New. Creator class for + guide lines. + + * Sketch/Editor/tools.py: Implement the guide tools. + (GuideToolInstance, VGuideToolInstance, HGuideTool, VGuideTool): New. + Classes that implement the guide tools. + + * Sketch/Editor/__init__.py: Add the guide tools to the toolmap and + and import GuideCreator. + + * Sketch/Editor/selectiontool.py (SelectionToolInstance): Add some + special cases for handling guide lines. + (SelectionToolInstance.PointerMotion): New. Hide the guide line if + the pointer leaves the canvas. + (SelectionToolInstance.ButtonRelease): Remove guides dragged outside + of canvas. + (SelectionToolInstance.InfoText): New. Announce that a guide line + will be removed. + + * Sketch/UI/mainwindow.py (SketchMainWindow.build_window): Add events + to rulers. + (SketchMainWindow.ruler_button_press, + SketchMainWindow.ruler_button_release, + SketchMainWindow.motion_notify): New. Callbacks for the new events. + + * Sketch/UI/canvas.py (SketchCanvas.ContainsPoint): New. Return + whether the point p, given in document coordinates, lies within the + viewable area. Used to detect whether a guide is dragged off the + canvas. + 2006-03-05 Bernhard Herzog * Sketch/Editor/doceditor.py, Sketch/UI/canvas.py: Remove unused diff -urN sketch.orig/Sketch/Editor/drawguide.py sketch/Sketch/Editor/drawguide.py --- sketch.orig/Sketch/Editor/drawguide.py 1970-01-01 02:00:00.000000000 +0200 +++ sketch/Sketch/Editor/drawguide.py 2006-03-07 17:06:38.000000000 +0200 @@ -0,0 +1,55 @@ +# Sketch - 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 + +import Sketch +from Sketch import _ +from Sketch.Editor import Creator + +class GuideCreator(Creator): + + creation_text = _("Create Guide") + + def __init__(self, horizontal = False): + self.horizontal = horizontal + Creator.__init__(self) + + def ButtonDown(self, p, button, state): + Creator.DragStart(self, p) + + def ButtonUp(self, p, button, state): + self.point = p + + def DrawDragged(self, device, partially): + device.DrawGuideLine(self.drag_cur, self.horizontal) + + def CurrentInfoText(self): + if self.horizontal: + text = _("Horizontal Guide Line at %(coord)[length]") + dict = {'coord': self.drag_cur.y} + else: + text = _("Vertical Guide Line at %(coord)[length]") + dict = {'coord': self.drag_cur.x} + return text, dict + + def CreatedObject(self): + # Normally the tool (tools.GuideToolInstance in this case) obtains an + # object by calling this method and uses it as an argument to + # EditorWithSelection.Insert(). + # Since the document has a AddGuideLine method we use that instead as + # it takes care of inserting the guide in the proper layer. + return None + diff -urN sketch.orig/Sketch/Editor/__init__.py sketch/Sketch/Editor/__init__.py --- sketch.orig/Sketch/Editor/__init__.py 2006-03-07 17:06:39.000000000 +0200 +++ sketch/Sketch/Editor/__init__.py 2006-03-07 17:06:38.000000000 +0200 @@ -56,6 +56,7 @@ from drawrectangle import RectangleCreator from drawellipse import EllipseCreator from drawbezier import PolyBezierCreator, PolyLineCreator, FreehandCreator +from drawguide import GuideCreator from editellipse import EllipseEditor from editrectangle import RectangleEditor @@ -295,6 +296,8 @@ 'AlignTool': aligntool.AlignTool, 'TextTool': texttool.TextTool, 'ZoomTool': tools.ZoomTool, + 'HGuideTool': tools.HGuideTool, + 'VGuideTool': tools.VGuideTool, 'CreateRect': tools.RectangleTool, 'CreateCurve': tools.PolyBezierTool, 'CreateEllipse': tools.EllipseTool, diff -urN sketch.orig/Sketch/Editor/selectiontool.py sketch/Sketch/Editor/selectiontool.py --- sketch.orig/Sketch/Editor/selectiontool.py 2006-03-07 17:06:39.000000000 +0200 +++ sketch/Sketch/Editor/selectiontool.py 2006-03-07 17:06:38.000000000 +0200 @@ -29,6 +29,7 @@ from Sketch import _, Point, Polar, Identity, Trafo, TrafoType, Rotation from Sketch.Base.warn import pdebug from Sketch.Base.const import SelectSet, SelectDrag, SELECTION, EDITED +from Sketch.Editor import GuideEditor from drawer import SelectAndDrag from tools import ToolInstance, ToolInfo, selection_type @@ -351,6 +352,7 @@ ToolInstance.__init__(self, editor) self.rectangle = SizeRectangle(self.editor.Selection().coord_rect) self.center = None + self.remove_guide = False self.editor.Subscribe(SELECTION, self.selection_changed) self.editor.Subscribe(EDITED, self.doc_was_edited) @@ -409,8 +411,26 @@ object = SelectionRectangle(p) self.begin_edit_object(context, object, snapped, button, state) + def PointerMotion(self, context, p, snapped, state): + # Hide guideline if the pointer leaves the canvas to signal that + # it will be deleted if the button is released: + if isinstance(self.current, GuideEditor) \ + and not context.ContainsPoint(snapped): + self.current.Hide(context.inverting_device()) + self.remove_guide = True + else: + self.remove_guide = False + ToolInstance.PointerMotion(self, context, p, snapped, state) + def ButtonRelease(self, context, p, snapped, button, state): ToolInstance.ButtonRelease(self, context, p, snapped, button, state) + + # Remove the guides dragged outside of canvas: + if isinstance(self.current, GuideEditor) \ + and not context.ContainsPoint(snapped): + context.document.RemoveGuideLine(self.current.object) + return + object = self.end_edit_object(context, snapped, button, state) if isinstance(object, SizeRectangle) \ or isinstance(object, TrafoRectangle): @@ -453,6 +473,12 @@ handles = [] return handles + def InfoText(self): + if self.remove_guide: + text = _("Remove Guide Line") + else: + text = ToolInstance.InfoText(self) + return text SelectionTool = ToolInfo("SelectionTool", SelectionToolInstance, active_cursor = 1) diff -urN sketch.orig/Sketch/Editor/tools.py sketch/Sketch/Editor/tools.py --- sketch.orig/Sketch/Editor/tools.py 2006-03-07 17:06:39.000000000 +0200 +++ sketch/Sketch/Editor/tools.py 2006-03-07 17:06:38.000000000 +0200 @@ -18,6 +18,7 @@ from types import TupleType, StringType import Sketch +from Sketch import _ from Sketch.Base.const import SelectSet, SelectAdd, SelectSubtract, \ SelectSubobjects @@ -366,3 +367,55 @@ PolyLineTool = ToolInfo("PolyLineTool", PolyLineToolInstance, cursor = 'CurDraw', icon = 'CreatePoly') + + +class GuideToolInstance(TemporaryToolInstance): + + title = ''"Horizontal Guide" + horizontal = True + + def ButtonPress(self, context, p, snapped, button, state, handle = None): + self.dragged_out = False + TemporaryToolInstance.ButtonPress(self, context, p, snapped, button, + state, handle = None) + self.begin_edit_object(context, + Sketch.Editor.GuideCreator(self.horizontal), + snapped, button, state) + + def ButtonRelease(self, context, p, snapped, button, state): + self.dragged_out = not context.ContainsPoint(snapped) + TemporaryToolInstance.ButtonRelease(self, context, p, snapped, button, + state) + obj = self.end_edit_object(context, p, button, state) + if obj is not None and not self.dragged_out: + context.document.AddGuideLine(obj.point, self.horizontal) + self.finished() + + def PointerMotion(self, context, p, snapped, state): + self.dragged_out = not context.ContainsPoint(snapped) + if self.dragged_out: + self.Hide(context.inverting_device()) + else: + TemporaryToolInstance.PointerMotion(self, context, p, snapped, + state) + + def ButtonClick(self, context, p, snapped, button, state, handle = None): + self.Cancel(context) + + def InfoText(self): + if self.dragged_out: + text = _("Cancel Guide Line") + else: + text = TemporaryToolInstance.InfoText(self) + return text + +HGuideTool = ToolInfo("HGuideTool", GuideToolInstance, cursor = 'CurHGuide') + + +class VGuideToolInstance(GuideToolInstance): + + title = ''"Vertical Guide" + horizontal = False + + +VGuideTool = ToolInfo("VGuideTool", VGuideToolInstance, cursor = 'CurVGuide') diff -urN sketch.orig/Sketch/UI/canvas.py sketch/Sketch/UI/canvas.py --- sketch.orig/Sketch/UI/canvas.py 2006-03-07 17:06:39.000000000 +0200 +++ sketch/Sketch/UI/canvas.py 2006-03-07 17:06:39.000000000 +0200 @@ -371,6 +371,14 @@ # + def ContainsPoint(self, p): + """Return whether the point p lies within the viewable area. + p is given in document coordinates. + """ + px, py = self.DocToWin(p) + w, h = self.window_size() + return not (px <= 0 or px >= w or py <= 0 or py >= h) + def ResizedMethod(self, width, height): SketchView.ResizedMethod(self, width, height) diff -urN sketch.orig/Sketch/UI/mainwindow.py sketch/Sketch/UI/mainwindow.py --- sketch.orig/Sketch/UI/mainwindow.py 2006-03-07 17:06:39.000000000 +0200 +++ sketch/Sketch/UI/mainwindow.py 2006-03-07 17:06:39.000000000 +0200 @@ -136,9 +136,19 @@ table.attach(menubutton, 0, 1, 0, 1, FILL, FILL) self.hruler = gtk.HRuler() + self.hruler.set_events(gtk.gdk.BUTTON_PRESS_MASK | + gtk.gdk.BUTTON_RELEASE_MASK) + self.hruler.connect('button_press_event', self.ruler_button_press) + self.hruler.connect('button_release_event', self.ruler_button_release) + self.hruler.connect('motion_notify_event', self.ruler_motion_notify) table.attach(self.hruler, 1, 2, 0, 1, EXPAND|FILL, 0) self.vruler = gtk.VRuler() + self.vruler.set_events(gtk.gdk.BUTTON_PRESS_MASK | + gtk.gdk.BUTTON_RELEASE_MASK) + self.vruler.connect('button_press_event', self.ruler_button_press) + self.vruler.connect('button_release_event', self.ruler_button_release) + self.vruler.connect('motion_notify_event', self.ruler_motion_notify) table.attach(self.vruler, 0, 1, 1, 2, 0, EXPAND|FILL) hadjust = gtk.Adjustment() @@ -217,6 +227,20 @@ menu.popup(None, None, None, event.button, event.time) widget.emit_stop_by_name('button_press_event') + def ruler_button_press(self, widget, event): + if widget is self.hruler: + tool = 'HGuideTool' + else: + tool = 'VGuideTool' + self.application.context.SetTool(tool) + self.canvas.button_press(widget, event) + + def ruler_button_release(self, widget, event): + self.canvas.button_release(widget, event) + + def ruler_motion_notify(self, widget, event): + self.canvas.motion_notify(widget, event) + def Document(self): return self.document diff -urN sketch.orig/test/ChangeLog sketch/test/ChangeLog --- sketch.orig/test/ChangeLog 2006-03-07 17:06:39.000000000 +0200 +++ sketch/test/ChangeLog 2006-03-07 17:06:38.000000000 +0200 @@ -1,3 +1,10 @@ +2006-03-07 + + * test_tools.py (MockCanvasWithArea): New. A mock canvas that can + tell if it contains a point. + (TestGuides): New. Tests for creating and deleting guides by dragging + them. + 2006-03-05 Bernhard Herzog * test_tools.py (TestTools.setUp, TestTools.tearDown): Don't diff -urN sketch.orig/test/test_tools.py sketch/test/test_tools.py --- sketch.orig/test/test_tools.py 2006-03-07 17:06:39.000000000 +0200 +++ sketch/test/test_tools.py 2006-03-07 17:06:38.000000000 +0200 @@ -32,8 +32,10 @@ from Sketch.Graphics import PolyBezier from Sketch.Editor import EditorWithSelection, InteractiveDocument, \ Button1Mask, ZoomOutMask, toolmap, Button1 -from Sketch.Editor.selectiontool import SelectionRectangle -from Sketch.Editor.tools import ToolInfo, TemporaryToolInstance +from Sketch.Editor.selectiontool import SelectionRectangle, \ + SelectionToolInstance +from Sketch.Editor.tools import ToolInfo, TemporaryToolInstance, \ + GuideToolInstance from test_doceditor import MockCanvas, MockToolInstance @@ -460,6 +462,108 @@ "ZoomTool: point zoom is not temporary") +class MockCanvasWithArea(MockCanvas): + + rect = Rect(0, 0, 500, 500) + + def ContainsPoint(self, p): + return self.rect.contains_point(p) + + +class TestGuides(unittest.TestCase): + + def setUp(self): + self.document = InteractiveDocument(create_layer = 1) + self.editor = EditorWithSelection(self.document) + self.canvas = MockCanvasWithArea() + self.canvas.document = self.document + self.editor.SetTool('SelectionTool') + + def tearDown(self): + if self.editor is not None: + self.editor.Destroy() + self.editor = self.document = self.canvas = None + + def test_create_hguide(self): + """Test creating a horizontal guide.""" + self.editor.SetTool('HGuideTool') + self.failUnless(isinstance(self.editor.Tool(), GuideToolInstance)) + + self.editor.ButtonPress(self.canvas, Point(10, 10), Button1, + Button1Mask, handle=None) + self.editor.PointerMotion(self.canvas, Point(50, 50), Button1Mask) + self.editor.ButtonRelease(self.canvas, Point(50, 50), Button1, + Button1Mask) + guides = self.document.GuideLines() + self.assertEquals(len(guides), 1) + guide = guides[0] + self.failUnless(guide.is_GuideLine) + self.failUnless(guide.Horizontal()) + self.assertEquals(guide.Coordinates(), (50, 1)) + + def test_create_vguide(self): + """Test creating a vertical guide.""" + self.editor.SetTool('VGuideTool') + self.failUnless(isinstance(self.editor.Tool(), GuideToolInstance)) + + self.editor.ButtonPress(self.canvas, Point(10, 10), Button1, + Button1Mask, handle=None) + self.editor.PointerMotion(self.canvas, Point(50, 50), Button1Mask) + self.editor.ButtonRelease(self.canvas, Point(50, 50), Button1, + Button1Mask) + guides = self.document.GuideLines() + self.assertEquals(len(guides), 1) + guide = guides[0] + self.failUnless(guide.is_GuideLine) + self.failIf(guide.Horizontal()) + self.assertEquals(guide.Coordinates(), (50, 0)) + + def test_cancel_guide(self): + """Test canceling guide creation.""" + self.editor.SetTool('HGuideTool') + self.failUnless(isinstance(self.editor.Tool(), GuideToolInstance)) + + self.editor.ButtonPress(self.canvas, Point(10, 10), Button1, + Button1Mask, handle=None) + self.editor.PointerMotion(self.canvas, Point(50, 50), Button1Mask) + self.editor.ButtonRelease(self.canvas, Point(-10, -10), Button1, + Button1Mask) + # no guide should have been created: + self.assertEquals(len(self.document.GuideLines()), 0) + # and the editor should have been reverted to the Selection tool: + self.failUnless(isinstance(self.editor.Tool(), SelectionToolInstance)) + + def test_drag_guide(self): + """Test dragging a guide line""" + self.document.AddGuideLine(Point(10, 10), True) + guide = self.document.GuideLines()[0] + self.assertEquals(guide.Coordinates(), (10, 1)) + + self.editor.ButtonPress(self.canvas, Point(10, 10), Button1, + Button1Mask, handle=None) + self.editor.PointerMotion(self.canvas, Point(50, 50), Button1Mask) + self.editor.ButtonRelease(self.canvas, Point(50, 50), Button1, + Button1Mask) + # the guide should have been moved + self.assertEquals(guide.Coordinates(), (50, 1)) + self.failUnless(isinstance(self.editor.Tool(), SelectionToolInstance)) + + def test_drag_guide_out(self): + """Test dragging a guide line outside of canvas area.""" + self.document.AddGuideLine(Point(10, 10), True) + guide = self.document.GuideLines()[0] + self.assertEquals(guide.Coordinates(), (10, 1)) + + self.editor.ButtonPress(self.canvas, Point(10, 10), Button1, + Button1Mask, handle=None) + self.editor.PointerMotion(self.canvas, Point(-10, -10), Button1Mask) + self.editor.ButtonRelease(self.canvas, Point(-10, -10), Button1, + Button1Mask) + # the guide should have been removed + self.assertEquals(len(self.document.GuideLines()), 0) + self.failUnless(isinstance(self.editor.Tool(), SelectionToolInstance)) + + if __name__ == "__main__": unittest.main()