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

Reply via email to