Christopher Barker wrote:
Yes, I added the "Center" property to the BBox class when I wrote that
demo -- you need the latest SVN version of FloatCanvas to use it.
-Chris
Thanks for all that.
Its mostly working now, I've attached the example Im using and only 1
bug left.
I've added functionalities like rubberbands and collapse and expand
nodes and find and select nodes.
The bug that Im having problem is on line 242 of the code. In a
nutshell, if there are children nodes/shape with the same name the
connector lines becomes weird and the expand and collapse functions are
incomplete. Im not sure how to handle this. Any suggestions (aside from
having each node a unique name)? Also I would like to add a rotate
function to the canvas, has anyone done this to FloatCanvas before (and
would like to share)?
Thanks again for all your help
Astan
PS:again, i know my code can be improved alot, but Im also learning how
to use FloatCanvas at the same time.
#!/usr/bin/env python2.5
"""
This is a demo, showing how to work with a "tree" structure
It demonstrates moving objects around, etc, etc.
"""
import wx
ver = 'local'
#ver = 'installed'
if ver == 'installed': ## import the installed version
from wx.lib.floatcanvas import NavCanvas, Resources
from wx.lib.floatcanvas import FloatCanvas as FC
from wx.lib.floatcanvas.Utilities import BBox
print "using installed version:", wx.lib.floatcanvas.__version__
elif ver == 'local':
## import a local version
import sys
sys.path.append("../")
from floatcanvas import NavCanvas, Resources
from floatcanvas import FloatCanvas as FC
from floatcanvas.Utilities import BBox
import numpy as N
## here we create some new mixins:
## fixme: These really belong in floatcanvas package -- but I kind of want to
clean it up some first
class MovingObjectMixin:
"""
Methods required for a Moving object
"""
def GetOutlinePoints(self):
"""
Returns a set of points with which to draw the outline when moving the
object.
Points are a NX2 array of (x,y) points in World coordinates.
"""
BB = self.BoundingBox
OutlinePoints = N.array( ( (BB[0,0], BB[0,1]),
(BB[0,0], BB[1,1]),
(BB[1,0], BB[1,1]),
(BB[1,0], BB[0,1]),
)
)
return OutlinePoints
class ConnectorObjectMixin:
"""
Mixin class for DrawObjects that can be connected with lines
Note that this versionony works for Objects that have an "XY" attribute:
that is, one that is derived from XHObjectMixin.
"""
def GetConnectPoint(self):
return self.XY
class MovingBitmap(FC.ScaledBitmap, MovingObjectMixin, ConnectorObjectMixin):
"""
ScaledBitmap Object that can be moved
"""
## All we need to do is is inherit from:
## ScaledBitmap, MovingObjectMixin and ConnectorObjectMixin
pass
class MovingCircle(FC.Circle, MovingObjectMixin, ConnectorObjectMixin):
"""
ScaledBitmap Object that can be moved
"""
## All we need to do is is inherit from:
## Circle MovingObjectMixin and ConnectorObjectMixin
pass
class MovingGroup(FC.Group, MovingObjectMixin, ConnectorObjectMixin):
def GetConnectPoint(self):
return self.BoundingBox.Center
class NodeObject(FC.Group, MovingObjectMixin, ConnectorObjectMixin):
"""
A version of the moving group for nodes -- an ellipse with text on it.
"""
def __init__(self,
Label,
XY,
WH,
BackgroundColor = "Yellow",
TextColor = "Black",
InForeground = False,
IsVisible = True):
XY = N.asarray(XY, N.float).reshape(2,)
WH = N.asarray(WH, N.float).reshape(2,)
self.String = Label
Label = FC.ScaledText(Label,
XY,
Size = WH[1] / 2.0,
Color = TextColor,
Position = 'cc',
)
self.Ellipse = FC.Ellipse( (XY - WH/2.0),
WH,
FillColor = BackgroundColor,
LineStyle = None,
)
FC.Group.__init__(self, [self.Ellipse, Label], InForeground, IsVisible)
def GetConnectPoint(self):
return self.BoundingBox.Center
class MovingText(FC.ScaledText, MovingObjectMixin, ConnectorObjectMixin):
"""
ScaledBitmap Object that can be moved
"""
## All we need to do is is inherit from:
## ScaledBitmap, MovingObjectMixin and ConnectorObjectMixin
pass
class ConnectorLine(FC.LineOnlyMixin, FC.DrawObject,):
"""
A Line that connects two objects -- it uses the objects to get its
coordinates
The objects must have a GetConnectPoint() method.
"""
##fixme: this should be added to the Main FloatCanvas Objects some day.
def __init__(self,
Object1,
Object2,
LineColor = "Black",
LineStyle = "Solid",
LineWidth = 1,
InForeground = False):
FC.DrawObject.__init__(self, InForeground)
self.Object1 = Object1
self.Object2 = Object2
self.LineColor = LineColor
self.LineStyle = LineStyle
self.LineWidth = LineWidth
self.CalcBoundingBox()
self.SetPen(LineColor,LineStyle,LineWidth)
self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
def CalcBoundingBox(self):
self.BoundingBox = BBox.fromPoints((self.Object1.GetConnectPoint(),
self.Object2.GetConnectPoint()) )
if self._Canvas:
self._Canvas.BoundingBoxDirty = True
def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
Points = N.array( (self.Object1.GetConnectPoint(),
self.Object2.GetConnectPoint()) )
Points = WorldToPixel(Points)
dc.SetPen(self.Pen)
dc.DrawLines(Points)
if HTdc and self.HitAble:
HTdc.SetPen(self.HitPen)
HTdc.DrawLines(Points)
class TriangleShape1(FC.Polygon, MovingObjectMixin):
def __init__(self, XY, L):
"""
An equilateral triangle object
XY is the middle of the triangle
L is the length of one side of the Triangle
"""
XY = N.asarray(XY)
XY.shape = (2,)
Points = self.CompPoints(XY, L)
FC.Polygon.__init__(self, Points,
LineColor = "Black",
LineStyle = "Solid",
LineWidth = 2,
FillColor = "Red",
FillStyle = "Solid")
## Override the default OutlinePoints
def GetOutlinePoints(self):
return self.Points
def CompPoints(self, XY, L):
c = L/ N.sqrt(3)
Points = N.array(((0, c),
( L/2.0, -c/2.0),
(-L/2.0, -c/2.0)),
N.float_)
Points += XY
return Points
### Tree Utilities
### And some hard codes data...
class TreeNode:
dx = 15
dy = 4
def __init__(self, name, Children = []):
self.Name = name
#self.parent = None -- Is this needed?
self.Children = Children
self.Point = None # The coords of the node.
def __str__(self):
return "TreeNode: %s"%self.Name
__repr__ = __str__
## Build Tree:
leaves = [TreeNode(name) for name in ["Assistant VP 1","Assistant VP
2","Assistant VP 3"] ]
pos = 0
elem = []
while pos < 5:
elem.append("mis"+str(pos))
pos+=1
leaves = [TreeNode(name) for name in elem]
VP1 = TreeNode("VP1", Children = leaves)
VP2 = TreeNode("VP2", Children = leaves) #doing this does not work, but VP2 =
TreeNode("VP2") does
CEO = TreeNode("CEO", [VP1, VP2])
Father = TreeNode("Father", [TreeNode("Daughter"), TreeNode("Son")])
elements = TreeNode("Root", [CEO, Father])
def LayoutTree(root, x, y, level):
NumNodes = len(root.Children)
root.Point = (x,y)
x += root.dx
y += (root.dy * level * (NumNodes-1) / 2.0)
for node in root.Children:
LayoutTree(node, x, y, level-1)
y -= root.dy * level
def TraverseTree(root, func):
func(root)
for child in (root.Children):
TraverseTree(child, func)
class DrawFrame(wx.Frame):
"""
A simple frame used for the Demo
"""
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.CreateStatusBar()
# Add the Canvas
Canvas = NavCanvas.NavCanvas(self,-1,(500,500),
ProjectionFun = None,
Debug = 0,
BackgroundColor = "White",
).Canvas
self.Canvas = Canvas
self._isctrl= False
Canvas.Bind(FC.EVT_MOTION, self.OnMove )
Canvas.Bind(FC.EVT_LEFT_DOWN, self.OnLeftDown )
Canvas.Bind(FC.EVT_LEFT_UP, self.OnLeftUp )
Canvas.Bind(FC.EVT_RIGHT_UP, self.EvtRightButton)
Canvas.Bind(wx.EVT_KEY_DOWN, self.SetCtrl)
Canvas.Bind(wx.EVT_KEY_UP, self.UnSetCtrl)
self.elements = elements
self.objects = []
self.connectors = []
self.selected_objects = []
self.expand = False
LayoutTree(self.elements, 0, 0, 4)
self.AddTree(self.elements)
self.Show(True)
self.Canvas.ZoomToBB(BBox.BBox( ( (-10.0, -20.0), (40.0, 40.0) ) ))
self.MoveObject = None
self.Moving = False
self.selected = None
self.m_stpoint=wx.Point(0,0)
self.m_endpoint=wx.Point(0,0)
self.m_savepoint=wx.Point(0,0)
self._leftclicked=False
self._selected=False
self.start = wx.Point(0,0)
self.end = wx.Point(0,0)
return None
def SetCtrl(self,event):
if event.ControlDown():
self._isctrl = True
def UnSetCtrl(self,event):
if self._isctrl:
self._isctrl = False
def AddTree(self, root):
Nodes = []
Connectors = []
EllipseW = 15
EllipseH = 4
def CreateObject(node):
if node.Children:
object = NodeObject(node.Name,
node.Point,
(15, 4),
BackgroundColor = "Yellow",
TextColor = "Black",
)
else:
object = MovingText(node.Name,
node.Point,
2.0,
BackgroundColor = "Yellow",
Color = "Red",
Position = "cl",
)
node.DrawObject = object
Nodes.append(object)
def AddConnectors(node):
for child in node.Children:
Connector = ConnectorLine(node.DrawObject, child.DrawObject,
LineWidth=3, LineColor="Red")
Connectors.append(Connector)
## create the Objects
TraverseTree(root, CreateObject)
## create the Connectors
TraverseTree(root, AddConnectors)
## Add the conenctos to the Canvas first, so they are underneither the
nodes
self.Canvas.AddObjects(Connectors)
## now add the nodes
self.Canvas.AddObjects(Nodes)
# Now bind the Nodes -- DrawObjects must be Added to a Canvas before
they can be bound.
for node in Nodes:
#pass
node.Bind(FC.EVT_FC_LEFT_DOWN, self.ObjectHit)
node.Bind(FC.EVT_FC_LEFT_DCLICK, self.ObjectDo)
self.objects = Nodes
self.connectors = Connectors
def ObjectHit(self, object):
if not self.Moving:
self.Moving = True
self.StartPoint = object.HitCoordsPixel
self.StartObject =
self.Canvas.WorldToPixel(object.GetOutlinePoints())
self.MoveObject = None
self.MovingObject = object
self.selected = object
self.MovingObject.BackgroundColor = "Blue"
self.MovingObject.SetFillColor("Blue")
if not self._isctrl:
for obj in self.selected_objects:
obj.BackgroundColor = "Yellow"
obj.SetFillColor("Yellow")
self.selected_objects = [object]
else:
self.selected_objects.append(object)
self.Canvas.Draw(True)
def ObjectDo(self,object):
self.found_node = None
self.FindNode(self.elements,object.String)
if self.found_node:
self.DoChildren(self.found_node)
if self.expand:
self.expand = False
else:
self.expand = True
self.found_node = None
self.Canvas.Draw(True)
def EvtRightButton(self,event):
menu = wx.Menu()
printID = wx.NewId()
printCID = wx.NewId()
expandID = wx.NewId()
colapseID = wx.NewId()
colapseSID = wx.NewId()
expandSID = wx.NewId()
findID = wx.NewId()
self.Refresh()
menu.Append(printID,"Display selected")
menu.Append(printCID,"Display selected Children")
menu.Append(findID,"Find and select Nodes")
menu.Append(expandID,"Expand all")
menu.Append(colapseID,"Colapse all")
menu.Append(colapseSID,"Colapse selected")
menu.Append(expandSID,"Expand selected")
def displaySel(event,self=self):
for shape in self.selected_objects:
print shape.String
def displaySelChildren(event,self=self):
for shape in self.selected_objects:
self.found_node_children = []
self.FindNodeChildren(self.elements,shape.String,0)
if self.found_node_children:
for child in self.found_node_children:
print child.Name
self.found_node_children = []
def expandAll(event,self=self):
self.found_node = None
self.FindNode(self.elements,"Root")
self.expand = True
if self.found_node:
self.DoChildren(self.found_node)
self.found_node = None
self.Canvas.Draw(True)
def colapseAll(event,self=self):
self.found_node = None
self.FindNode(self.elements,"Root")
self.expand = False
if self.found_node:
self.DoChildren(self.found_node)
self.found_node = None
self.Canvas.Draw(True)
def colapseSel(event,self=self):
for shape in self.selected_objects:
self.found_node = None
self.FindNode(self.elements,shape.String)
self.expand = False
if self.found_node:
self.DoChildren(self.found_node)
self.found_node = None
self.Canvas.Draw(True)
def expandSel(event,self=self):
for shape in self.selected_objects:
self.found_node = None
self.FindNode(self.elements,shape.String)
self.expand = True
if self.found_node:
self.DoChildren(self.found_node)
self.found_node = None
self.Canvas.Draw(True)
def findNote(event,self=self):
dlg = wx.TextEntryDialog(self, 'Please enter node you wish to find
and select','Find', 'Find')
dlg.SetValue("Root")
if dlg.ShowModal() == wx.ID_OK and dlg.GetValue()!= "":
finds = str(dlg.GetValue())
if not self._isctrl:
for obj in self.selected_objects:
obj.BackgroundColor = "Yellow"
obj.SetFillColor("Yellow")
tempc = self._isctrl
self._isctrl = True
maxX = maxY = minX = minY = 0
unset = True
founds = []
for find in finds.split(","):
thm = find.strip()
if thm not in founds:
founds.append(thm)
for found in founds:
self.found_node = None
self.FindNode(self.elements,found)
if self.found_node:
x,y = self.found_node.Point
if x > maxX:
maxX = x
unset = False
if x < minX:
minX = x
unset = False
if y > maxY:
maxY = y
unset = False
if y < minY:
minY = y
unset = False
self.search_object = None
self.FindDrawObject(self.found_node)
self.search_object.BackgroundColor = "Blue"
self.search_object.SetFillColor("Blue")
self.selected_objects.append(self.search_object)
self.search_object = None
self.found_node = None
if not unset:
self.Canvas.ZoomToBB(BBox.BBox( ( (minX-10, minY-10),
(maxX+10, maxY+10) ) ))
self.Canvas.Draw(True)
self._isctrl = tempc
wx.EVT_MENU(self,printID,displaySel)
wx.EVT_MENU(self,expandID,expandAll)
wx.EVT_MENU(self,colapseID,colapseAll)
wx.EVT_MENU(self,printCID,displaySelChildren)
wx.EVT_MENU(self,colapseSID,colapseSel)
wx.EVT_MENU(self,expandSID,expandSel)
wx.EVT_MENU(self,findID,findNote)
x,y = event.GetPosition()
self.PopupMenu(menu, wx.Point(x, y))
menu.Destroy()
def FindNode(self,elements,name):
if elements.Name == name:
self.found_node = elements
for child in elements.Children:
self.FindNode(child,name)
def FindNodeChildren(self,elements,name,pfound):
found = 0
if elements.Name == name or pfound:
found = 1
for child in elements.Children:
if not child.Children:
self.found_node_children.append(child)
for child in elements.Children:
self.FindNodeChildren(child,name,found)
found = 0
def FixNode(self,elements,name,pos):
if elements.Name == name:
elements.Point = pos
for child in elements.Children:
self.FixNode(child,name,pos)
def DoChildren(self,node):
parent = None
for m in self.objects:
if m.String == node.Name:
parent = m
if node.Children:
for child in node.Children:
fobj = None
for obj in self.objects:
if obj.String == child.Name:
fobj = obj
fcon = None
for con in self.connectors:
if con.Object1.String == parent.String and
con.Object2.String == fobj.String:
fcon = con
if fcon and fobj:
if self.expand:
fobj.Show()
fcon.Show()
else:
fobj.Hide()
fcon.Hide()
self.DoChildren(child)
def OnMove(self, event):
"""
Updates the status bar with the world coordinates
and moves the object it is clicked on
"""
self.SetStatusText("%.4f, %.4f"%tuple(event.Coords))
if self.Moving:
dxy = event.GetPosition() - self.StartPoint
# Draw the Moving Object:
dc = wx.ClientDC(self.Canvas)
dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH))
dc.SetBrush(wx.TRANSPARENT_BRUSH)
dc.SetLogicalFunction(wx.XOR)
if self.MoveObject is not None:
dc.DrawPolygon(self.MoveObject)
self.MoveObject = self.StartObject + dxy
dc.DrawPolygon(self.MoveObject)
if self._leftclicked:
dc = wx.ClientDC(self.Canvas)
dc.SetPen(wx.Pen(wx.Colour(200,200,200), 1, wx.SOLID))
dc.SetBrush(wx.Brush(wx.Colour(255,255,255),wx.TRANSPARENT))
dc.SetLogicalFunction(wx.XOR)
dc.ResetBoundingBox()
dc.BeginDrawing()
w = (self.m_savepoint.x - self.m_stpoint.x)
h = (self.m_savepoint.y - self.m_stpoint.y)
# To erase previous rectangle
dc.DrawRectangle(self.m_stpoint.x, self.m_stpoint.y, w, h)
# Draw new rectangle
self.m_endpoint = event.GetPosition()
self.end = wx.Point(event.Coords[0],event.Coords[1])
w = (self.m_endpoint.x - self.m_stpoint.x)
h = (self.m_endpoint.y - self.m_stpoint.y)
# Set clipping region to rectangle corners
dc.SetClippingRegion(self.m_stpoint.x, self.m_stpoint.y, w,h)
dc.DrawRectangle(self.m_stpoint.x, self.m_stpoint.y, w, h)
dc.EndDrawing()
self.m_savepoint = self.m_endpoint
def FindTree(self,node):
x,y = node.Point
pos = wx.Point(x,y)
left = self.start.x
right = self.end.x
top = self.start.y
bottom = self.end.y
if self.end.x > self.start.x:
right = self.end.x
left = self.start.x
if self.end.y > self.start.y:
top = self.end.y
bottom = self.start.y
if self.end.x < self.start.x:
right = self.start.x
left = self.end.x
if self.end.y < self.start.y:
top = self.start.y
bottom = self.end.y
if right > pos.x and left < pos.x and top > pos.y and bottom < pos.y:
self.search_object = None
self.FindDrawObject(node)
self.search_object.BackgroundColor = "Blue"
self.search_object.SetFillColor("Blue")
if not self._isctrl:
for obj in self.selected_objects:
obj.BackgroundColor = "Yellow"
obj.SetFillColor("Yellow")
self.selected_objects = [self.search_object]
else:
self.selected_objects.append(self.search_object)
self.search_object = None
def FindDrawObject(self,node):
for m in self.objects:
if m.String == node.Name:
self.search_object = m
if node.Children and not self.search_object:
for child in node.Children:
self.FindDrawObject(child)
def OnLeftUp(self, event):
if self._leftclicked:
dc = wx.ClientDC(self.Canvas)
dc.SetPen(wx.Pen(wx.Colour(200,200,200), 1, wx.SOLID))
dc.SetBrush(wx.Brush(wx.Colour(255,255,255),wx.TRANSPARENT))
dc.SetLogicalFunction(wx.XOR)
dc.ResetBoundingBox()
dc.BeginDrawing()
w = (self.m_savepoint.x - self.m_stpoint.x)
h = (self.m_savepoint.y - self.m_stpoint.y)
# To erase previous rectangle
dc.DrawRectangle(self.m_stpoint.x, self.m_stpoint.y, w, h)
dc.EndDrawing()
if not self._isctrl:
for obj in self.selected_objects:
obj.BackgroundColor = "Yellow"
obj.SetFillColor("Yellow")
temp = self._isctrl
self._isctrl = True
TraverseTree(self.elements,self.FindTree)
self.Canvas.Draw(True)
self._isctrl = temp
self._selected = True
self._leftclicked = False
if self.Moving:
self.Moving = False
if self.MoveObject is not None:
pos = wx.Point(event.Coords[0],event.Coords[1])
self.FixNode(self.elements,self.selected.String,pos)
dxy = event.GetPosition() - self.StartPoint
dxy = self.Canvas.ScalePixelToWorld(dxy)
self.MovingObject.Move(dxy)
self.MoveTri = None
self.MovingObject.BackgroundColor = "Yellow"
self.MovingObject.SetFillColor("Yellow")
self.selected = None
self.selected_objects = []
self.Canvas.Draw(True)
def OnLeftDown(self,event):
self.m_stpoint = event.GetPosition()
self.m_savepoint = self.m_stpoint
self._selected = False
self._leftclicked = True
self.start=wx.Point(event.Coords[0],event.Coords[1])
app = wx.PySimpleApp(0)
DrawFrame(None, -1, "FloatCanvas Tree Demo App", wx.DefaultPosition, (700,700) )
app.MainLoop()
_______________________________________________
FloatCanvas mailing list
[email protected]
http://mail.mithis.com/cgi-bin/mailman/listinfo/floatcanvas