]>
Commit | Line | Data |
---|---|---|
1f780e48 RD |
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 | |
b792147d | 24 | INACTIVE_SELECT_BRUSH = wx.Brush("LIGHT BLUE", wx.SOLID) |
1f780e48 | 25 | |
02b800ce RD |
26 | NORMALFONT = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) |
27 | SLANTFONT = wx.Font(NORMALFONT.GetPointSize(), NORMALFONT.GetFamily(), wx.SLANT, NORMALFONT.GetWeight()) | |
28 | BOLDFONT = wx.Font(NORMALFONT.GetPointSize(), NORMALFONT.GetFamily(), NORMALFONT.GetStyle(), wx.BOLD) | |
29 | ||
30 | DEFAULT_BACKGROUND_COLOR = wx.Colour(0xEE, 0xEE, 0xEE) | |
31 | HEADER_BRUSH = wx.Brush(wx.Colour(0xDB, 0xEB, 0xFF), wx.SOLID) | |
32 | BODY_BRUSH = wx.Brush(wx.WHITE, wx.SOLID) | |
33 | ||
34 | ||
35 | PARKING_VERTICAL = 1 | |
36 | PARKING_HORIZONTAL = 2 | |
37 | PARKING_OFFSET = 30 # space between shapes | |
38 | ||
aca310e5 | 39 | FORCE_REDRAW_METHOD = "ForceRedraw" |
1f780e48 RD |
40 | |
41 | def GetRawModel(model): | |
42 | if hasattr(model, "GetRawModel"): | |
43 | rawModel = model.GetRawModel() | |
44 | else: | |
45 | rawModel = model | |
46 | return rawModel | |
47 | ||
48 | ||
02b800ce RD |
49 | def GetLabel(model): |
50 | model = GetRawModel(model) | |
51 | if hasattr(model, "__xmlname__"): | |
52 | label = model.__xmlname__ | |
53 | try: | |
54 | if (len(label) > 0): | |
55 | label = label[0].upper() + label[1:] | |
56 | if (hasattr(model, "complexType")): | |
57 | label += ': %s/%s' % (model.complexType.name, model.name) | |
58 | else: | |
59 | if model.name: | |
60 | label += ': %s' % model.name | |
61 | elif model.ref: | |
62 | label += ': %s' % model.ref | |
63 | except AttributeError: | |
64 | pass | |
65 | else: | |
66 | label = str(model) | |
67 | return label | |
68 | ||
69 | ||
1f780e48 RD |
70 | class CanvasView(wx.lib.docview.View): |
71 | ||
72 | ||
73 | #---------------------------------------------------------------------------- | |
74 | # Overridden methods | |
75 | #---------------------------------------------------------------------------- | |
76 | ||
77 | ||
02b800ce | 78 | def __init__(self, brush=SHAPE_BRUSH, background=DEFAULT_BACKGROUND_COLOR): |
1f780e48 RD |
79 | wx.lib.docview.View.__init__(self) |
80 | self._brush = brush | |
02b800ce | 81 | self._backgroundColor = background |
1f780e48 RD |
82 | self._canvas = None |
83 | self._pt1 = None | |
84 | self._pt2 = None | |
85 | self._needEraseLasso = False | |
86 | self._propShape = None | |
26ee3a06 RD |
87 | self._maxWidth = 2000 |
88 | self._maxHeight = 16000 | |
aca310e5 | 89 | self._valetParking = False |
1f780e48 RD |
90 | |
91 | ||
26ee3a06 RD |
92 | def OnDraw(self, dc): |
93 | """ for Print Preview and Print """ | |
94 | dc.BeginDrawing() | |
95 | self._canvas.Redraw(dc) | |
96 | dc.EndDrawing() | |
97 | ||
98 | ||
1f780e48 RD |
99 | def OnCreate(self, doc, flags): |
100 | frame = wx.GetApp().CreateDocumentFrame(self, doc, flags) | |
101 | frame.Show() | |
102 | sizer = wx.BoxSizer() | |
103 | self._CreateCanvas(frame) | |
104 | sizer.Add(self._canvas, 1, wx.EXPAND, 0) | |
105 | frame.SetSizer(sizer) | |
106 | frame.Layout() | |
107 | self.Activate() | |
02b800ce | 108 | wx.EVT_RIGHT_DOWN(self._canvas, self.OnRightClick) |
1f780e48 | 109 | return True |
b792147d | 110 | |
1f780e48 RD |
111 | |
112 | def OnActivateView(self, activate, activeView, deactiveView): | |
113 | if activate and self._canvas: | |
114 | # In MDI mode just calling set focus doesn't work and in SDI mode using CallAfter causes an endless loop | |
115 | if self.GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI: | |
6f1a3f9c | 116 | self.SetFocus() |
1f780e48 | 117 | else: |
6f1a3f9c RD |
118 | wx.CallAfter(self.SetFocus) |
119 | ||
120 | ||
121 | def SetFocus(self): | |
122 | if self._canvas: | |
123 | self._canvas.SetFocus() | |
1f780e48 RD |
124 | |
125 | ||
b792147d | 126 | def OnFocus(self, event): |
b792147d RD |
127 | self.FocusColorPropertyShape(True) |
128 | event.Skip() | |
129 | ||
130 | ||
2eeaec19 RD |
131 | def FocusOnClick(self, event): |
132 | self.SetFocus() | |
133 | event.Skip() | |
134 | ||
135 | ||
b792147d RD |
136 | def OnKillFocus(self, event): |
137 | self.FocusColorPropertyShape(False) | |
138 | event.Skip() | |
139 | ||
140 | ||
6f1a3f9c RD |
141 | def HasFocus(self): |
142 | winWithFocus = wx.Window.FindFocus() | |
143 | if not winWithFocus: | |
144 | return False | |
145 | while winWithFocus: | |
146 | if winWithFocus == self._canvas: | |
147 | return True | |
148 | winWithFocus = winWithFocus.GetParent() | |
149 | return False | |
150 | ||
151 | ||
1f780e48 RD |
152 | def OnClose(self, deleteWindow = True): |
153 | statusC = wx.GetApp().CloseChildDocuments(self.GetDocument()) | |
154 | statusP = wx.lib.docview.View.OnClose(self, deleteWindow = deleteWindow) | |
155 | if hasattr(self, "ClearOutline"): | |
156 | wx.CallAfter(self.ClearOutline) # need CallAfter because when closing the document, it is Activated and then Close, so need to match OnActivateView's CallAfter | |
157 | if not (statusC and statusP): | |
158 | return False | |
159 | self.Activate(False) | |
160 | if deleteWindow and self.GetFrame(): | |
161 | self.GetFrame().Destroy() | |
162 | return True | |
163 | ||
164 | ||
165 | def _CreateCanvas(self, parent): | |
166 | self._canvas = ogl.ShapeCanvas(parent) | |
167 | wx.EVT_LEFT_DOWN(self._canvas, self.OnLeftClick) | |
168 | wx.EVT_LEFT_UP(self._canvas, self.OnLeftUp) | |
169 | wx.EVT_MOTION(self._canvas, self.OnLeftDrag) | |
170 | wx.EVT_LEFT_DCLICK(self._canvas, self.OnLeftDoubleClick) | |
171 | wx.EVT_KEY_DOWN(self._canvas, self.OnKeyPressed) | |
b792147d RD |
172 | |
173 | # need this otherwise mouse clicks don't set focus to this view | |
2eeaec19 RD |
174 | wx.EVT_LEFT_DOWN(self._canvas, self.FocusOnClick) |
175 | wx.EVT_LEFT_DCLICK(self._canvas, self.FocusOnClick) | |
176 | wx.EVT_RIGHT_DOWN(self._canvas, self.FocusOnClick) | |
177 | wx.EVT_RIGHT_DCLICK(self._canvas, self.FocusOnClick) | |
178 | wx.EVT_MIDDLE_DOWN(self._canvas, self.FocusOnClick) | |
179 | wx.EVT_MIDDLE_DCLICK(self._canvas, self.FocusOnClick) | |
b792147d RD |
180 | |
181 | wx.EVT_KILL_FOCUS(self._canvas, self.OnKillFocus) | |
182 | wx.EVT_SET_FOCUS(self._canvas, self.OnFocus) | |
1f780e48 | 183 | |
26ee3a06 | 184 | self._canvas.SetScrollbars(20, 20, self._maxWidth / 20, self._maxHeight / 20) |
1f780e48 | 185 | |
02b800ce | 186 | self._canvas.SetBackgroundColour(self._backgroundColor) |
1f780e48 RD |
187 | self._diagram = ogl.Diagram() |
188 | self._canvas.SetDiagram(self._diagram) | |
189 | self._diagram.SetCanvas(self._canvas) | |
02b800ce | 190 | self._canvas.SetFont(NORMALFONT) |
1f780e48 RD |
191 | |
192 | ||
02b800ce RD |
193 | def OnClear(self, event): |
194 | """ Deletion of selected objects from view. | |
195 | *Must Override* | |
196 | """ | |
197 | self.SetPropertyModel(None) | |
198 | ||
199 | ||
aca310e5 RD |
200 | def SetLastRightClick(self, x, y): |
201 | self._lastRightClick = (x,y) | |
202 | ||
203 | ||
204 | def GetLastRightClick(self): | |
205 | if hasattr(self, "_lastRightClick"): | |
206 | return self._lastRightClick | |
207 | return (-1,-1) | |
208 | ||
209 | ||
1f780e48 RD |
210 | def OnKeyPressed(self, event): |
211 | key = event.KeyCode() | |
212 | if key == wx.WXK_DELETE: | |
213 | self.OnClear(event) | |
214 | else: | |
215 | event.Skip() | |
216 | ||
217 | ||
02b800ce RD |
218 | def OnRightClick(self, event): |
219 | """ force selection underneath right click position. """ | |
220 | self.Activate() | |
221 | self._canvas.SetFocus() | |
222 | ||
223 | dc = wx.ClientDC(self._canvas) | |
224 | self._canvas.PrepareDC(dc) | |
225 | x, y = event.GetLogicalPosition(dc) # this takes into account scrollbar offset | |
aca310e5 | 226 | self.SetLastRightClick(x, y) |
02b800ce RD |
227 | shape = self._canvas.FindShape(x, y)[0] |
228 | ||
229 | model = None | |
230 | if not shape: | |
231 | self.SetSelection(None) | |
232 | self.SetPropertyShape(None) | |
233 | elif hasattr(shape, "GetModel"): | |
234 | self.BringToFront(shape) | |
235 | self.SetPropertyShape(shape) | |
236 | self.SetSelection(shape) | |
237 | shape.Select(True, dc) | |
238 | model = shape.GetModel() | |
239 | elif shape.GetParent() and isinstance(shape.GetParent(), ogl.CompositeShape): # ComplexTypeHeader for ComplexTypeShape | |
240 | self.BringToFront(shape) | |
241 | self.SetPropertyShape(shape.GetParent()) | |
242 | self.SetSelection(shape.GetParent()) | |
243 | shape.GetParent().Select(True, dc) | |
244 | model = shape.GetParent().GetModel() | |
245 | ||
246 | self.SetPropertyModel(model) | |
247 | ||
248 | return (shape, model) | |
249 | ||
250 | ||
1f780e48 | 251 | def OnLeftClick(self, event): |
02b800ce RD |
252 | self.Activate() |
253 | self._canvas.SetFocus() | |
254 | ||
1f780e48 RD |
255 | self.EraseRubberBand() |
256 | ||
257 | dc = wx.ClientDC(self._canvas) | |
258 | self._canvas.PrepareDC(dc) | |
259 | ||
260 | # keep track of mouse down for group select | |
261 | self._pt1 = event.GetLogicalPosition(dc) # this takes into account scrollbar offset | |
262 | self._pt2 = None | |
263 | ||
264 | shape = self._canvas.FindShape(self._pt1[0], self._pt1[1])[0] | |
265 | if shape: | |
266 | self.BringToFront(shape) | |
267 | ||
268 | self._pt1 = None | |
269 | event.Skip() # pass on event to shape handler to take care of selection | |
270 | ||
271 | return | |
272 | elif event.ControlDown() or event.ShiftDown(): # extend select, don't deselect | |
273 | pass | |
274 | else: | |
275 | # click on empty part of canvas, deselect everything | |
aca310e5 | 276 | forceRedrawShapes = [] |
1f780e48 RD |
277 | needRefresh = False |
278 | for shape in self._diagram.GetShapeList(): | |
279 | if hasattr(shape, "GetModel"): | |
280 | if shape.Selected(): | |
281 | needRefresh = True | |
282 | shape.Select(False, dc) | |
aca310e5 RD |
283 | if hasattr(shape, FORCE_REDRAW_METHOD): |
284 | forceRedrawShapes.append(shape) | |
1f780e48 RD |
285 | if needRefresh: |
286 | self._canvas.Redraw(dc) | |
287 | ||
288 | self.SetPropertyModel(None) | |
289 | ||
290 | if len(self.GetSelection()) == 0: | |
291 | self.SetPropertyShape(None) | |
292 | ||
aca310e5 RD |
293 | for shape in forceRedrawShapes: |
294 | shape.ForceRedraw() | |
1f780e48 RD |
295 | |
296 | def OnLeftDoubleClick(self, event): | |
297 | propertyService = wx.GetApp().GetService(PropertyService.PropertyService) | |
298 | if propertyService: | |
299 | propertyService.ShowWindow() | |
300 | ||
301 | ||
302 | def OnLeftDrag(self, event): | |
303 | # draw lasso for group select | |
304 | if self._pt1 and event.LeftIsDown(): # we are in middle of lasso selection | |
305 | self.EraseRubberBand() | |
306 | ||
307 | dc = wx.ClientDC(self._canvas) | |
308 | self._canvas.PrepareDC(dc) | |
309 | self._pt2 = event.GetLogicalPosition(dc) # this takes into account scrollbar offset | |
310 | self.DrawRubberBand() | |
311 | else: | |
312 | event.Skip() | |
313 | ||
314 | ||
315 | def OnLeftUp(self, event): | |
316 | # do group select | |
317 | if self._needEraseLasso: | |
318 | self.EraseRubberBand() | |
319 | ||
320 | dc = wx.ClientDC(self._canvas) | |
321 | self._canvas.PrepareDC(dc) | |
322 | x1, y1 = self._pt1 | |
323 | x2, y2 = event.GetLogicalPosition(dc) # this takes into account scrollbar offset | |
324 | ||
325 | tol = self._diagram.GetMouseTolerance() | |
326 | if abs(x1 - x2) > tol or abs(y1 - y2) > tol: | |
327 | # make sure x1 < x2 and y1 < y2 to make comparison test easier | |
328 | if x1 > x2: | |
329 | temp = x1 | |
330 | x1 = x2 | |
331 | x2 = temp | |
332 | if y1 > y2: | |
333 | temp = y1 | |
334 | y1 = y2 | |
335 | y2 = temp | |
336 | ||
337 | for shape in self._diagram.GetShapeList(): | |
338 | if not shape.GetParent() and hasattr(shape, "GetModel"): # if part of a composite, don't select it | |
339 | x, y = shape.GetX(), shape.GetY() | |
340 | width, height = shape.GetBoundingBoxMax() | |
341 | selected = x1 < x - width/2 and x2 > x + width/2 and y1 < y - height/2 and y2 > y + height/2 | |
342 | if event.ControlDown() or event.ShiftDown(): # extend select, don't deselect | |
343 | if selected: | |
344 | shape.Select(selected, dc) | |
345 | else: # select items in lasso and deselect items out of lasso | |
346 | shape.Select(selected, dc) | |
347 | self._canvas.Redraw(dc) | |
348 | else: | |
349 | event.Skip() | |
350 | else: | |
351 | event.Skip() | |
352 | ||
353 | ||
354 | def EraseRubberBand(self): | |
355 | if self._needEraseLasso: | |
356 | self._needEraseLasso = False | |
357 | ||
358 | dc = wx.ClientDC(self._canvas) | |
359 | self._canvas.PrepareDC(dc) | |
360 | dc.SetLogicalFunction(wx.XOR) | |
361 | pen = wx.Pen(wx.Colour(200, 200, 200), 1, wx.SHORT_DASH) | |
362 | dc.SetPen(pen) | |
363 | brush = wx.Brush(wx.Colour(255, 255, 255), wx.TRANSPARENT) | |
364 | dc.SetBrush(brush) | |
365 | dc.ResetBoundingBox() | |
366 | dc.BeginDrawing() | |
367 | ||
368 | x1, y1 = self._pt1 | |
369 | x2, y2 = self._pt2 | |
370 | ||
371 | # make sure x1 < x2 and y1 < y2 | |
372 | # this will make (x1, y1) = upper left corner | |
373 | if x1 > x2: | |
374 | temp = x1 | |
375 | x1 = x2 | |
376 | x2 = temp | |
377 | if y1 > y2: | |
378 | temp = y1 | |
379 | y1 = y2 | |
380 | y2 = temp | |
381 | ||
382 | # erase previous outline | |
383 | dc.SetClippingRegion(x1, y1, x2 - x1, y2 - y1) | |
384 | dc.DrawRectangle(x1, y1, x2 - x1, y2 - y1) | |
385 | dc.EndDrawing() | |
386 | ||
387 | ||
388 | def DrawRubberBand(self): | |
389 | self._needEraseLasso = True | |
390 | ||
391 | dc = wx.ClientDC(self._canvas) | |
392 | self._canvas.PrepareDC(dc) | |
393 | dc.SetLogicalFunction(wx.XOR) | |
394 | pen = wx.Pen(wx.Colour(200, 200, 200), 1, wx.SHORT_DASH) | |
395 | dc.SetPen(pen) | |
396 | brush = wx.Brush(wx.Colour(255, 255, 255), wx.TRANSPARENT) | |
397 | dc.SetBrush(brush) | |
398 | dc.ResetBoundingBox() | |
399 | dc.BeginDrawing() | |
400 | ||
401 | x1, y1 = self._pt1 | |
402 | x2, y2 = self._pt2 | |
403 | ||
404 | # make sure x1 < x2 and y1 < y2 | |
405 | # this will make (x1, y1) = upper left corner | |
406 | if x1 > x2: | |
407 | temp = x1 | |
408 | x1 = x2 | |
409 | x2 = temp | |
410 | if y1 > y2: | |
411 | temp = y1 | |
412 | y1 = y2 | |
413 | y2 = temp | |
414 | ||
415 | # draw outline | |
416 | dc.SetClippingRegion(x1, y1, x2 - x1, y2 - y1) | |
417 | dc.DrawRectangle(x1, y1, x2 - x1, y2 - y1) | |
418 | dc.EndDrawing() | |
419 | ||
420 | ||
aca310e5 RD |
421 | def SetValetParking(self, enable=True): |
422 | """ If valet parking is enabled, remember last parking spot and try for a spot near it """ | |
423 | self._valetParking = enable | |
424 | if enable: | |
425 | self._valetPosition = None | |
426 | ||
427 | ||
02b800ce | 428 | def FindParkingSpot(self, width, height, parking=PARKING_HORIZONTAL, x=PARKING_OFFSET, y=PARKING_OFFSET): |
aca310e5 RD |
429 | """ |
430 | Given a width and height, find a upper left corner where shape can be parked without overlapping other shape | |
431 | """ | |
432 | if self._valetParking and self._valetPosition: | |
433 | x, y = self._valetPosition | |
434 | ||
02b800ce | 435 | max = 700 # max distance to the right where we'll place tables |
1f780e48 RD |
436 | noParkingSpot = True |
437 | ||
438 | while noParkingSpot: | |
439 | point = self.isSpotOccupied(x, y, width, height) | |
440 | if point: | |
02b800ce RD |
441 | if parking == PARKING_HORIZONTAL: |
442 | x = point[0] + PARKING_OFFSET | |
443 | if x > max: | |
444 | x = PARKING_OFFSET | |
445 | y = point[1] + PARKING_OFFSET | |
446 | else: # parking == PARKING_VERTICAL: | |
447 | y = point[1] + PARKING_OFFSET | |
448 | if y > max: | |
449 | y = PARKING_OFFSET | |
450 | x = point[0] + PARKING_OFFSET | |
1f780e48 RD |
451 | else: |
452 | noParkingSpot = False | |
453 | ||
aca310e5 RD |
454 | if self._valetParking: |
455 | self._valetPosition = (x, y) | |
456 | ||
1f780e48 RD |
457 | return x, y |
458 | ||
459 | ||
460 | def isSpotOccupied(self, x, y, width, height): | |
461 | """ returns None if at x,y,width,height no object occupies that rectangle, | |
462 | otherwise returns lower right corner of object that occupies given x,y position | |
463 | """ | |
464 | x2 = x + width | |
465 | y2 = y + height | |
466 | ||
467 | for shape in self._diagram.GetShapeList(): | |
02b800ce | 468 | if isinstance(shape, ogl.RectangleShape) or isinstance(shape, ogl.EllipseShape) or isinstance(shape, ogl.PolygonShape): |
1f780e48 RD |
469 | if shape.GetParent() and isinstance(shape.GetParent(), ogl.CompositeShape): |
470 | # skip, part of a composite shape | |
471 | continue | |
472 | ||
473 | if hasattr(shape, "GetModel"): | |
474 | other_x, other_y, other_width, other_height = shape.GetModel().getEditorBounds() | |
475 | other_x2 = other_x + other_width | |
476 | other_y2 = other_y + other_height | |
477 | else: | |
478 | # shapes x,y are at the center of the shape, need to transform to upper left coordinate | |
479 | other_width, other_height = shape.GetBoundingBoxMax() | |
480 | other_x = shape.GetX() - other_width/2 | |
481 | other_y = shape.GetY() - other_height/2 | |
482 | ||
483 | other_x2 = other_x + other_width | |
484 | other_y2 = other_y + other_height | |
485 | # intersection check | |
486 | if ((other_x2 < other_x or other_x2 > x) and | |
487 | (other_y2 < other_y or other_y2 > y) and | |
488 | (x2 < x or x2 > other_x) and | |
489 | (y2 < y or y2 > other_y)): | |
490 | return (other_x2, other_y2) | |
491 | return None | |
492 | ||
493 | ||
494 | #---------------------------------------------------------------------------- | |
495 | # Canvas methods | |
496 | #---------------------------------------------------------------------------- | |
497 | ||
02b800ce | 498 | def AddShape(self, shape, x = None, y = None, pen = None, brush = None, text = None, eventHandler = None, shown=True): |
1f780e48 RD |
499 | if isinstance(shape, ogl.CompositeShape): |
500 | dc = wx.ClientDC(self._canvas) | |
501 | self._canvas.PrepareDC(dc) | |
502 | shape.Move(dc, x, y) | |
503 | else: | |
504 | shape.SetDraggable(True, True) | |
505 | shape.SetCanvas(self._canvas) | |
506 | ||
507 | if x: | |
508 | shape.SetX(x) | |
509 | if y: | |
510 | shape.SetY(y) | |
511 | shape.SetCentreResize(False) | |
512 | if pen: | |
513 | shape.SetPen(pen) | |
514 | if brush: | |
515 | shape.SetBrush(brush) | |
516 | if text: | |
517 | shape.AddText(text) | |
2eeaec19 | 518 | shape.SetShadowMode(ogl.SHADOW_NONE) |
1f780e48 | 519 | self._diagram.AddShape(shape) |
02b800ce | 520 | shape.Show(shown) |
1f780e48 RD |
521 | if not eventHandler: |
522 | eventHandler = EditorCanvasShapeEvtHandler(self) | |
523 | eventHandler.SetShape(shape) | |
524 | eventHandler.SetPreviousHandler(shape.GetEventHandler()) | |
525 | shape.SetEventHandler(eventHandler) | |
526 | return shape | |
527 | ||
528 | ||
529 | def RemoveShape(self, model = None, shape = None): | |
530 | if not model and not shape: | |
531 | return | |
532 | ||
533 | if not shape: | |
534 | shape = self.GetShape(model) | |
535 | ||
536 | if shape: | |
537 | shape.Select(False) | |
2eeaec19 RD |
538 | for line in shape.GetLines(): |
539 | shape.RemoveLine(line) | |
540 | self._diagram.RemoveShape(line) | |
02b800ce | 541 | line.Delete() |
2eeaec19 RD |
542 | for obj in self._diagram.GetShapeList(): |
543 | for line in obj.GetLines(): | |
544 | if self.IsShapeContained(shape, line.GetTo()) or self.IsShapeContained(shape, line.GetFrom()): | |
545 | obj.RemoveLine(line) | |
546 | self._diagram.RemoveShape(line) | |
02b800ce | 547 | line.Delete() |
2eeaec19 RD |
548 | if line == shape: |
549 | obj.RemoveLine(line) | |
02b800ce RD |
550 | self._diagram.RemoveShape(line) |
551 | line.Delete() | |
2eeaec19 | 552 | |
aca310e5 RD |
553 | if self._canvas: |
554 | shape.RemoveFromCanvas(self._canvas) | |
1f780e48 | 555 | self._diagram.RemoveShape(shape) |
02b800ce | 556 | shape.Delete() |
2eeaec19 RD |
557 | |
558 | ||
559 | def IsShapeContained(self, parent, shape): | |
560 | if parent == shape: | |
561 | return True | |
562 | elif shape.GetParent(): | |
563 | return self.IsShapeContained(parent, shape.GetParent()) | |
564 | ||
565 | return False | |
1f780e48 RD |
566 | |
567 | ||
568 | def UpdateShape(self, model): | |
569 | for shape in self._diagram.GetShapeList(): | |
570 | if hasattr(shape, "GetModel") and shape.GetModel() == model: | |
02b800ce RD |
571 | oldw, oldh = shape.GetBoundingBoxMax() |
572 | oldx = shape.GetX() | |
573 | oldy = shape.GetY() | |
574 | ||
1f780e48 RD |
575 | x, y, w, h = model.getEditorBounds() |
576 | newX = x + w / 2 | |
577 | newY = y + h / 2 | |
02b800ce RD |
578 | |
579 | if oldw != w or oldh != h or oldx != newX or oldy != newY: | |
580 | dc = wx.ClientDC(self._canvas) | |
581 | self._canvas.PrepareDC(dc) | |
582 | shape.SetSize(w, h, True) # wxBug: SetSize must be before Move because links won't go to the right place | |
583 | shape.Move(dc, newX, newY) # wxBug: Move must be after SetSize because links won't go to the right place | |
1f780e48 RD |
584 | shape.ResetControlPoints() |
585 | self._canvas.Refresh() | |
02b800ce | 586 | |
1f780e48 RD |
587 | break |
588 | ||
589 | ||
590 | def GetShape(self, model): | |
591 | for shape in self._diagram.GetShapeList(): | |
592 | if hasattr(shape, "GetModel") and shape.GetModel() == model: | |
593 | return shape | |
594 | return None | |
595 | ||
596 | ||
02b800ce RD |
597 | def GetShapeCount(self): |
598 | return self._diagram.GetCount() | |
599 | ||
600 | ||
1f780e48 RD |
601 | def GetSelection(self): |
602 | return filter(lambda shape: shape.Selected(), self._diagram.GetShapeList()) | |
603 | ||
604 | ||
605 | def SetSelection(self, models, extendSelect = False): | |
606 | dc = wx.ClientDC(self._canvas) | |
607 | self._canvas.PrepareDC(dc) | |
608 | update = False | |
609 | if not isinstance(models, type([])) and not isinstance(models, type(())): | |
610 | models = [models] | |
611 | for shape in self._diagram.GetShapeList(): | |
612 | if hasattr(shape, "GetModel"): | |
613 | if shape.Selected() and not shape.GetModel() in models: # was selected, but not in new list, so deselect, unless extend select | |
614 | if not extendSelect: | |
615 | shape.Select(False, dc) | |
616 | update = True | |
617 | elif not shape.Selected() and shape.GetModel() in models: # was not selected and in new list, so select | |
618 | shape.Select(True, dc) | |
619 | update = True | |
620 | elif extendSelect and shape.Selected() and shape.GetModel() in models: # was selected, but extend select means to deselect | |
621 | shape.Select(False, dc) | |
622 | update = True | |
623 | if update: | |
624 | self._canvas.Redraw(dc) | |
625 | ||
626 | ||
627 | def BringToFront(self, shape): | |
628 | if shape.GetParent() and isinstance(shape.GetParent(), ogl.CompositeShape): | |
629 | self._diagram.RemoveShape(shape.GetParent()) | |
630 | self._diagram.AddShape(shape.GetParent()) | |
631 | else: | |
632 | self._diagram.RemoveShape(shape) | |
633 | self._diagram.AddShape(shape) | |
634 | ||
635 | ||
636 | def SendToBack(self, shape): | |
637 | if shape.GetParent() and isinstance(shape.GetParent(), ogl.CompositeShape): | |
638 | self._diagram.RemoveShape(shape.GetParent()) | |
639 | self._diagram.InsertShape(shape.GetParent()) | |
640 | else: | |
641 | self._diagram.RemoveShape(shape) | |
642 | self._diagram.InsertShape(shape) | |
643 | ||
644 | ||
645 | def ScrollVisible(self, shape): | |
02b800ce RD |
646 | if not shape: |
647 | return | |
648 | ||
649 | xUnit, yUnit = self._canvas.GetScrollPixelsPerUnit() | |
1f780e48 RD |
650 | scrollX, scrollY = self._canvas.GetViewStart() # in scroll units |
651 | scrollW, scrollH = self._canvas.GetSize() # in pixels | |
652 | w, h = shape.GetBoundingBoxMax() # in pixels | |
653 | x = shape.GetX() - w/2 # convert to upper left coordinate from center | |
654 | y = shape.GetY() - h/2 # convert to upper left coordinate from center | |
655 | ||
656 | if x >= scrollX*xUnit and x <= scrollX*xUnit + scrollW: # don't scroll if already visible | |
657 | x = -1 | |
658 | else: | |
659 | x = x/xUnit | |
660 | ||
661 | if y >= scrollY*yUnit and y <= scrollY*yUnit + scrollH: # don't scroll if already visible | |
662 | y = -1 | |
663 | else: | |
664 | y = y/yUnit | |
665 | ||
666 | self._canvas.Scroll(x, y) # in scroll units | |
667 | ||
668 | ||
669 | def SetPropertyShape(self, shape): | |
670 | # no need to highlight if no PropertyService is running | |
671 | propertyService = wx.GetApp().GetService(PropertyService.PropertyService) | |
672 | if not propertyService: | |
673 | return | |
674 | ||
675 | if shape == self._propShape: | |
676 | return | |
677 | ||
678 | if hasattr(shape, "GetPropertyShape"): | |
679 | shape = shape.GetPropertyShape() | |
680 | ||
681 | dc = wx.ClientDC(self._canvas) | |
682 | self._canvas.PrepareDC(dc) | |
683 | dc.BeginDrawing() | |
684 | ||
685 | # erase old selection if it still exists | |
686 | if self._propShape and self._propShape in self._diagram.GetShapeList(): | |
02b800ce RD |
687 | if hasattr(self._propShape, "DEFAULT_BRUSH"): |
688 | self._propShape.SetBrush(self._propShape.DEFAULT_BRUSH) | |
689 | else: | |
690 | self._propShape.SetBrush(self._brush) | |
1f780e48 RD |
691 | if (self._propShape._textColourName in ["BLACK", "WHITE"]): # Would use GetTextColour() but it is broken |
692 | self._propShape.SetTextColour("BLACK", 0) | |
693 | self._propShape.Draw(dc) | |
694 | ||
695 | # set new selection | |
696 | self._propShape = shape | |
697 | ||
698 | # draw new selection | |
699 | if self._propShape and self._propShape in self._diagram.GetShapeList(): | |
6f1a3f9c RD |
700 | if self.HasFocus(): |
701 | self._propShape.SetBrush(SELECT_BRUSH) | |
702 | else: | |
703 | self._propShape.SetBrush(INACTIVE_SELECT_BRUSH) | |
1f780e48 RD |
704 | if (self._propShape._textColourName in ["BLACK", "WHITE"]): # Would use GetTextColour() but it is broken |
705 | self._propShape.SetTextColour("WHITE", 0) | |
706 | self._propShape.Draw(dc) | |
707 | ||
708 | dc.EndDrawing() | |
709 | ||
710 | ||
b792147d RD |
711 | def FocusColorPropertyShape(self, gotFocus=False): |
712 | # no need to change highlight if no PropertyService is running | |
713 | propertyService = wx.GetApp().GetService(PropertyService.PropertyService) | |
714 | if not propertyService: | |
715 | return | |
716 | ||
717 | if not self._propShape: | |
718 | return | |
719 | ||
720 | dc = wx.ClientDC(self._canvas) | |
721 | self._canvas.PrepareDC(dc) | |
722 | dc.BeginDrawing() | |
723 | ||
724 | # draw deactivated selection | |
725 | if self._propShape and self._propShape in self._diagram.GetShapeList(): | |
726 | if gotFocus: | |
727 | self._propShape.SetBrush(SELECT_BRUSH) | |
728 | else: | |
729 | self._propShape.SetBrush(INACTIVE_SELECT_BRUSH) | |
730 | if (self._propShape._textColourName in ["BLACK", "WHITE"]): # Would use GetTextColour() but it is broken | |
731 | self._propShape.SetTextColour("WHITE", 0) | |
732 | self._propShape.Draw(dc) | |
733 | ||
aca310e5 RD |
734 | if hasattr(self._propShape, FORCE_REDRAW_METHOD): |
735 | self._propShape.ForceRedraw() | |
736 | ||
b792147d RD |
737 | dc.EndDrawing() |
738 | ||
739 | ||
1f780e48 RD |
740 | #---------------------------------------------------------------------------- |
741 | # Property Service methods | |
742 | #---------------------------------------------------------------------------- | |
743 | ||
744 | def GetPropertyModel(self): | |
745 | if hasattr(self, "_propModel"): | |
746 | return self._propModel | |
747 | return None | |
748 | ||
749 | ||
750 | def SetPropertyModel(self, model): | |
751 | # no need to set the model if no PropertyService is running | |
752 | propertyService = wx.GetApp().GetService(PropertyService.PropertyService) | |
753 | if not propertyService: | |
754 | return | |
755 | ||
756 | if hasattr(self, "_propModel") and model == self._propModel: | |
757 | return | |
758 | ||
759 | self._propModel = model | |
760 | propertyService.LoadProperties(self._propModel, self.GetDocument()) | |
761 | ||
762 | ||
763 | class EditorCanvasShapeMixin: | |
764 | ||
765 | def GetModel(self): | |
766 | return self._model | |
767 | ||
768 | ||
769 | def SetModel(self, model): | |
770 | self._model = model | |
771 | ||
772 | ||
773 | class EditorCanvasShapeEvtHandler(ogl.ShapeEvtHandler): | |
774 | ||
775 | """ wxBug: Bug in OLG package. With wxShape.SetShadowMode() turned on, when we set the size, | |
776 | the width/height is larger by 6 pixels. Need to subtract this value from width and height when we | |
777 | resize the object. | |
778 | """ | |
779 | SHIFT_KEY = 1 | |
780 | CONTROL_KEY = 2 | |
781 | ||
782 | def __init__(self, view): | |
783 | ogl.ShapeEvtHandler.__init__(self) | |
784 | self._view = view | |
785 | ||
786 | ||
787 | def OnLeftClick(self, x, y, keys = 0, attachment = 0): | |
788 | shape = self.GetShape() | |
789 | if hasattr(shape, "GetModel"): # Workaround, on drag, we should deselect all other objects and select the clicked on object | |
790 | model = shape.GetModel() | |
791 | else: | |
792 | shape = shape.GetParent() | |
793 | if shape: | |
794 | model = shape.GetModel() | |
795 | ||
02b800ce RD |
796 | if model: |
797 | self._view.SetSelection(model, keys == self.SHIFT_KEY or keys == self.CONTROL_KEY) | |
798 | self._view.SetPropertyShape(shape) | |
799 | self._view.SetPropertyModel(model) | |
1f780e48 RD |
800 | |
801 | ||
802 | def OnEndDragLeft(self, x, y, keys = 0, attachment = 0): | |
803 | ogl.ShapeEvtHandler.OnEndDragLeft(self, x, y, keys, attachment) | |
804 | shape = self.GetShape() | |
805 | if hasattr(shape, "GetModel"): # Workaround, on drag, we should deselect all other objects and select the clicked on object | |
806 | model = shape.GetModel() | |
807 | else: | |
808 | parentShape = shape.GetParent() | |
809 | if parentShape: | |
810 | model = parentShape.GetModel() | |
811 | self._view.SetSelection(model, keys == self.SHIFT_KEY or keys == self.CONTROL_KEY) | |
812 | ||
813 | ||
26ee3a06 RD |
814 | def OnMovePre(self, dc, x, y, oldX, oldY, display): |
815 | """ Prevent objects from being dragged outside of viewable area """ | |
02b800ce | 816 | if (x < 0) or (y < 0) or (x > self._view._maxWidth) or (y > self._view._maxHeight): |
26ee3a06 RD |
817 | return False |
818 | ||
819 | return ogl.ShapeEvtHandler.OnMovePre(self, dc, x, y, oldX, oldY, display) | |
820 | ||
821 | ||
1f780e48 | 822 | def OnMovePost(self, dc, x, y, oldX, oldY, display): |
26ee3a06 | 823 | """ Update the model's record of where the shape should be. Also enable redo/undo. """ |
1f780e48 RD |
824 | if x == oldX and y == oldY: |
825 | return | |
826 | if not self._view.GetDocument(): | |
827 | return | |
828 | shape = self.GetShape() | |
829 | if isinstance(shape, EditorCanvasShapeMixin) and shape.Draggable(): | |
830 | model = shape.GetModel() | |
831 | if hasattr(model, "getEditorBounds") and model.getEditorBounds(): | |
832 | x, y, w, h = model.getEditorBounds() | |
833 | newX = shape.GetX() - shape.GetBoundingBoxMax()[0] / 2 | |
834 | newY = shape.GetY() - shape.GetBoundingBoxMax()[1] / 2 | |
835 | newWidth = shape.GetBoundingBoxMax()[0] | |
836 | newHeight = shape.GetBoundingBoxMax()[1] | |
837 | if shape._shadowMode != ogl.SHADOW_NONE: | |
838 | newWidth -= shape._shadowOffsetX | |
839 | newHeight -= shape._shadowOffsetY | |
840 | newbounds = (newX, newY, newWidth, newHeight) | |
841 | ||
842 | if x != newX or y != newY or w != newWidth or h != newHeight: | |
843 | self._view.GetDocument().GetCommandProcessor().Submit(EditorCanvasUpdateShapeBoundariesCommand(self._view.GetDocument(), model, newbounds)) | |
844 | ||
845 | ||
846 | def Draw(self, dc): | |
847 | pass | |
848 | ||
849 | ||
850 | class EditorCanvasUpdateShapeBoundariesCommand(wx.lib.docview.Command): | |
851 | ||
852 | ||
853 | def __init__(self, canvasDocument, model, newbounds): | |
854 | wx.lib.docview.Command.__init__(self, canUndo = True) | |
855 | self._canvasDocument = canvasDocument | |
856 | self._model = model | |
857 | self._oldbounds = model.getEditorBounds() | |
858 | self._newbounds = newbounds | |
859 | ||
860 | ||
861 | def GetName(self): | |
862 | name = self._canvasDocument.GetNameForObject(self._model) | |
863 | if not name: | |
864 | name = "" | |
865 | print "ERROR: AbstractEditor.EditorCanvasUpdateShapeBoundariesCommand.GetName: unable to get name for ", self._model | |
866 | return _("Move/Resize %s") % name | |
867 | ||
868 | ||
869 | def Do(self): | |
870 | return self._canvasDocument.UpdateEditorBoundaries(self._model, self._newbounds) | |
871 | ||
872 | ||
873 | def Undo(self): | |
874 | return self._canvasDocument.UpdateEditorBoundaries(self._model, self._oldbounds) | |
875 |