]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/samples/ide/activegrid/tool/AbstractEditor.py
Added the ActiveGrid IDE as a sample application
[wxWidgets.git] / wxPython / samples / ide / activegrid / tool / AbstractEditor.py
diff --git a/wxPython/samples/ide/activegrid/tool/AbstractEditor.py b/wxPython/samples/ide/activegrid/tool/AbstractEditor.py
new file mode 100644 (file)
index 0000000..8bda446
--- /dev/null
@@ -0,0 +1,641 @@
+#----------------------------------------------------------------------------
+# Name:         AbstractEditor.py
+# Purpose:      Non-text editor for DataModel and Process
+#
+# Author:       Peter Yared, Morgan Hua
+#
+# Created:      7/28/04
+# CVS-ID:       $Id$
+# Copyright:    (c) 2004-2005 ActiveGrid, Inc.
+# License:      wxWindows License
+#----------------------------------------------------------------------------
+
+
+import wx
+import wx.lib.docview
+import wx.lib.ogl as ogl
+import PropertyService
+_ = wx.GetTranslation
+
+
+SELECT_BRUSH = wx.Brush("BLUE", wx.SOLID)
+SHAPE_BRUSH = wx.Brush("WHEAT", wx.SOLID)
+LINE_BRUSH = wx.BLACK_BRUSH
+
+
+def GetRawModel(model):
+    if hasattr(model, "GetRawModel"):
+        rawModel = model.GetRawModel()
+    else:
+        rawModel = model
+    return rawModel
+
+
+class CanvasView(wx.lib.docview.View):
+
+
+    #----------------------------------------------------------------------------
+    # Overridden methods
+    #----------------------------------------------------------------------------
+
+
+    def __init__(self, brush = SHAPE_BRUSH):
+        wx.lib.docview.View.__init__(self)
+        self._brush = brush
+        self._canvas = None
+        self._pt1 = None
+        self._pt2 = None
+        self._needEraseLasso = False
+        self._propShape = None
+
+
+    def OnCreate(self, doc, flags):
+        frame = wx.GetApp().CreateDocumentFrame(self, doc, flags)
+        frame.Show()
+        sizer = wx.BoxSizer()
+        self._CreateCanvas(frame)
+        sizer.Add(self._canvas, 1, wx.EXPAND, 0)
+        frame.SetSizer(sizer)
+        frame.Layout()        
+        self.Activate()
+        return True
+
+
+    def OnActivateView(self, activate, activeView, deactiveView):
+        if activate and self._canvas:
+            # In MDI mode just calling set focus doesn't work and in SDI mode using CallAfter causes an endless loop
+            if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI:
+                self._canvas.SetFocus()
+            else:
+                wx.CallAfter(self._canvas.SetFocus)
+
+
+    def OnClose(self, deleteWindow = True):
+        statusC = wx.GetApp().CloseChildDocuments(self.GetDocument())
+        statusP = wx.lib.docview.View.OnClose(self, deleteWindow = deleteWindow)
+        if hasattr(self, "ClearOutline"):
+            wx.CallAfter(self.ClearOutline)  # need CallAfter because when closing the document, it is Activated and then Close, so need to match OnActivateView's CallAfter
+        if not (statusC and statusP):
+            return False
+        self.Activate(False)
+        if deleteWindow and self.GetFrame():
+            self.GetFrame().Destroy()
+        return True
+
+
+    def _CreateCanvas(self, parent):
+        self._canvas = ogl.ShapeCanvas(parent)
+        wx.EVT_LEFT_DOWN(self._canvas, self.OnLeftClick)
+        wx.EVT_LEFT_UP(self._canvas, self.OnLeftUp)
+        wx.EVT_MOTION(self._canvas, self.OnLeftDrag)
+        wx.EVT_LEFT_DCLICK(self._canvas, self.OnLeftDoubleClick)
+        wx.EVT_KEY_DOWN(self._canvas, self.OnKeyPressed)
+
+        maxWidth = 2000
+        maxHeight = 16000
+        self._canvas.SetScrollbars(20, 20, maxWidth / 20, maxHeight / 20)
+        
+        self._canvas.SetBackgroundColour(wx.WHITE)
+        self._diagram = ogl.Diagram()
+        self._canvas.SetDiagram(self._diagram)
+        self._diagram.SetCanvas(self._canvas)
+
+
+    def OnKeyPressed(self, event):
+        key = event.KeyCode()
+        if key == wx.WXK_DELETE:
+            self.OnClear(event)
+        else:
+            event.Skip()
+
+
+    def OnLeftClick(self, event):
+        self.EraseRubberBand()
+
+        dc = wx.ClientDC(self._canvas)
+        self._canvas.PrepareDC(dc)
+
+        # keep track of mouse down for group select
+        self._pt1 = event.GetLogicalPosition(dc)  # this takes into account scrollbar offset
+        self._pt2 = None
+
+        shape = self._canvas.FindShape(self._pt1[0], self._pt1[1])[0]
+        if shape:
+            self.BringToFront(shape)
+
+            self._pt1 = None
+            event.Skip()  # pass on event to shape handler to take care of selection
+
+            return
+        elif event.ControlDown() or event.ShiftDown():    # extend select, don't deselect
+            pass
+        else:
+            # click on empty part of canvas, deselect everything
+            needRefresh = False
+            for shape in self._diagram.GetShapeList():
+                if hasattr(shape, "GetModel"):
+                    if shape.Selected():
+                        needRefresh = True
+                        shape.Select(False, dc)
+            if needRefresh:
+                self._canvas.Redraw(dc)
+
+            self.SetPropertyModel(None)
+
+        if len(self.GetSelection()) == 0:
+            self.SetPropertyShape(None)
+
+
+
+    def OnLeftDoubleClick(self, event):
+        propertyService = wx.GetApp().GetService(PropertyService.PropertyService)
+        if propertyService:
+            propertyService.ShowWindow()
+
+
+    def OnLeftDrag(self, event):
+        # draw lasso for group select
+        if self._pt1 and event.LeftIsDown():   # we are in middle of lasso selection
+            self.EraseRubberBand()
+
+            dc = wx.ClientDC(self._canvas)
+            self._canvas.PrepareDC(dc)
+            self._pt2 = event.GetLogicalPosition(dc)  # this takes into account scrollbar offset
+            self.DrawRubberBand()
+        else:
+            event.Skip()
+
+
+    def OnLeftUp(self, event):
+        # do group select
+        if self._needEraseLasso:
+            self.EraseRubberBand()
+
+            dc = wx.ClientDC(self._canvas)
+            self._canvas.PrepareDC(dc)
+            x1, y1 = self._pt1
+            x2, y2 = event.GetLogicalPosition(dc)  # this takes into account scrollbar offset
+
+            tol = self._diagram.GetMouseTolerance()
+            if abs(x1 - x2) > tol or abs(y1 - y2) > tol:
+                # make sure x1 < x2 and y1 < y2 to make comparison test easier
+                if x1 > x2:
+                    temp = x1
+                    x1 = x2
+                    x2 = temp
+                if y1 > y2:
+                    temp = y1
+                    y1 = y2
+                    y2 = temp
+
+                for shape in self._diagram.GetShapeList():
+                    if not shape.GetParent() and hasattr(shape, "GetModel"):  # if part of a composite, don't select it
+                        x, y = shape.GetX(), shape.GetY()
+                        width, height = shape.GetBoundingBoxMax()
+                        selected = x1 < x - width/2 and x2 > x + width/2 and y1 < y - height/2 and y2 > y + height/2
+                        if event.ControlDown() or event.ShiftDown():    # extend select, don't deselect
+                            if selected:
+                                shape.Select(selected, dc)
+                        else:   # select items in lasso and deselect items out of lasso
+                            shape.Select(selected, dc)
+                self._canvas.Redraw(dc)
+            else:
+                event.Skip()
+        else:
+            event.Skip()
+
+
+    def EraseRubberBand(self):
+        if self._needEraseLasso:
+            self._needEraseLasso = False
+
+            dc = wx.ClientDC(self._canvas)
+            self._canvas.PrepareDC(dc)
+            dc.SetLogicalFunction(wx.XOR)
+            pen = wx.Pen(wx.Colour(200, 200, 200), 1, wx.SHORT_DASH)
+            dc.SetPen(pen)
+            brush = wx.Brush(wx.Colour(255, 255, 255), wx.TRANSPARENT)
+            dc.SetBrush(brush)
+            dc.ResetBoundingBox()
+            dc.BeginDrawing()
+
+            x1, y1 = self._pt1
+            x2, y2 = self._pt2
+
+            # make sure x1 < x2 and y1 < y2
+            # this will make (x1, y1) = upper left corner
+            if x1 > x2:
+                temp = x1
+                x1 = x2
+                x2 = temp
+            if y1 > y2:
+                temp = y1
+                y1 = y2
+                y2 = temp
+
+            # erase previous outline
+            dc.SetClippingRegion(x1, y1, x2 - x1, y2 - y1)
+            dc.DrawRectangle(x1, y1, x2 - x1, y2 - y1)
+            dc.EndDrawing()
+
+
+    def DrawRubberBand(self):
+        self._needEraseLasso = True
+
+        dc = wx.ClientDC(self._canvas)
+        self._canvas.PrepareDC(dc)
+        dc.SetLogicalFunction(wx.XOR)
+        pen = wx.Pen(wx.Colour(200, 200, 200), 1, wx.SHORT_DASH)
+        dc.SetPen(pen)
+        brush = wx.Brush(wx.Colour(255, 255, 255), wx.TRANSPARENT)
+        dc.SetBrush(brush)
+        dc.ResetBoundingBox()
+        dc.BeginDrawing()
+
+        x1, y1 = self._pt1
+        x2, y2 = self._pt2
+
+        # make sure x1 < x2 and y1 < y2
+        # this will make (x1, y1) = upper left corner
+        if x1 > x2:
+            temp = x1
+            x1 = x2
+            x2 = temp
+        if y1 > y2:
+            temp = y1
+            y1 = y2
+            y2 = temp
+
+        # draw outline
+        dc.SetClippingRegion(x1, y1, x2 - x1, y2 - y1)
+        dc.DrawRectangle(x1, y1, x2 - x1, y2 - y1)
+        dc.EndDrawing()
+
+
+    def FindParkingSpot(self, width, height):
+        """ given a width and height, find a upper left corner where shape can be parked without overlapping other shape """
+        offset = 30  # space between shapes
+        x = offset
+        y = offset
+        maxX = 700  # max distance to the right where we'll place tables
+        noParkingSpot = True
+
+        while noParkingSpot:
+            point = self.isSpotOccupied(x, y, width, height)
+            if point:
+                x = point[0] + offset
+                if x > maxX:
+                    x = offset
+                    y = point[1] + offset
+            else:
+                noParkingSpot = False
+
+        return x, y
+
+
+    def isSpotOccupied(self, x, y, width, height):
+        """ returns None if at x,y,width,height no object occupies that rectangle,
+            otherwise returns lower right corner of object that occupies given x,y position
+        """
+        x2 = x + width
+        y2 = y + height
+
+        for shape in self._diagram.GetShapeList():
+            if isinstance(shape, ogl.RectangleShape) or isinstance(shape, ogl.EllipseShape):
+                if shape.GetParent() and isinstance(shape.GetParent(), ogl.CompositeShape):
+                    # skip, part of a composite shape
+                    continue
+
+                if hasattr(shape, "GetModel"):
+                    other_x, other_y, other_width, other_height = shape.GetModel().getEditorBounds()
+                    other_x2 = other_x + other_width
+                    other_y2 = other_y + other_height
+                else:
+                    # shapes x,y are at the center of the shape, need to transform to upper left coordinate
+                    other_width, other_height = shape.GetBoundingBoxMax()
+                    other_x = shape.GetX() - other_width/2
+                    other_y = shape.GetY() - other_height/2
+
+                other_x2 = other_x + other_width
+                other_y2 = other_y + other_height
+                # intersection check
+                if ((other_x2 < other_x or other_x2 > x) and
+                    (other_y2 < other_y or other_y2 > y) and
+                    (x2 < x or x2 > other_x) and
+                    (y2 < y or y2 > other_y)):
+                    return (other_x2, other_y2)
+        return None
+
+
+    #----------------------------------------------------------------------------
+    # Canvas methods
+    #----------------------------------------------------------------------------
+
+    def AddShape(self, shape, x = None, y = None, pen = None, brush = None, text = None, eventHandler = None):
+        if isinstance(shape, ogl.CompositeShape):
+            dc = wx.ClientDC(self._canvas)
+            self._canvas.PrepareDC(dc)
+            shape.Move(dc, x, y)
+        else:
+            shape.SetDraggable(True, True)
+        shape.SetCanvas(self._canvas)
+
+        if x:
+            shape.SetX(x)
+        if y:
+            shape.SetY(y)
+        shape.SetCentreResize(False)
+        if pen:
+            shape.SetPen(pen)
+        if brush:
+            shape.SetBrush(brush)
+        if text:
+            shape.AddText(text)
+        shape.SetShadowMode(ogl.SHADOW_RIGHT)
+        self._diagram.AddShape(shape)
+        shape.Show(True)
+        if not eventHandler:
+            eventHandler = EditorCanvasShapeEvtHandler(self)
+        eventHandler.SetShape(shape)
+        eventHandler.SetPreviousHandler(shape.GetEventHandler())
+        shape.SetEventHandler(eventHandler)
+        return shape
+
+
+    def RemoveShape(self, model = None, shape = None):
+        if not model and not shape:
+            return
+
+        if not shape:
+            shape = self.GetShape(model)
+
+        if shape:
+            shape.Select(False)
+            self._diagram.RemoveShape(shape)
+            if isinstance(shape, ogl.CompositeShape):
+                shape.RemoveFromCanvas(self._canvas)
+
+
+    def UpdateShape(self, model):
+        for shape in self._diagram.GetShapeList():
+            if hasattr(shape, "GetModel") and shape.GetModel() == model:
+                x, y, w, h = model.getEditorBounds()
+                newX = x + w / 2
+                newY = y + h / 2
+                changed = False
+                if isinstance(shape, ogl.CompositeShape):
+                    if shape.GetX() != newX or shape.GetY() != newY:
+                        dc = wx.ClientDC(self._canvas)
+                        self._canvas.PrepareDC(dc)
+                        shape.SetSize(w, h, True)  # wxBug: SetSize must be before Move because links won't go to the right place
+                        shape.Move(dc, newX, newY)  # wxBug: Move must be before SetSize because links won't go to the right place
+                        changed = True
+                else:
+                    oldw, oldh = shape.GetBoundingBoxMax()
+                    oldx = shape.GetX()
+                    oldy = shape.GetY()
+                    if oldw != w or oldh != h or oldx != newX or oldy != newY:
+                        shape.SetSize(w, h)
+                        shape.SetX(newX)
+                        shape.SetY(newY)
+                        changed = True
+                if changed:
+                    shape.ResetControlPoints()
+                    self._canvas.Refresh()
+                break
+
+
+    def GetShape(self, model):
+        for shape in self._diagram.GetShapeList():
+            if hasattr(shape, "GetModel") and shape.GetModel() == model:
+                return shape
+        return None
+
+
+    def GetSelection(self):
+        return filter(lambda shape: shape.Selected(), self._diagram.GetShapeList())
+
+
+    def SetSelection(self, models, extendSelect = False):
+        dc = wx.ClientDC(self._canvas)
+        self._canvas.PrepareDC(dc)
+        update = False
+        if not isinstance(models, type([])) and not isinstance(models, type(())):
+            models = [models]
+        for shape in self._diagram.GetShapeList():
+            if hasattr(shape, "GetModel"):
+                if shape.Selected() and not shape.GetModel() in models:  # was selected, but not in new list, so deselect, unless extend select
+                    if not extendSelect:
+                        shape.Select(False, dc)
+                        update = True
+                elif not shape.Selected() and shape.GetModel() in models: # was not selected and in new list, so select
+                    shape.Select(True, dc)
+                    update = True
+                elif extendSelect and shape.Selected() and shape.GetModel() in models: # was selected, but extend select means to deselect
+                    shape.Select(False, dc)
+                    update = True
+        if update:
+            self._canvas.Redraw(dc)
+
+
+    def BringToFront(self, shape):
+        if shape.GetParent() and isinstance(shape.GetParent(), ogl.CompositeShape):
+            self._diagram.RemoveShape(shape.GetParent())
+            self._diagram.AddShape(shape.GetParent())
+        else:
+            self._diagram.RemoveShape(shape)
+            self._diagram.AddShape(shape)
+
+
+    def SendToBack(self, shape):
+        if shape.GetParent() and isinstance(shape.GetParent(), ogl.CompositeShape):
+            self._diagram.RemoveShape(shape.GetParent())
+            self._diagram.InsertShape(shape.GetParent())
+        else:
+            self._diagram.RemoveShape(shape)
+            self._diagram.InsertShape(shape)
+
+
+    def ScrollVisible(self, shape):
+        xUnit, yUnit = shape._canvas.GetScrollPixelsPerUnit()
+        scrollX, scrollY = self._canvas.GetViewStart()  # in scroll units
+        scrollW, scrollH = self._canvas.GetSize()  # in pixels
+        w, h = shape.GetBoundingBoxMax() # in pixels
+        x = shape.GetX() - w/2  # convert to upper left coordinate from center
+        y = shape.GetY() - h/2  # convert to upper left coordinate from center
+
+        if x >= scrollX*xUnit and x <= scrollX*xUnit + scrollW:  # don't scroll if already visible
+            x = -1
+        else:
+            x = x/xUnit
+
+        if y >= scrollY*yUnit and y <= scrollY*yUnit + scrollH:  # don't scroll if already visible
+            y = -1
+        else:
+            y = y/yUnit
+
+        self._canvas.Scroll(x, y)  # in scroll units
+
+
+    def SetPropertyShape(self, shape):
+        # no need to highlight if no PropertyService is running
+        propertyService = wx.GetApp().GetService(PropertyService.PropertyService)
+        if not propertyService:
+            return
+
+        if shape == self._propShape:
+            return
+
+        if hasattr(shape, "GetPropertyShape"):
+            shape = shape.GetPropertyShape()
+
+        dc = wx.ClientDC(self._canvas)
+        self._canvas.PrepareDC(dc)
+        dc.BeginDrawing()
+
+        # erase old selection if it still exists
+        if self._propShape and self._propShape in self._diagram.GetShapeList():
+            self._propShape.SetBrush(self._brush)
+            if (self._propShape._textColourName in ["BLACK", "WHITE"]):  # Would use GetTextColour() but it is broken
+                self._propShape.SetTextColour("BLACK", 0)
+            self._propShape.Draw(dc)
+
+        # set new selection
+        self._propShape = shape
+
+        # draw new selection
+        if self._propShape and self._propShape in self._diagram.GetShapeList():
+            self._propShape.SetBrush(SELECT_BRUSH)
+            if (self._propShape._textColourName in ["BLACK", "WHITE"]):  # Would use GetTextColour() but it is broken
+                self._propShape.SetTextColour("WHITE", 0)
+            self._propShape.Draw(dc)
+
+        dc.EndDrawing()
+
+
+    #----------------------------------------------------------------------------
+    # Property Service methods
+    #----------------------------------------------------------------------------
+
+    def GetPropertyModel(self):
+        if hasattr(self, "_propModel"):
+            return self._propModel
+        return None
+        
+
+    def SetPropertyModel(self, model):
+        # no need to set the model if no PropertyService is running
+        propertyService = wx.GetApp().GetService(PropertyService.PropertyService)
+        if not propertyService:
+            return
+            
+        if hasattr(self, "_propModel") and model == self._propModel:
+            return
+            
+        self._propModel = model
+        propertyService.LoadProperties(self._propModel, self.GetDocument())
+
+
+class EditorCanvasShapeMixin:
+
+    def GetModel(self):
+        return self._model
+
+
+    def SetModel(self, model):
+        self._model = model
+
+
+class EditorCanvasShapeEvtHandler(ogl.ShapeEvtHandler):
+
+    """ wxBug: Bug in OLG package.  With wxShape.SetShadowMode() turned on, when we set the size,
+        the width/height is larger by 6 pixels.  Need to subtract this value from width and height when we
+        resize the object.
+    """
+    SHIFT_KEY = 1
+    CONTROL_KEY = 2
+
+    def __init__(self, view):
+        ogl.ShapeEvtHandler.__init__(self)
+        self._view = view
+
+
+    def OnLeftClick(self, x, y, keys = 0, attachment = 0):
+        shape = self.GetShape()
+        if hasattr(shape, "GetModel"):  # Workaround, on drag, we should deselect all other objects and select the clicked on object
+            model = shape.GetModel()
+        else:
+            shape = shape.GetParent()
+            if shape:
+                model = shape.GetModel()
+
+        self._view.SetSelection(model, keys == self.SHIFT_KEY or keys == self.CONTROL_KEY)
+        self._view.SetPropertyShape(shape)
+        self._view.SetPropertyModel(model)
+
+
+    def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
+        ogl.ShapeEvtHandler.OnEndDragLeft(self, x, y, keys, attachment)
+        shape = self.GetShape()
+        if hasattr(shape, "GetModel"):  # Workaround, on drag, we should deselect all other objects and select the clicked on object
+            model = shape.GetModel()
+        else:
+            parentShape = shape.GetParent()
+            if parentShape:
+                model = parentShape.GetModel()
+        self._view.SetSelection(model, keys == self.SHIFT_KEY or keys == self.CONTROL_KEY)
+
+
+    def OnMovePost(self, dc, x, y, oldX, oldY, display):
+        if x == oldX and y == oldY:
+            return
+        if not self._view.GetDocument():
+            return
+        shape = self.GetShape()
+        if isinstance(shape, EditorCanvasShapeMixin) and shape.Draggable():
+            model = shape.GetModel()
+            if hasattr(model, "getEditorBounds") and model.getEditorBounds():
+                x, y, w, h = model.getEditorBounds()
+                newX = shape.GetX() - shape.GetBoundingBoxMax()[0] / 2
+                newY = shape.GetY() - shape.GetBoundingBoxMax()[1] / 2
+                newWidth = shape.GetBoundingBoxMax()[0]
+                newHeight = shape.GetBoundingBoxMax()[1]
+                if shape._shadowMode != ogl.SHADOW_NONE:
+                    newWidth -= shape._shadowOffsetX
+                    newHeight -= shape._shadowOffsetY
+                newbounds = (newX, newY, newWidth, newHeight)
+    
+                if x != newX or y != newY or w != newWidth or h != newHeight:
+                    self._view.GetDocument().GetCommandProcessor().Submit(EditorCanvasUpdateShapeBoundariesCommand(self._view.GetDocument(), model, newbounds))
+
+
+    def Draw(self, dc):
+        pass
+
+
+class EditorCanvasUpdateShapeBoundariesCommand(wx.lib.docview.Command):
+
+
+    def __init__(self, canvasDocument, model, newbounds):
+        wx.lib.docview.Command.__init__(self, canUndo = True)
+        self._canvasDocument = canvasDocument
+        self._model = model
+        self._oldbounds = model.getEditorBounds()
+        self._newbounds = newbounds
+
+
+    def GetName(self):
+        name = self._canvasDocument.GetNameForObject(self._model)
+        if not name:
+            name = ""
+            print "ERROR: AbstractEditor.EditorCanvasUpdateShapeBoundariesCommand.GetName: unable to get name for ", self._model
+        return _("Move/Resize %s") % name
+
+
+    def Do(self):
+        return self._canvasDocument.UpdateEditorBoundaries(self._model, self._newbounds)
+
+
+    def Undo(self):
+        return self._canvasDocument.UpdateEditorBoundaries(self._model, self._oldbounds)
+