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
This commit is contained in:
Robin Dunn 2004-05-26 02:13:04 +00:00
parent 6033bbc1ff
commit f847103a32
17 changed files with 7717 additions and 62 deletions

View File

@ -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);

View File

@ -30,7 +30,7 @@ import images
_treeList = [
# new stuff
('Recent Additions', [
('Recent Additions and Updates', [
'VListBox',
'Listbook',
'MaskedNumCtrl',

View File

@ -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 = """<html><body>
<h2>Object Graphics Library</h2>
overview = """\
The Object Graphics Library is a library supporting the creation and
manipulation of simple and complex graphic images on a canvas.
<p>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.
<p>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:])

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
----------------

View File

@ -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',

View File

@ -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 *

3173
wxPython/wx/lib/ogl/basic.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,66 @@
# -*- coding: iso-8859-1 -*-
#----------------------------------------------------------------------------
# Name: bmpshape.py
# Purpose: Bitmap shape
#
# 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
from basic import RectangleShape
class BitmapShape(RectangleShape):
"""Draws a bitmap (non-resizable)."""
def __init__(self):
RectangleShape.__init__(self, 100, 50)
self._filename=""
def OnDraw(self, dc):
if not self._bitmap.Ok():
return
x = self._xpos-self._bitmap.GetWidth() / 2
y = self._ypos-self._bitmap.GetHeight() / 2
dc.DrawBitmap(self._bitmap, x, y, True)
def SetSize(self, w, h, recursive = True):
if self._bitmap.Ok():
w = self._bitmap.GetWidth()
h = self._bitmap.GetHeight()
self.SetAttachmentSize(w, h)
self._width = w
self._height = h
self.SetDefaultRegionSize()
def GetBitmap(self):
"""Return a the bitmap associated with this shape."""
return self._bitmap
def SetBitmap(self, bitmap):
"""Set the bitmap associated with this shape.
You can delete the bitmap from the calling application, since
reference counting will take care of holding on to the internal bitmap
data.
"""
self._bitmap = bitmap
if self._bitmap.Ok():
self.SetSize(self._bitmap.GetWidth(), self._bitmap.GetHeight())
def SetFilename(self, f):
"""Set the bitmap filename."""
self._filename = f
def GetFilename(self):
"""Return the bitmap filename."""
return self._filename

View File

@ -0,0 +1,360 @@
# -*- coding: iso-8859-1 -*-
#----------------------------------------------------------------------------
# Name: canvas.py
# Purpose: The canvas 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
from lines import LineShape
from composit import *
NoDragging, StartDraggingLeft, ContinueDraggingLeft, StartDraggingRight, ContinueDraggingRight = 0, 1, 2, 3, 4
KEY_SHIFT, KEY_CTRL = 1, 2
# Helper function: True if 'contains' wholly contains 'contained'.
def WhollyContains(contains, contained):
xp1, yp1 = contains.GetX(), contains.GetY()
xp2, yp2 = contained.GetX(), contained.GetY()
w1, h1 = contains.GetBoundingBoxMax()
w2, h2 = contained.GetBoundingBoxMax()
left1 = xp1-w1 / 2.0
top1 = yp1-h1 / 2.0
right1 = xp1 + w1 / 2.0
bottom1 = yp1 + h1 / 2.0
left2 = xp2-w2 / 2.0
top2 = yp2-h2 / 2.0
right2 = xp2 + w2 / 2.0
bottom2 = yp2 + h2 / 2.0
return ((left1 <= left2) and (top1 <= top2) and (right1 >= 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<nearest:
nearest = dist
nearest_object = object
nearest_attachment = temp_attachment
for object in self.GetDiagram().GetShapeList()[::-1]:
# On second pass, only ever consider non-composites or
# divisions. If children want to pass up control to
# the composite, that's up to them.
if (object.IsShown() and
(isinstance(object, DivisionShape) or
not isinstance(object, CompositeShape)) 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)
if not isinstance(object, LineShape):
# If we've hit a container, and we have already
# found a line in the first pass, then ignore
# the container in case the line is in the container.
# Check for division in case line straddles divisions
# (i.e. is not wholly contained).
if not nearest_object or not (isinstance(object, DivisionShape) or WhollyContains(object, nearest_object)):
nearest_object = object
nearest_attachment = temp_attachment
break
return nearest_object, nearest_attachment
def AddShape(self, object, addAfter = None):
self.GetDiagram().AddShape(object, addAfter)
def InsertShape(self, object):
self.GetDiagram().InsertShape(object)
def RemoveShape(self, object):
self.GetDiagram().RemoveShape(object)
def GetQuickEditMode(self):
return self.GetDiagram().GetQuickEditMode()
def Redraw(self, dc):
self.GetDiagram().Redraw(dc)
def Snap(self, x, y):
return self.GetDiagram().Snap(x, y)
def OnLeftClick(self, x, y, keys = 0):
pass
def OnRightClick(self, x, y, keys = 0):
pass
def OnDragLeft(self, draw, x, y, keys = 0):
pass
def OnBeginDragLeft(self, x, y, keys = 0):
pass
def OnEndDragLeft(self, x, y, keys = 0):
pass
def OnDragRight(self, draw, x, y, keys = 0):
pass
def OnBeginDragRight(self, x, y, keys = 0):
pass
def OnEndDragRight(self, x, y, keys = 0):
pass

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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<len(dividedObject.GetRegions()):
nextRegion = dividedObject.GetRegions()[i + 1]
if region == nextRegion:
nextRegionBottom = actualY
currentY = actualY
if not nextRegion:
return
# Check that we haven't gone above this region or below
# next region.
if y <= thisRegionTop or y >= 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.x<left:
x = left
elif point.x>right:
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.x<left:
x = left
elif point.x>right:
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.y<bottom:
y = bottom
elif point.y>top:
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)

1533
wxPython/wx/lib/ogl/lines.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,416 @@
# -*- coding: iso-8859-1 -*-
#----------------------------------------------------------------------------
# Name: oglmisc.py
# Purpose: Miscellaneous OGL support functions
#
# 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
from math import *
import wx
# Control point types
# Rectangle and most other shapes
CONTROL_POINT_VERTICAL = 1
CONTROL_POINT_HORIZONTAL = 2
CONTROL_POINT_DIAGONAL = 3
# Line
CONTROL_POINT_ENDPOINT_TO = 4
CONTROL_POINT_ENDPOINT_FROM = 5
CONTROL_POINT_LINE = 6
# Types of formatting: can be combined in a bit list
FORMAT_NONE = 0 # Left justification
FORMAT_CENTRE_HORIZ = 1 # Centre horizontally
FORMAT_CENTRE_VERT = 2 # Centre vertically
FORMAT_SIZE_TO_CONTENTS = 4 # Resize shape to contents
# Attachment modes
ATTACHMENT_MODE_NONE, ATTACHMENT_MODE_EDGE, ATTACHMENT_MODE_BRANCHING = 0, 1, 2
# Shadow mode
SHADOW_NONE, SHADOW_LEFT, SHADOW_RIGHT = 0, 1, 2
OP_CLICK_LEFT, OP_CLICK_RIGHT, OP_DRAG_LEFT, OP_DRAG_RIGHT = 1, 2, 4, 8
OP_ALL = OP_CLICK_LEFT | OP_CLICK_RIGHT | OP_DRAG_LEFT | OP_DRAG_RIGHT
# Sub-modes for branching attachment mode
BRANCHING_ATTACHMENT_NORMAL = 1
BRANCHING_ATTACHMENT_BLOB = 2
# logical function to use when drawing rubberband boxes, etc.
OGLRBLF = wx.INVERT
CONTROL_POINT_SIZE = 6
# Types of arrowhead
# (i) Built-in
ARROW_HOLLOW_CIRCLE = 1
ARROW_FILLED_CIRCLE = 2
ARROW_ARROW = 3
ARROW_SINGLE_OBLIQUE = 4
ARROW_DOUBLE_OBLIQUE = 5
# (ii) Custom
ARROW_METAFILE = 20
# Position of arrow on line
ARROW_POSITION_START = 0
ARROW_POSITION_END = 1
ARROW_POSITION_MIDDLE = 2
# Line alignment flags
# Vertical by default
LINE_ALIGNMENT_HORIZ = 1
LINE_ALIGNMENT_VERT = 0
LINE_ALIGNMENT_TO_NEXT_HANDLE = 2
LINE_ALIGNMENT_NONE = 0
# Format a string to a list of strings that fit in the given box.
# Interpret %n and 10 or 13 as a new line.
def FormatText(dc, text, width, height, formatMode):
i = 0
word=""
word_list = []
end_word = False
new_line = False
while i<len(text):
if text[i]=="%":
i += 1
if i == len(text):
word+="%"
else:
if text[i]=="n":
new_line = True
end_word = True
i += 1
else:
word+="%"+text[i]
i += 1
elif text[i] in ["\012","\015"]:
new_line = True
end_word = True
i += 1
elif text[i]==" ":
end_word = True
i += 1
else:
word += text[i]
i += 1
if i == len(text):
end_word = True
if end_word:
word_list.append(word)
word=""
end_word = False
if new_line:
word_list.append(None)
new_line = False
# Now, make a list of strings which can fit in the box
string_list = []
buffer=""
for s in word_list:
oldBuffer = buffer
if s is None:
# FORCE NEW LINE
if len(buffer)>0:
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<height:
yoffset = ypos - height / 2 + (height - max_height) / 2
else:
yoffset = ypos - height / 2
yOffset = ypos
else:
yoffset = 0.0
yOffset = 0.0
if formatMode & FORMAT_CENTRE_HORIZ:
xoffset = xpos - width / 2
xOffset = xpos
else:
xoffset = 0.0
xOffset = 0.0
for i, line in enumerate(text_list):
if formatMode & FORMAT_CENTRE_HORIZ and widths[i]<width:
x = (width - widths[i]) / 2 + xoffset
else:
x = xoffset
y = i * char_height + yoffset
line.SetX(x - xOffset)
line.SetY(y - yOffset)
def DrawFormattedText(dc, text_list, xpos, ypos, width, height, formatMode):
if formatMode & FORMAT_CENTRE_HORIZ:
xoffset = xpos
else:
xoffset = xpos - width / 2
if formatMode & FORMAT_CENTRE_VERT:
yoffset = ypos
else:
yoffset = ypos - height / 2
# +1 to allow for rounding errors
dc.SetClippingRegion(xpos - width / 2, ypos - height / 2, width + 1, height + 1)
for line in text_list:
dc.DrawText(line.GetText(), xoffset + line.GetX(), yoffset + line.GetY())
dc.DestroyClippingRegion()
def RoughlyEqual(val1, val2, tol = 0.00001):
return val1<(val2 + tol) and val1>(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_ratio<min_ratio:
min_ratio = line_ratio
# Do last (implicit) line if last and first doubles are not identical
if not (xvec[0] == lastx and yvec[0] == lasty):
line_ratio, other_ratio = CheckLineIntersection(x1, y1, x2, y2, lastx, lasty, xvec[0], yvec[0])
if line_ratio<min_ratio:
min_ratio = line_ratio
return x1 + (x2 - x1) * min_ratio, y1 + (y2 - y1) * min_ratio
def PolylineHitTest(xvec, yvec, x1, y1, x2, y2):
isAHit = False
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])
if line_ratio != 1.0:
isAHit = True
lastx = xvec[i]
lasty = yvec[i]
if line_ratio<min_ratio:
min_ratio = line_ratio
# Do last (implicit) line if last and first doubles are not identical
if not (xvec[0] == lastx and yvec[0] == lasty):
line_ratio, other_ratio = CheckLineIntersection(x1, y1, x2, y2, lastx, lasty, xvec[0], yvec[0])
if line_ratio != 1.0:
isAHit = True
return isAHit
def GraphicsStraightenLine(point1, point2):
dx = point2[0] - point1[0]
dy = point2[1] - point1[1]
if dx == 0:
return
elif abs(dy / dx)>1:
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