1 #----------------------------------------------------------------------------
2 # Name: AbstractEditor.py
3 # Purpose: Non-text editor for DataModel and Process
5 # Author: Peter Yared, Morgan Hua
9 # Copyright: (c) 2004-2005 ActiveGrid, Inc.
10 # License: wxWindows License
11 #----------------------------------------------------------------------------
16 import wx
.lib
.ogl
as ogl
17 import PropertyService
21 SELECT_BRUSH
= wx
.Brush("BLUE", wx
.SOLID
)
22 SHAPE_BRUSH
= wx
.Brush("WHEAT", wx
.SOLID
)
23 LINE_BRUSH
= wx
.BLACK_BRUSH
24 INACTIVE_SELECT_BRUSH
= wx
.Brush("LIGHT BLUE", wx
.SOLID
)
27 def GetRawModel(model
):
28 if hasattr(model
, "GetRawModel"):
29 rawModel
= model
.GetRawModel()
35 class CanvasView(wx
.lib
.docview
.View
):
38 #----------------------------------------------------------------------------
40 #----------------------------------------------------------------------------
43 def __init__(self
, brush
= SHAPE_BRUSH
):
44 wx
.lib
.docview
.View
.__init
__(self
)
49 self
._needEraseLasso
= False
50 self
._propShape
= None
52 self
._maxHeight
= 16000
56 """ for Print Preview and Print """
58 self
._canvas
.Redraw(dc
)
62 def OnCreate(self
, doc
, flags
):
63 frame
= wx
.GetApp().CreateDocumentFrame(self
, doc
, flags
)
66 self
._CreateCanvas
(frame
)
67 sizer
.Add(self
._canvas
, 1, wx
.EXPAND
, 0)
74 def OnActivateView(self
, activate
, activeView
, deactiveView
):
75 if activate
and self
._canvas
:
76 # In MDI mode just calling set focus doesn't work and in SDI mode using CallAfter causes an endless loop
77 if self
.GetDocumentManager().GetFlags() & wx
.lib
.docview
.DOC_SDI
:
80 wx
.CallAfter(self
.SetFocus
)
85 self
._canvas
.SetFocus()
88 def OnFocus(self
, event
):
89 self
.FocusColorPropertyShape(True)
93 def FocusOnClick(self
, event
):
98 def OnKillFocus(self
, event
):
99 self
.FocusColorPropertyShape(False)
104 winWithFocus
= wx
.Window
.FindFocus()
108 if winWithFocus
== self
._canvas
:
110 winWithFocus
= winWithFocus
.GetParent()
114 def OnClose(self
, deleteWindow
= True):
115 statusC
= wx
.GetApp().CloseChildDocuments(self
.GetDocument())
116 statusP
= wx
.lib
.docview
.View
.OnClose(self
, deleteWindow
= deleteWindow
)
117 if hasattr(self
, "ClearOutline"):
118 wx
.CallAfter(self
.ClearOutline
) # need CallAfter because when closing the document, it is Activated and then Close, so need to match OnActivateView's CallAfter
119 if not (statusC
and statusP
):
122 if deleteWindow
and self
.GetFrame():
123 self
.GetFrame().Destroy()
127 def _CreateCanvas(self
, parent
):
128 self
._canvas
= ogl
.ShapeCanvas(parent
)
129 wx
.EVT_LEFT_DOWN(self
._canvas
, self
.OnLeftClick
)
130 wx
.EVT_LEFT_UP(self
._canvas
, self
.OnLeftUp
)
131 wx
.EVT_MOTION(self
._canvas
, self
.OnLeftDrag
)
132 wx
.EVT_LEFT_DCLICK(self
._canvas
, self
.OnLeftDoubleClick
)
133 wx
.EVT_KEY_DOWN(self
._canvas
, self
.OnKeyPressed
)
135 # need this otherwise mouse clicks don't set focus to this view
136 wx
.EVT_LEFT_DOWN(self
._canvas
, self
.FocusOnClick
)
137 wx
.EVT_LEFT_DCLICK(self
._canvas
, self
.FocusOnClick
)
138 wx
.EVT_RIGHT_DOWN(self
._canvas
, self
.FocusOnClick
)
139 wx
.EVT_RIGHT_DCLICK(self
._canvas
, self
.FocusOnClick
)
140 wx
.EVT_MIDDLE_DOWN(self
._canvas
, self
.FocusOnClick
)
141 wx
.EVT_MIDDLE_DCLICK(self
._canvas
, self
.FocusOnClick
)
143 wx
.EVT_KILL_FOCUS(self
._canvas
, self
.OnKillFocus
)
144 wx
.EVT_SET_FOCUS(self
._canvas
, self
.OnFocus
)
146 self
._canvas
.SetScrollbars(20, 20, self
._maxWidth
/ 20, self
._maxHeight
/ 20)
148 self
._canvas
.SetBackgroundColour(wx
.WHITE
)
149 self
._diagram
= ogl
.Diagram()
150 self
._canvas
.SetDiagram(self
._diagram
)
151 self
._diagram
.SetCanvas(self
._canvas
)
154 def OnKeyPressed(self
, event
):
155 key
= event
.KeyCode()
156 if key
== wx
.WXK_DELETE
:
162 def OnLeftClick(self
, event
):
163 self
.EraseRubberBand()
165 dc
= wx
.ClientDC(self
._canvas
)
166 self
._canvas
.PrepareDC(dc
)
168 # keep track of mouse down for group select
169 self
._pt
1 = event
.GetLogicalPosition(dc
) # this takes into account scrollbar offset
172 shape
= self
._canvas
.FindShape(self
._pt
1[0], self
._pt
1[1])[0]
174 self
.BringToFront(shape
)
177 event
.Skip() # pass on event to shape handler to take care of selection
180 elif event
.ControlDown() or event
.ShiftDown(): # extend select, don't deselect
183 # click on empty part of canvas, deselect everything
185 for shape
in self
._diagram
.GetShapeList():
186 if hasattr(shape
, "GetModel"):
189 shape
.Select(False, dc
)
191 self
._canvas
.Redraw(dc
)
193 self
.SetPropertyModel(None)
195 if len(self
.GetSelection()) == 0:
196 self
.SetPropertyShape(None)
200 def OnLeftDoubleClick(self
, event
):
201 propertyService
= wx
.GetApp().GetService(PropertyService
.PropertyService
)
203 propertyService
.ShowWindow()
206 def OnLeftDrag(self
, event
):
207 # draw lasso for group select
208 if self
._pt
1 and event
.LeftIsDown(): # we are in middle of lasso selection
209 self
.EraseRubberBand()
211 dc
= wx
.ClientDC(self
._canvas
)
212 self
._canvas
.PrepareDC(dc
)
213 self
._pt
2 = event
.GetLogicalPosition(dc
) # this takes into account scrollbar offset
214 self
.DrawRubberBand()
219 def OnLeftUp(self
, event
):
221 if self
._needEraseLasso
:
222 self
.EraseRubberBand()
224 dc
= wx
.ClientDC(self
._canvas
)
225 self
._canvas
.PrepareDC(dc
)
227 x2
, y2
= event
.GetLogicalPosition(dc
) # this takes into account scrollbar offset
229 tol
= self
._diagram
.GetMouseTolerance()
230 if abs(x1
- x2
) > tol
or abs(y1
- y2
) > tol
:
231 # make sure x1 < x2 and y1 < y2 to make comparison test easier
241 for shape
in self
._diagram
.GetShapeList():
242 if not shape
.GetParent() and hasattr(shape
, "GetModel"): # if part of a composite, don't select it
243 x
, y
= shape
.GetX(), shape
.GetY()
244 width
, height
= shape
.GetBoundingBoxMax()
245 selected
= x1
< x
- width
/2 and x2
> x
+ width
/2 and y1
< y
- height
/2 and y2
> y
+ height
/2
246 if event
.ControlDown() or event
.ShiftDown(): # extend select, don't deselect
248 shape
.Select(selected
, dc
)
249 else: # select items in lasso and deselect items out of lasso
250 shape
.Select(selected
, dc
)
251 self
._canvas
.Redraw(dc
)
258 def EraseRubberBand(self
):
259 if self
._needEraseLasso
:
260 self
._needEraseLasso
= False
262 dc
= wx
.ClientDC(self
._canvas
)
263 self
._canvas
.PrepareDC(dc
)
264 dc
.SetLogicalFunction(wx
.XOR
)
265 pen
= wx
.Pen(wx
.Colour(200, 200, 200), 1, wx
.SHORT_DASH
)
267 brush
= wx
.Brush(wx
.Colour(255, 255, 255), wx
.TRANSPARENT
)
269 dc
.ResetBoundingBox()
275 # make sure x1 < x2 and y1 < y2
276 # this will make (x1, y1) = upper left corner
286 # erase previous outline
287 dc
.SetClippingRegion(x1
, y1
, x2
- x1
, y2
- y1
)
288 dc
.DrawRectangle(x1
, y1
, x2
- x1
, y2
- y1
)
292 def DrawRubberBand(self
):
293 self
._needEraseLasso
= True
295 dc
= wx
.ClientDC(self
._canvas
)
296 self
._canvas
.PrepareDC(dc
)
297 dc
.SetLogicalFunction(wx
.XOR
)
298 pen
= wx
.Pen(wx
.Colour(200, 200, 200), 1, wx
.SHORT_DASH
)
300 brush
= wx
.Brush(wx
.Colour(255, 255, 255), wx
.TRANSPARENT
)
302 dc
.ResetBoundingBox()
308 # make sure x1 < x2 and y1 < y2
309 # this will make (x1, y1) = upper left corner
320 dc
.SetClippingRegion(x1
, y1
, x2
- x1
, y2
- y1
)
321 dc
.DrawRectangle(x1
, y1
, x2
- x1
, y2
- y1
)
325 def FindParkingSpot(self
, width
, height
):
326 """ given a width and height, find a upper left corner where shape can be parked without overlapping other shape """
327 offset
= 30 # space between shapes
330 maxX
= 700 # max distance to the right where we'll place tables
334 point
= self
.isSpotOccupied(x
, y
, width
, height
)
336 x
= point
[0] + offset
339 y
= point
[1] + offset
341 noParkingSpot
= False
346 def isSpotOccupied(self
, x
, y
, width
, height
):
347 """ returns None if at x,y,width,height no object occupies that rectangle,
348 otherwise returns lower right corner of object that occupies given x,y position
353 for shape
in self
._diagram
.GetShapeList():
354 if isinstance(shape
, ogl
.RectangleShape
) or isinstance(shape
, ogl
.EllipseShape
):
355 if shape
.GetParent() and isinstance(shape
.GetParent(), ogl
.CompositeShape
):
356 # skip, part of a composite shape
359 if hasattr(shape
, "GetModel"):
360 other_x
, other_y
, other_width
, other_height
= shape
.GetModel().getEditorBounds()
361 other_x2
= other_x
+ other_width
362 other_y2
= other_y
+ other_height
364 # shapes x,y are at the center of the shape, need to transform to upper left coordinate
365 other_width
, other_height
= shape
.GetBoundingBoxMax()
366 other_x
= shape
.GetX() - other_width
/2
367 other_y
= shape
.GetY() - other_height
/2
369 other_x2
= other_x
+ other_width
370 other_y2
= other_y
+ other_height
372 if ((other_x2
< other_x
or other_x2
> x
) and
373 (other_y2
< other_y
or other_y2
> y
) and
374 (x2
< x
or x2
> other_x
) and
375 (y2
< y
or y2
> other_y
)):
376 return (other_x2
, other_y2
)
380 #----------------------------------------------------------------------------
382 #----------------------------------------------------------------------------
384 def AddShape(self
, shape
, x
= None, y
= None, pen
= None, brush
= None, text
= None, eventHandler
= None):
385 if isinstance(shape
, ogl
.CompositeShape
):
386 dc
= wx
.ClientDC(self
._canvas
)
387 self
._canvas
.PrepareDC(dc
)
390 shape
.SetDraggable(True, True)
391 shape
.SetCanvas(self
._canvas
)
397 shape
.SetCentreResize(False)
401 shape
.SetBrush(brush
)
404 shape
.SetShadowMode(ogl
.SHADOW_NONE
)
405 self
._diagram
.AddShape(shape
)
408 eventHandler
= EditorCanvasShapeEvtHandler(self
)
409 eventHandler
.SetShape(shape
)
410 eventHandler
.SetPreviousHandler(shape
.GetEventHandler())
411 shape
.SetEventHandler(eventHandler
)
415 def RemoveShape(self
, model
= None, shape
= None):
416 if not model
and not shape
:
420 shape
= self
.GetShape(model
)
424 for line
in shape
.GetLines():
425 shape
.RemoveLine(line
)
426 self
._diagram
.RemoveShape(line
)
427 for obj
in self
._diagram
.GetShapeList():
428 for line
in obj
.GetLines():
429 if self
.IsShapeContained(shape
, line
.GetTo()) or self
.IsShapeContained(shape
, line
.GetFrom()):
431 self
._diagram
.RemoveShape(line
)
435 shape
.RemoveFromCanvas(self
._canvas
)
436 self
._diagram
.RemoveShape(shape
)
439 def IsShapeContained(self
, parent
, shape
):
442 elif shape
.GetParent():
443 return self
.IsShapeContained(parent
, shape
.GetParent())
448 def UpdateShape(self
, model
):
449 for shape
in self
._diagram
.GetShapeList():
450 if hasattr(shape
, "GetModel") and shape
.GetModel() == model
:
451 x
, y
, w
, h
= model
.getEditorBounds()
455 if isinstance(shape
, ogl
.CompositeShape
):
456 if shape
.GetX() != newX
or shape
.GetY() != newY
:
457 dc
= wx
.ClientDC(self
._canvas
)
458 self
._canvas
.PrepareDC(dc
)
459 shape
.SetSize(w
, h
, True) # wxBug: SetSize must be before Move because links won't go to the right place
460 shape
.Move(dc
, newX
, newY
) # wxBug: Move must be before SetSize because links won't go to the right place
463 oldw
, oldh
= shape
.GetBoundingBoxMax()
466 if oldw
!= w
or oldh
!= h
or oldx
!= newX
or oldy
!= newY
:
472 shape
.ResetControlPoints()
473 self
._canvas
.Refresh()
477 def GetShape(self
, model
):
478 for shape
in self
._diagram
.GetShapeList():
479 if hasattr(shape
, "GetModel") and shape
.GetModel() == model
:
484 def GetSelection(self
):
485 return filter(lambda shape
: shape
.Selected(), self
._diagram
.GetShapeList())
488 def SetSelection(self
, models
, extendSelect
= False):
489 dc
= wx
.ClientDC(self
._canvas
)
490 self
._canvas
.PrepareDC(dc
)
492 if not isinstance(models
, type([])) and not isinstance(models
, type(())):
494 for shape
in self
._diagram
.GetShapeList():
495 if hasattr(shape
, "GetModel"):
496 if shape
.Selected() and not shape
.GetModel() in models
: # was selected, but not in new list, so deselect, unless extend select
498 shape
.Select(False, dc
)
500 elif not shape
.Selected() and shape
.GetModel() in models
: # was not selected and in new list, so select
501 shape
.Select(True, dc
)
503 elif extendSelect
and shape
.Selected() and shape
.GetModel() in models
: # was selected, but extend select means to deselect
504 shape
.Select(False, dc
)
507 self
._canvas
.Redraw(dc
)
510 def BringToFront(self
, shape
):
511 if shape
.GetParent() and isinstance(shape
.GetParent(), ogl
.CompositeShape
):
512 self
._diagram
.RemoveShape(shape
.GetParent())
513 self
._diagram
.AddShape(shape
.GetParent())
515 self
._diagram
.RemoveShape(shape
)
516 self
._diagram
.AddShape(shape
)
519 def SendToBack(self
, shape
):
520 if shape
.GetParent() and isinstance(shape
.GetParent(), ogl
.CompositeShape
):
521 self
._diagram
.RemoveShape(shape
.GetParent())
522 self
._diagram
.InsertShape(shape
.GetParent())
524 self
._diagram
.RemoveShape(shape
)
525 self
._diagram
.InsertShape(shape
)
528 def ScrollVisible(self
, shape
):
529 xUnit
, yUnit
= shape
._canvas
.GetScrollPixelsPerUnit()
530 scrollX
, scrollY
= self
._canvas
.GetViewStart() # in scroll units
531 scrollW
, scrollH
= self
._canvas
.GetSize() # in pixels
532 w
, h
= shape
.GetBoundingBoxMax() # in pixels
533 x
= shape
.GetX() - w
/2 # convert to upper left coordinate from center
534 y
= shape
.GetY() - h
/2 # convert to upper left coordinate from center
536 if x
>= scrollX
*xUnit
and x
<= scrollX
*xUnit
+ scrollW
: # don't scroll if already visible
541 if y
>= scrollY
*yUnit
and y
<= scrollY
*yUnit
+ scrollH
: # don't scroll if already visible
546 self
._canvas
.Scroll(x
, y
) # in scroll units
549 def SetPropertyShape(self
, shape
):
550 # no need to highlight if no PropertyService is running
551 propertyService
= wx
.GetApp().GetService(PropertyService
.PropertyService
)
552 if not propertyService
:
555 if shape
== self
._propShape
:
558 if hasattr(shape
, "GetPropertyShape"):
559 shape
= shape
.GetPropertyShape()
561 dc
= wx
.ClientDC(self
._canvas
)
562 self
._canvas
.PrepareDC(dc
)
565 # erase old selection if it still exists
566 if self
._propShape
and self
._propShape
in self
._diagram
.GetShapeList():
567 self
._propShape
.SetBrush(self
._brush
)
568 if (self
._propShape
._textColourName
in ["BLACK", "WHITE"]): # Would use GetTextColour() but it is broken
569 self
._propShape
.SetTextColour("BLACK", 0)
570 self
._propShape
.Draw(dc
)
573 self
._propShape
= shape
576 if self
._propShape
and self
._propShape
in self
._diagram
.GetShapeList():
578 self
._propShape
.SetBrush(SELECT_BRUSH
)
580 self
._propShape
.SetBrush(INACTIVE_SELECT_BRUSH
)
581 if (self
._propShape
._textColourName
in ["BLACK", "WHITE"]): # Would use GetTextColour() but it is broken
582 self
._propShape
.SetTextColour("WHITE", 0)
583 self
._propShape
.Draw(dc
)
588 def FocusColorPropertyShape(self
, gotFocus
=False):
589 # no need to change highlight if no PropertyService is running
590 propertyService
= wx
.GetApp().GetService(PropertyService
.PropertyService
)
591 if not propertyService
:
594 if not self
._propShape
:
597 dc
= wx
.ClientDC(self
._canvas
)
598 self
._canvas
.PrepareDC(dc
)
601 # draw deactivated selection
602 if self
._propShape
and self
._propShape
in self
._diagram
.GetShapeList():
604 self
._propShape
.SetBrush(SELECT_BRUSH
)
606 self
._propShape
.SetBrush(INACTIVE_SELECT_BRUSH
)
607 if (self
._propShape
._textColourName
in ["BLACK", "WHITE"]): # Would use GetTextColour() but it is broken
608 self
._propShape
.SetTextColour("WHITE", 0)
609 self
._propShape
.Draw(dc
)
614 #----------------------------------------------------------------------------
615 # Property Service methods
616 #----------------------------------------------------------------------------
618 def GetPropertyModel(self
):
619 if hasattr(self
, "_propModel"):
620 return self
._propModel
624 def SetPropertyModel(self
, model
):
625 # no need to set the model if no PropertyService is running
626 propertyService
= wx
.GetApp().GetService(PropertyService
.PropertyService
)
627 if not propertyService
:
630 if hasattr(self
, "_propModel") and model
== self
._propModel
:
633 self
._propModel
= model
634 propertyService
.LoadProperties(self
._propModel
, self
.GetDocument())
637 class EditorCanvasShapeMixin
:
643 def SetModel(self
, model
):
647 class EditorCanvasShapeEvtHandler(ogl
.ShapeEvtHandler
):
649 """ wxBug: Bug in OLG package. With wxShape.SetShadowMode() turned on, when we set the size,
650 the width/height is larger by 6 pixels. Need to subtract this value from width and height when we
656 def __init__(self
, view
):
657 ogl
.ShapeEvtHandler
.__init
__(self
)
661 def OnLeftClick(self
, x
, y
, keys
= 0, attachment
= 0):
662 shape
= self
.GetShape()
663 if hasattr(shape
, "GetModel"): # Workaround, on drag, we should deselect all other objects and select the clicked on object
664 model
= shape
.GetModel()
666 shape
= shape
.GetParent()
668 model
= shape
.GetModel()
670 self
._view
.SetSelection(model
, keys
== self
.SHIFT_KEY
or keys
== self
.CONTROL_KEY
)
671 self
._view
.SetPropertyShape(shape
)
672 self
._view
.SetPropertyModel(model
)
675 def OnEndDragLeft(self
, x
, y
, keys
= 0, attachment
= 0):
676 ogl
.ShapeEvtHandler
.OnEndDragLeft(self
, x
, y
, keys
, attachment
)
677 shape
= self
.GetShape()
678 if hasattr(shape
, "GetModel"): # Workaround, on drag, we should deselect all other objects and select the clicked on object
679 model
= shape
.GetModel()
681 parentShape
= shape
.GetParent()
683 model
= parentShape
.GetModel()
684 self
._view
.SetSelection(model
, keys
== self
.SHIFT_KEY
or keys
== self
.CONTROL_KEY
)
687 def OnMovePre(self
, dc
, x
, y
, oldX
, oldY
, display
):
688 """ Prevent objects from being dragged outside of viewable area """
689 if (x
> self
._view
._maxWidth
) or (y
> self
._view
._maxHeight
):
692 return ogl
.ShapeEvtHandler
.OnMovePre(self
, dc
, x
, y
, oldX
, oldY
, display
)
695 def OnMovePost(self
, dc
, x
, y
, oldX
, oldY
, display
):
696 """ Update the model's record of where the shape should be. Also enable redo/undo. """
697 if x
== oldX
and y
== oldY
:
699 if not self
._view
.GetDocument():
701 shape
= self
.GetShape()
702 if isinstance(shape
, EditorCanvasShapeMixin
) and shape
.Draggable():
703 model
= shape
.GetModel()
704 if hasattr(model
, "getEditorBounds") and model
.getEditorBounds():
705 x
, y
, w
, h
= model
.getEditorBounds()
706 newX
= shape
.GetX() - shape
.GetBoundingBoxMax()[0] / 2
707 newY
= shape
.GetY() - shape
.GetBoundingBoxMax()[1] / 2
708 newWidth
= shape
.GetBoundingBoxMax()[0]
709 newHeight
= shape
.GetBoundingBoxMax()[1]
710 if shape
._shadowMode
!= ogl
.SHADOW_NONE
:
711 newWidth
-= shape
._shadowOffsetX
712 newHeight
-= shape
._shadowOffsetY
713 newbounds
= (newX
, newY
, newWidth
, newHeight
)
715 if x
!= newX
or y
!= newY
or w
!= newWidth
or h
!= newHeight
:
716 self
._view
.GetDocument().GetCommandProcessor().Submit(EditorCanvasUpdateShapeBoundariesCommand(self
._view
.GetDocument(), model
, newbounds
))
723 class EditorCanvasUpdateShapeBoundariesCommand(wx
.lib
.docview
.Command
):
726 def __init__(self
, canvasDocument
, model
, newbounds
):
727 wx
.lib
.docview
.Command
.__init
__(self
, canUndo
= True)
728 self
._canvasDocument
= canvasDocument
730 self
._oldbounds
= model
.getEditorBounds()
731 self
._newbounds
= newbounds
735 name
= self
._canvasDocument
.GetNameForObject(self
._model
)
738 print "ERROR: AbstractEditor.EditorCanvasUpdateShapeBoundariesCommand.GetName: unable to get name for ", self
._model
739 return _("Move/Resize %s") % name
743 return self
._canvasDocument
.UpdateEditorBoundaries(self
._model
, self
._newbounds
)
747 return self
._canvasDocument
.UpdateEditorBoundaries(self
._model
, self
._oldbounds
)