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