]> git.saurik.com Git - wxWidgets.git/blob - wxPython/samples/ide/activegrid/tool/AbstractEditor.py
8bda44696df8f65588c0e0e7ec4e0ae82fb565ef
[wxWidgets.git] / wxPython / samples / ide / activegrid / tool / AbstractEditor.py
1 #----------------------------------------------------------------------------
2 # Name: AbstractEditor.py
3 # Purpose: Non-text editor for DataModel and Process
4 #
5 # Author: Peter Yared, Morgan Hua
6 #
7 # Created: 7/28/04
8 # CVS-ID: $Id$
9 # Copyright: (c) 2004-2005 ActiveGrid, Inc.
10 # License: wxWindows License
11 #----------------------------------------------------------------------------
12
13
14 import wx
15 import wx.lib.docview
16 import wx.lib.ogl as ogl
17 import PropertyService
18 _ = wx.GetTranslation
19
20
21 SELECT_BRUSH = wx.Brush("BLUE", wx.SOLID)
22 SHAPE_BRUSH = wx.Brush("WHEAT", wx.SOLID)
23 LINE_BRUSH = wx.BLACK_BRUSH
24
25
26 def GetRawModel(model):
27 if hasattr(model, "GetRawModel"):
28 rawModel = model.GetRawModel()
29 else:
30 rawModel = model
31 return rawModel
32
33
34 class CanvasView(wx.lib.docview.View):
35
36
37 #----------------------------------------------------------------------------
38 # Overridden methods
39 #----------------------------------------------------------------------------
40
41
42 def __init__(self, brush = SHAPE_BRUSH):
43 wx.lib.docview.View.__init__(self)
44 self._brush = brush
45 self._canvas = None
46 self._pt1 = None
47 self._pt2 = None
48 self._needEraseLasso = False
49 self._propShape = None
50
51
52 def OnCreate(self, doc, flags):
53 frame = wx.GetApp().CreateDocumentFrame(self, doc, flags)
54 frame.Show()
55 sizer = wx.BoxSizer()
56 self._CreateCanvas(frame)
57 sizer.Add(self._canvas, 1, wx.EXPAND, 0)
58 frame.SetSizer(sizer)
59 frame.Layout()
60 self.Activate()
61 return True
62
63
64 def OnActivateView(self, activate, activeView, deactiveView):
65 if activate and self._canvas:
66 # In MDI mode just calling set focus doesn't work and in SDI mode using CallAfter causes an endless loop
67 if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI:
68 self._canvas.SetFocus()
69 else:
70 wx.CallAfter(self._canvas.SetFocus)
71
72
73 def OnClose(self, deleteWindow = True):
74 statusC = wx.GetApp().CloseChildDocuments(self.GetDocument())
75 statusP = wx.lib.docview.View.OnClose(self, deleteWindow = deleteWindow)
76 if hasattr(self, "ClearOutline"):
77 wx.CallAfter(self.ClearOutline) # need CallAfter because when closing the document, it is Activated and then Close, so need to match OnActivateView's CallAfter
78 if not (statusC and statusP):
79 return False
80 self.Activate(False)
81 if deleteWindow and self.GetFrame():
82 self.GetFrame().Destroy()
83 return True
84
85
86 def _CreateCanvas(self, parent):
87 self._canvas = ogl.ShapeCanvas(parent)
88 wx.EVT_LEFT_DOWN(self._canvas, self.OnLeftClick)
89 wx.EVT_LEFT_UP(self._canvas, self.OnLeftUp)
90 wx.EVT_MOTION(self._canvas, self.OnLeftDrag)
91 wx.EVT_LEFT_DCLICK(self._canvas, self.OnLeftDoubleClick)
92 wx.EVT_KEY_DOWN(self._canvas, self.OnKeyPressed)
93
94 maxWidth = 2000
95 maxHeight = 16000
96 self._canvas.SetScrollbars(20, 20, maxWidth / 20, maxHeight / 20)
97
98 self._canvas.SetBackgroundColour(wx.WHITE)
99 self._diagram = ogl.Diagram()
100 self._canvas.SetDiagram(self._diagram)
101 self._diagram.SetCanvas(self._canvas)
102
103
104 def OnKeyPressed(self, event):
105 key = event.KeyCode()
106 if key == wx.WXK_DELETE:
107 self.OnClear(event)
108 else:
109 event.Skip()
110
111
112 def OnLeftClick(self, event):
113 self.EraseRubberBand()
114
115 dc = wx.ClientDC(self._canvas)
116 self._canvas.PrepareDC(dc)
117
118 # keep track of mouse down for group select
119 self._pt1 = event.GetLogicalPosition(dc) # this takes into account scrollbar offset
120 self._pt2 = None
121
122 shape = self._canvas.FindShape(self._pt1[0], self._pt1[1])[0]
123 if shape:
124 self.BringToFront(shape)
125
126 self._pt1 = None
127 event.Skip() # pass on event to shape handler to take care of selection
128
129 return
130 elif event.ControlDown() or event.ShiftDown(): # extend select, don't deselect
131 pass
132 else:
133 # click on empty part of canvas, deselect everything
134 needRefresh = False
135 for shape in self._diagram.GetShapeList():
136 if hasattr(shape, "GetModel"):
137 if shape.Selected():
138 needRefresh = True
139 shape.Select(False, dc)
140 if needRefresh:
141 self._canvas.Redraw(dc)
142
143 self.SetPropertyModel(None)
144
145 if len(self.GetSelection()) == 0:
146 self.SetPropertyShape(None)
147
148
149
150 def OnLeftDoubleClick(self, event):
151 propertyService = wx.GetApp().GetService(PropertyService.PropertyService)
152 if propertyService:
153 propertyService.ShowWindow()
154
155
156 def OnLeftDrag(self, event):
157 # draw lasso for group select
158 if self._pt1 and event.LeftIsDown(): # we are in middle of lasso selection
159 self.EraseRubberBand()
160
161 dc = wx.ClientDC(self._canvas)
162 self._canvas.PrepareDC(dc)
163 self._pt2 = event.GetLogicalPosition(dc) # this takes into account scrollbar offset
164 self.DrawRubberBand()
165 else:
166 event.Skip()
167
168
169 def OnLeftUp(self, event):
170 # do group select
171 if self._needEraseLasso:
172 self.EraseRubberBand()
173
174 dc = wx.ClientDC(self._canvas)
175 self._canvas.PrepareDC(dc)
176 x1, y1 = self._pt1
177 x2, y2 = event.GetLogicalPosition(dc) # this takes into account scrollbar offset
178
179 tol = self._diagram.GetMouseTolerance()
180 if abs(x1 - x2) > tol or abs(y1 - y2) > tol:
181 # make sure x1 < x2 and y1 < y2 to make comparison test easier
182 if x1 > x2:
183 temp = x1
184 x1 = x2
185 x2 = temp
186 if y1 > y2:
187 temp = y1
188 y1 = y2
189 y2 = temp
190
191 for shape in self._diagram.GetShapeList():
192 if not shape.GetParent() and hasattr(shape, "GetModel"): # if part of a composite, don't select it
193 x, y = shape.GetX(), shape.GetY()
194 width, height = shape.GetBoundingBoxMax()
195 selected = x1 < x - width/2 and x2 > x + width/2 and y1 < y - height/2 and y2 > y + height/2
196 if event.ControlDown() or event.ShiftDown(): # extend select, don't deselect
197 if selected:
198 shape.Select(selected, dc)
199 else: # select items in lasso and deselect items out of lasso
200 shape.Select(selected, dc)
201 self._canvas.Redraw(dc)
202 else:
203 event.Skip()
204 else:
205 event.Skip()
206
207
208 def EraseRubberBand(self):
209 if self._needEraseLasso:
210 self._needEraseLasso = False
211
212 dc = wx.ClientDC(self._canvas)
213 self._canvas.PrepareDC(dc)
214 dc.SetLogicalFunction(wx.XOR)
215 pen = wx.Pen(wx.Colour(200, 200, 200), 1, wx.SHORT_DASH)
216 dc.SetPen(pen)
217 brush = wx.Brush(wx.Colour(255, 255, 255), wx.TRANSPARENT)
218 dc.SetBrush(brush)
219 dc.ResetBoundingBox()
220 dc.BeginDrawing()
221
222 x1, y1 = self._pt1
223 x2, y2 = self._pt2
224
225 # make sure x1 < x2 and y1 < y2
226 # this will make (x1, y1) = upper left corner
227 if x1 > x2:
228 temp = x1
229 x1 = x2
230 x2 = temp
231 if y1 > y2:
232 temp = y1
233 y1 = y2
234 y2 = temp
235
236 # erase previous outline
237 dc.SetClippingRegion(x1, y1, x2 - x1, y2 - y1)
238 dc.DrawRectangle(x1, y1, x2 - x1, y2 - y1)
239 dc.EndDrawing()
240
241
242 def DrawRubberBand(self):
243 self._needEraseLasso = True
244
245 dc = wx.ClientDC(self._canvas)
246 self._canvas.PrepareDC(dc)
247 dc.SetLogicalFunction(wx.XOR)
248 pen = wx.Pen(wx.Colour(200, 200, 200), 1, wx.SHORT_DASH)
249 dc.SetPen(pen)
250 brush = wx.Brush(wx.Colour(255, 255, 255), wx.TRANSPARENT)
251 dc.SetBrush(brush)
252 dc.ResetBoundingBox()
253 dc.BeginDrawing()
254
255 x1, y1 = self._pt1
256 x2, y2 = self._pt2
257
258 # make sure x1 < x2 and y1 < y2
259 # this will make (x1, y1) = upper left corner
260 if x1 > x2:
261 temp = x1
262 x1 = x2
263 x2 = temp
264 if y1 > y2:
265 temp = y1
266 y1 = y2
267 y2 = temp
268
269 # draw outline
270 dc.SetClippingRegion(x1, y1, x2 - x1, y2 - y1)
271 dc.DrawRectangle(x1, y1, x2 - x1, y2 - y1)
272 dc.EndDrawing()
273
274
275 def FindParkingSpot(self, width, height):
276 """ given a width and height, find a upper left corner where shape can be parked without overlapping other shape """
277 offset = 30 # space between shapes
278 x = offset
279 y = offset
280 maxX = 700 # max distance to the right where we'll place tables
281 noParkingSpot = True
282
283 while noParkingSpot:
284 point = self.isSpotOccupied(x, y, width, height)
285 if point:
286 x = point[0] + offset
287 if x > maxX:
288 x = offset
289 y = point[1] + offset
290 else:
291 noParkingSpot = False
292
293 return x, y
294
295
296 def isSpotOccupied(self, x, y, width, height):
297 """ returns None if at x,y,width,height no object occupies that rectangle,
298 otherwise returns lower right corner of object that occupies given x,y position
299 """
300 x2 = x + width
301 y2 = y + height
302
303 for shape in self._diagram.GetShapeList():
304 if isinstance(shape, ogl.RectangleShape) or isinstance(shape, ogl.EllipseShape):
305 if shape.GetParent() and isinstance(shape.GetParent(), ogl.CompositeShape):
306 # skip, part of a composite shape
307 continue
308
309 if hasattr(shape, "GetModel"):
310 other_x, other_y, other_width, other_height = shape.GetModel().getEditorBounds()
311 other_x2 = other_x + other_width
312 other_y2 = other_y + other_height
313 else:
314 # shapes x,y are at the center of the shape, need to transform to upper left coordinate
315 other_width, other_height = shape.GetBoundingBoxMax()
316 other_x = shape.GetX() - other_width/2
317 other_y = shape.GetY() - other_height/2
318
319 other_x2 = other_x + other_width
320 other_y2 = other_y + other_height
321 # intersection check
322 if ((other_x2 < other_x or other_x2 > x) and
323 (other_y2 < other_y or other_y2 > y) and
324 (x2 < x or x2 > other_x) and
325 (y2 < y or y2 > other_y)):
326 return (other_x2, other_y2)
327 return None
328
329
330 #----------------------------------------------------------------------------
331 # Canvas methods
332 #----------------------------------------------------------------------------
333
334 def AddShape(self, shape, x = None, y = None, pen = None, brush = None, text = None, eventHandler = None):
335 if isinstance(shape, ogl.CompositeShape):
336 dc = wx.ClientDC(self._canvas)
337 self._canvas.PrepareDC(dc)
338 shape.Move(dc, x, y)
339 else:
340 shape.SetDraggable(True, True)
341 shape.SetCanvas(self._canvas)
342
343 if x:
344 shape.SetX(x)
345 if y:
346 shape.SetY(y)
347 shape.SetCentreResize(False)
348 if pen:
349 shape.SetPen(pen)
350 if brush:
351 shape.SetBrush(brush)
352 if text:
353 shape.AddText(text)
354 shape.SetShadowMode(ogl.SHADOW_RIGHT)
355 self._diagram.AddShape(shape)
356 shape.Show(True)
357 if not eventHandler:
358 eventHandler = EditorCanvasShapeEvtHandler(self)
359 eventHandler.SetShape(shape)
360 eventHandler.SetPreviousHandler(shape.GetEventHandler())
361 shape.SetEventHandler(eventHandler)
362 return shape
363
364
365 def RemoveShape(self, model = None, shape = None):
366 if not model and not shape:
367 return
368
369 if not shape:
370 shape = self.GetShape(model)
371
372 if shape:
373 shape.Select(False)
374 self._diagram.RemoveShape(shape)
375 if isinstance(shape, ogl.CompositeShape):
376 shape.RemoveFromCanvas(self._canvas)
377
378
379 def UpdateShape(self, model):
380 for shape in self._diagram.GetShapeList():
381 if hasattr(shape, "GetModel") and shape.GetModel() == model:
382 x, y, w, h = model.getEditorBounds()
383 newX = x + w / 2
384 newY = y + h / 2
385 changed = False
386 if isinstance(shape, ogl.CompositeShape):
387 if shape.GetX() != newX or shape.GetY() != newY:
388 dc = wx.ClientDC(self._canvas)
389 self._canvas.PrepareDC(dc)
390 shape.SetSize(w, h, True) # wxBug: SetSize must be before Move because links won't go to the right place
391 shape.Move(dc, newX, newY) # wxBug: Move must be before SetSize because links won't go to the right place
392 changed = True
393 else:
394 oldw, oldh = shape.GetBoundingBoxMax()
395 oldx = shape.GetX()
396 oldy = shape.GetY()
397 if oldw != w or oldh != h or oldx != newX or oldy != newY:
398 shape.SetSize(w, h)
399 shape.SetX(newX)
400 shape.SetY(newY)
401 changed = True
402 if changed:
403 shape.ResetControlPoints()
404 self._canvas.Refresh()
405 break
406
407
408 def GetShape(self, model):
409 for shape in self._diagram.GetShapeList():
410 if hasattr(shape, "GetModel") and shape.GetModel() == model:
411 return shape
412 return None
413
414
415 def GetSelection(self):
416 return filter(lambda shape: shape.Selected(), self._diagram.GetShapeList())
417
418
419 def SetSelection(self, models, extendSelect = False):
420 dc = wx.ClientDC(self._canvas)
421 self._canvas.PrepareDC(dc)
422 update = False
423 if not isinstance(models, type([])) and not isinstance(models, type(())):
424 models = [models]
425 for shape in self._diagram.GetShapeList():
426 if hasattr(shape, "GetModel"):
427 if shape.Selected() and not shape.GetModel() in models: # was selected, but not in new list, so deselect, unless extend select
428 if not extendSelect:
429 shape.Select(False, dc)
430 update = True
431 elif not shape.Selected() and shape.GetModel() in models: # was not selected and in new list, so select
432 shape.Select(True, dc)
433 update = True
434 elif extendSelect and shape.Selected() and shape.GetModel() in models: # was selected, but extend select means to deselect
435 shape.Select(False, dc)
436 update = True
437 if update:
438 self._canvas.Redraw(dc)
439
440
441 def BringToFront(self, shape):
442 if shape.GetParent() and isinstance(shape.GetParent(), ogl.CompositeShape):
443 self._diagram.RemoveShape(shape.GetParent())
444 self._diagram.AddShape(shape.GetParent())
445 else:
446 self._diagram.RemoveShape(shape)
447 self._diagram.AddShape(shape)
448
449
450 def SendToBack(self, shape):
451 if shape.GetParent() and isinstance(shape.GetParent(), ogl.CompositeShape):
452 self._diagram.RemoveShape(shape.GetParent())
453 self._diagram.InsertShape(shape.GetParent())
454 else:
455 self._diagram.RemoveShape(shape)
456 self._diagram.InsertShape(shape)
457
458
459 def ScrollVisible(self, shape):
460 xUnit, yUnit = shape._canvas.GetScrollPixelsPerUnit()
461 scrollX, scrollY = self._canvas.GetViewStart() # in scroll units
462 scrollW, scrollH = self._canvas.GetSize() # in pixels
463 w, h = shape.GetBoundingBoxMax() # in pixels
464 x = shape.GetX() - w/2 # convert to upper left coordinate from center
465 y = shape.GetY() - h/2 # convert to upper left coordinate from center
466
467 if x >= scrollX*xUnit and x <= scrollX*xUnit + scrollW: # don't scroll if already visible
468 x = -1
469 else:
470 x = x/xUnit
471
472 if y >= scrollY*yUnit and y <= scrollY*yUnit + scrollH: # don't scroll if already visible
473 y = -1
474 else:
475 y = y/yUnit
476
477 self._canvas.Scroll(x, y) # in scroll units
478
479
480 def SetPropertyShape(self, shape):
481 # no need to highlight if no PropertyService is running
482 propertyService = wx.GetApp().GetService(PropertyService.PropertyService)
483 if not propertyService:
484 return
485
486 if shape == self._propShape:
487 return
488
489 if hasattr(shape, "GetPropertyShape"):
490 shape = shape.GetPropertyShape()
491
492 dc = wx.ClientDC(self._canvas)
493 self._canvas.PrepareDC(dc)
494 dc.BeginDrawing()
495
496 # erase old selection if it still exists
497 if self._propShape and self._propShape in self._diagram.GetShapeList():
498 self._propShape.SetBrush(self._brush)
499 if (self._propShape._textColourName in ["BLACK", "WHITE"]): # Would use GetTextColour() but it is broken
500 self._propShape.SetTextColour("BLACK", 0)
501 self._propShape.Draw(dc)
502
503 # set new selection
504 self._propShape = shape
505
506 # draw new selection
507 if self._propShape and self._propShape in self._diagram.GetShapeList():
508 self._propShape.SetBrush(SELECT_BRUSH)
509 if (self._propShape._textColourName in ["BLACK", "WHITE"]): # Would use GetTextColour() but it is broken
510 self._propShape.SetTextColour("WHITE", 0)
511 self._propShape.Draw(dc)
512
513 dc.EndDrawing()
514
515
516 #----------------------------------------------------------------------------
517 # Property Service methods
518 #----------------------------------------------------------------------------
519
520 def GetPropertyModel(self):
521 if hasattr(self, "_propModel"):
522 return self._propModel
523 return None
524
525
526 def SetPropertyModel(self, model):
527 # no need to set the model if no PropertyService is running
528 propertyService = wx.GetApp().GetService(PropertyService.PropertyService)
529 if not propertyService:
530 return
531
532 if hasattr(self, "_propModel") and model == self._propModel:
533 return
534
535 self._propModel = model
536 propertyService.LoadProperties(self._propModel, self.GetDocument())
537
538
539 class EditorCanvasShapeMixin:
540
541 def GetModel(self):
542 return self._model
543
544
545 def SetModel(self, model):
546 self._model = model
547
548
549 class EditorCanvasShapeEvtHandler(ogl.ShapeEvtHandler):
550
551 """ wxBug: Bug in OLG package. With wxShape.SetShadowMode() turned on, when we set the size,
552 the width/height is larger by 6 pixels. Need to subtract this value from width and height when we
553 resize the object.
554 """
555 SHIFT_KEY = 1
556 CONTROL_KEY = 2
557
558 def __init__(self, view):
559 ogl.ShapeEvtHandler.__init__(self)
560 self._view = view
561
562
563 def OnLeftClick(self, x, y, keys = 0, attachment = 0):
564 shape = self.GetShape()
565 if hasattr(shape, "GetModel"): # Workaround, on drag, we should deselect all other objects and select the clicked on object
566 model = shape.GetModel()
567 else:
568 shape = shape.GetParent()
569 if shape:
570 model = shape.GetModel()
571
572 self._view.SetSelection(model, keys == self.SHIFT_KEY or keys == self.CONTROL_KEY)
573 self._view.SetPropertyShape(shape)
574 self._view.SetPropertyModel(model)
575
576
577 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
578 ogl.ShapeEvtHandler.OnEndDragLeft(self, x, y, keys, attachment)
579 shape = self.GetShape()
580 if hasattr(shape, "GetModel"): # Workaround, on drag, we should deselect all other objects and select the clicked on object
581 model = shape.GetModel()
582 else:
583 parentShape = shape.GetParent()
584 if parentShape:
585 model = parentShape.GetModel()
586 self._view.SetSelection(model, keys == self.SHIFT_KEY or keys == self.CONTROL_KEY)
587
588
589 def OnMovePost(self, dc, x, y, oldX, oldY, display):
590 if x == oldX and y == oldY:
591 return
592 if not self._view.GetDocument():
593 return
594 shape = self.GetShape()
595 if isinstance(shape, EditorCanvasShapeMixin) and shape.Draggable():
596 model = shape.GetModel()
597 if hasattr(model, "getEditorBounds") and model.getEditorBounds():
598 x, y, w, h = model.getEditorBounds()
599 newX = shape.GetX() - shape.GetBoundingBoxMax()[0] / 2
600 newY = shape.GetY() - shape.GetBoundingBoxMax()[1] / 2
601 newWidth = shape.GetBoundingBoxMax()[0]
602 newHeight = shape.GetBoundingBoxMax()[1]
603 if shape._shadowMode != ogl.SHADOW_NONE:
604 newWidth -= shape._shadowOffsetX
605 newHeight -= shape._shadowOffsetY
606 newbounds = (newX, newY, newWidth, newHeight)
607
608 if x != newX or y != newY or w != newWidth or h != newHeight:
609 self._view.GetDocument().GetCommandProcessor().Submit(EditorCanvasUpdateShapeBoundariesCommand(self._view.GetDocument(), model, newbounds))
610
611
612 def Draw(self, dc):
613 pass
614
615
616 class EditorCanvasUpdateShapeBoundariesCommand(wx.lib.docview.Command):
617
618
619 def __init__(self, canvasDocument, model, newbounds):
620 wx.lib.docview.Command.__init__(self, canUndo = True)
621 self._canvasDocument = canvasDocument
622 self._model = model
623 self._oldbounds = model.getEditorBounds()
624 self._newbounds = newbounds
625
626
627 def GetName(self):
628 name = self._canvasDocument.GetNameForObject(self._model)
629 if not name:
630 name = ""
631 print "ERROR: AbstractEditor.EditorCanvasUpdateShapeBoundariesCommand.GetName: unable to get name for ", self._model
632 return _("Move/Resize %s") % name
633
634
635 def Do(self):
636 return self._canvasDocument.UpdateEditorBoundaries(self._model, self._newbounds)
637
638
639 def Undo(self):
640 return self._canvasDocument.UpdateEditorBoundaries(self._model, self._oldbounds)
641