From: Robin Dunn Date: Wed, 26 May 2004 02:13:04 +0000 (+0000) Subject: Added a Python port of the OGL library, deprecated the C++ wrapped version. X-Git-Url: https://git.saurik.com/wxWidgets.git/commitdiff_plain/f847103a32507f629c581fa900d95e97dfe16df0?hp=6033bbc1ff95fb69b65b081d03b4a679dd958e75 Added a Python port of the OGL library, deprecated the C++ wrapped version. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@27447 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- diff --git a/wxPython/contrib/ogl/ogl.i b/wxPython/contrib/ogl/ogl.i index b0fe5ede3a..419d5a4361 100644 --- a/wxPython/contrib/ogl/ogl.i +++ b/wxPython/contrib/ogl/ogl.i @@ -12,7 +12,7 @@ %define DOCSTRING "The Object Graphics Library provides for simple drawing and manipulation -of 2D objects." +of 2D objects. (This version is deprecated, please use wx.lib.ogl instead.)" %enddef %module(docstring=DOCSTRING) ogl @@ -29,6 +29,12 @@ of 2D objects." %pythoncode { wx = _core } %pythoncode { __docfilter__ = wx.__DocFilter(globals()) } +%pythoncode { + import warnings + warnings.warn("This module is deprecated. Please use the wx.lib.ogl pacakge instead.", + DeprecationWarning, stacklevel=2) +} + MAKE_CONST_WXSTRING_NOSWIG(EmptyString); diff --git a/wxPython/demo/Main.py b/wxPython/demo/Main.py index 7cd330ffa4..899e4aa563 100644 --- a/wxPython/demo/Main.py +++ b/wxPython/demo/Main.py @@ -30,7 +30,7 @@ import images _treeList = [ # new stuff - ('Recent Additions', [ + ('Recent Additions and Updates', [ 'VListBox', 'Listbook', 'MaskedNumCtrl', diff --git a/wxPython/demo/OGL.py b/wxPython/demo/OGL.py index 7412974ea1..cb9b11e8ff 100644 --- a/wxPython/demo/OGL.py +++ b/wxPython/demo/OGL.py @@ -1,15 +1,19 @@ +# -*- coding: iso-8859-1 -*- # 11/20/2003 - Jeff Grimmett (grimmtooth@softhome.net) # # o Updated for wx namespace # +# 20040508 - Pierre Hjälm +# +# o Changed to use the python version of OGL +# o Added TextShape, CompositeShape and CompositeShape with divisions +# -import wx -import wx.ogl as ogl +import wx +import wx.lib.ogl as ogl import images -##wx.Trap() - #---------------------------------------------------------------------- class DiamondShape(ogl.PolygonShape): @@ -20,13 +24,6 @@ class DiamondShape(ogl.PolygonShape): if h == 0.0: h = 60.0 - # Either ogl.RealPoints or 2-tuples of floats works. - - #points = [ ogl.RealPoint(0.0, -h/2.0), - # ogl.RealPoint(w/2.0, 0.0), - # ogl.RealPoint(0.0, h/2.0), - # ogl.RealPoint(-w/2.0, 0.0), - # ] points = [ (0.0, -h/2.0), (w/2.0, 0.0), (0.0, h/2.0), @@ -44,6 +41,67 @@ class RoundedRectangleShape(ogl.RectangleShape): self.SetCornerRadius(-0.3) +#---------------------------------------------------------------------- + +class CompositeDivisionShape(ogl.CompositeShape): + def __init__(self, canvas): + ogl.CompositeShape.__init__(self) + + self.SetCanvas(canvas) + + # create a division in the composite + self.MakeContainer() + + # add a shape to the original division + shape2 = ogl.RectangleShape(40, 60) + self.GetDivisions()[0].AddChild(shape2) + + # now divide the division so we get 2 + self.GetDivisions()[0].Divide(wx.HORIZONTAL) + + # and add a shape to the second division (and move it to the + # centre of the division) + shape3 = ogl.CircleShape(40) + shape3.SetBrush(wx.CYAN_BRUSH) + self.GetDivisions()[1].AddChild(shape3) + shape3.SetX(self.GetDivisions()[1].GetX()) + + for division in self.GetDivisions(): + division.SetSensitivityFilter(0) + +#---------------------------------------------------------------------- + +class CompositeShape(ogl.CompositeShape): + def __init__(self, canvas): + ogl.CompositeShape.__init__(self) + + self.SetCanvas(canvas) + + constraining_shape = ogl.RectangleShape(120, 100) + constrained_shape1 = ogl.CircleShape(50) + constrained_shape2 = ogl.RectangleShape(80, 20) + + constraining_shape.SetBrush(wx.BLUE_BRUSH) + constrained_shape2.SetBrush(wx.RED_BRUSH) + + self.AddChild(constraining_shape) + self.AddChild(constrained_shape1) + self.AddChild(constrained_shape2) + + constraint = ogl.Constraint(ogl.CONSTRAINT_MIDALIGNED_BOTTOM, constraining_shape, [constrained_shape1, constrained_shape2]) + self.AddConstraint(constraint) + self.Recompute() + + # If we don't do this, the shapes will be able to move on their + # own, instead of moving the composite + constraining_shape.SetDraggable(False) + constrained_shape1.SetDraggable(False) + constrained_shape2.SetDraggable(False) + + # If we don't do this the shape will take all left-clicks for itself + constraining_shape.SetSensitivityFilter(0) + + #---------------------------------------------------------------------- class DividedShape(ogl.DividedShape): @@ -88,7 +146,7 @@ class DividedShape(ogl.DividedShape): def OnSizingEndDragLeft(self, pt, x, y, keys, attch): print "***", self - self.base_OnSizingEndDragLeft(pt, x, y, keys, attch) + ogl.DividedShape.OnSizingEndDragLeft(self, pt, x, y, keys, attch) self.SetRegionSizes() self.ReformatRegions() self.GetCanvas().Refresh() @@ -103,15 +161,14 @@ class MyEvtHandler(ogl.ShapeEvtHandler): self.statbarFrame = frame def UpdateStatusBar(self, shape): - x,y = shape.GetX(), shape.GetY() + x, y = shape.GetX(), shape.GetY() width, height = shape.GetBoundingBoxMax() - self.statbarFrame.SetStatusText("Pos: (%d,%d) Size: (%d, %d)" % + self.statbarFrame.SetStatusText("Pos: (%d, %d) Size: (%d, %d)" % (x, y, width, height)) - def OnLeftClick(self, x, y, keys = 0, attachment = 0): + def OnLeftClick(self, x, y, keys=0, attachment=0): shape = self.GetShape() - print shape.__class__, shape.GetClassName() canvas = shape.GetCanvas() dc = wx.ClientDC(canvas) canvas.PrepareDC(dc) @@ -142,9 +199,9 @@ class MyEvtHandler(ogl.ShapeEvtHandler): self.UpdateStatusBar(shape) - def OnEndDragLeft(self, x, y, keys = 0, attachment = 0): + def OnEndDragLeft(self, x, y, keys=0, attachment=0): shape = self.GetShape() - self.base_OnEndDragLeft(x, y, keys, attachment) + ogl.ShapeEvtHandler.OnEndDragLeft(self, x, y, keys, attachment) if not shape.Selected(): self.OnLeftClick(x, y, keys, attachment) @@ -153,12 +210,12 @@ class MyEvtHandler(ogl.ShapeEvtHandler): def OnSizingEndDragLeft(self, pt, x, y, keys, attch): - self.base_OnSizingEndDragLeft(pt, x, y, keys, attch) + ogl.ShapeEvtHandler.OnSizingEndDragLeft(self, pt, x, y, keys, attch) self.UpdateStatusBar(self.GetShape()) def OnMovePost(self, dc, x, y, oldX, oldY, display): - self.base_OnMovePost(dc, x, y, oldX, oldY, display) + ogl.ShapeEvtHandler.OnMovePost(self, dc, x, y, oldX, oldY, display) self.UpdateStatusBar(self.GetShape()) @@ -188,29 +245,44 @@ class TestWindow(ogl.ShapeCanvas): rRectBrush = wx.Brush("MEDIUM TURQUOISE", wx.SOLID) dsBrush = wx.Brush("WHEAT", wx.SOLID) + self.MyAddShape( + CompositeDivisionShape(self), + 310, 310, wx.BLACK_PEN, wx.BLUE_BRUSH, "Division" + ) + + self.MyAddShape( + CompositeShape(self), + 100, 260, wx.BLACK_PEN, wx.RED_BRUSH, "Composite" + ) + self.MyAddShape( ogl.CircleShape(80), 100, 100, wx.Pen(wx.BLUE, 3), wx.GREEN_BRUSH, "Circle" ) + self.MyAddShape( + ogl.TextShape(45, 30), + 205, 60, wx.GREEN_PEN, wx.LIGHT_GREY_BRUSH, "Text" + ) + self.MyAddShape( ogl.RectangleShape(85, 50), 305, 60, wx.BLACK_PEN, wx.LIGHT_GREY_BRUSH, "Rectangle" ) ds = self.MyAddShape( - DividedShape(140, 150, self), - 495, 145, wx.BLACK_PEN, dsBrush, '' - ) + DividedShape(140, 150, self), + 515, 145, wx.BLACK_PEN, dsBrush, '' + ) self.MyAddShape( DiamondShape(90, 90), - 345, 235, wx.Pen(wx.BLUE, 3, wx.DOT), wx.RED_BRUSH, "Polygon" + 445, 305, wx.Pen(wx.BLUE, 3, wx.DOT), wx.RED_BRUSH, "Polygon" ) self.MyAddShape( - RoundedRectangleShape(95,70), - 140, 255, wx.Pen(wx.RED, 2), rRectBrush, "Rounded Rect" + RoundedRectangleShape(95, 70), + 345, 145, wx.Pen(wx.RED, 2), rRectBrush, "Rounded Rect" ) bmp = images.getTest2Bitmap() @@ -219,7 +291,7 @@ class TestWindow(ogl.ShapeCanvas): s = ogl.BitmapShape() s.SetBitmap(bmp) - self.MyAddShape(s, 225, 150, None, None, "Bitmap") + self.MyAddShape(s, 225, 130, None, None, "Bitmap") dc = wx.ClientDC(self) self.PrepareDC(dc) @@ -241,14 +313,15 @@ class TestWindow(ogl.ShapeCanvas): self.diagram.AddShape(line) line.Show(True) - # for some reason, the shapes have to be moved for the line to show up... - fromShape.Move(dc, fromShape.GetX(), fromShape.GetY()) - - self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy) - def MyAddShape(self, shape, x, y, pen, brush, text): - shape.SetDraggable(True, True) + # Composites have to be moved for all children to get in place + if isinstance(shape, ogl.CompositeShape): + dc = wx.ClientDC(self) + self.PrepareDC(dc) + shape.Move(dc, x, y) + else: + shape.SetDraggable(True, True) shape.SetCanvas(self) shape.SetX(x) shape.SetY(y) @@ -268,16 +341,6 @@ class TestWindow(ogl.ShapeCanvas): return shape - def OnDestroy(self, evt): - # Do some cleanup - for shape in self.diagram.GetShapeList(): - if shape.GetParent() == None: - shape.SetCanvas(None) - shape.Destroy() - - self.diagram.Destroy() - - def OnBeginDragLeft(self, x, y, keys): self.log.write("OnBeginDragLeft: %s, %s, %s\n" % (x, y, keys)) @@ -298,25 +361,32 @@ def runTest(frame, nb, log): #---------------------------------------------------------------------- -# The OGL library holds some resources that need to be freed before -# the app shuts down. -class __Cleanup: - def __del__(self, cleanup=ogl.OGLCleanUp): - cleanup() -# When this module gets cleaned up by Python then __cu will be cleaned -# up and it's __dell__ is called, which will then call ogl.OGLCleanUp. -__cu = __Cleanup() +overview = """ +

Object Graphics Library

- -overview = """\ The Object Graphics Library is a library supporting the creation and manipulation of simple and complex graphic images on a canvas. +

The OGL library was originally written in C++ and provided to +wxPython via an extension module wrapper as is most of the rest of +wxPython. The code has now been ported to Python (with many thanks to +Pierre Hjälm!) in order to make it be more easily maintainable and +less likely to get rusty because nobody cares about the C++ lib any +more. + +

The Python version should be mostly drop-in compatible with the +wrapped C++ version, except for the location of the package +(wx.lib.ogl instead of wx.ogl) and that the base class methods are +called the normal Python way (superclass.Method(self, ...)) instead of the +hacky way that had to be done to support overloaded methods with the +old SWIG (self.base_Method(...)) + + """ if __name__ == '__main__': - import sys,os + import sys, os import run run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:]) diff --git a/wxPython/distrib/DIRLIST b/wxPython/distrib/DIRLIST index 179edf1201..2fc48e13ae 100644 --- a/wxPython/distrib/DIRLIST +++ b/wxPython/distrib/DIRLIST @@ -71,6 +71,7 @@ wxPython/wx/lib/colourchooser wxPython/wx/lib/editor wxPython/wx/lib/masked wxPython/wx/lib/mixins +wxPython/wx/lib/ogl wxPython/wx/py wxPython/wx/py/tests wxPython/wxPython diff --git a/wxPython/distrib/make_installer.py b/wxPython/distrib/make_installer.py index 49ac90cd2e..f16f158a73 100644 --- a/wxPython/distrib/make_installer.py +++ b/wxPython/distrib/make_installer.py @@ -106,6 +106,8 @@ Source: "wx\lib\colourchooser\*.py"; DestDir: "{app}\wx\lib\colourchoo Source: "wx\lib\editor\*.py"; DestDir: "{app}\wx\lib\editor"; Components: core Source: "wx\lib\editor\*.txt"; DestDir: "{app}\wx\lib\editor"; Components: core Source: "wx\lib\mixins\*.py"; DestDir: "{app}\wx\lib\mixins"; Components: core +Source: "wx\lib\masked\*.py"; DestDir: "{app}\wx\lib\masked"; Components: core +Source: "wx\lib\ogl\*.py"; DestDir: "{app}\wx\lib\ogl"; Components: core Source: "wx\py\*.py"; DestDir: "{app}\wx\py"; Components: core Source: "wx\py\*.txt"; DestDir: "{app}\wx\py"; Components: core Source: "wx\py\*.ico"; DestDir: "{app}\wx\py"; Components: core diff --git a/wxPython/docs/CHANGES.txt b/wxPython/docs/CHANGES.txt index 30254e59c4..2873dcb92e 100644 --- a/wxPython/docs/CHANGES.txt +++ b/wxPython/docs/CHANGES.txt @@ -80,6 +80,17 @@ remaining compatible with "C". Switched gizmos.TreeListCtrl to the newer version of the code from the wxCode project. +OGL is dead! LONG LIVE OGL! (Oops, sorry. A bit of my dramatic side +leaked out there...) The wx.ogl module has been deprecated in favor +of the new Python port of the OGL library located at wx.lib.ogl +contributed by Pierre Hjälm. This will hopefully greatly extend the +life of OGL within wxPython by making it more easily maintainable and +less prone to getting rusty as there seems to be less and less +interest in maintaining the C++ version. At this point there are just +a couple minor known compatibility differences, please see the +MigrationGuide_ file for details. + +.. _MigrationGuide: MigrationGuide.html diff --git a/wxPython/docs/MigrationGuide.txt b/wxPython/docs/MigrationGuide.txt index d2f34ac2b1..f4f08c27d2 100644 --- a/wxPython/docs/MigrationGuide.txt +++ b/wxPython/docs/MigrationGuide.txt @@ -3,10 +3,10 @@ wxPython 2.5 Migration Guide ============================ This document will help explain some of the major changes in wxPython -2.5 and let you know what you need to do to adapt your programs to -those changes. Be sure to also check in the CHANGES_ file like -usual to see info about the not so major changes and other things that -have been added to wxPython. +2.5 since the 2.4 series and let you know what you need to do to adapt +your programs to those changes. Be sure to also check in the CHANGES_ +file like usual to see info about the not so major changes and other +things that have been added to wxPython. .. _CHANGES: CHANGES.html @@ -20,8 +20,8 @@ The **wxWindows** project and library is now known as .. _here: http://www.wxwidgets.org/name.htm This won't really affect wxPython all that much, other than the fact -that the wxwindows.org domain name will be changing to wxwidgets.org, -so mail list, CVS, and etc. addresses will be changing. We're going +that the wxwindows.org domain name has changed to wxwidgets.org, +so mail list, CVS, and etc. addresses have also changed. We're going to try and smooth the transition as much as possible, but I wanted you all to be aware of this change if you run into any issues. @@ -585,6 +585,34 @@ provided by the makers of the ActiveX control that you are using. +OGL is dead! LONG LIVE OGL! +--------------------------- + +The wx.ogl module has been deprecated in favor of the new Python port +of the OGL library located at wx.lib.ogl contributed by Pierre Hjälm. +This will hopefully greatly extend the life of OGL within wxPython by +making it more easily maintainable and less prone to getting rusty as +there seems to be less and less interest in maintaining the C++ +version. + +There are only a few known compatibility issues at this time. First +is the location of OGL. The deprecated version is located in the +wx.ogl module, and the new version is in the wx.lib.ogl package. So +this just means that to start using the new version you need to adjust +your imports. So if your code currently has something like this:: + + import wx + import wx.ogl as ogl + +Then just change it to this:: + + import wx + import wx.lib.ogl as ogl + + + + + Obsolete Modules ---------------- diff --git a/wxPython/setup.py b/wxPython/setup.py index 7822c28dcc..4960145ec4 100755 --- a/wxPython/setup.py +++ b/wxPython/setup.py @@ -707,6 +707,7 @@ if __name__ == "__main__": 'wx.lib.editor', 'wx.lib.masked', 'wx.lib.mixins', + 'wx.lib.ogl', 'wx.py', 'wx.tools', 'wx.tools.XRCed', diff --git a/wxPython/wx/lib/ogl/__init__.py b/wxPython/wx/lib/ogl/__init__.py new file mode 100644 index 0000000000..667ed0afe1 --- /dev/null +++ b/wxPython/wx/lib/ogl/__init__.py @@ -0,0 +1,14 @@ +""" +The Object Graphics Library provides for simple drawing and manipulation +of 2D objects. +""" + +__all__ = ["basic", "diagram", "canvas", "lines", "bmpshape", "divided", "composit"] + +from basic import * +from diagram import * +from canvas import * +from lines import * +from bmpshape import * +from divided import * +from composit import * diff --git a/wxPython/wx/lib/ogl/basic.py b/wxPython/wx/lib/ogl/basic.py new file mode 100644 index 0000000000..3aacb596d0 --- /dev/null +++ b/wxPython/wx/lib/ogl/basic.py @@ -0,0 +1,3173 @@ +# -*- coding: iso-8859-1 -*- +#---------------------------------------------------------------------------- +# Name: basic.py +# Purpose: The basic OGL shapes +# +# Author: Pierre Hjälm (from C++ original by Julian Smart) +# +# Created: 20040508 +# RCS-ID: +# Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart +# Licence: wxWindows license +#---------------------------------------------------------------------------- + +from __future__ import division + +import wx +from math import pi, sqrt, atan, sin, cos + +from oglmisc import * + +DragOffsetX = 0.0 +DragOffsetY = 0.0 + + +def OGLInitialize(): + global WhiteBackgroundPen, WhiteBackgroundBrush, TransparentPen + global BlackForegroundPen, NormalFont + + WhiteBackgroundPen = wx.Pen(wx.WHITE, 1, wx.SOLID) + WhiteBackgroundBrush = wx.Brush(wx.WHITE, wx.SOLID) + + TransparentPen = wx.Pen(wx.WHITE, 1, wx.TRANSPARENT) + BlackForegroundPen = wx.Pen(wx.BLACK, 1, wx.SOLID) + + NormalFont = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL) + + +def OGLCleanUp(): + pass + + +class ShapeTextLine(object): + def __init__(self, the_x, the_y, the_line): + self._x = the_x + self._y = the_y + self._line = the_line + + def GetX(self): + return self._x + + def GetY(self): + return self._y + + def SetX(self, x): + self._x = x + + def SetY(self, y): + self._y = y + + def SetText(self, text): + self._line = text + + def GetText(self): + return self._line + + + +class ShapeEvtHandler(object): + def __init__(self, prev = None, shape = None): + self._previousHandler = prev + self._handlerShape = shape + + def __del__(self): + pass + + def SetShape(self, sh): + self._handlerShape = sh + + def GetShape(self): + return self._handlerShape + + def SetPreviousHandler(self, handler): + self._previousHandler = handler + + def GetPreviousHandler(self): + return self._previousHandler + + def OnDraw(self, dc): + if self._previousHandler: + self._previousHandler.OnDraw(dc) + + def OnMoveLinks(self, dc): + if self._previousHandler: + self._previousHandler.OnMoveLinks(dc) + + def OnMoveLink(self, dc, moveControlPoints = True): + if self._previousHandler: + self._previousHandler.OnMoveLink(dc, moveControlPoints) + + def OnDrawContents(self, dc): + if self._previousHandler: + self._previousHandler.OnDrawContents(dc) + + def OnDrawBranches(self, dc, erase = False): + if self._previousHandler: + self._previousHandler.OnDrawBranches(dc, erase = erase) + + def OnSize(self, x, y): + if self._previousHandler: + self._previousHandler.OnSize(x, y) + + def OnMovePre(self, dc, x, y, old_x, old_y, display = True): + if self._previousHandler: + return self._previousHandler.OnMovePre(dc, x, y, old_x, old_y, display) + else: + return True + + def OnMovePost(self, dc, x, y, old_x, old_y, display = True): + if self._previousHandler: + return self._previousHandler.OnMovePost(dc, x, y, old_x, old_y, display) + else: + return True + + def OnErase(self, dc): + if self._previousHandler: + self._previousHandler.OnErase(dc) + + def OnEraseContents(self, dc): + if self._previousHandler: + self._previousHandler.OnEraseContents(dc) + + def OnHighlight(self, dc): + if self._previousHandler: + self._previousHandler.OnHighlight(dc) + + def OnLeftClick(self, x, y, keys, attachment): + if self._previousHandler: + self._previousHandler.OnLeftClick(x, y, keys, attachment) + + def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0): + if self._previousHandler: + self._previousHandler.OnLeftDoubleClick(x, y, keys, attachment) + + def OnRightClick(self, x, y, keys = 0, attachment = 0): + if self._previousHandler: + self._previousHandler.OnRightClick(x, y, keys, attachment) + + def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0): + if self._previousHandler: + self._previousHandler.OnDragLeft(draw, x, y, keys, attachment) + + def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0): + if self._previousHandler: + self._previousHandler.OnBeginDragLeft(x, y, keys, attachment) + + def OnEndDragLeft(self, x, y, keys = 0, attachment = 0): + if self._previousHandler: + self._previousHandler.OnEndDragLeft(x, y, keys, attachment) + + def OnDragRight(self, draw, x, y, keys = 0, attachment = 0): + if self._previousHandler: + self._previousHandler.OnDragRight(draw, x, y, keys, attachment) + + def OnBeginDragRight(self, x, y, keys = 0, attachment = 0): + if self._previousHandler: + self._previousHandler.OnBeginDragRight(x, y, keys, attachment) + + def OnEndDragRight(self, x, y, keys = 0, attachment = 0): + if self._previousHandler: + self._previousHandler.OnEndDragRight(x, y, keys, attachment) + + # Control points ('handles') redirect control to the actual shape, + # to make it easier to override sizing behaviour. + def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0): + if self._previousHandler: + self._previousHandler.OnSizingDragLeft(pt, draw, x, y, keys, attachment) + + def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0): + if self._previousHandler: + self._previousHandler.OnSizingBeginDragLeft(pt, x, y, keys, attachment) + + def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0): + if self._previousHandler: + self._previousHandler.OnSizingEndDragLeft(pt, x, y, keys, attachment) + + def OnBeginSize(self, w, h): + pass + + def OnEndSize(self, w, h): + pass + + def OnDrawOutline(self, dc, x, y, w, h): + if self._previousHandler: + self._previousHandler.OnDrawOutline(dc, x, y, w, h) + + def OnDrawControlPoints(self, dc): + if self._previousHandler: + self._previousHandler.OnDrawControlPoints(dc) + + def OnEraseControlPoints(self, dc): + if self._previousHandler: + self._previousHandler.OnEraseControlPoints(dc) + + # Can override this to prevent or intercept line reordering. + def OnChangeAttachment(self, attachment, line, ordering): + if self._previousHandler: + self._previousHandler.OnChangeAttachment(attachment, line, ordering) + + + +class Shape(ShapeEvtHandler): + """OGL base class + + Shape(canvas = None) + + The wxShape is the top-level, abstract object that all other objects + are derived from. All common functionality is represented by wxShape's + members, and overriden members that appear in derived classes and have + behaviour as documented for wxShape, are not documented separately. + """ + + GraphicsInSizeToContents = False + + def __init__(self, canvas = None): + ShapeEvtHandler.__init__(self) + + self._eventHandler = self + self.SetShape(self) + self._id = 0 + self._formatted = False + self._canvas = canvas + self._xpos = 0.0 + self._ypos = 0.0 + self._pen = wx.Pen(wx.BLACK, 1, wx.SOLID) + self._brush = wx.WHITE_BRUSH + self._font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL) + self._textColour = wx.BLACK + self._textColourName = wx.BLACK + self._visible = False + self._selected = False + self._attachmentMode = ATTACHMENT_MODE_NONE + self._spaceAttachments = True + self._disableLabel = False + self._fixedWidth = False + self._fixedHeight = False + self._drawHandles = True + self._sensitivity = OP_ALL + self._draggable = True + self._parent = None + self._formatMode = FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT + self._shadowMode = SHADOW_NONE + self._shadowOffsetX = 6 + self._shadowOffsetY = 6 + self._shadowBrush = wx.BLACK_BRUSH + self._textMarginX = 5 + self._textMarginY = 5 + self._regionName="0" + self._centreResize = True + self._maintainAspectRatio = False + self._highlighted = False + self._rotation = 0.0 + self._branchNeckLength = 10 + self._branchStemLength = 10 + self._branchSpacing = 10 + self._branchStyle = BRANCHING_ATTACHMENT_NORMAL + + self._regions = [] + self._lines = [] + self._controlPoints = [] + self._attachmentPoints = [] + self._text = [] + self._children = [] + + # Set up a default region. Much of the above will be put into + # the region eventually (the duplication is for compatibility) + region = ShapeRegion() + region.SetName("0") + region.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL)) + region.SetFormatMode(FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT) + region.SetColour("BLACK") + self._regions.append(region) + + def __str__(self): + return "<%s.%s>" % (self.__class__.__module__, self.__class__.__name__) + + def GetClassName(self): + return str(self.__class__).split(".")[-1][:-2] + + def __del__(self): + if self._parent: + i = self._parent.GetChildren().index(self) + self._parent.GetChildren(i).remove(self) + + self.ClearText() + self.ClearRegions() + self.ClearAttachments() + + if self._canvas: + self._canvas.RemoveShape(self) + + self.GetEventHandler().OnDelete() + + def Draggable(self): + """TRUE if the shape may be dragged by the user.""" + return True + + def SetShape(self, sh): + self._handlerShape = sh + + def GetCanvas(self): + """Get the internal canvas.""" + return self._canvas + + def GetBranchStyle(self): + return self._branchStyle + + def GetRotation(self): + """Return the angle of rotation in radians.""" + return self._rotation + + def SetRotation(self, rotation): + self._rotation = rotation + + def SetHighlight(self, hi, recurse = False): + """Set the highlight for a shape. Shape highlighting is unimplemented.""" + self._highlighted = hi + if recurse: + for shape in self._children: + shape.SetHighlight(hi, recurse) + + def SetSensitivityFilter(self, sens = OP_ALL, recursive = False): + """Set the shape to be sensitive or insensitive to specific mouse + operations. + + sens is a bitlist of the following: + + * OP_CLICK_LEFT + * OP_CLICK_RIGHT + * OP_DRAG_LEFT + * OP_DRAG_RIGHT + * OP_ALL (equivalent to a combination of all the above). + """ + self._draggable = sens & OP_DRAG_LEFT + + self._sensitivity = sens + if recursive: + for shape in self._children: + shape.SetSensitivityFilter(sens, True) + + def SetDraggable(self, drag, recursive = False): + """Set the shape to be draggable or not draggable.""" + self._draggable = drag + if drag: + self._sensitivity |= OP_DRAG_LEFT + elif self._sensitivity & OP_DRAG_LEFT: + self._sensitivity -= OP_DRAG_LEFT + + if recursive: + for shape in self._children: + shape.SetDraggable(drag, True) + + def SetDrawHandles(self, drawH): + """Set the drawHandles flag for this shape and all descendants. + If drawH is TRUE (the default), any handles (control points) will + be drawn. Otherwise, the handles will not be drawn. + """ + self._drawHandles = drawH + for shape in self._children: + shape.SetDrawHandles(drawH) + + def SetShadowMode(self, mode, redraw = False): + """Set the shadow mode (whether a shadow is drawn or not). + mode can be one of the following: + + SHADOW_NONE + No shadow (the default). + SHADOW_LEFT + Shadow on the left side. + SHADOW_RIGHT + Shadow on the right side. + """ + if redraw and self.GetCanvas(): + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + self.Erase(dc) + self._shadowMode = mode + self.Draw(dc) + else: + self._shadowMode = mode + + def SetCanvas(self, theCanvas): + """Identical to Shape.Attach.""" + self._canvas = theCanvas + for shape in self._children: + shape.SetCanvas(theCanvas) + + def AddToCanvas(self, theCanvas, addAfter = None): + """Add the shape to the canvas's shape list. + If addAfter is non-NULL, will add the shape after this one. + """ + theCanvas.AddShape(self, addAfter) + + lastImage = self + for object in self._children: + object.AddToCanvas(theCanvas, lastImage) + lastImage = object + + def InsertInCanvas(self, theCanvas): + """Insert the shape at the front of the shape list of canvas.""" + theCanvas.InsertShape(self) + + lastImage = self + for object in self._children: + object.AddToCanvas(theCanvas, lastImage) + lastImage = object + + def RemoveFromCanvas(self, theCanvas): + """Remove the shape from the canvas.""" + if self.Selected(): + self.Select(False) + theCanvas.RemoveShape(self) + for object in self._children: + object.RemoveFromCanvas(theCanvas) + + def ClearAttachments(self): + """Clear internal custom attachment point shapes (of class + wxAttachmentPoint). + """ + self._attachmentPoints = [] + + def ClearText(self, regionId = 0): + """Clear the text from the specified text region.""" + if regionId == 0: + self._text="" + if regionId= left and x <= right and y >= top and y <= bottom: + n = self.GetNumberOfAttachments() + nearest = 999999 + + # GetAttachmentPosition[Edge] takes a logical attachment position, + # i.e. if it's rotated through 90%, position 0 is East-facing. + + for i in range(n): + e = self.GetAttachmentPositionEdge(i) + if e: + xp, yp = e + l = sqrt(((xp - x) * (xp - x)) + (yp - y) * (yp - y)) + if llen(self._regions): + return + + region = self._regions[i] + region._regionText = s + dc.SetFont(region.GetFont()) + + w, h = region.GetSize() + + stringList = FormatText(dc, s, (w - 2 * self._textMarginX), (h - 2 * self._textMarginY), region.GetFormatMode()) + for s in stringList: + line = ShapeTextLine(0.0, 0.0, s) + region.GetFormattedText().append(line) + + actualW = w + actualH = h + # Don't try to resize an object with more than one image (this + # case should be dealt with by overriden handlers) + if (region.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS) and \ + region.GetFormattedText().GetCount() and \ + len(self._regions) == 1 and \ + not Shape.GraphicsInSizeToContents: + + actualW, actualH = GetCentredTextExtent(dc, region.GetFormattedText()) + if actualW + 2 * self._textMarginX != w or actualH + 2 * self._textMarginY != h: + # If we are a descendant of a composite, must make sure + # the composite gets resized properly + + topAncestor = self.GetTopAncestor() + if topAncestor != self: + Shape.GraphicsInSizeToContents = True + + composite = topAncestor + composite.Erase(dc) + self.SetSize(actualW + 2 * self._textMarginX, actualH + 2 * self._textMarginY) + self.Move(dc, self._xpos, self._ypos) + composite.CalculateSize() + if composite.Selected(): + composite.DeleteControlPoints(dc) + composite.MakeControlPoints() + composite.MakeMandatoryControlPoints() + # Where infinite recursion might happen if we didn't stop it + composite.Draw(dc) + Shape.GraphicsInSizeToContents = False + else: + self.Erase(dc) + + self.SetSize(actualW + 2 * self._textMarginX, actualH + 2 * self._textMarginY) + self.Move(dc, self._xpos, self._ypos) + self.EraseContents(dc) + CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, actualW - 2 * self._textMarginX, actualH - 2 * self._textMarginY, region.GetFormatMode()) + self._formatted = True + + def Recentre(self, dc): + """Do recentring (or other formatting) for all the text regions + for this shape. + """ + w, h = self.GetBoundingBoxMin() + for region in self._regions: + CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, w - 2 * self._textMarginX, h - 2 * self._textMarginY, region.GetFormatMode()) + + def GetPerimeterPoint(self, x1, y1, x2, y2): + """Get the point at which the line from (x1, y1) to (x2, y2) hits + the shape. Returns False if the line doesn't hit the perimeter. + """ + return False + + def SetPen(self, the_pen): + """Set the pen for drawing the shape's outline.""" + self._pen = the_pen + + def SetBrush(self, the_brush): + """Set the brush for filling the shape's shape.""" + self._brush = the_brush + + # Get the top - most (non-division) ancestor, or self + def GetTopAncestor(self): + """Return the top-most ancestor of this shape (the root of + the composite). + """ + if not self.GetParent(): + return self + + if isinstance(self.GetParent(), DivisionShape): + return self + return self.GetParent().GetTopAncestor() + + # Region functions + def SetFont(self, the_font, regionId = 0): + """Set the font for the specified text region.""" + self._font = the_font + if regionId= len(self._regions): + return None + return self._regions[regionId].GetFont() + + def SetFormatMode(self, mode, regionId = 0): + """Set the format mode of the default text region. The argument + can be a bit list of the following: + + FORMAT_NONE + No formatting. + FORMAT_CENTRE_HORIZ + Horizontal centring. + FORMAT_CENTRE_VERT + Vertical centring. + """ + if regionId= len(self._regions): + return 0 + return self._regions[regionId].GetFormatMode() + + def SetTextColour(self, the_colour, regionId = 0): + """Set the colour for the specified text region.""" + self._textColour = wx.TheColourDatabase.Find(the_colour) + self._textColourName = the_colour + + if regionId= len(self._regions): + return "" + return self._regions[regionId].GetTextColour() + + def SetRegionName(self, name, regionId = 0): + """Set the name for this region. + The name for a region is unique within the scope of the whole + composite, whereas a region id is unique only for a single image. + """ + if regionId= len(self._regions): + return "" + return self._regions[regionId].GetName() + + def GetRegionId(self, name): + """Get the region's identifier by name. + This is not unique for within an entire composite, but is unique + for the image. + """ + for i, r in enumerate(self._regions): + if r.GetName() == name: + return i + return -1 + + # Name all _regions in all subimages recursively + def NameRegions(self, parentName=""): + """Make unique names for all the regions in a shape or composite shape.""" + n = self.GetNumberOfTextRegions() + for i in range(n): + if parentName: + buff = parentName+"."+str(i) + else: + buff = str(i) + self.SetRegionName(buff, i) + + for j, child in enumerate(self._children): + if parentName: + buff = parentName+"."+str(j) + else: + buff = str(j) + child.NameRegions(buff) + + # Get a region by name, possibly looking recursively into composites + def FindRegion(self, name): + """Find the actual image ('this' if non-composite) and region id + for the given region name. + """ + id = self.GetRegionId(name) + if id>-1: + return self, id + + for child in self._children: + actualImage, regionId = child.FindRegion(name) + if actualImage: + return actualImage, regionId + + return None,-1 + + # Finds all region names for this image (composite or simple). + def FindRegionNames(self): + """Get a list of all region names for this image (composite or simple).""" + list = [] + n = self.GetNumberOfTextRegions() + for i in range(n): + list.append(self.GetRegionName(i)) + + for child in self._children: + list += child.FindRegionNames() + + return list + + def AssignNewIds(self): + """Assign new ids to this image and its children.""" + self._id = wx.NewId() + for child in self._children: + child.AssignNewIds() + + def OnDraw(self, dc): + pass + + def OnMoveLinks(self, dc): + # Want to set the ends of all attached links + # to point to / from this object + + for line in self._lines: + line.GetEventHandler().OnMoveLink(dc) + + def OnDrawContents(self, dc): + if not self._regions: + return + + bound_x, bound_y = self.GetBoundingBoxMin() + + if self._pen: + dc.SetPen(self._pen) + + region = self._regions[0] + if region.GetFont(): + dc.SetFont(region.GetFont()) + + dc.SetTextForeground(region.GetActualColourObject()) + dc.SetBackgroundMode(wx.TRANSPARENT) + if not self._formatted: + CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, bound_x - 2 * self._textMarginX, bound_y - 2 * self._textMarginY, region.GetFormatMode()) + self._formatted = True + + if not self.GetDisableLabel(): + DrawFormattedText(dc, region.GetFormattedText(), self._xpos, self._ypos, bound_x - 2 * self._textMarginX, bound_y - 2 * self._textMarginY, region.GetFormatMode()) + + + def DrawContents(self, dc): + """Draw the internal graphic of the shape (such as text). + + Do not override this function: override OnDrawContents, which + is called by this function. + """ + self.GetEventHandler().OnDrawContents(dc) + + def OnSize(self, x, y): + pass + + def OnMovePre(self, dc, x, y, old_x, old_y, display = True): + return True + + def OnErase(self, dc): + if not self._visible: + return + + # Erase links + for line in self._lines: + line.GetEventHandler().OnErase(dc) + + self.GetEventHandler().OnEraseContents(dc) + + def OnEraseContents(self, dc): + if not self._visible: + return + + xp, yp = self.GetX(), self.GetY() + minX, minY = self.GetBoundingBoxMin() + maxX, maxY = self.GetBoundingBoxMax() + + topLeftX = xp - maxX / 2 - 2 + topLeftY = yp - maxY / 2 - 2 + + penWidth = 0 + if self._pen: + penWidth = self._pen.GetWidth() + + dc.SetPen(self.GetBackgroundPen()) + dc.SetBrush(self.GetBackgroundBrush()) + + dc.DrawRectangle(topLeftX - penWidth, topLeftY - penWidth, maxX + penWidth * 2 + 4, maxY + penWidth * 2 + 4) + + def EraseLinks(self, dc, attachment=-1, recurse = False): + """Erase links attached to this shape, but do not repair damage + caused to other shapes. + """ + if not self._visible: + return + + for line in self._lines: + if attachment==-1 or (line.GetTo() == self and line.GetAttachmentTo() == attachment or line.GetFrom() == self and line.GetAttachmentFrom() == attachment): + line.GetEventHandler().OnErase(dc) + + if recurse: + for child in self._children: + child.EraseLinks(dc, attachment, recurse) + + def DrawLinks(self, dc, attachment=-1, recurse = False): + """Draws any lines linked to this shape.""" + if not self._visible: + return + + for line in self._lines: + if attachment==-1 or (line.GetTo() == self and line.GetAttachmentTo() == attachment or line.GetFrom() == self and line.GetAttachmentFrom() == attachment): + line.GetEventHandler().Draw(dc) + + if recurse: + for child in self._children: + child.DrawLinks(dc, attachment, recurse) + + # Returns TRUE if pt1 <= pt2 in the sense that one point comes before + # another on an edge of the shape. + # attachmentPoint is the attachment point (= side) in question. + + # This is the default, rectangular implementation. + def AttachmentSortTest(self, attachmentPoint, pt1, pt2): + """Return TRUE if pt1 is less than or equal to pt2, in the sense + that one point comes before another on an edge of the shape. + + attachment is the attachment point (side) in question. + + This function is used in Shape.MoveLineToNewAttachment to determine + the new line ordering. + """ + physicalAttachment = self.LogicalToPhysicalAttachment(attachmentPoint) + if physicalAttachment in [0, 2]: + return pt1.x <= pt2.x + elif physicalAttachment in [1, 3]: + return pt1.y <= pt2.y + + return False + + def MoveLineToNewAttachment(self, dc, to_move, x, y): + """Move the given line (which must already be attached to the shape) + to a different attachment point on the shape, or a different order + on the same attachment. + + Calls Shape.AttachmentSortTest and then + ShapeEvtHandler.OnChangeAttachment. + """ + if self.GetAttachmentMode() == ATTACHMENT_MODE_NONE: + return False + + # Is (x, y) on this object? If so, find the new attachment point + # the user has moved the point to + hit = self.HitTest(x, y) + if not hit: + return False + + newAttachment, distance = hit + + self.EraseLinks(dc) + + if to_move.GetTo() == self: + oldAttachment = to_move.GetAttachmentTo() + else: + oldAttachment = to_move.GetAttachmentFrom() + + # The links in a new ordering + # First, add all links to the new list + newOrdering = self._lines[:] + + # Delete the line object from the list of links; we're going to move + # it to another position in the list + del newOrdering[newOrdering.index(to_move)] + + old_x=-99999.9 + old_y=-99999.9 + + found = False + + for line in newOrdering: + if line.GetTo() == self and oldAttachment == line.GetAttachmentTo() or \ + line.GetFrom() == self and oldAttachment == line.GetAttachmentFrom(): + startX, startY, endX, endY = line.GetEnds() + if line.GetTo() == self: + xp = endX + yp = endY + else: + xp = startX + yp = startY + + thisPoint = wx.RealPoint(xp, yp) + lastPoint = wx.RealPoint(old_x, old_y) + newPoint = wx.RealPoint(x, y) + + if self.AttachmentSortTest(newAttachment, newPoint, thisPoint) and self.AttachmentSortTest(newAttachment, lastPoint, newPoint): + found = True + newOrdering.insert(newOrdering.index(line), to_move) + + old_x = xp + old_y = yp + if found: + break + + if not found: + newOrdering.append(to_move) + + self.GetEventHandler().OnChangeAttachment(newAttachment, to_move, newOrdering) + return True + + def OnChangeAttachment(self, attachment, line, ordering): + if line.GetTo() == self: + line.SetAttachmentTo(attachment) + else: + line.SetAttachmentFrom(attachment) + + self.ApplyAttachmentOrdering(ordering) + + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + self.MoveLinks(dc) + + if not self.GetCanvas().GetQuickEditMode(): + self.GetCanvas().Redraw(dc) + + # Reorders the lines according to the given list + def ApplyAttachmentOrdering(self, linesToSort): + """Apply the line ordering in linesToSort to the shape, to reorder + the way lines are attached. + """ + linesStore = self._lines[:] + + self._lines = [] + + for line in linesToSort: + if line in linesStore: + del linesStore[linesStore.index(line)] + self._lines.append(line) + + # Now add any lines that haven't been listed in linesToSort + self._lines += linesStore + + def SortLines(self, attachment, linesToSort): + """ Reorder the lines coming into the node image at this attachment + position, in the order in which they appear in linesToSort. + + Any remaining lines not in the list will be added to the end. + """ + # This is a temporary store of all the lines at this attachment + # point. We'll tick them off as we've processed them. + linesAtThisAttachment = [] + + for line in self._lines[:]: + if line.GetTo() == self and line.GetAttachmentTo() == attachment or \ + line.GetFrom() == self and line.GetAttachmentFrom() == attachment: + linesAtThisAttachment.append(line) + del self._lines[self._lines.index(line)] + + for line in linesToSort: + if line in linesAtThisAttachment: + # Done this one + del linesAtThisAttachment[linesAtThisAttachment.index(line)] + self._lines.Append(line) + + # Now add any lines that haven't been listed in linesToSort + self._lines += linesAtThisAttachment + + def OnHighlight(self, dc): + pass + + def OnLeftClick(self, x, y, keys = 0, attachment = 0): + if self._sensitivity & OP_CLICK_LEFT != OP_CLICK_LEFT: + if self._parent: + attachment, dist = self._parent.HitTest(x, y) + self._parent.GetEventHandler().OnLeftClick(x, y, keys, attachment) + + def OnRightClick(self, x, y, keys = 0, attachment = 0): + if self._sensitivity & OP_CLICK_RIGHT != OP_CLICK_RIGHT: + attachment, dist = self._parent.HitTest(x, y) + self._parent.GetEventHandler().OnRightClick(x, y, keys, attachment) + + def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0): + if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT: + if self._parent: + hit = self._parent.HitTest(x, y) + if hit: + attachment, dist = hit + self._parent.GetEventHandler().OnDragLeft(draw, x, y, keys, attachment) + return + + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + dc.SetLogicalFunction(OGLRBLF) + + dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT) + dc.SetPen(dottedPen) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + + xx = x + DragOffsetX + yy = y + DragOffsetY + + xx, yy = self._canvas.Snap(xx, yy) + w, h = self.GetBoundingBoxMax() + self.GetEventHandler().OnDrawOutline(dc, xx, yy, w, h) + + def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0): + global DragOffsetX, DragOffsetY + + if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT: + if self._parent: + hit = self._parent.HitTest(x, y) + if hit: + attachment, dist = hit + self._parent.GetEventHandler().OnBeginDragLeft(x, y, keys, attachment) + return + + DragOffsetX = self._xpos - x + DragOffsetY = self._ypos - y + + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + + # New policy: don't erase shape until end of drag. + # self.Erase(dc) + xx = x + DragOffsetX + yy = y + DragOffsetY + xx, yy = self._canvas.Snap(xx, yy) + dc.SetLogicalFunction(OGLRBLF) + + dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT) + dc.SetPen(dottedPen) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + + w, h = self.GetBoundingBoxMax() + self.GetEventHandler().OnDrawOutline(dc, xx, yy, w, h) + self._canvas.CaptureMouse() + + def OnEndDragLeft(self, x, y, keys = 0, attachment = 0): + if self._canvas.HasCapture(): + self._canvas.ReleaseMouse() + if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT: + if self._parent: + hit = self._parent.HitTest(x, y) + if hit: + attachment, dist = hit + self._parent.GetEventHandler().OnEndDragLeft(x, y, keys, attachment) + return + + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + + dc.SetLogicalFunction(wx.COPY) + xx = x + DragOffsetX + yy = y + DragOffsetY + xx, yy = self._canvas.Snap(xx, yy) + + # New policy: erase shape at end of drag. + self.Erase(dc) + + self.Move(dc, xx, yy) + if self._canvas and not self._canvas.GetQuickEditMode(): + self._canvas.Redraw(dc) + + def OnDragRight(self, draw, x, y, keys = 0, attachment = 0): + if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT: + if self._parent: + attachment, dist = self._parent.HitTest(x, y) + self._parent.GetEventHandler().OnDragRight(draw, x, y, keys, attachment) + return + + def OnBeginDragRight(self, x, y, keys = 0, attachment = 0): + if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT: + if self._parent: + attachment, dist = self._parent.HitTest(x, y) + self._parent.GetEventHandler().OnBeginDragRight(x, y, keys, attachment) + return + + def OnEndDragRight(self, x, y, keys = 0, attachment = 0): + if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT: + if self._parent: + attachment, dist = self._parent.HitTest(x, y) + self._parent.GetEventHandler().OnEndDragRight(x, y, keys, attachment) + return + + def OnDrawOutline(self, dc, x, y, w, h): + points = [[x - w / 2, y - h / 2], + [x + w / 2, y - h / 2], + [x + w / 2, y + h / 2], + [x - w / 2, y + h / 2], + [x - w / 2, y - h / 2], + ] + + #dc.DrawLines([[round(x), round(y)] for x, y in points]) + dc.DrawLines(points) + + def Attach(self, can): + """Set the shape's internal canvas pointer to point to the given canvas.""" + self._canvas = can + + def Detach(self): + """Disassociates the shape from its canvas.""" + self._canvas = None + + def Move(self, dc, x, y, display = True): + """Move the shape to the given position. + Redraw if display is TRUE. + """ + old_x = self._xpos + old_y = self._ypos + + if not self.GetEventHandler().OnMovePre(dc, x, y, old_x, old_y, display): + return + + self._xpos, self._ypos = x, y + + self.ResetControlPoints() + + if display: + self.Draw(dc) + + self.MoveLinks(dc) + + self.GetEventHandler().OnMovePost(dc, x, y, old_x, old_y, display) + + def MoveLinks(self, dc): + """Redraw all the lines attached to the shape.""" + self.GetEventHandler().OnMoveLinks(dc) + + def Draw(self, dc): + """Draw the whole shape and any lines attached to it. + + Do not override this function: override OnDraw, which is called + by this function. + """ + if self._visible: + self.GetEventHandler().OnDraw(dc) + self.GetEventHandler().OnDrawContents(dc) + self.GetEventHandler().OnDrawControlPoints(dc) + self.GetEventHandler().OnDrawBranches(dc) + + def Flash(self): + """Flash the shape.""" + if self.GetCanvas(): + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas.PrepareDC(dc) + + dc.SetLogicalFunction(OGLRBLF) + self.Draw(dc) + dc.SetLogicalFunction(wx.COPY) + self.Draw(dc) + + def Show(self, show): + """Set a flag indicating whether the shape should be drawn.""" + self._visible = show + for child in self._children: + child.Show(show) + + def Erase(self, dc): + """Erase the shape. + Does not repair damage caused to other shapes. + """ + self.GetEventHandler().OnErase(dc) + self.GetEventHandler().OnEraseControlPoints(dc) + self.GetEventHandler().OnDrawBranches(dc, erase = True) + + def EraseContents(self, dc): + """Erase the shape contents, that is, the area within the shape's + minimum bounding box. + """ + self.GetEventHandler().OnEraseContents(dc) + + def AddText(self, string): + """Add a line of text to the shape's default text region.""" + if not self._regions: + return + + region = self._regions[0] + region.ClearText() + new_line = ShapeTextLine(0, 0, string) + text = region.GetFormattedText() + text.append(new_line) + + self._formatted = False + + def SetSize(self, x, y, recursive = True): + """Set the shape's size.""" + self.SetAttachmentSize(x, y) + self.SetDefaultRegionSize() + + def SetAttachmentSize(self, w, h): + width, height = self.GetBoundingBoxMin() + if width == 0: + scaleX = 1.0 + else: + scaleX = long(w) / width + if height == 0: + scaleY = 1.0 + else: + scaleY = long(h) / height + + for point in self._attachmentPoints: + point._x = point._x * scaleX + point._y = point._y * scaleY + + # Add line FROM this object + def AddLine(self, line, other, attachFrom = 0, attachTo = 0, positionFrom=-1, positionTo=-1): + """Add a line between this shape and the given other shape, at the + specified attachment points. + + The position in the list of lines at each end can also be specified, + so that the line will be drawn at a particular point on its attachment + point. + """ + if positionFrom==-1: + if not line in self._lines: + self._lines.append(line) + else: + # Don't preserve old ordering if we have new ordering instructions + try: + self._lines.remove(line) + except ValueError: + pass + if positionFrommaxN: + maxN = point._id + return maxN + 1 + + def AttachmentIsValid(self, attachment): + """TRUE if attachment is a valid attachment point.""" + if len(self._attachmentPoints) == 0: + return attachment in range(4) + + for point in self._attachmentPoints: + if point._id == attachment: + return True + return False + + def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None): + """Get the position at which the given attachment point should be drawn. + + If attachment isn't found among the attachment points of the shape, + returns None. + """ + if self._attachmentMode == ATTACHMENT_MODE_NONE: + return self._xpos, self._ypos + elif self._attachmentMode == ATTACHMENT_MODE_BRANCHING: + pt, stemPt = self.GetBranchingAttachmentPoint(attachment, nth) + return pt.x, pt.y + elif self._attachmentMode == ATTACHMENT_MODE_EDGE: + if len(self._attachmentPoints): + for point in self._attachmentPoints: + if point._id == attachment: + return self._xpos + point._x, self._ypos + point._y + return None + else: + # Assume is rectangular + w, h = self.GetBoundingBoxMax() + top = self._ypos + h / 2 + bottom = self._ypos - h / 2 + left = self._xpos - w / 2 + right = self._xpos + w / 2 + + # wtf? + line and line.IsEnd(self) + + physicalAttachment = self.LogicalToPhysicalAttachment(attachment) + + # Simplified code + if physicalAttachment == 0: + pt = self.CalcSimpleAttachment((left, bottom), (right, bottom), nth, no_arcs, line) + elif physicalAttachment == 1: + pt = self.CalcSimpleAttachment((right, bottom), (right, top), nth, no_arcs, line) + elif physicalAttachment == 2: + pt = self.CalcSimpleAttachment((left, top), (right, top), nth, no_arcs, line) + elif physicalAttachment == 3: + pt = self.CalcSimpleAttachment((left, bottom), (left, top), nth, no_arcs, line) + else: + return None + return pt[0], pt[1] + return None + + def GetBoundingBoxMax(self): + """Get the maximum bounding box for the shape, taking into account + external features such as shadows. + """ + ww, hh = self.GetBoundingBoxMin() + if self._shadowMode != SHADOW_NONE: + ww += self._shadowOffsetX + hh += self._shadowOffsetY + return ww, hh + + def GetBoundingBoxMin(self): + """Get the minimum bounding box for the shape, that defines the area + available for drawing the contents (such as text). + + Must be overridden. + """ + return 0, 0 + + def HasDescendant(self, image): + """TRUE if image is a descendant of this composite.""" + if image == self: + return True + for child in self._children: + if child.HasDescendant(image): + return True + return False + + # Clears points from a list of wxRealPoints, and clears list + # Useless in python? /pi + def ClearPointList(self, list): + list = [] + + # Assuming the attachment lies along a vertical or horizontal line, + # calculate the position on that point. + def CalcSimpleAttachment(self, pt1, pt2, nth, noArcs, line): + """Assuming the attachment lies along a vertical or horizontal line, + calculate the position on that point. + + Parameters: + + pt1 + The first point of the line repesenting the edge of the shape. + + pt2 + The second point of the line representing the edge of the shape. + + nth + The position on the edge (for example there may be 6 lines at + this attachment point, and this may be the 2nd line. + + noArcs + The number of lines at this edge. + + line + The line shape. + + Remarks + + This function expects the line to be either vertical or horizontal, + and determines which. + """ + isEnd = line and line.IsEnd(self) + + # Are we horizontal or vertical? + isHorizontal = RoughlyEqual(pt1[1], pt2[1]) + + if isHorizontal: + if pt1[0]>pt2[0]: + firstPoint = pt2 + secondPoint = pt1 + else: + firstPoint = pt1 + secondPoint = pt2 + + if self._spaceAttachments: + if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE: + # Align line according to the next handle along + point = line.GetNextControlPoint(self) + if point[0]secondPoint[0]: + x = secondPoint[0] + else: + x = point[0] + else: + x = firstPoint[0] + (nth + 1) * (secondPoint[0] - firstPoint[0]) / (noArcs + 1) + else: + x = (secondPoint[0] - firstPoint[0]) / 2 # Midpoint + y = pt1[1] + else: + assert RoughlyEqual(pt1[0], pt2[0]) + + if pt1[1]>pt2[1]: + firstPoint = pt2 + secondPoint = pt1 + else: + firstPoint = pt1 + secondPoint = pt2 + + if self._spaceAttachments: + if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE: + # Align line according to the next handle along + point = line.GetNextControlPoint(self) + if point[1]secondPoint[1]: + y = secondPoint[1] + else: + y = point[1] + else: + y = firstPoint[1] + (nth + 1) * (secondPoint[1] - firstPoint[1]) / (noArcs + 1) + else: + y = (secondPoint[1] - firstPoint[1]) / 2 # Midpoint + x = pt1[0] + + return x, y + + # Return the zero-based position in m_lines of line + def GetLinePosition(self, line): + """Get the zero-based position of line in the list of lines + for this shape. + """ + try: + return self._lines.index(line) + except: + return 0 + + + # |________| + # | <- root + # | <- neck + # shoulder1 ->---------<- shoulder2 + # | | | | | + # <- branching attachment point N-1 + + def GetBranchingAttachmentInfo(self, attachment): + """Get information about where branching connections go. + + Returns FALSE if there are no lines at this attachment. + """ + physicalAttachment = self.LogicalToPhysicalAttachment(attachment) + + # Number of lines at this attachment + lineCount = self.GetAttachmentLineCount(attachment) + + if not lineCount: + return False + + totalBranchLength = self._branchSpacing * (lineCount - 1) + root = self.GetBranchingAttachmentRoot(attachment) + + neck = wx.RealPoint() + shoulder1 = wx.RealPoint() + shoulder2 = wx.RealPoint() + + # Assume that we have attachment points 0 to 3: top, right, bottom, left + if physicalAttachment == 0: + neck.x = self.GetX() + neck.y = root.y - self._branchNeckLength + + shoulder1.x = root.x - totalBranchLength / 2 + shoulder2.x = root.x + totalBranchLength / 2 + + shoulder1.y = neck.y + shoulder2.y = neck.y + elif physicalAttachment == 1: + neck.x = root.x + self._branchNeckLength + neck.y = root.y + + shoulder1.x = neck.x + shoulder2.x = neck.x + + shoulder1.y = neck.y - totalBranchLength / 2 + shoulder1.y = neck.y + totalBranchLength / 2 + elif physicalAttachment == 2: + neck.x = self.GetX() + neck.y = root.y + self._branchNeckLength + + shoulder1.x = root.x - totalBranchLength / 2 + shoulder2.x = root.x + totalBranchLength / 2 + + shoulder1.y = neck.y + shoulder2.y = neck.y + elif physicalAttachment == 3: + neck.x = root.x - self._branchNeckLength + neck.y = root.y + + shoulder1.x = neck.x + shoulder2.x = neck.x + + shoulder1.y = neck.y - totalBranchLength / 2 + shoulder2.y = neck.y + totalBranchLength / 2 + else: + raise "Unrecognised attachment point in GetBranchingAttachmentInfo" + return root, neck, shoulder1, shoulder2 + + def GetBranchingAttachmentPoint(self, attachment, n): + physicalAttachment = self.LogicalToPhysicalAttachment(attachment) + + root, neck, shoulder1, shoulder2 = self.GetBranchingAttachmentInfo(attachment) + pt = wx.RealPoint() + stemPt = wx.RealPoint() + + if physicalAttachment == 0: + pt.y = neck.y - self._branchStemLength + pt.x = shoulder1.x + n * self._branchSpacing + + stemPt.x = pt.x + stemPt.y = neck.y + elif physicalAttachment == 2: + pt.y = neck.y + self._branchStemLength + pt.x = shoulder1.x + n * self._branchStemLength + + stemPt.x = pt.x + stemPt.y = neck.y + elif physicalAttachment == 1: + pt.x = neck.x + self._branchStemLength + pt.y = shoulder1.y + n * self._branchSpacing + + stemPt.x = neck.x + stemPt.y = pt.y + elif physicalAttachment == 3: + pt.x = neck.x - self._branchStemLength + pt.y = shoulder1.y + n * self._branchSpacing + + stemPt.x = neck.x + stemPt.y = pt.y + else: + raise "Unrecognised attachment point in GetBranchingAttachmentPoint" + + return pt, stemPt + + def GetAttachmentLineCount(self, attachment): + """Get the number of lines at this attachment position.""" + count = 0 + for lineShape in self._lines: + if lineShape.GetFrom() == self and lineShape.GetAttachmentFrom() == attachment: + count += 1 + elif lineShape.GetTo() == self and lineShape.GetAttachmentTo() == attachment: + count += 1 + return count + + def GetBranchingAttachmentRoot(self, attachment): + """Get the root point at the given attachment.""" + physicalAttachment = self.LogicalToPhysicalAttachment(attachment) + + root = wx.RealPoint() + + width, height = self.GetBoundingBoxMax() + + # Assume that we have attachment points 0 to 3: top, right, bottom, left + if physicalAttachment == 0: + root.x = self.GetX() + root.y = self.GetY() - height / 2 + elif physicalAttachment == 1: + root.x = self.GetX() + width / 2 + root.y = self.GetY() + elif physicalAttachment == 2: + root.x = self.GetX() + root.y = self.GetY() + height / 2 + elif physicalAttachment == 3: + root.x = self.GetX() - width / 2 + root.y = self.GetY() + else: + raise "Unrecognised attachment point in GetBranchingAttachmentRoot" + + return root + + # Draw or erase the branches (not the actual arcs though) + def OnDrawBranchesAttachment(self, dc, attachment, erase = False): + count = self.GetAttachmentLineCount(attachment) + if count == 0: + return + + root, neck, shoulder1, shoulder2 = self.GetBranchingAttachmentInfo(attachment) + + if erase: + dc.SetPen(wx.WHITE_PEN) + dc.SetBrush(wx.WHITE_BRUSH) + else: + dc.SetPen(wx.BLACK_PEN) + dc.SetBrush(wx.BLACK_BRUSH) + + # Draw neck + dc.DrawLine(root, neck) + + if count>1: + # Draw shoulder-to-shoulder line + dc.DrawLine(shoulder1, shoulder2) + # Draw all the little branches + for i in range(count): + pt, stemPt = self.GetBranchingAttachmentPoint(attachment, i) + dc.DrawLine(stemPt, pt) + + if self.GetBranchStyle() & BRANCHING_ATTACHMENT_BLOB and count>1: + blobSize = 6 + dc.DrawEllipse(stemPt.x - blobSize / 2, stemPt.y - blobSize / 2, blobSize, blobSize) + + def OnDrawBranches(self, dc, erase = False): + if self._attachmentMode != ATTACHMENT_MODE_BRANCHING: + return + for i in range(self.GetNumberOfAttachments()): + self.OnDrawBranchesAttachment(dc, i, erase) + + def GetAttachmentPositionEdge(self, attachment, nth = 0, no_arcs = 1, line = None): + """ Only get the attachment position at the _edge_ of the shape, + ignoring branching mode. This is used e.g. to indicate the edge of + interest, not the point on the attachment branch. + """ + oldMode = self._attachmentMode + + # Calculate as if to edge, not branch + if self._attachmentMode == ATTACHMENT_MODE_BRANCHING: + self._attachmentMode = ATTACHMENT_MODE_EDGE + res = self.GetAttachmentPosition(attachment, nth, no_arcs, line) + self._attachmentMode = oldMode + + return res + + def PhysicalToLogicalAttachment(self, physicalAttachment): + """ Rotate the standard attachment point from physical + (0 is always North) to logical (0 -> 1 if rotated by 90 degrees) + """ + if RoughlyEqual(self.GetRotation(), 0): + i = physicalAttachment + elif RoughlyEqual(self.GetRotation(), pi / 2): + i = physicalAttachment - 1 + elif RoughlyEqual(self.GetRotation(), pi): + i = physicalAttachment - 2 + elif RoughlyEqual(self.GetRotation(), 3 * pi / 2): + i = physicalAttachment - 3 + else: + # Can't handle -- assume the same + return physicalAttachment + + if i<0: + i += 4 + + return i + + def LogicalToPhysicalAttachment(self, logicalAttachment): + """Rotate the standard attachment point from logical + to physical (0 is always North). + """ + if RoughlyEqual(self.GetRotation(), 0): + i = logicalAttachment + elif RoughlyEqual(self.GetRotation(), pi / 2): + i = logicalAttachment + 1 + elif RoughlyEqual(self.GetRotation(), pi): + i = logicalAttachment + 2 + elif RoughlyEqual(self.GetRotation(), 3 * pi / 2): + i = logicalAttachment + 3 + else: + return logicalAttachment + + if i>3: + i -= 4 + + return i + + def Rotate(self, x, y, theta): + """Rotate about the given axis by the given amount in radians.""" + self._rotation = theta + if self._rotation<0: + self._rotation += 2 * pi + elif self._rotation>2 * pi: + self._rotation -= 2 * pi + + def GetBackgroundPen(self): + """Return pen of the right colour for the background.""" + if self.GetCanvas(): + return wx.Pen(self.GetCanvas().GetBackgroundColour(), 1, wx.SOLID) + return WhiteBackgroundPen + + def GetBackgroundBrush(self): + """Return brush of the right colour for the background.""" + if self.GetCanvas(): + return wx.Brush(self.GetCanvas().GetBackgroundColour(), wx.SOLID) + return WhiteBackgroundBrush + + def GetX(self): + """Get the x position of the centre of the shape.""" + return self._xpos + + def GetY(self): + """Get the y position of the centre of the shape.""" + return self._ypos + + def SetX(self, x): + """Set the x position of the shape.""" + self._xpos = x + + def SetY(self, y): + """Set the y position of the shape.""" + self._ypos = y + + def GetParent(self): + """Return the parent of this shape, if it is part of a composite.""" + return self._parent + + def SetParent(self, p): + self._parent = p + + def GetChildren(self): + """Return the list of children for this shape.""" + return self._children + + def GetDrawHandles(self): + """Return the list of drawhandles.""" + return self._drawHandles + + def GetEventHandler(self): + """Return the event handler for this shape.""" + return self._eventHandler + + def SetEventHandler(self, handler): + """Set the event handler for this shape.""" + self._eventHandler = handler + + def Recompute(self): + """Recomputes any constraints associated with the shape. + + Normally applicable to CompositeShapes only, but harmless for + other classes of Shape. + """ + return True + + def IsHighlighted(self): + """TRUE if the shape is highlighted. Shape highlighting is unimplemented.""" + return self._highlighted + + def GetSensitivityFilter(self): + """Return the sensitivity filter, a bitlist of values. + + See Shape.SetSensitivityFilter. + """ + return self._sensitivity + + def SetFixedSize(self, x, y): + """Set the shape to be fixed size.""" + self._fixedWidth = x + self._fixedHeight = y + + def GetFixedSize(self): + """Return flags indicating whether the shape is of fixed size in + either direction. + """ + return self._fixedWidth, self._fixedHeight + + def GetFixedWidth(self): + """TRUE if the shape cannot be resized in the horizontal plane.""" + return self._fixedWidth + + def GetFixedHeight(self): + """TRUE if the shape cannot be resized in the vertical plane.""" + return self._fixedHeight + + def SetSpaceAttachments(self, sp): + """Indicate whether lines should be spaced out evenly at the point + they touch the node (sp = True), or whether they should join at a single + point (sp = False). + """ + self._spaceAttachments = sp + + def GetSpaceAttachments(self): + """Return whether lines should be spaced out evenly at the point they + touch the node (True), or whether they should join at a single point + (False). + """ + return self._spaceAttachments + + def SetCentreResize(self, cr): + """Specify whether the shape is to be resized from the centre (the + centre stands still) or from the corner or side being dragged (the + other corner or side stands still). + """ + self._centreResize = cr + + def GetCentreResize(self): + """TRUE if the shape is to be resized from the centre (the centre stands + still), or FALSE if from the corner or side being dragged (the other + corner or side stands still) + """ + return self._centreResize + + def SetMaintainAspectRatio(self, ar): + """Set whether a shape that resizes should not change the aspect ratio + (width and height should be in the original proportion). + """ + self._maintainAspectRatio = ar + + def GetMaintainAspectRatio(self): + """TRUE if shape keeps aspect ratio during resize.""" + return self._maintainAspectRatio + + def GetLines(self): + """Return the list of lines connected to this shape.""" + return self._lines + + def SetDisableLabel(self, flag): + """Set flag to TRUE to stop the default region being shown.""" + self._disableLabel = flag + + def GetDisableLabel(self): + """TRUE if the default region will not be shown, FALSE otherwise.""" + return self._disableLabel + + def SetAttachmentMode(self, mode): + """Set the attachment mode. + + If TRUE, attachment points will be significant when drawing lines to + and from this shape. + If FALSE, lines will be drawn as if to the centre of the shape. + """ + self._attachmentMode = mode + + def GetAttachmentMode(self): + """Return the attachment mode. + + See Shape.SetAttachmentMode. + """ + return self._attachmentMode + + def SetId(self, i): + """Set the integer identifier for this shape.""" + self._id = i + + def GetId(self): + """Return the integer identifier for this shape.""" + return self._id + + def IsShown(self): + """TRUE if the shape is in a visible state, FALSE otherwise. + + Note that this has nothing to do with whether the window is hidden + or the shape has scrolled off the canvas; it refers to the internal + visibility flag. + """ + return self._visible + + def GetPen(self): + """Return the pen used for drawing the shape's outline.""" + return self._pen + + def GetBrush(self): + """Return the brush used for filling the shape.""" + return self._brush + + def GetNumberOfTextRegions(self): + """Return the number of text regions for this shape.""" + return len(self._regions) + + def GetRegions(self): + """Return the list of ShapeRegions.""" + return self._regions + + # Control points ('handles') redirect control to the actual shape, to + # make it easier to override sizing behaviour. + def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0): + bound_x, bound_y = self.GetBoundingBoxMin() + + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + + dc.SetLogicalFunction(OGLRBLF) + + dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT) + dc.SetPen(dottedPen) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + + if self.GetCentreResize(): + # Maintain the same centre point + new_width = 2.0 * abs(x - self.GetX()) + new_height = 2.0 * abs(y - self.GetY()) + + # Constrain sizing according to what control point you're dragging + if pt._type == CONTROL_POINT_HORIZONTAL: + if self.GetMaintainAspectRatio(): + new_height = bound_y * (new_width / bound_x) + else: + new_height = bound_y + elif pt._type == CONTROL_POINT_VERTICAL: + if self.GetMaintainAspectRatio(): + new_width = bound_x * (new_height / bound_y) + else: + new_width = bound_x + elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT): + new_height = bound_y * (new_width / bound_x) + + if self.GetFixedWidth(): + new_width = bound_x + + if self.GetFixedHeight(): + new_height = bound_y + + pt._controlPointDragEndWidth = new_width + pt._controlPointDragEndHeight = new_height + + self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), new_width, new_height) + else: + # Don't maintain the same centre point + newX1 = min(pt._controlPointDragStartX, x) + newY1 = min(pt._controlPointDragStartY, y) + newX2 = max(pt._controlPointDragStartX, x) + newY2 = max(pt._controlPointDragStartY, y) + if pt._type == CONTROL_POINT_HORIZONTAL: + newY1 = pt._controlPointDragStartY + newY2 = newY1 + pt._controlPointDragStartHeight + elif pt._type == CONTROL_POINT_VERTICAL: + newX1 = pt._controlPointDragStartX + newX2 = newX1 + pt._controlPointDragStartWidth + elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT or self.GetMaintainAspectRatio()): + newH = (newX2 - newX1) * (pt._controlPointDragStartHeight / pt._controlPointDragStartWidth) + if self.GetY()>pt._controlPointDragStartY: + newY2 = newY1 + newH + else: + newY1 = newY2 - newH + + newWidth = newX2 - newX1 + newHeight = newY2 - newY1 + + if pt._type == CONTROL_POINT_VERTICAL and self.GetMaintainAspectRatio(): + newWidth = bound_x * (newHeight / bound_y) + + if pt._type == CONTROL_POINT_HORIZONTAL and self.GetMaintainAspectRatio(): + newHeight = bound_y * (newWidth / bound_x) + + pt._controlPointDragPosX = newX1 + newWidth / 2 + pt._controlPointDragPosY = newY1 + newHeight / 2 + if self.GetFixedWidth(): + newWidth = bound_x + + if self.GetFixedHeight(): + newHeight = bound_y + + pt._controlPointDragEndWidth = newWidth + pt._controlPointDragEndHeight = newHeight + self.GetEventHandler().OnDrawOutline(dc, pt._controlPointDragPosX, pt._controlPointDragPosY, newWidth, newHeight) + + def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0): + self._canvas.CaptureMouse() + + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + + dc.SetLogicalFunction(OGLRBLF) + + bound_x, bound_y = self.GetBoundingBoxMin() + self.GetEventHandler().OnEndSize(bound_x, bound_y) + + # Choose the 'opposite corner' of the object as the stationary + # point in case this is non-centring resizing. + if pt.GetX()pt._controlPointDragStartY: + newY2 = newY1 + newH + else: + newY1 = newY2 - newH + + newWidth = newX2 - newX1 + newHeight = newY2 - newY1 + + if pt._type == CONTROL_POINT_VERTICAL and self.GetMaintainAspectRatio(): + newWidth = bound_x * (newHeight / bound_y) + + if pt._type == CONTROL_POINT_HORIZONTAL and self.GetMaintainAspectRatio(): + newHeight = bound_y * (newWidth / bound_x) + + pt._controlPointDragPosX = newX1 + newWidth / 2 + pt._controlPointDragPosY = newY1 + newHeight / 2 + if self.GetFixedWidth(): + newWidth = bound_x + + if self.GetFixedHeight(): + newHeight = bound_y + + pt._controlPointDragEndWidth = newWidth + pt._controlPointDragEndHeight = newHeight + self.GetEventHandler().OnDrawOutline(dc, pt._controlPointDragPosX, pt._controlPointDragPosY, newWidth, newHeight) + + def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0): + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + + if self._canvas.HasCapture(): + self._canvas.ReleaseMouse() + dc.SetLogicalFunction(wx.COPY) + self.Recompute() + self.ResetControlPoints() + + self.Erase(dc) + + self.SetSize(pt._controlPointDragEndWidth, pt._controlPointDragEndHeight) + + # The next operation could destroy this control point (it does for + # label objects, via formatting the text), so save all values we're + # going to use, or we'll be accessing garbage. + + #return + + if self.GetCentreResize(): + self.Move(dc, self.GetX(), self.GetY()) + else: + self.Move(dc, pt._controlPointDragPosX, pt._controlPointDragPosY) + + # Recursively redraw links if we have a composite + if len(self.GetChildren()): + self.DrawLinks(dc,-1, True) + + width, height = self.GetBoundingBoxMax() + self.GetEventHandler().OnEndSize(width, height) + + if not self._canvas.GetQuickEditMode() and pt._eraseObject: + self._canvas.Redraw(dc) + + + +class RectangleShape(Shape): + """ + The wxRectangleShape has rounded or square corners. + + Derived from: + Shape + """ + def __init__(self, w = 0.0, h = 0.0): + Shape.__init__(self) + self._width = w + self._height = h + self._cornerRadius = 0.0 + self.SetDefaultRegionSize() + + def OnDraw(self, dc): + x1 = self._xpos - self._width / 2 + y1 = self._ypos - self._height / 2 + + if self._shadowMode != SHADOW_NONE: + if self._shadowBrush: + dc.SetBrush(self._shadowBrush) + dc.SetPen(TransparentPen) + + if self._cornerRadius: + dc.DrawRoundedRectangle(x1 + self._shadowOffsetX, y1 + self._shadowOffsetY, self._width, self._height, self._cornerRadius) + else: + dc.DrawRectangle(x1 + self._shadowOffsetX, y1 + self._shadowOffsetY, self._width, self._height) + + if self._pen: + if self._pen.GetWidth() == 0: + dc.SetPen(TransparentPen) + else: + dc.SetPen(self._pen) + if self._brush: + dc.SetBrush(self._brush) + + if self._cornerRadius: + dc.DrawRoundedRectangle(x1, y1, self._width, self._height, self._cornerRadius) + else: + dc.DrawRectangle(x1, y1, self._width, self._height) + + def GetBoundingBoxMin(self): + return self._width, self._height + + def SetSize(self, x, y, recursive = False): + self.SetAttachmentSize(x, y) + self._width = max(x, 1) + self._height = max(y, 1) + self.SetDefaultRegionSize() + + def GetCornerRadius(self): + """Get the radius of the rectangle's rounded corners.""" + return self._cornerRadius + + def SetCornerRadius(self, rad): + """Set the radius of the rectangle's rounded corners. + + If the radius is zero, a non-rounded rectangle will be drawn. + If the radius is negative, the value is the proportion of the smaller + dimension of the rectangle. + """ + self._cornerRadius = rad + + # Assume (x1, y1) is centre of box (most generally, line end at box) + def GetPerimeterPoint(self, x1, y1, x2, y2): + bound_x, bound_y = self.GetBoundingBoxMax() + return FindEndForBox(bound_x, bound_y, self._xpos, self._ypos, x2, y2) + + def GetWidth(self): + return self._width + + def GetHeight(self): + return self._height + + def SetWidth(self, w): + self._width = w + + def SetHeight(self, h): + self._height = h + + + +class PolygonShape(Shape): + """A PolygonShape's shape is defined by a number of points passed to + the object's constructor. It can be used to create new shapes such as + diamonds and triangles. + """ + def __init__(self): + Shape.__init__(self) + + self._points = None + self._originalPoints = None + + def Create(self, the_points = None): + """Takes a list of wx.RealPoints or tuples; each point is an offset + from the centre. + """ + self.ClearPoints() + + if not the_points: + self._originalPoints = [] + self._points = [] + else: + self._originalPoints = the_points + + # Duplicate the list of points + self._points = [] + for point in the_points: + new_point = wx.Point(point[0], point[1]) + self._points.append(new_point) + self.CalculateBoundingBox() + self._originalWidth = self._boundWidth + self._originalHeight = self._boundHeight + self.SetDefaultRegionSize() + + def ClearPoints(self): + self._points = [] + self._originalPoints = [] + + # Width and height. Centre of object is centre of box + def GetBoundingBoxMin(self): + return self._boundWidth, self._boundHeight + + def GetPoints(self): + """Return the internal list of polygon vertices.""" + return self._points + + def GetOriginalPoints(self): + return self._originalPoints + + def GetOriginalWidth(self): + return self._originalWidth + + def GetOriginalHeight(self): + return self._originalHeight + + def SetOriginalWidth(self, w): + self._originalWidth = w + + def SetOriginalHeight(self, h): + self._originalHeight = h + + def CalculateBoundingBox(self): + # Calculate bounding box at construction (and presumably resize) time + left = 10000 + right=-10000 + top = 10000 + bottom=-10000 + + for point in self._points: + if point.xright: + right = point.x + + if point.ybottom: + bottom = point.y + + self._boundWidth = right - left + self._boundHeight = bottom - top + + def CalculatePolygonCentre(self): + """Recalculates the centre of the polygon, and + readjusts the point offsets accordingly. + Necessary since the centre of the polygon + is expected to be the real centre of the bounding + box. + """ + left = 10000 + right=-10000 + top = 10000 + bottom=-10000 + + for point in self._points: + if point.xright: + right = point.x + + if point.ybottom: + bottom = point.y + + bwidth = right - left + bheight = bottom - top + + newCentreX = left + bwidth / 2 + newCentreY = top + bheight / 2 + + for point in self._points: + point.x -= newCentreX + point.y -= newCentreY + self._xpos += newCentreX + self._ypos += newCentreY + + def HitTest(self, x, y): + # Imagine four lines radiating from this point. If all of these lines + # hit the polygon, we're inside it, otherwise we're not. Obviously + # we'd need more radiating lines to be sure of correct results for + # very strange (concave) shapes. + endPointsX = [x, x + 1000, x, x - 1000] + endPointsY = [y - 1000, y, y + 1000, y] + + xpoints = [] + ypoints = [] + + for point in self._points: + xpoints.append(point.x + self._xpos) + ypoints.append(point.y + self._ypos) + + # We assume it's inside the polygon UNLESS one or more + # lines don't hit the outline. + isContained = True + + for i in range(4): + if not PolylineHitTest(xpoints, ypoints, x, y, endPointsX[i], endPointsY[i]): + isContained = False + + if not isContained: + return False + + nearest_attachment = 0 + + # If a hit, check the attachment points within the object + nearest = 999999 + + for i in range(self.GetNumberOfAttachments()): + e = self.GetAttachmentPositionEdge(i) + if e: + xp, yp = e + l = sqrt((xp - x) * (xp - x) + (yp - y) * (yp - y)) + if l= len(self._points) - 1: + self._points.append(point) + else: + self._points.insert(pos + 1, point) + + self.UpdateOriginalPoints() + + if self._selected: + self.DeleteControlPoints() + self.MakeControlPoints() + + def DeletePolygonPoint(self, pos): + """Delete the given control point.""" + if posy1 and point.y>0: + return point.x + self._xpos, point.y + self._ypos + elif y2maxN: + maxN = point._id + return maxN + 1 + + def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None): + if self._attachmentMode == ATTACHMENT_MODE_EDGE and self._points and attachment= 0 and attachment= right2) and (bottom1 >= bottom2)) + + + +class ShapeCanvas(wx.ScrolledWindow): + def __init__(self, parent = None, id=-1, pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.BORDER, name="ShapeCanvas"): + wx.ScrolledWindow.__init__(self, parent, id, pos, size, style, name) + + self._shapeDiagram = None + self._dragState = NoDragging + self._draggedShape = None + self._oldDragX = 0 + self._oldDragY = 0 + self._firstDragX = 0 + self._firstDragY = 0 + self._checkTolerance = True + + wx.EVT_PAINT(self, self.OnPaint) + wx.EVT_MOUSE_EVENTS(self, self.OnMouseEvent) + + def SetDiagram(self, diag): + self._shapeDiagram = diag + + def GetDiagram(self): + return self._shapeDiagram + + def OnPaint(self, evt): + dc = wx.PaintDC(self) + self.PrepareDC(dc) + + dc.SetBackground(wx.Brush(self.GetBackgroundColour(), wx.SOLID)) + dc.Clear() + + if self.GetDiagram(): + self.GetDiagram().Redraw(dc) + + def OnMouseEvent(self, evt): + dc = wx.ClientDC(self) + self.PrepareDC(dc) + + x, y = evt.GetLogicalPosition(dc) + + keys = 0 + if evt.ShiftDown(): + keys |= KEY_SHIFT + if evt.ControlDown(): + keys |= KEY_CTRL + + dragging = evt.Dragging() + + # Check if we're within the tolerance for mouse movements. + # If we're very close to the position we started dragging + # from, this may not be an intentional drag at all. + if dragging: + dx = abs(dc.LogicalToDeviceX(x-self._firstDragX)) + dy = abs(dc.LogicalToDeviceY(y-self._firstDragY)) + if self._checkTolerance and (dx <= self.GetDiagram().GetMouseTolerance()) and (dy <= self.GetDiagram().GetMouseTolerance()): + return + # If we've ignored the tolerance once, then ALWAYS ignore + # tolerance in this drag, even if we come back within + # the tolerance range. + self._checkTolerance = False + + # Dragging - note that the effect of dragging is left entirely up + # to the object, so no movement is done unless explicitly done by + # object. + if dragging and self._draggedShape and self._dragState == StartDraggingLeft: + self._dragState = ContinueDraggingLeft + + # If the object isn't m_draggable, transfer message to canvas + if self._draggedShape.Draggable(): + self._draggedShape.GetEventHandler().OnBeginDragLeft(x, y, keys, self._draggedAttachment) + else: + self._draggedShape = None + self.OnBeginDragLeft(x, y, keys) + + self._oldDragX, self._oldDragY = x, y + + elif dragging and self._draggedShape and self._dragState == ContinueDraggingLeft: + # Continue dragging + self._draggedShape.GetEventHandler().OnDragLeft(False, self._oldDragX, self._oldDragY, keys, self._draggedAttachment) + self._draggedShape.GetEventHandler().OnDragLeft(True, x, y, keys, self._draggedAttachment) + self._oldDragX, self._oldDragY = x, y + + elif evt.LeftUp and self._draggedShape and self._dragState == ContinueDraggingLeft: + self._dragState = NoDragging + self._checkTolerance = True + + self._draggedShape.GetEventHandler().OnDragLeft(False, self._oldDragX, self._oldDragY, keys, self._draggedAttachment) + self._draggedShape.GetEventHandler().OnEndDragLeft(x, y, keys, self._draggedAttachment) + self._draggedShape = None + + elif dragging and self._draggedShape and self._dragState == StartDraggingRight: + self._dragState = ContinueDraggingRight + if self._draggedShape.Draggable: + self._draggedShape.GetEventHandler().OnBeginDragRight(x, y, keys, self._draggedAttachment) + else: + self._draggedShape = None + self.OnBeginDragRight(x, y, keys) + self._oldDragX, self._oldDragY = x, y + + elif dragging and self._draggedShape and self._dragState == ContinueDraggingRight: + # Continue dragging + self._draggedShape.GetEventHandler().OnDragRight(False, self._oldDragX, self._oldDragY, keys, self._draggedAttachment) + self._draggedShape.GetEventHandler().OnDragRight(True, x, y, keys, self._draggedAttachment) + self._oldDragX, self._oldDragY = x, y + + elif evt.RightUp() and self._draggedShape and self._dragState == ContinueDraggingRight: + self._dragState = NoDragging + self._checkTolerance = True + + self._draggedShape.GetEventHandler().OnDragRight(False, self._oldDragX, self._oldDragY, keys, self._draggedAttachment) + self._draggedShape.GetEventHandler().OnEndDragRight(x, y, keys, self._draggedAttachment) + self._draggedShape = None + + # All following events sent to canvas, not object + elif dragging and not self._draggedShape and self._dragState == StartDraggingLeft: + self._dragState = ContinueDraggingLeft + self.OnBeginDragLeft(x, y, keys) + self._oldDragX, self._oldDragY = x, y + + elif dragging and not self._draggedShape and self._dragState == ContinueDraggingLeft: + # Continue dragging + self.OnDragLeft(False, self._oldDragX, self._oldDragY, keys) + self.OnDragLeft(True, x, y, keys) + self._oldDragX, self._oldDragY = x, y + + elif evt.LeftUp() and not self._draggedShape and self._dragState == ContinueDraggingLeft: + self._dragState = NoDragging + self._checkTolerance = True + + self.OnDragLeft(False, self._oldDragX, self._oldDragY, keys) + self.OnEndDragLeft(x, y, keys) + self._draggedShape = None + + elif dragging and not self._draggedShape and self._dragState == StartDraggingRight: + self._dragState = ContinueDraggingRight + self.OnBeginDragRight(x, y, keys) + self._oldDragX, self._oldDragY = x, y + + elif dragging and not self._draggedShape and self._dragState == ContinueDraggingRight: + # Continue dragging + self.OnDragRight(False, self._oldDragX, self._oldDragY, keys) + self.OnDragRight(True, x, y, keys) + self._oldDragX, self._oldDragY = x, y + + elif evt.RightUp() and not self._draggedShape and self._dragState == ContinueDraggingRight: + self._dragState = NoDragging + self._checkTolerance = True + + self.OnDragRight(False, self._oldDragX, self._oldDragY, keys) + self.OnEndDragRight(x, y, keys) + self._draggedShape = None + + # Non-dragging events + elif evt.IsButton(): + self._checkTolerance = True + + # Find the nearest object + attachment = 0 + + nearest_object, attachment = self.FindShape(x, y) + if nearest_object: # Object event + if evt.LeftDown(): + self._draggedShape = nearest_object + self._draggedAttachment = attachment + self._dragState = StartDraggingLeft + self._firstDragX = x + self._firstDragY = y + + elif evt.LeftUp(): + # N.B. Only register a click if the same object was + # identified for down *and* up. + if nearest_object == self._draggedShape: + nearest_object.GetEventHandler().OnLeftClick(x, y, keys, attachment) + self._draggedShape = None + self._dragState = NoDragging + + elif evt.LeftDClick(): + nearest_object.GetEventHandler().OnLeftDoubleClick(x, y, keys, attachment) + self._draggedShape = None + self._dragState = NoDragging + + elif evt.RightDown(): + self._draggedShape = nearest_object + self._draggedAttachment = attachment + self._dragState = StartDraggingRight + self._firstDragX = x + self._firstDragY = y + + elif evt.RightUp(): + if nearest_object == self._draggedShape: + nearest_object.GetEventHandler().OnRightClick(x, y, keys, attachment) + self._draggedShape = None + self._dragState = NoDragging + + else: # Canvas event + if evt.LeftDown(): + self._draggedShape = None + self._dragState = StartDraggingLeft + self._firstDragX = x + self._firstDragY = y + + elif evt.LeftUp(): + self.OnLeftClick(x, y, keys) + self._draggedShape = None + self._dragState = NoDragging + + elif evt.RightDown(): + self._draggedShape = None + self._dragState = StartDraggingRight + self._firstDragX = x + self._firstDragY = y + + elif evt.RightUp(): + self.OnRightClick(x, y, keys) + self._draggedShape = None + self._dragState = NoDragging + + def FindShape(self, x, y, info = None, notObject = None): + nearest = 100000.0 + nearest_attachment = 0 + nearest_object = None + + # Go backward through the object list, since we want: + # (a) to have the control points drawn LAST to overlay + # the other objects + # (b) to find the control points FIRST if they exist + + for object in self.GetDiagram().GetShapeList()[::-1]: + # First pass for lines, which might be inside a container, so we + # want lines to take priority over containers. This first loop + # could fail if we clickout side a line, so then we'll + # try other shapes. + if object.IsShown() and \ + isinstance(object, LineShape) and \ + object.HitTest(x, y) and \ + ((info == None) or isinstance(object, info)) and \ + (not notObject or not notObject.HasDescendant(object)): + temp_attachment, dist = object.HitTest(x, y) + # A line is trickier to spot than a normal object. + # For a line, since it's the diagonal of the box + # we use for the hit test, we may have several + # lines in the box and therefore we need to be able + # to specify the nearest point to the centre of the line + # as our hit criterion, to give the user some room for + # manouevre. + if dist" % (self.__class__.__module__, self.__class__.__name__) + + def SetSpacing(self, x, y): + """Sets the horizontal and vertical spacing for the constraint.""" + self._xSpacing = x + self._ySpacing = y + + def Equals(self, a, b): + """Return TRUE if x and y are approximately equal (for the purposes + of evaluating the constraint). + """ + marg = 0.5 + + return b <= a + marg and b >= a-marg + + def Evaluate(self): + """Evaluate this constraint and return TRUE if anything changed.""" + maxWidth, maxHeight = self._constraintingObject.GetBoundingBoxMax() + minWidth, minHeight = self._constraintingObject.GetBoundingBoxMin() + x = self._constraintingObject.GetX() + y = self._constraintingObject.GetY() + + dc = wx.ClientDC(self._constraintingObject.GetCanvas()) + self._constraintingObject.GetCanvas().PrepareDC(dc) + + if self._constraintType == CONSTRAINT_CENTRED_VERTICALLY: + n = len(self._constrainedObjects) + totalObjectHeight = 0.0 + for constrainedObject in self._constrainedObjects: + width2, height2 = constrainedObject.GetBoundingBoxMax() + totalObjectHeight += height2 + + # Check if within the constraining object... + if totalObjectHeight + (n + 1) * self._ySpacing <= minHeight: + spacingY = (minHeight-totalObjectHeight) / (n + 1) + startY = y-minHeight / 2 + else: # Otherwise, use default spacing + spacingY = self._ySpacing + startY = y-(totalObjectHeight + (n + 1) * spacingY) / 2 + + # Now position the objects + changed = False + for constrainedObject in self._constrainedObjects: + width2, height2 = constrainedObject.GetBoundingBoxMax() + startY += spacingY + height2 / 2 + if not self.Equals(startY, constrainedObject.GetY()): + constrainedObject.Move(dc, constrainedObject.GetX(), startY, False) + changed = True + startY += height2 / 2 + return changed + elif self._constraintType == CONSTRAINT_CENTRED_HORIZONTALLY: + n = len(self._constrainedObjects) + totalObjectWidth = 0.0 + for constrainedObject in self._constrainedObjects: + width2, height2 = constrainedObject.GetBoundingBoxMax() + totalObjectWidth += width2 + + # Check if within the constraining object... + if totalObjectWidth + (n + 1) * self._xSpacingmaxX: + maxX = child.GetX() + w / 2 + if child.GetX()-w / 2maxY: + maxY = child.GetY() + h / 2 + if child.GetY()-h / 2= x2 or x >= dx2: + success = False + # Try it out first... + elif not division.ResizeAdjoining(DIVISION_SIDE_LEFT, x, True): + success = False + else: + division.ResizeAdjoining(DIVISION_SIDE_LEFT, x, False) + elif division.GetHandleSide() == DIVISION_SIDE_TOP: + if y <= y1 or y >= y2 or y >= dy2: + success = False + elif not division.ResizeAdjoining(DIVISION_SIDE_TOP, y, True): + success = False + else: + division.ResizingAdjoining(DIVISION_SIDE_TOP, y, False) + elif division.GetHandleSide() == DIVISION_SIDE_RIGHT: + if x <= x1 or x >= x2 or x <= dx1: + success = False + elif not division.ResizeAdjoining(DIVISION_SIDE_RIGHT, x, True): + success = False + else: + division.ResizeAdjoining(DIVISION_SIDE_RIGHT, x, False) + elif division.GetHandleSide() == DIVISION_SIDE_BOTTOM: + if y <= y1 or y >= y2 or y <= dy1: + success = False + elif not division.ResizeAdjoining(DIVISION_SIDE_BOTTOM, y, True): + success = False + else: + division.ResizeAdjoining(DIVISION_SIDE_BOTTOM, y, False) + + if not success: + division.SetSize(originalW, originalH) + division.Move(dc, originalX, originalY) + + divisionParent.Draw(dc) + division.GetEventHandler().OnDrawControlPoints(dc) + + + +DIVISION_MENU_SPLIT_HORIZONTALLY =1 +DIVISION_MENU_SPLIT_VERTICALLY =2 +DIVISION_MENU_EDIT_LEFT_EDGE =3 +DIVISION_MENU_EDIT_TOP_EDGE =4 +DIVISION_MENU_EDIT_RIGHT_EDGE =5 +DIVISION_MENU_EDIT_BOTTOM_EDGE =6 +DIVISION_MENU_DELETE_ALL =7 + + + +class PopupDivisionMenu(wx.Menu): + def __init__(self): + wx.Menu.__init__(self) + self.Append(DIVISION_MENU_SPLIT_HORIZONTALLY,"Split horizontally") + self.Append(DIVISION_MENU_SPLIT_VERTICALLY,"Split vertically") + self.AppendSeparator() + self.Append(DIVISION_MENU_EDIT_LEFT_EDGE,"Edit left edge") + self.Append(DIVISION_MENU_EDIT_TOP_EDGE,"Edit top edge") + + wx.EVT_MENU_RANGE(self, DIVISION_MENU_SPLIT_HORIZONTALLY, DIVISION_MENU_EDIT_BOTTOM_EDGE, self.OnMenu) + + def SetClientData(self, data): + self._clientData = data + + def GetClientData(self): + return self._clientData + + def OnMenu(self, event): + division = self.GetClientData() + if event.GetId() == DIVISION_MENU_SPLIT_HORIZONTALLY: + division.Divide(wx.HORIZONTAL) + elif event.GetId() == DIVISION_MENU_SPLIT_VERTICALLY: + division.Divide(wx.VERTICAL) + elif event.GetId() == DIVISION_MENU_EDIT_LEFT_EDGE: + division.EditEdge(DIVISION_SIDE_LEFT) + elif event.GetId() == DIVISION_MENU_EDIT_TOP_EDGE: + division.EditEdge(DIVISION_SIDE_TOP) + + + +class DivisionShape(CompositeShape): + """A division shape is like a composite in that it can contain further + objects, but is used exclusively to divide another shape into regions, + or divisions. A wxDivisionShape is never free-standing. + + Derived from: + wxCompositeShape + """ + def __init__(self): + CompositeShape.__init__(self) + self.SetSensitivityFilter(OP_CLICK_LEFT | OP_CLICK_RIGHT | OP_DRAG_RIGHT) + self.SetCentreResize(False) + self.SetAttachmentMode(True) + self._leftSide = None + self._rightSide = None + self._topSide = None + self._bottomSide = None + self._handleSide = DIVISION_SIDE_NONE + self._leftSidePen = wx.BLACK_PEN + self._topSidePen = wx.BLACK_PEN + self._leftSideColour="BLACK" + self._topSideColour="BLACK" + self._leftSideStyle="Solid" + self._topSideStyle="Solid" + self.ClearRegions() + + def SetLeftSide(self, shape): + """Set the the division on the left side of this division.""" + self._leftSide = shape + + def SetTopSide(self, shape): + """Set the the division on the top side of this division.""" + self._topSide = shape + + def SetRightSide(self, shape): + """Set the the division on the right side of this division.""" + self._rightSide = shape + + def SetBottomSide(self, shape): + """Set the the division on the bottom side of this division.""" + self._bottomSide = shape + + def GetLeftSide(self): + """Return the division on the left side of this division.""" + return self._leftSide + + def GetTopSide(self): + """Return the division on the top side of this division.""" + return self._topSide + + def GetRightSide(self): + """Return the division on the right side of this division.""" + return self._rightSide + + def GetBottomSide(self): + """Return the division on the bottom side of this division.""" + return self._bottomSide + + def SetHandleSide(self, side): + """Sets the side which the handle appears on. + + Either DIVISION_SIDE_LEFT or DIVISION_SIDE_TOP. + """ + self._handleSide = side + + def GetHandleSide(self): + """Return the side which the handle appears on.""" + return self._handleSide + + def SetLeftSidePen(self, pen): + """Set the colour for drawing the left side of the division.""" + self._leftSidePen = pen + + def SetTopSidePen(self, pen): + """Set the colour for drawing the top side of the division.""" + self._topSidePen = pen + + def GetLeftSidePen(self): + """Return the pen used for drawing the left side of the division.""" + return self._leftSidePen + + def GetTopSidePen(self): + """Return the pen used for drawing the top side of the division.""" + return self._topSidePen + + def GetLeftSideColour(self): + """Return the colour used for drawing the left side of the division.""" + return self._leftSideColour + + def GetTopSideColour(self): + """Return the colour used for drawing the top side of the division.""" + return self._topSideColour + + def SetLeftSideColour(self, colour): + """Set the colour for drawing the left side of the division.""" + self._leftSideColour = colour + + def SetTopSideColour(self, colour): + """Set the colour for drawing the top side of the division.""" + self._topSideColour = colour + + def GetLeftSideStyle(self): + """Return the style used for the left side of the division.""" + return self._leftSideStyle + + def GetTopSideStyle(self): + """Return the style used for the top side of the division.""" + return self._topSideStyle + + def SetLeftSideStyle(self, style): + self._leftSideStyle = style + + def SetTopSideStyle(self, style): + self._lefttopStyle = style + + def OnDraw(self, dc): + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.SetBackgroundMode(wx.TRANSPARENT) + + x1 = self.GetX()-self.GetWidth() / 2 + y1 = self.GetY()-self.GetHeight() / 2 + x2 = self.GetX() + self.GetWidth() / 2 + y2 = self.GetY() + self.GetHeight() / 2 + + # Should subtract 1 pixel if drawing under Windows + if sys.platform[:3]=="win": + y2 -= 1 + + if self._leftSide: + dc.SetPen(self._leftSidePen) + dc.DrawLine(x1, y2, x1, y1) + + if self._topSide: + dc.SetPen(self._topSidePen) + dc.DrawLine(x1, y1, x2, y1) + + # For testing purposes, draw a rectangle so we know + # how big the division is. + #dc.SetBrush(wx.RED_BRUSH) + #dc.DrawRectangle(x1, y1, self.GetWidth(), self.GetHeight()) + + def OnDrawContents(self, dc): + CompositeShape.OnDrawContents(self, dc) + + def OnMovePre(self, dc, x, y, oldx, oldy, display = True): + diffX = x-oldx + diffY = y-oldy + for object in self._children: + object.Erase(dc) + object.Move(dc, object.GetX() + diffX, object.GetY() + diffY, display) + return True + + def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0): + if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT: + if self._parent: + hit = self._parent.HitTest(x, y) + if hit: + attachment, dist = hit + self._parent.GetEventHandler().OnDragLeft(draw, x, y, keys, attachment) + return + Shape.OnDragLeft(self, draw, x, y, keys, attachment) + + def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0): + if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT: + if self._parent: + hit = self._parent.HitTest(x, y) + if hit: + attachment, dist = hit + self._parent.GetEventHandler().OnBeginDragLeft(x, y, keys, attachment) + return + Shape.OnBeginDragLeft(x, y, keys, attachment) + + def OnEndDragLeft(self, x, y, keys = 0, attachment = 0): + if self._canvas.HasCapture(): + self._canvas.ReleaseMouse() + if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT: + if self._parent: + hit = self._parent.HitTest(x, y) + if hit: + attachment, dist = hit + self._parent.GetEventHandler().OnEndDragLeft(x, y, keys, attachment) + return + + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + + dc.SetLogicalFunction(wx.COPY) + + self._xpos, self._ypos = self._canvas.Snap(self._xpos, self._ypos) + self.GetEventHandler().OnMovePre(dc, x, y, self._oldX, self._oldY) + + self.ResetControlPoints() + self.Draw(dc) + self.MoveLinks(dc) + self.GetEventHandler().OnDrawControlPoints(dc) + + if self._canvas and not self._canvas.GetQuickEditMode(): + self._canvas.Redraw(dc) + + def SetSize(self, w, h, recursive = True): + self._width = w + self._height = h + RectangleShape.SetSize(self, w, h, recursive) + + def CalculateSize(self): + pass + + # Experimental + def OnRightClick(self, x, y, keys = 0, attachment = 0): + if keys & KEY_CTRL: + self.PopupMenu(x, y) + else: + if self._parent: + hit = self._parent.HitTest(x, y) + if hit: + attachment, dist = hit + self._parent.GetEventHandler().OnRightClick(x, y, keys, attachment) + + # Divide wx.HORIZONTALly or wx.VERTICALly + def Divide(self, direction): + """Divide this division into two further divisions, + horizontally (direction is wxHORIZONTAL) or + vertically (direction is wxVERTICAL). + """ + # Calculate existing top-left, bottom-right + x1 = self.GetX()-self.GetWidth() / 2 + y1 = self.GetY()-self.GetHeight() / 2 + + compositeParent = self.GetParent() + oldWidth = self.GetWidth() + oldHeight = self.GetHeight() + if self.Selected(): + self.Select(False) + + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + + if direction == wx.VERTICAL: + # Dividing vertically means notionally putting a horizontal + # line through it. + # Break existing piece into two. + newXPos1 = self.GetX() + newYPos1 = y1 + self.GetHeight() / 4 + newXPos2 = self.GetX() + newYPos2 = y1 + 3 * self.GetHeight() / 4 + newDivision = compositeParent.OnCreateDivision() + newDivision.Show(True) + + self.Erase(dc) + + # Anything adjoining the bottom of this division now adjoins the + # bottom of the new division. + for obj in compositeParent.GetDivisions(): + if obj.GetTopSide() == self: + obj.SetTopSide(newDivision) + + newDivision.SetTopSide(self) + newDivision.SetBottomSide(self._bottomSide) + newDivision.SetLeftSide(self._leftSide) + newDivision.SetRightSide(self._rightSide) + self._bottomSide = newDivision + + compositeParent.GetDivisions().append(newDivision) + + # CHANGE: Need to insert this division at start of divisions in the + # object list, because e.g.: + # 1) Add division + # 2) Add contained object + # 3) Add division + # Division is now receiving mouse events _before_ the contained + # object, because it was added last (on top of all others) + + # Add after the image that visualizes the container + compositeParent.AddChild(newDivision, compositeParent.FindContainerImage()) + + self._handleSide = DIVISION_SIDE_BOTTOM + newDivision.SetHandleSide(DIVISION_SIDE_TOP) + + self.SetSize(oldWidth, oldHeight / 2) + self.Move(dc, newXPos1, newYPos1) + + newDivision.SetSize(oldWidth, oldHeight / 2) + newDivision.Move(dc, newXPos2, newYPos2) + else: + # Dividing horizontally means notionally putting a vertical line + # through it. + # Break existing piece into two. + newXPos1 = x1 + self.GetWidth() / 4 + newYPos1 = self.GetY() + newXPos2 = x1 + 3 * self.GetWidth() / 4 + newYPos2 = self.GetY() + newDivision = compositeParent.OnCreateDivision() + newDivision.Show(True) + + self.Erase(dc) + + # Anything adjoining the left of this division now adjoins the + # left of the new division. + for obj in compositeParent.GetDivisions(): + if obj.GetLeftSide() == self: + obj.SetLeftSide(newDivision) + + newDivision.SetTopSide(self._topSide) + newDivision.SetBottomSide(self._bottomSide) + newDivision.SetLeftSide(self) + newDivision.SetRightSide(self._rightSide) + self._rightSide = newDivision + + compositeParent.GetDivisions().append(newDivision) + compositeParent.AddChild(newDivision, compositeParent.FindContainerImage()) + + self._handleSide = DIVISION_SIDE_RIGHT + newDivision.SetHandleSide(DIVISION_SIDE_LEFT) + + self.SetSize(oldWidth / 2, oldHeight) + self.Move(dc, newXPos1, newYPos1) + + newDivision.SetSize(oldWidth / 2, oldHeight) + newDivision.Move(dc, newXPos2, newYPos2) + + if compositeParent.Selected(): + compositeParent.DeleteControlPoints(dc) + compositeParent.MakeControlPoints() + compositeParent.MakeMandatoryControlPoints() + + compositeParent.Draw(dc) + return True + + def MakeControlPoints(self): + self.MakeMandatoryControlPoints() + + def MakeMandatoryControlPoints(self): + maxX, maxY = self.GetBoundingBoxMax() + x = y = 0.0 + direction = 0 + + if self._handleSide == DIVISION_SIDE_LEFT: + x=-maxX / 2 + direction = CONTROL_POINT_HORIZONTAL + elif self._handleSide == DIVISION_SIDE_TOP: + y=-maxY / 2 + direction = CONTROL_POINT_VERTICAL + elif self._handleSide == DIVISION_SIDE_RIGHT: + x = maxX / 2 + direction = CONTROL_POINT_HORIZONTAL + elif self._handleSide == DIVISION_SIDE_BOTTOM: + y = maxY / 2 + direction = CONTROL_POINT_VERTICAL + + if self._handleSide != DIVISION_SIDE_NONE: + control = DivisionControlPoint(self._canvas, self, CONTROL_POINT_SIZE, x, y, direction) + self._canvas.AddShape(control) + self._controlPoints.append(control) + + def ResetControlPoints(self): + self.ResetMandatoryControlPoints() + + def ResetMandatoryControlPoints(self): + if not self._controlPoints: + return + + maxX, maxY = self.GetBoundingBoxMax() + + node = self._controlPoints[0] + + if self._handleSide == DIVISION_SIDE_LEFT and node: + node._xoffset=-maxX / 2 + node._yoffset = 0.0 + + if self._handleSide == DIVISION_SIDE_TOP and node: + node._xoffset = 0.0 + node._yoffset=-maxY / 2 + + if self._handleSide == DIVISION_SIDE_RIGHT and node: + node._xoffset = maxX / 2 + node._yoffset = 0.0 + + if self._handleSide == DIVISION_SIDE_BOTTOM and node: + node._xoffset = 0.0 + node._yoffset = maxY / 2 + + def AdjustLeft(self, left, test): + """Adjust a side. + + Returns FALSE if it's not physically possible to adjust it to + this point. + """ + x2 = self.GetX() + self.GetWidth() / 2 + + if left >= x2: + return False + + if test: + return True + + newW = x2-left + newX = left + newW / 2 + self.SetSize(newW, self.GetHeight()) + + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + + self.Move(dc, newX, self.GetY()) + return True + + def AdjustTop(self, top, test): + """Adjust a side. + + Returns FALSE if it's not physically possible to adjust it to + this point. + """ + y2 = self.GetY() + self.GetHeight() / 2 + + if top >= y2: + return False + + if test: + return True + + newH = y2-top + newY = top + newH / 2 + self.SetSize(self.GetWidth(), newH) + + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + + self.Move(dc, self.GetX(), newY) + return True + + def AdjustRight(self, right, test): + """Adjust a side. + + Returns FALSE if it's not physically possible to adjust it to + this point. + """ + x1 = self.GetX()-self.GetWidth() / 2 + + if right <= x1: + return False + + if test: + return True + + newW = right-x1 + newX = x1 + newW / 2 + self.SetSize(newW, self.GetHeight()) + + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + + self.Move(dc, newX, self.GetY()) + return True + + def AdjustTop(self, top, test): + """Adjust a side. + + Returns FALSE if it's not physically possible to adjust it to + this point. + """ + y1 = self.GetY()-self.GetHeight() / 2 + + if bottom <= y1: + return False + + if test: + return True + + newH = bottom-y1 + newY = y1 + newH / 2 + self.SetSize(self.GetWidth(), newH) + + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + + self.Move(dc, self.GetX(), newY) + return True + + # Resize adjoining divisions. + + # Behaviour should be as follows: + # If right edge moves, find all objects whose left edge + # adjoins this object, and move left edge accordingly. + # If left..., move ... right. + # If top..., move ... bottom. + # If bottom..., move top. + # If size goes to zero or end position is other side of start position, + # resize to original size and return. + # + def ResizeAdjoining(self, side, newPos, test): + """Resize adjoining divisions at the given side. + + If test is TRUE, just see whether it's possible for each adjoining + region, returning FALSE if it's not. + + side can be one of: + + * DIVISION_SIDE_NONE + * DIVISION_SIDE_LEFT + * DIVISION_SIDE_TOP + * DIVISION_SIDE_RIGHT + * DIVISION_SIDE_BOTTOM + """ + divisionParent = self.GetParent() + for division in divisionParent.GetDivisions(): + if side == DIVISION_SIDE_LEFT: + if division._rightSide == self: + success = division.AdjustRight(newPos, test) + if not success and test: + return false + elif side == DIVISION_SIDE_TOP: + if division._bottomSide == self: + success = division.AdjustBottom(newPos, test) + if not success and test: + return False + elif side == DIVISION_SIDE_RIGHT: + if division._leftSide == self: + success = division.AdjustLeft(newPos, test) + if not success and test: + return False + elif side == DIVISION_SIDE_BOTTOM: + if division._topSide == self: + success = division.AdjustTop(newPos, test) + if not success and test: + return False + return True + + def EditEdge(self, side): + print "EditEdge() not implemented." + + def PopupMenu(self, x, y): + menu = PopupDivisionMenu() + menu.SetClientData(self) + if self._leftSide: + menu.Enable(DIVISION_MENU_EDIT_LEFT_EDGE, True) + else: + menu.Enable(DIVISION_MENU_EDIT_LEFT_EDGE, False) + if self._topSide: + menu.Enable(DIVISION_MENU_EDIT_TOP_EDGE, True) + else: + menu.Enable(DIVISION_MENU_EDIT_TOP_EDGE, False) + + x1, y1 = self._canvas.GetViewStart() + unit_x, unit_y = self._canvas.GetScrollPixelsPerUnit() + + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + + mouse_x = dc.LogicalToDeviceX(x-x1 * unit_x) + mouse_y = dc.LogicalToDeviceY(y-y1 * unit_y) + + self._canvas.PopupMenu(menu, (mouse_x, mouse_y)) + + diff --git a/wxPython/wx/lib/ogl/diagram.py b/wxPython/wx/lib/ogl/diagram.py new file mode 100644 index 0000000000..86c0dd9e02 --- /dev/null +++ b/wxPython/wx/lib/ogl/diagram.py @@ -0,0 +1,160 @@ +# -*- coding: iso-8859-1 -*- +#---------------------------------------------------------------------------- +# Name: diagram.py +# Purpose: Diagram class +# +# Author: Pierre Hjälm (from C++ original by Julian Smart) +# +# Created: 20040508 +# RCS-ID: +# Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart +# Licence: wxWindows license +#---------------------------------------------------------------------------- + +from __future__ import division + +import wx + +DEFAULT_MOUSE_TOLERANCE = 3 + + + +class Diagram(object): + """Encapsulates an entire diagram, with methods for drawing. A diagram has + an associated ShapeCanvas. + + Derived from: + Object + """ + def __init__(self): + self._diagramCanvas = None + self._quickEditMode = False + self._snapToGrid = True + self._gridSpacing = 5.0 + self._shapeList = [] + self._mouseTolerance = DEFAULT_MOUSE_TOLERANCE + + def Redraw(self, dc): + """Draw the shapes in the diagram on the specified device context.""" + if self._shapeList: + if self.GetCanvas(): + self.GetCanvas().SetCursor(wx.HOURGLASS_CURSOR) + for object in self._shapeList: + object.Draw(dc) + if self.GetCanvas(): + self.GetCanvas().SetCursor(wx.STANDARD_CURSOR) + + def Clear(self, dc): + """Clear the specified device context.""" + dc.Clear() + + def AddShape(self, object, addAfter = None): + """Adds a shape to the diagram. If addAfter is not None, the shape + will be added after addAfter. + """ + if not object in self._shapeList: + if addAfter: + self._shapeList.insert(self._shapeList.index(addAfter) + 1, object) + else: + self._shapeList.append(object) + + object.SetCanvas(self.GetCanvas()) + + def InsertShape(self, object): + """Insert a shape at the front of the shape list.""" + self._shapeList.insert(0, object) + + def RemoveShape(self, object): + """Remove the shape from the diagram (non-recursively) but do not + delete it. + """ + if object in self._shapeList: + self._shapeList.remove(object) + + def RemoveAllShapes(self): + """Remove all shapes from the diagram but do not delete the shapes.""" + self._shapeList = [] + + def DeleteAllShapes(self): + """Remove and delete all shapes in the diagram.""" + for shape in self._shapeList[:]: + if not shape.GetParent(): + self.RemoveShape(shape) + + def ShowAll(self, show): + """Call Show for each shape in the diagram.""" + for shape in self._shapeList: + shape.Show() + + def DrawOutLine(self, dc, x1, y1, x2, y2): + """Draw an outline rectangle on the current device context.""" + dc.SetPen(wx.Pen(wx.Color(0, 0, 0), 1, wx.DOT)) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + + dc.DrawLines([[x1, y1], [x2, y1], [x2, y2], [x1, y2], [x1, y1]]) + + def RecentreAll(self, dc): + """Make sure all text that should be centred, is centred.""" + for shape in self._shapeList: + shape.Recentre(dc) + + def SetCanvas(self, canvas): + """Set the canvas associated with this diagram.""" + self._diagramCanvas = canvas + + def GetCanvas(self): + """Return the shape canvas associated with this diagram.""" + return self._diagramCanvas + + def FindShape(self, id): + """Return the shape for the given identifier.""" + for shape in self._shapeList: + if shape.GetId() == id: + return shape + return None + + def Snap(self, x, y): + """'Snaps' the coordinate to the nearest grid position, if + snap-to-grid is on.""" + if self._snapToGrid: + return self._gridSpacing * int(x / self._gridSpacing + 0.5), self._gridSpacing * int(y / self._gridSpacing + 0.5) + return x, y + + def GetGridSpacing(self): + """Return the grid spacing.""" + return self._gridSpacing + + def GetSnapToGrid(self): + """Return snap-to-grid mode.""" + return self._snapToGrid + + def SetQuickEditMode(self, mode): + """Set quick-edit-mode on of off. + + In this mode, refreshes are minimized, but the diagram may need + manual refreshing occasionally. + """ + self._quickEditMode = mode + + def GetQuickEditMode(self): + """Return quick edit mode.""" + return self._quickEditMode + + def SetMouseTolerance(self, tolerance): + """Set the tolerance within which a mouse move is ignored. + + The default is 3 pixels. + """ + self._mouseTolerance = tolerance + + def GetMouseTolerance(self): + """Return the tolerance within which a mouse move is ignored.""" + return self._mouseTolerance + + def GetShapeList(self): + """Return the internal shape list.""" + return self._shapeList + + def GetCount(self): + """Return the number of shapes in the diagram.""" + return len(self._shapeList) diff --git a/wxPython/wx/lib/ogl/divided.py b/wxPython/wx/lib/ogl/divided.py new file mode 100644 index 0000000000..73ea006c06 --- /dev/null +++ b/wxPython/wx/lib/ogl/divided.py @@ -0,0 +1,404 @@ +# -*- coding: iso-8859-1 -*- +#---------------------------------------------------------------------------- +# Name: divided.py +# Purpose: DividedShape class +# +# Author: Pierre Hjälm (from C++ original by Julian Smart) +# +# Created: 20040508 +# RCS-ID: +# Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart +# Licence: wxWindows license +#---------------------------------------------------------------------------- + +from __future__ import division + +import sys +import wx + +from basic import ControlPoint, RectangleShape, Shape +from oglmisc import * + + + +class DividedShapeControlPoint(ControlPoint): + def __init__(self, the_canvas, object, region, size, the_m_xoffset, the_m_yoffset, the_type): + ControlPoint.__init__(self, the_canvas, object, size, the_m_xoffset, the_m_yoffset, the_type) + self.regionId = region + + # Implement resizing of divided object division + def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0): + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + + dc.SetLogicalFunction(OGLRBLF) + dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT) + dc.SetPen(dottedPen) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + + dividedObject = self._shape + x1 = dividedObject.GetX()-dividedObject.GetWidth() / 2 + y1 = y + x2 = dividedObject.GetX() + dividedObject.GetWidth() / 2 + y2 = y + + dc.DrawLine(x1, y1, x2, y2) + + def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0): + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + + dc.SetLogicalFunction(OGLRBLF) + dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT) + dc.SetPen(dottedPen) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + + dividedObject = self._shape + + x1 = dividedObject.GetX()-dividedObject.GetWidth() / 2 + y1 = y + x2 = dividedObject.GetX() + dividedObject.GetWidth() / 2 + y2 = y + + dc.DrawLine(x1, y1, x2, y2) + self._canvas.CaptureMouse() + + def OnEndDragLeft(self, x, y, keys = 0, attachment = 0): + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + + dividedObject = self._shape + if not dividedObject.GetRegions()[self.regionId]: + return + + thisRegion = dividedObject.GetRegions()[self.regionId] + nextRegion = None + + dc.SetLogicalFunction(wx.COPY) + + if self._canvas.HasCapture(): + self._canvas.ReleaseMouse() + + # Find the old top and bottom of this region, + # and calculate the new proportion for this region + # if legal. + currentY = dividedObject.GetY()-dividedObject.GetHeight() / 2 + maxY = dividedObject.GetY() + dividedObject.GetHeight() / 2 + + # Save values + theRegionTop = 0 + nextRegionBottom = 0 + + for i in range(len(dividedObject.GetRegions())): + region = dividedObject.GetRegions()[i] + proportion = region._regionProportionY + yy = currentY + dividedObject.GetHeight() * proportion + actualY = min(maxY, yy) + + if region == thisRegion: + thisRegionTop = currentY + + if i + 1= nextRegionBottom: + return + + dividedObject.EraseLinks(dc) + + # Now calculate the new proportions of this region and the next region + thisProportion = (y-thisRegionTop) / dividedObject.GetHeight() + nextProportion = (nextRegionBottom-y) / dividedObject.GetHeight() + + thisRegion.SetProportions(0, thisProportion) + nextRegion.SetProportions(0, nextProportion) + self._yoffset = y-dividedObject.GetY() + + # Now reformat text + for i, region in enumerate(dividedObject.GetRegions()): + if region.GetText(): + s = region.GetText() + dividedObject.FormatText(dc, s, i) + + dividedObject.SetRegionSizes() + dividedObject.Draw(dc) + dividedObject.GetEventHandler().OnMoveLinks(dc) + + + +class DividedShape(RectangleShape): + """A DividedShape is a rectangle with a number of vertical divisions. + Each division may have its text formatted with independent characteristics, + and the size of each division relative to the whole image may be specified. + + Derived from: + RectangleShape + """ + def __init__(self, w, h): + RectangleShape.__init__(self, w, h) + self.ClearRegions() + + def OnDraw(self, dc): + RectangleShape.OnDraw(self, dc) + + def OnDrawContents(self, dc): + if self.GetRegions(): + defaultProportion = 1 / len(self.GetRegions()) + else: + defaultProportion = 0 + currentY = self._ypos-self._height / 2 + maxY = self._ypos + self._height / 2 + + leftX = self._xpos-self._width / 2 + rightX = self._xpos + self._width / 2 + + if self._pen: + dc.SetPen(self._pen) + + dc.SetTextForeground(self._textColour) + + # For efficiency, don't do this under X - doesn't make + # any visible difference for our purposes. + if sys.platform[:3]=="win": + dc.SetTextBackground(self._brush.GetColour()) + + if self.GetDisableLabel(): + return + + xMargin = 2 + yMargin = 2 + + dc.SetBackgroundMode(wx.TRANSPARENT) + + for region in self.GetRegions(): + dc.SetFont(region.GetFont()) + dc.SetTextForeground(region.GetActualColourObject()) + + if region._regionProportionY<0: + proportion = defaultProportion + else: + proportion = region._regionProportionY + + y = currentY + self._height * proportion + actualY = min(maxY, y) + + centreX = self._xpos + centreY = currentY + (actualY-currentY) / 2 + + DrawFormattedText(dc, region._formattedText, centreX, centreY, self._width-2 * xMargin, actualY-currentY-2 * yMargin, region._formatMode) + + if y <= maxY and region != self.GetRegions()[-1]: + regionPen = region.GetActualPen() + if regionPen: + dc.SetPen(regionPen) + dc.DrawLine(leftX, y, rightX, y) + + currentY = actualY + + def SetSize(self, w, h, recursive = True): + self.SetAttachmentSize(w, h) + self._width = w + self._height = h + self.SetRegionSizes() + + def SetRegionSizes(self): + """Set all region sizes according to proportions and this object + total size. + """ + if not self.GetRegions(): + return + + if self.GetRegions(): + defaultProportion = 1 / len(self.GetRegions()) + else: + defaultProportion = 0 + currentY = self._ypos-self._height / 2 + maxY = self._ypos + self._height / 2 + + for region in self.GetRegions(): + if region._regionProportionY <= 0: + proportion = defaultProportion + else: + proportion = region._regionProportionY + + sizeY = proportion * self._height + y = currentY + sizeY + actualY = min(maxY, y) + + centreY = currentY + (actualY-currentY) / 2 + + region.SetSize(self._width, sizeY) + region.SetPosition(0, centreY-self._ypos) + + currentY = actualY + + # Attachment points correspond to regions in the divided box + def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None): + totalNumberAttachments = len(self.GetRegions()) * 2 + 2 + if self.GetAttachmentMode() == ATTACHMENT_MODE_NONE or attachment >= totalNumberAttachments: + return Shape.GetAttachmentPosition(self, attachment, nth, no_arcs) + + n = len(self.GetRegions()) + isEnd = line and line.IsEnd(self) + + left = self._xpos-self._width / 2 + right = self._xpos + self._width / 2 + top = self._ypos-self._height / 2 + bottom = self._ypos + self._height / 2 + + # Zero is top, n + 1 is bottom + if attachment == 0: + y = top + if self._spaceAttachments: + if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE: + # Align line according to the next handle along + point = line.GetNextControlPoint(self) + if point.xright: + x = right + else: + x = point.x + else: + x = left + (nth + 1) * self._width / (no_arcs + 1) + else: + x = self._xpos + elif attachment == n + 1: + y = bottom + if self._spaceAttachments: + if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE: + # Align line according to the next handle along + point = line.GetNextControlPoint(self) + if point.xright: + x = right + else: + x = point.x + else: + x = left + (nth + 1) * self._width / (no_arcs + 1) + else: + x = self._xpos + else: # Left or right + isLeft = not attachment<(n + 1) + if isLeft: + i = totalNumberAttachments-attachment-1 + else: + i = attachment-1 + + region = self.GetRegions()[i] + if region: + if isLeft: + x = left + else: + x = right + + # Calculate top and bottom of region + top = self._ypos + region._y-region._height / 2 + bottom = self._ypos + region._y + region._height / 2 + + # Assuming we can trust the absolute size and + # position of these regions + if self._spaceAttachments: + if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE: + # Align line according to the next handle along + point = line.GetNextControlPoint(self) + if point.ytop: + y = top + else: + y = point.y + else: + y = top + (nth + 1) * region._height / (no_arcs + 1) + else: + y = self._ypos + region._y + else: + return False + return x, y + + def GetNumberOfAttachments(self): + # There are two attachments for each region (left and right), + # plus one on the top and one on the bottom. + n = len(self.GetRegions()) * 2 + 2 + + maxN = n-1 + for point in self._attachmentPoints: + if point._id>maxN: + maxN = point._id + + return maxN + 1 + + def AttachmentIsValid(self, attachment): + totalNumberAttachments = len(self.GetRegions()) * 2 + 2 + if attachment >= totalNumberAttachments: + return Shape.AttachmentIsValid(self, attachment) + else: + return attachment >= 0 + + def MakeControlPoints(self): + RectangleShape.MakeControlPoints(self) + self.MakeMandatoryControlPoints() + + def MakeMandatoryControlPoints(self): + currentY = self.GetY()-self._height / 2 + maxY = self.GetY() + self._height / 2 + + for i, region in enumerate(self.GetRegions()): + proportion = region._regionProportionY + + y = currentY + self._height * proportion + actualY = min(maxY, y) + + if region != self.GetRegions()[-1]: + controlPoint = DividedShapeControlPoint(self._canvas, self, i, CONTROL_POINT_SIZE, 0, actualY-self.GetY(), 0) + self._canvas.AddShape(controlPoint) + self._controlPoints.append(controlPoint) + + currentY = actualY + + def ResetControlPoints(self): + # May only have the region handles, (n - 1) of them + if len(self._controlPoints)>len(self.GetRegions())-1: + RectangleShape.ResetControlPoints(self) + + self.ResetMandatoryControlPoints() + + def ResetMandatoryControlPoints(self): + currentY = self.GetY()-self._height / 2 + maxY = self.GetY() + self._height / 2 + + i = 0 + for controlPoint in self._controlPoints: + if isinstance(controlPoint, DividedShapeControlPoint): + region = self.GetRegions()[i] + proportion = region._regionProportionY + + y = currentY + self._height * proportion + actualY = min(maxY, y) + + controlPoint._xoffset = 0 + controlPoint._yoffset = actualY-self.GetY() + + currentY = actualY + + i += 1 + + def EditRegions(self): + """Edit the region colours and styles. Not implemented.""" + print "EditRegions() is unimplemented" + + def OnRightClick(self, x, y, keys = 0, attachment = 0): + if keys & KEY_CTRL: + self.EditRegions() + else: + RectangleShape.OnRightClick(self, x, y, keys, attachment) diff --git a/wxPython/wx/lib/ogl/lines.py b/wxPython/wx/lib/ogl/lines.py new file mode 100644 index 0000000000..cb02964527 --- /dev/null +++ b/wxPython/wx/lib/ogl/lines.py @@ -0,0 +1,1533 @@ +# -*- coding: iso-8859-1 -*- +#---------------------------------------------------------------------------- +# Name: lines.py +# Purpose: LineShape class +# +# Author: Pierre Hjälm (from C++ original by Julian Smart) +# +# Created: 20040508 +# RCS-ID: +# Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart +# Licence: wxWindows license +#---------------------------------------------------------------------------- + +from __future__ import division + +import sys + +from math import sqrt + +from basic import Shape, ShapeRegion, ControlPoint, RectangleShape +from oglmisc import * + +# Line alignment flags +# Vertical by default +LINE_ALIGNMENT_HORIZ= 1 +LINE_ALIGNMENT_VERT= 0 +LINE_ALIGNMENT_TO_NEXT_HANDLE= 2 +LINE_ALIGNMENT_NONE= 0 + + + +class LineControlPoint(ControlPoint): + def __init__(self, theCanvas = None, object = None, size = 0.0, x = 0.0, y = 0.0, the_type = 0): + ControlPoint.__init__(self, theCanvas, object, size, x, y, the_type) + self._xpos = x + self._ypos = y + self._type = the_type + self._point = None + self._originalPos = None + + def OnDraw(self, dc): + RectangleShape.OnDraw(self, dc) + + # Implement movement of Line point + def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0): + self._shape.GetEventHandler().OnSizingDragLeft(self, draw, x, y, keys, attachment) + + def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0): + self._shape.GetEventHandler().OnSizingBeginDragLeft(self, x, y, keys, attachment) + + def OnEndDragLeft(self, x, y, keys = 0, attachment = 0): + self._shape.GetEventHandler().OnSizingEndDragLeft(self, x, y, keys, attachment) + + + +class ArrowHead(object): + def __init__(self, type = 0, end = 0, size = 0.0, dist = 0.0, name="",mf = None, arrowId=-1): + if isinstance(type, ArrowHead): + pass + else: + self._arrowType = type + self._arrowEnd = end + self._arrowSize = size + self._xOffset = dist + self._yOffset = 0.0 + self._spacing = 5.0 + + self._arrowName = name + self._metaFile = mf + self._id = arrowId + if self._id==-1: + self._id = wx.NewId() + + def _GetType(self): + return self._arrowType + + def GetPosition(self): + return self._arrowEnd + + def SetPosition(self, pos): + self._arrowEnd = pos + + def GetXOffset(self): + return self._xOffset + + def GetYOffset(self): + return self._yOffset + + def GetSpacing(self): + return self._spacing + + def GetSize(self): + return self._arrowSize + + def SetSize(self, size): + self._arrowSize = size + if self._arrowType == ARROW_METAFILE and self._metaFile: + oldWidth = self._metaFile._width + if oldWidth == 0: + return + + scale = size / oldWidth + if scale != 1: + self._metaFile.Scale(scale, scale) + + def GetName(self): + return self._arrowName + + def SetXOffset(self, x): + self._xOffset = x + + def SetYOffset(self, y): + self._yOffset = y + + def GetMetaFile(self): + return self._metaFile + + def GetId(self): + return self._id + + def GetArrowEnd(self): + return self._arrowEnd + + def GetArrowSize(self): + return self._arrowSize + + def SetSpacing(self, sp): + self._spacing = sp + + + +class LabelShape(RectangleShape): + def __init__(self, parent, region, w, h): + RectangleShape.__init__(self, w, h) + self._lineShape = parent + self._shapeRegion = region + self.SetPen(wx.ThePenList.FindOrCreatePen(wx.Colour(0, 0, 0), 1, wx.DOT)) + + def OnDraw(self, dc): + if self._lineShape and not self._lineShape.GetDrawHandles(): + return + + x1 = self._xpos-self._width / 2 + y1 = self._ypos-self._height / 2 + + if self._pen: + if self._pen.GetWidth() == 0: + dc.SetPen(wx.Pen(wx.WHITE, 1, wx.TRANSPARENT)) + else: + dc.SetPen(self._pen) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + + if self._cornerRadius>0: + dc.DrawRoundedRectangle(x1, y1, self._width, self._height, self._cornerRadius) + else: + dc.DrawRectangle(x1, y1, self._width, self._height) + + def OnDrawContents(self, dc): + pass + + def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0): + RectangleShape.OnDragLeft(self, draw, x, y, keys, attachment) + + def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0): + RectangleShape.OnBeginDragLeft(self, x, y, keys, attachment) + + def OnEndDragLeft(self, x, y, keys = 0, attachment = 0): + RectangleShape.OnEndDragLeft(self, x, y, keys, attachment) + + def OnMovePre(self, dc, x, y, old_x, old_y, display): + return self._lineShape.OnLabelMovePre(dc, self, x, y, old_x, old_y, display) + + # Divert left and right clicks to line object + def OnLeftClick(self, x, y, keys = 0, attachment = 0): + self._lineShape.GetEventHandler().OnLeftClick(x, y, keys, attachment) + + def OnRightClick(self, x, y, keys = 0, attachment = 0): + self._lineShape.GetEventHandler().OnRightClick(x, y, keys, attachment) + + + +class LineShape(Shape): + """LineShape may be attached to two nodes; + it may be segmented, in which case a control point is drawn for each joint. + + A wxLineShape may have arrows at the beginning, end and centre. + + Derived from: + Shape + """ + def __init__(self): + Shape.__init__(self) + + self._sensitivity = OP_CLICK_LEFT | OP_CLICK_RIGHT + self._draggable = False + self._attachmentTo = 0 + self._attachmentFrom = 0 + self._from = None + self._to = None + self._erasing = False + self._arrowSpacing = 5.0 + self._ignoreArrowOffsets = False + self._isSpline = False + self._maintainStraightLines = False + self._alignmentStart = 0 + self._alignmentEnd = 0 + + self._lineControlPoints = None + + # Clear any existing regions (created in an earlier constructor) + # and make the three line regions. + self.ClearRegions() + for name in ["Middle","Start","End"]: + newRegion = ShapeRegion() + newRegion.SetName(name) + newRegion.SetSize(150, 50) + self._regions.append(newRegion) + + self._labelObjects = [None, None, None] + self._lineOrientations = [] + self._lineControlPoints = [] + self._arcArrows = [] + + def __del__(self): + if self._lineControlPoints: + self.ClearPointList(self._lineControlPoints) + self._lineControlPoints = [] + for i in range(3): + if self._labelObjects[i]: + self._labelObjects[i].Select(False) + self._labelObjects[i].RemoveFromCanvas(self._canvas) + self._labelObjects = [] + self.ClearArrowsAtPosition(-1) + + def GetFrom(self): + """Return the 'from' object.""" + return self._from + + def GetTo(self): + """Return the 'to' object.""" + return self._to + + def GetAttachmentFrom(self): + """Return the attachment point on the 'from' node.""" + return self._attachmentFrom + + def GetAttachmentTo(self): + """Return the attachment point on the 'to' node.""" + return self._attachmentTo + + def GetLineControlPoints(self): + return self._lineControlPoints + + def SetSpline(self, spline): + """Specifies whether a spline is to be drawn through the control points.""" + self._isSpline = spline + + def IsSpline(self): + """TRUE if a spline is drawn through the control points.""" + return self._isSpline + + def SetAttachmentFrom(self, attach): + """Set the 'from' shape attachment.""" + self._attachmentFrom = attach + + def SetAttachmentTo(self, attach): + """Set the 'to' shape attachment.""" + self._attachmentTo = attach + + # This is really to distinguish between lines and other images. + # For lines, want to pass drag to canvas, since lines tend to prevent + # dragging on a canvas (they get in the way.) + def Draggable(self): + return False + + def SetIgnoreOffsets(self, ignore): + """Set whether to ignore offsets from the end of the line when drawing.""" + self._ignoreArrowOffsets = ignore + + def GetArrows(self): + return self._arcArrows + + def GetAlignmentStart(self): + return self._alignmentStart + + def GetAlignmentEnd(self): + return self._alignmentEnd + + def IsEnd(self, nodeObject): + """TRUE if shape is at the end of the line.""" + return self._to == nodeObject + + def MakeLineControlPoints(self, n): + """Make a given number of control points (minimum of two).""" + if self._lineControlPoints: + self.ClearPointList(self._lineControlPoints) + self._lineControlPoints = [] + + for _ in range(n): + point = wx.RealPoint(-999,-999) + self._lineControlPoints.append(point) + + def InsertLineControlPoint(self, dc = None): + """Insert a control point at an arbitrary position.""" + if dc: + self.Erase(dc) + + last_point = self._lineControlPoints[-1] + second_last_point = self._lineControlPoints[-2] + + line_x = (last_point[0] + second_last_point[0]) / 2 + line_y = (last_point[1] + second_last_point[1]) / 2 + + point = wx.RealPoint(line_x, line_y) + self._lineControlPoints.insert(len(self._lineControlPoints), point) + + def DeleteLineControlPoint(self): + """Delete an arbitary point on the line.""" + if len(self._lineControlPoints)<3: + return False + + del self._lineControlPoints[-2] + return True + + def Initialise(self): + """Initialise the line object.""" + if self._lineControlPoints: + # Just move the first and last control points + first_point = self._lineControlPoints[0] + last_point = self._lineControlPoints[-1] + + # If any of the line points are at -999, we must + # initialize them by placing them half way between the first + # and the last. + + for point in self._lineControlPoints[1:]: + if point[0]==-999: + if first_point[0]= len(self._regions): + return + + region = self._regions[i] + region.SetText(s) + dc.SetFont(region.GetFont()) + + w, h = region.GetSize() + # Initialize the size if zero + if (w == 0 or h == 0) and s: + w, h = 100, 50 + region.SetSize(w, h) + + string_list = FormatText(dc, s, w-5, h-5, region.GetFormatMode()) + for s in string_list: + line = ShapeTextLine(0.0, 0.0, s) + region.GetFormattedText().append(line) + + actualW = w + actualH = h + if region.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS: + actualW, actualH = GetCentredTextExtent(dc, region.GetFormattedText(), self._xpos, self._ypos, w, h) + if actualW != w or actualH != h: + xx, yy = self.GetLabelPosition(i) + self.EraseRegion(dc, region, xx, yy) + if len(self._labelObjects)rLeft and xrTop and y= 0 and distance_from_prev <= seg_len or inLabelRegion: + return 0, distance_from_seg + + return False + + def DrawArrows(self, dc): + """Draw all arrows.""" + # Distance along line of each arrow: space them out evenly + startArrowPos = 0.0 + endArrowPos = 0.0 + middleArrowPos = 0.0 + + for arrow in self._arcArrows: + ah = arrow.GetArrowEnd() + if ah == ARROW_POSITION_START: + if arrow.GetXOffset() and not self._ignoreArrowOffsets: + # If specified, x offset is proportional to line length + self.DrawArrow(dc, arrow, arrow.GetXOffset(), True) + else: + self.DrawArrow(dc, arrow, startArrowPos, False) + startArrowPos += arrow.GetSize() + arrow.GetSpacing() + elif ah == ARROW_POSITION_END: + if arrow.GetXOffset() and not self._ignoreArrowOffsets: + self.DrawArrow(dc, arrow, arrow.GetXOffset(), True) + else: + self.DrawArrow(dc, arrow, endArrowPos, False) + endArrowPos += arrow.GetSize() + arrow.GetSpacing() + elif ah == ARROW_POSITION_MIDDLE: + arrow.SetXOffset(middleArrowPos) + if arrow.GetXOffset() and not self._ignoreArrowOffsets: + self.DrawArrow(dc, arrow, arrow.GetXOffset(), True) + else: + self.DrawArrow(dc, arrow, middleArrowPos, False) + middleArrowPos += arrow.GetSize() + arrow.GetSpacing() + + def DrawArrow(self, dc, arrow, XOffset, proportionalOffset): + """Draw the given arrowhead (or annotation).""" + first_line_point = self._lineControlPoints[0] + second_line_point = self._lineControlPoints[1] + + last_line_point = self._lineControlPoints[-1] + second_last_line_point = self._lineControlPoints[-2] + + # Position of start point of line, at the end of which we draw the arrow + startPositionX, startPositionY = 0.0, 0.0 + + ap = arrow.GetPosition() + if ap == ARROW_POSITION_START: + # If we're using a proportional offset, calculate just where this + # will be on the line. + realOffset = XOffset + if proportionalOffset: + totalLength = sqrt((second_line_point[0]-first_line_point[0]) * (second_line_point[0]-first_line_point[0]) + (second_line_point[1]-first_line_point[1]) * (second_line_point[1]-first_line_point[1])) + realOffset = XOffset * totalLength + + positionOnLineX, positionOnLineY = GetPointOnLine(second_line_point[0], second_line_point[1], first_line_point[0], first_line_point[1], realOffset) + + startPositionX = second_line_point[0] + startPositionY = second_line_point[1] + elif ap == ARROW_POSITION_END: + # If we're using a proportional offset, calculate just where this + # will be on the line. + realOffset = XOffset + if proportionalOffset: + totalLength = sqrt((second_last_line_point[0]-last_line_point[0]) * (second_last_line_point[0]-last_line_point[0]) + (second_last_line_point[1]-last_line_point[1]) * (second_last_line_point[1]-last_line_point[1])); + realOffset = XOffset * totalLength + + positionOnLineX, positionOnLineY = GetPointOnLine(second_last_line_point[0], second_last_line_point[1], last_line_point[0], last_line_point[1], realOffset) + + startPositionX = second_last_line_point[0] + startPositionY = second_last_line_point[1] + elif ap == ARROW_POSITION_MIDDLE: + # Choose a point half way between the last and penultimate points + x = (last_line_point[0] + second_last_line_point[0]) / 2 + y = (last_line_point[1] + second_last_line_point[1]) / 2 + + # If we're using a proportional offset, calculate just where this + # will be on the line. + realOffset = XOffset + if proportionalOffset: + totalLength = sqrt((second_last_line_point[0]-x) * (second_last_line_point[0]-x) + (second_last_line_point[1]-y) * (second_last_line_point[1]-y)); + realOffset = XOffset * totalLength + + positionOnLineX, positionOnLineY = GetPointOnLine(second_last_line_point[0], second_last_line_point[1], x, y, realOffset) + startPositionX = second_last_line_point[0] + startPositionY = second_last_line_point[1] + + # Add yOffset to arrow, if any + + # The translation that the y offset may give + deltaX = 0.0 + deltaY = 0.0 + if arrow.GetYOffset and not self._ignoreArrowOffsets: + # |(x4, y4) + # |d + # | + # (x1, y1)--------------(x3, y3)------------------(x2, y2) + # x4 = x3 - d * sin(theta) + # y4 = y3 + d * cos(theta) + # + # Where theta = tan(-1) of (y3-y1) / (x3-x1) + x1 = startPositionX + y1 = startPositionY + x3 = positionOnLineX + y3 = positionOnLineY + d=-arrow.GetYOffset() # Negate so +offset is above line + + if x3 == x1: + theta = pi / 2 + else: + theta = atan((y3-y1) / (x3-x1)) + + x4 = x3-d * sin(theta) + y4 = y3 + d * cos(theta) + + deltaX = x4-positionOnLineX + deltaY = y4-positionOnLineY + + at = arrow._GetType() + if at == ARROW_ARROW: + arrowLength = arrow.GetSize() + arrowWidth = arrowLength / 3 + + tip_x, tip_y, side1_x, side1_y, side2_x, side2_y = GetArrowPoints(startPositionX + deltaX, startPositionY + deltaY, positionOnLineX + deltaX, positionOnLineY + deltaY, arrowLength, arrowWidth) + + points = [[tip_x, tip_y], + [side1_x, side1_y], + [side2_x, side2_y], + [tip_x, tip_y]] + + dc.SetPen(self._pen) + dc.SetBrush(self._brush) + dc.DrawPolygon(points) + elif at in [ARROW_HOLLOW_CIRCLE, ARROW_FILLED_CIRCLE]: + # Find point on line of centre of circle, which is a radius away + # from the end position + diameter = arrow.GetSize() + x, y = GetPointOnLine(startPositionX + deltaX, startPositionY + deltaY, + positionOnLineX + deltaX, positionOnLineY + deltaY, + diameter / 2) + x1 = x-diameter / 2 + y1 = y-diameter / 2 + dc.SetPen(self._pen) + if arrow._GetType() == ARROW_HOLLOW_CIRCLE: + dc.SetBrush(self.GetBackgroundBrush()) + else: + dc.SetBrush(self._brush) + + dc.DrawEllipse(x1, y1, diameter, diameter) + elif at == ARROW_SINGLE_OBLIQUE: + pass + elif at == ARROW_METAFILE: + if arrow.GetMetaFile(): + # Find point on line of centre of object, which is a half-width away + # from the end position + # + # width + # <-- start pos <-----><-- positionOnLineX + # _____ + # --------------| x | <-- e.g. rectangular arrowhead + # ----- + # + x, y = GetPointOnLine(startPositionX, startPositionY, + positionOnLineX, positionOnLineY, + arrow.GetMetaFile()._width / 2) + # Calculate theta for rotating the metafile. + # + # | + # | o(x2, y2) 'o' represents the arrowhead. + # | / + # | / + # | /theta + # | /(x1, y1) + # |______________________ + # + theta = 0.0 + x1 = startPositionX + y1 = startPositionY + x2 = positionOnLineX + y2 = positionOnLineY + + if x1 == x2 and y1 == y2: + theta = 0.0 + elif x1 == x2 and y1>y2: + theta = 3.0 * pi / 2 + elif x1 == x2 and y2>y1: + theta = pi / 2 + elif x2>x1 and y2 >= y1: + theta = atan((y2-y1) / (x2-x1)) + elif x2x1 and y21: + dc.DrawRectangle(self._xpos-bound_x / 2-2, self._ypos-bound_y / 2-2, + bound_x + 4, bound_y + 4) + else: + self._erasing = True + self.GetEventHandler().OnDraw(dc) + self.GetEventHandler().OnEraseControlPoints(dc) + self._erasing = False + + if old_pen: + self.SetPen(old_pen) + if old_brush: + self.SetBrush(old_brush) + + def GetBoundingBoxMin(self): + x1, y1 = 10000, 10000 + x2, y2=-10000,-10000 + + for point in self._lineControlPoints: + if point[0]x2: + x2 = point[0] + if point[1]>y2: + y2 = point[1] + + return x2-x1, y2-y1 + + # For a node image of interest, finds the position of this arc + # amongst all the arcs which are attached to THIS SIDE of the node image, + # and the number of same. + def FindNth(self, image, incoming): + """Find the position of the line on the given object. + + Specify whether incoming or outgoing lines are being considered + with incoming. + """ + n=-1 + num = 0 + + if image == self._to: + this_attachment = self._attachmentTo + else: + this_attachment = self._attachmentFrom + + # Find number of lines going into / out of this particular attachment point + for line in image.GetLines(): + if line._from == image: + # This is the nth line attached to 'image' + if line == self and not incoming: + n = num + + # Increment num count if this is the same side (attachment number) + if line._attachmentFrom == this_attachment: + num += 1 + + if line._to == image: + # This is the nth line attached to 'image' + if line == self and incoming: + n = num + + # Increment num count if this is the same side (attachment number) + if line._attachmentTo == this_attachment: + num += 1 + + return n, num + + def OnDrawOutline(self, dc, x, y, w, h): + old_pen = self._pen + old_brush = self._brush + + dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT) + self.SetPen(dottedPen) + self.SetBrush(wx.TRANSPARENT_BRUSH) + + self.GetEventHandler().OnDraw(dc) + + if old_pen: + self.SetPen(old_pen) + else: + self.SetPen(None) + if old_brush: + self.SetBrush(old_brush) + else: + self.SetBrush(None) + + def OnMovePre(self, dc, x, y, old_x, old_y, display = True): + x_offset = x-old_x + y_offset = y-old_y + + if self._lineControlPoints and not (x_offset == 0 and y_offset == 0): + for point in self._lineControlPoints: + point[0] += x_offset + point[1] += y_offset + + # Move temporary label rectangles if necessary + for i in range(3): + if self._labelObjects[i]: + self._labelObjects[i].Erase(dc) + xp, yp = self.GetLabelPosition(i) + if i2: + self.Initialise() + + # Do each end - nothing in the middle. User has to move other points + # manually if necessary + end_x, end_y, other_end_x, other_end_y = self.FindLineEndPoints() + + first = self._lineControlPoints[0] + last = self._lineControlPoints[-1] + + oldX, oldY = self._xpos, self._ypos + + self.SetEnds(end_x, end_y, other_end_x, other_end_y) + + # Do a second time, because one may depend on the other + end_x, end_y, other_end_x, other_end_y = self.FindLineEndPoints() + self.SetEnds(end_x, end_y, other_end_x, other_end_y) + + # Try to move control points with the arc + x_offset = self._xpos-oldX + y_offset = self._ypos-oldY + + # Only move control points if it's a self link. And only works + # if attachment mode is ON + if self._from == self._to and self._from.GetAttachmentMode() != ATTACHMENT_MODE_NONE and moveControlPoints and self._lineControlPoints and not (x_offset == 0 and y_offset == 0): + for point in self._lineControlPoints[1:-1]: + point.x += x_offset + point.y += y_offset + + self.Move(dc, self._xpos, self._ypos) + + def FindLineEndPoints(self): + """Finds the x, y points at the two ends of the line. + + This function can be used by e.g. line-routing routines to + get the actual points on the two node images where the lines will be + drawn to / from. + """ + if not self._from or not self._to: + return + + # Do each end - nothing in the middle. User has to move other points + # manually if necessary. + second_point = self._lineControlPoints[1] + second_last_point = self._lineControlPoints[-2] + + if len(self._lineControlPoints)>2: + if self._from.GetAttachmentMode() != ATTACHMENT_MODE_NONE: + nth, no_arcs = self.FindNth(self._from, False) # Not incoming + end_x, end_y = self._from.GetAttachmentPosition(self._attachmentFrom, nth, no_arcs, self) + else: + end_x, end_y = self._from.GetPerimeterPoint(self._from.GetX(), self._from.GetY(), second_point[0], second_point[1]) + + if self._to.GetAttachmentMode() != ATTACHMENT_MODE_NONE: + nth, no_arch = self.FindNth(self._to, True) # Incoming + other_end_x, other_end_y = self._to.GetAttachmentPosition(self._attachmentTo, nth, no_arch, self) + else: + other_end_x, other_end_y = self._to.GetPerimeterPoint(self._to.GetX(), self._to.GetY(), second_last_point[0], second_last_point[1]) + else: + fromX = self._from.GetX() + fromY = self._from.GetY() + toX = self._to.GetX() + toY = self._to.GetY() + + if self._from.GetAttachmentMode() != ATTACHMENT_MODE_NONE: + nth, no_arcs = self.FindNth(self._from, False) + end_x, end_y = self._from.GetAttachmentPosition(self._attachmentFrom, nth, no_arcs, self) + fromX = end_x + fromY = end_y + + if self._to.GetAttachmentMode() != ATTACHMENT_MODE_NONE: + nth, no_arcs = self.FindNth(self._to, True) + other_end_x, other_end_y = self._to.GetAttachmentPosition(self._attachmentTo, nth, no_arcs, self) + toX = other_end_x + toY = other_end_y + + if self._from.GetAttachmentMode() == ATTACHMENT_MODE_NONE: + end_x, end_y = self._from.GetPerimeterPoint(self._from.GetX(), self._from.GetY(), toX, toY) + + if self._to.GetAttachmentMode() == ATTACHMENT_MODE_NONE: + other_end_x, other_end_y = self._to.GetPerimeterPoint(self._to.GetX(), self._to.GetY(), fromX, fromY) + + #print type(self._from), type(self._to), end_x, end_y, other_end_x, other_end_y + return end_x, end_y, other_end_x, other_end_y + + def OnDraw(self, dc): + if not self._lineControlPoints: + return + + if self._pen: + dc.SetPen(self._pen) + if self._brush: + dc.SetBrush(self._brush) + + points = [] + for point in self._lineControlPoints: + points.append(wx.Point(point.x, point.y)) + + #print points + if self._isSpline: + dc.DrawSpline(points) + else: + dc.DrawLines(points) + + if sys.platform[:3]=="win": + # For some reason, last point isn't drawn under Windows + dc.DrawPoint(points[-1]) + + # Problem with pen - if not a solid pen, does strange things + # to the arrowhead. So make (get) a new pen that's solid. + if self._pen and self._pen.GetStyle() != wx.SOLID: + solid_pen = wx.ThePenList().FindOrCreatePen(self._pen.GetColour(), 1, wx.SOLID) + if solid_pen: + dc.SetPen(solid_pen) + + self.DrawArrows(dc) + + def OnDrawControlPoints(self, dc): + if not self._drawHandles: + return + + # Draw temporary label rectangles if necessary + for i in range(3): + if self._labelObjects[i]: + self._labelObjects[i].Draw(dc) + + Shape.OnDrawControlPoints(self, dc) + + def OnEraseControlPoints(self, dc): + # Erase temporary label rectangles if necessary + + for i in range(3): + if self._labelObjects[i]: + self._labelObjects[i].Erase(dc) + + Shape.OnEraseControlPoints(self, dc) + + def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0): + pass + + def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0): + pass + + def OnEndDragLeft(self, x, y, keys = 0, attachment = 0): + pass + + def OnDrawContents(self, dc): + if self.GetDisableLabel(): + return + + for i in range(3): + if self._regions[i]: + x, y = self.GetLabelPosition(i) + self.DrawRegion(dc, self._regions[i], x, y) + + def SetTo(self, object): + """Set the 'to' object for the line.""" + self._to = object + + def SetFrom(self, object): + """Set the 'from' object for the line.""" + self._from = object + + def MakeControlPoints(self): + """Make handle control points.""" + if self._canvas and self._lineControlPoints: + first = self._lineControlPoints[0] + last = self._lineControlPoints[-1] + + control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, first[0], first[1], CONTROL_POINT_ENDPOINT_FROM) + control._point = first + self._canvas.AddShape(control) + self._controlPoints.Append(control) + + for point in self._lineControlPoints[1:-1]: + control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, point[0], point[1], CONTROL_POINT_LINE) + control._point = point + self._canvas.AddShape(control) + self._controlPoints.Append(control) + + control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, last[0], last[1], CONTROL_POINT_ENDPOINT_TO) + control._point = last + self._canvas.AddShape(control) + self._controlPoints.Append(control) + + def ResetControlPoints(self): + if self._canvas and self._lineControlPoints: + for i in range(min(len(self._controlPoints), len(self._lineControlPoints))): + point = self._lineControlPoints[i] + control = self._controlPoints[i] + control.SetX(point[0]) + control.SetY(point[1]) + + # Override select, to create / delete temporary label-moving objects + def Select(self, select, dc = None): + Shape.Select(self, select, dc) + if select: + for i in range(3): + if self._regions[i]: + region = self._regions[i] + if region._formattedText: + w, h = region.GetSize() + x, y = region.GetPosition() + xx, yy = self.GetLabelPosition(i) + + if self._labelObjects[i]: + self._labelObjects[i].Select(False) + self._labelObjects[i].RemoveFromCanvas(self._canvas) + + self._labelObjects[i] = self.OnCreateLabelShape(self, region, w, h) + self._labelObjects[i].AddToCanvas(self._canvas) + self._labelObjects[i].Show(True) + if dc: + self._labelObjects[i].Move(dc, x + xx, y + yy) + self._labelObjects[i].Select(True, dc) + else: + for i in range(3): + if self._labelObjects[i]: + self._labelObjects[i].Select(False, dc) + self._labelObjects[i].Erase(dc) + self._labelObjects[i].RemoveFromCanvas(self._canvas) + self._labelObjects[i] = None + + # Control points ('handles') redirect control to the actual shape, to + # make it easier to override sizing behaviour. + def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0): + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + + dc.SetLogicalFunction(OGLRBLF) + + dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT) + dc.SetPen(dottedPen) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + + if pt._type == CONTROL_POINT_LINE: + x, y = self._canvas.Snap() + + pt.SetX(x) + pt.SetY(y) + pt._point[0] = x + pt._point[1] = y + + old_pen = self.GetPen() + old_brush = self.GetBrush() + + self.SetPen(dottedPen) + self.SetBrush(wx.TRANSPARENT_BRUSH) + + self.GetEventHandler().OnMoveLink(dc, False) + + self.SetPen(old_pen) + self.SetBrush(old_brush) + + def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0): + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + + if pt._type == CONTROL_POINT_LINE: + pt._originalPos = pt._point + x, y = self._canvas.Snap() + + self.Erase(dc) + + # Redraw start and end objects because we've left holes + # when erasing the line + self.GetFrom().OnDraw(dc) + self.GetFrom().OnDrawContents(dc) + self.GetTo().OnDraw(dc) + self.GetTo().OnDrawContents(dc) + + self.SetDisableLabel(True) + dc.SetLogicalFunction(OGLRBLF) + + pt._xpos = x + pt._ypos = y + pt._point[0] = x + pt._point[1] = y + + old_pen = self.GetPen() + old_brush = self.GetBrush() + + dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT) + self.SetPen(dottedPen) + self.SetBrush(wx.TRANSPARENT_BRUSH) + + self.GetEventHandler().OnMoveLink(dc, False) + + self.SetPen(old_pen) + self.SetBrush(old_brush) + + if pt._type == CONTROL_POINT_ENDPOINT_FROM or pt._type == CONTROL_POINT_ENDPOINT_TO: + self._canvas.SetCursor(wx.Cursor(wx.CURSOR_BULLSEYE)) + pt._oldCursor = wx.STANDARD_CURSOR + + def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0): + dc = wx.ClientDC(self.GetCanvas()) + self.GetCanvas().PrepareDC(dc) + + self.SetDisableLabel(False) + + if pt._type == CONTROL_POINT_LINE: + x, y = self._canvas.Snap() + + rpt = wx.RealPoint(x, y) + + # Move the control point back to where it was; + # MoveControlPoint will move it to the new position + # if it decides it wants. We only moved the position + # during user feedback so we could redraw the line + # as it changed shape. + pt._xpos = pt._originalPos[0] + pt._ypos = pt._originalPos[1] + pt._point[0] = pt._originalPos[0] + pt._point[1] = pt._originalPos[1] + + self.OnMoveMiddleControlPoint(dc, pt, rpt) + + if pt._type == CONTROL_POINT_ENDPOINT_FROM: + if pt._oldCursor: + self._canvas.SetCursor(pt._oldCursor) + + if self.GetFrom(): + self.GetFrom().MoveLineToNewAttachment(dc, self, x, y) + + if pt._type == CONTROL_POINT_ENDPOINT_TO: + if pt._oldCursor: + self._canvas.SetCursor(pt._oldCursor) + + if self.GetTo(): + self.GetTo().MoveLineToNewAttachment(dc, self, x, y) + + # This is called only when a non-end control point is moved + def OnMoveMiddleControlPoint(self, dc, lpt, pt): + lpt._xpos = pt[0] + lpt._ypos = pt[1] + + lpt._point[0] = pt[0] + lpt._point[1] = pt[1] + + self.GetEventHandler().OnMoveLink(dc) + + return True + + def AddArrow(self, type, end = ARROW_POSITION_END, size = 10.0, xOffset = 0.0, name="",mf = None, arrowId=-1): + """Add an arrow (or annotation) to the line. + + type may currently be one of: + + ARROW_HOLLOW_CIRCLE + Hollow circle. + ARROW_FILLED_CIRCLE + Filled circle. + ARROW_ARROW + Conventional arrowhead. + ARROW_SINGLE_OBLIQUE + Single oblique stroke. + ARROW_DOUBLE_OBLIQUE + Double oblique stroke. + ARROW_DOUBLE_METAFILE + Custom arrowhead. + + end may currently be one of: + + ARROW_POSITION_END + Arrow appears at the end. + ARROW_POSITION_START + Arrow appears at the start. + + arrowSize specifies the length of the arrow. + + xOffset specifies the offset from the end of the line. + + name specifies a name for the arrow. + + mf can be a wxPseduoMetaFile, perhaps loaded from a simple Windows + metafile. + + arrowId is the id for the arrow. + """ + arrow = ArrowHead(type, end, size, xOffset, name, mf, arrowId) + self._arcArrows.append(arrow) + return arrow + + # Add arrowhead at a particular position in the arrowhead list + def AddArrowOrdered(self, arrow, referenceList, end): + """Add an arrowhead in the position indicated by the reference list + of arrowheads, which contains all legal arrowheads for this line, in + the correct order. E.g. + + Reference list: a b c d e + Current line list: a d + + Add c, then line list is: a c d. + + If no legal arrowhead position, return FALSE. Assume reference list + is for one end only, since it potentially defines the ordering for + any one of the 3 positions. So we don't check the reference list for + arrowhead position. + """ + if not referenceList: + return False + + targetName = arrow.GetName() + + # First check whether we need to insert in front of list, + # because this arrowhead is the first in the reference + # list and should therefore be first in the current list. + refArrow = referenceList[0] + if refArrow.GetName() == targetName: + self._arcArrows.insert(0, arrow) + return True + + i1 = i2 = 0 + while i10: + minWidth = minWidth * 1.4 + else: + minWidth = 20.0 + + self.SetEnds(0.0, 0.0, minWidth, 0.0) + self.Initialise() + + return minWidth + + def FindLinePosition(self, x, y): + """Find which position we're talking about at this x, y. + + Returns ARROW_POSITION_START, ARROW_POSITION_MIDDLE, ARROW_POSITION_END. + """ + startX, startY, endX, endY = self.GetEnds() + + # Find distances from centre, start and end. The smallest wins + centreDistance = sqrt((x-self._xpos) * (x-self._xpos) + (y-self._ypos) * (y-self._ypos)) + startDistance = sqrt((x-startX) * (x-startX) + (y-startY) * (y-startY)) + endDistance = sqrt((x-endX) * (x-endX) + (y-endY) * (y-endY)) + + if centreDistance0: + string_list.append(buffer) + buffer="" + else: + if len(buffer): + buffer+=" " + buffer += s + x, y = dc.GetTextExtent(buffer) + + # Don't fit within the bounding box if we're fitting + # shape to contents + if (x>width) and not (formatMode & FORMAT_SIZE_TO_CONTENTS): + # Deal with first word being wider than box + if len(oldBuffer): + string_list.append(oldBuffer) + buffer = s + if len(buffer): + string_list.append(buffer) + + return string_list + + + +def GetCentredTextExtent(dc, text_list, xpos = 0, ypos = 0, width = 0, height = 0): + if not text_list: + return 0, 0 + + max_width = 0 + for line in text_list: + current_width, char_height = dc.GetTextExtent(line) + if current_width>max_width: + max_width = current_width + + return max_width, len(text_list) * char_height + + + +def CentreText(dc, text_list, xpos, ypos, width, height, formatMode): + if not text_list: + return + + # First, get maximum dimensions of box enclosing text + char_height = 0 + max_width = 0 + current_width = 0 + + # Store text extents for speed + widths = [] + for line in text_list: + current_width, char_height = dc.GetTextExtent(line.GetText()) + widths.append(current_width) + if current_width>max_width: + max_width = current_width + + max_height = len(text_list) * char_height + + if formatMode & FORMAT_CENTRE_VERT: + if max_height(val2 - tol) and \ + val2<(val1 + tol) and val2>(val1 - tol) + + + +def FindEndForBox(width, height, x1, y1, x2, y2): + xvec = [x1 - width / 2, x1 - width / 2, x1 + width / 2, x1 + width / 2, x1 - width / 2] + yvec = [y1 - height / 2, y1 + height / 2, y1 + height / 2, y1 - height / 2, y1 - height / 2] + + return FindEndForPolyline(xvec, yvec, x2, y2, x1, y1) + + + +def CheckLineIntersection(x1, y1, x2, y2, x3, y3, x4, y4): + denominator_term = (y4 - y3) * (x2 - x1) - (y2 - y1) * (x4 - x3) + numerator_term = (x3 - x1) * (y4 - y3) + (x4 - x3) * (y1 - y3) + + length_ratio = 1.0 + k_line = 1.0 + + # Check for parallel lines + if denominator_term<0.005 and denominator_term>-0.005: + line_constant=-1.0 + else: + line_constant = float(numerator_term) / denominator_term + + # Check for intersection + if line_constant<1.0 and line_constant>0.0: + # Now must check that other line hits + if (y4 - y3)<0.005 and (y4 - y3)>-0.005: + k_line = (x1 - x3 + line_constant * (x2 - x1)) / (x4 - x3) + else: + k_line = (y1 - y3 + line_constant * (y2 - y1)) / (y4 - y3) + if k_line >= 0 and k_line<1: + length_ratio = line_constant + else: + k_line = 1 + + return length_ratio, k_line + + + +def FindEndForPolyline(xvec, yvec, x1, y1, x2, y2): + lastx = xvec[0] + lasty = yvec[0] + + min_ratio = 1.0 + + for i in range(1, len(xvec)): + line_ratio, other_ratio = CheckLineIntersection(x1, y1, x2, y2, lastx, lasty, xvec[i], yvec[i]) + lastx = xvec[i] + lasty = yvec[i] + + if line_ratio1: + point2[0] = point1[0] + else: + point2[1] = point1[0] + + + +def GetPointOnLine(x1, y1, x2, y2, length): + l = sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) + if l<0.01: + l = 0.01 + + i_bar = (x2 - x1) / l + j_bar = (y2 - y1) / l + + return -length * i_bar + x2,-length * j_bar + y2 + + + +def GetArrowPoints(x1, y1, x2, y2, length, width): + l = sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) + + if l<0.01: + l = 0.01 + + i_bar = (x2 - x1) / l + j_bar = (y2 - y1) / l + + x3=-length * i_bar + x2 + y3=-length * j_bar + y2 + + return x2, y2, width*-j_bar + x3, width * i_bar + y3,-width*-j_bar + x3,-width * i_bar + y3 + + + +def DrawArcToEllipse(x1, y1, width1, height1, x2, y2, x3, y3): + a1 = width1 / 2 + b1 = height1 / 2 + + # Check that x2 != x3 + if abs(x2 - x3)<0.05: + x4 = x2 + if y3>y2: + y4 = y1 - sqrt((b1 * b1 - (((x2 - x1) * (x2 - x1)) * (b1 * b1) / (a1 * a1)))) + else: + y4 = y1 + sqrt((b1 * b1 - (((x2 - x1) * (x2 - x1)) * (b1 * b1) / (a1 * a1)))) + return x4, y4 + + # Calculate the x and y coordinates of the point where arc intersects ellipse + A = (1 / (a1 * a1)) + B = ((y3 - y2) * (y3 - y2)) / ((x3 - x2) * (x3 - x2) * b1 * b1) + C = (2 * (y3 - y2) * (y2 - y1)) / ((x3 - x2) * b1 * b1) + D = ((y2 - y1) * (y2 - y1)) / (b1 * b1) + E = (A + B) + F = (C - (2 * A * x1) - (2 * B * x2)) + G = ((A * x1 * x1) + (B * x2 * x2) - (C * x2) + D - 1) + H = ((y3 - y2) / (x32 - x2)) + K = ((F * F) - (4 * E * G)) + + if K >= 0: + # In this case the line intersects the ellipse, so calculate intersection + if x2 >= x1: + ellipse1_x = ((F*-1) + sqrt(K)) / (2 * E) + ellipse1_y = ((H * (ellipse1_x - x2)) + y2) + else: + ellipse1_x = (((F*-1) - sqrt(K)) / (2 * E)) + ellipse1_y = ((H * (ellipse1_x - x2)) + y2) + else: + # in this case, arc does not intersect ellipse, so just draw arc + ellipse1_x = x3 + ellipse1_y = y3 + + return ellipse1_x, ellipse1_y + + + +def FindEndForCircle(radius, x1, y1, x2, y2): + H = sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) + + if H == 0: + return x1, y1 + else: + return radius * (x2 - x1) / H + x1, radius * (y2 - y1) / H + y1