]>
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 RD |
25 | |
26 | ||
27 | def GetRawModel(model): | |
28 | if hasattr(model, "GetRawModel"): | |
29 | rawModel = model.GetRawModel() | |
30 | else: | |
31 | rawModel = model | |
32 | return rawModel | |
33 | ||
34 | ||
35 | class CanvasView(wx.lib.docview.View): | |
36 | ||
37 | ||
38 | #---------------------------------------------------------------------------- | |
39 | # Overridden methods | |
40 | #---------------------------------------------------------------------------- | |
41 | ||
42 | ||
43 | def __init__(self, brush = SHAPE_BRUSH): | |
44 | wx.lib.docview.View.__init__(self) | |
45 | self._brush = brush | |
46 | self._canvas = None | |
47 | self._pt1 = None | |
48 | self._pt2 = None | |
49 | self._needEraseLasso = False | |
50 | self._propShape = None | |
26ee3a06 RD |
51 | self._maxWidth = 2000 |
52 | self._maxHeight = 16000 | |
1f780e48 RD |
53 | |
54 | ||
26ee3a06 RD |
55 | def OnDraw(self, dc): |
56 | """ for Print Preview and Print """ | |
57 | dc.BeginDrawing() | |
58 | self._canvas.Redraw(dc) | |
59 | dc.EndDrawing() | |
60 | ||
61 | ||
1f780e48 RD |
62 | def OnCreate(self, doc, flags): |
63 | frame = wx.GetApp().CreateDocumentFrame(self, doc, flags) | |
64 | frame.Show() | |
65 | sizer = wx.BoxSizer() | |
66 | self._CreateCanvas(frame) | |
67 | sizer.Add(self._canvas, 1, wx.EXPAND, 0) | |
68 | frame.SetSizer(sizer) | |
69 | frame.Layout() | |
70 | self.Activate() | |
71 | return True | |
b792147d | 72 | |
1f780e48 RD |
73 | |
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: | |
6f1a3f9c | 78 | self.SetFocus() |
1f780e48 | 79 | else: |
6f1a3f9c RD |
80 | wx.CallAfter(self.SetFocus) |
81 | ||
82 | ||
83 | def SetFocus(self): | |
84 | if self._canvas: | |
85 | self._canvas.SetFocus() | |
1f780e48 RD |
86 | |
87 | ||
b792147d | 88 | def OnFocus(self, event): |
b792147d RD |
89 | self.FocusColorPropertyShape(True) |
90 | event.Skip() | |
91 | ||
92 | ||
2eeaec19 RD |
93 | def FocusOnClick(self, event): |
94 | self.SetFocus() | |
95 | event.Skip() | |
96 | ||
97 | ||
b792147d RD |
98 | def OnKillFocus(self, event): |
99 | self.FocusColorPropertyShape(False) | |
100 | event.Skip() | |
101 | ||
102 | ||
6f1a3f9c RD |
103 | def HasFocus(self): |
104 | winWithFocus = wx.Window.FindFocus() | |
105 | if not winWithFocus: | |
106 | return False | |
107 | while winWithFocus: | |
108 | if winWithFocus == self._canvas: | |
109 | return True | |
110 | winWithFocus = winWithFocus.GetParent() | |
111 | return False | |
112 | ||
113 | ||
1f780e48 RD |
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): | |
120 | return False | |
121 | self.Activate(False) | |
122 | if deleteWindow and self.GetFrame(): | |
123 | self.GetFrame().Destroy() | |
124 | return True | |
125 | ||
126 | ||
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) | |
b792147d RD |
134 | |
135 | # need this otherwise mouse clicks don't set focus to this view | |
2eeaec19 RD |
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) | |
b792147d RD |
142 | |
143 | wx.EVT_KILL_FOCUS(self._canvas, self.OnKillFocus) | |
144 | wx.EVT_SET_FOCUS(self._canvas, self.OnFocus) | |
1f780e48 | 145 | |
26ee3a06 | 146 | self._canvas.SetScrollbars(20, 20, self._maxWidth / 20, self._maxHeight / 20) |
1f780e48 RD |
147 | |
148 | self._canvas.SetBackgroundColour(wx.WHITE) | |
149 | self._diagram = ogl.Diagram() | |
150 | self._canvas.SetDiagram(self._diagram) | |
151 | self._diagram.SetCanvas(self._canvas) | |
152 | ||
153 | ||
154 | def OnKeyPressed(self, event): | |
155 | key = event.KeyCode() | |
156 | if key == wx.WXK_DELETE: | |
157 | self.OnClear(event) | |
158 | else: | |
159 | event.Skip() | |
160 | ||
161 | ||
162 | def OnLeftClick(self, event): | |
163 | self.EraseRubberBand() | |
164 | ||
165 | dc = wx.ClientDC(self._canvas) | |
166 | self._canvas.PrepareDC(dc) | |
167 | ||
168 | # keep track of mouse down for group select | |
169 | self._pt1 = event.GetLogicalPosition(dc) # this takes into account scrollbar offset | |
170 | self._pt2 = None | |
171 | ||
172 | shape = self._canvas.FindShape(self._pt1[0], self._pt1[1])[0] | |
173 | if shape: | |
174 | self.BringToFront(shape) | |
175 | ||
176 | self._pt1 = None | |
177 | event.Skip() # pass on event to shape handler to take care of selection | |
178 | ||
179 | return | |
180 | elif event.ControlDown() or event.ShiftDown(): # extend select, don't deselect | |
181 | pass | |
182 | else: | |
183 | # click on empty part of canvas, deselect everything | |
184 | needRefresh = False | |
185 | for shape in self._diagram.GetShapeList(): | |
186 | if hasattr(shape, "GetModel"): | |
187 | if shape.Selected(): | |
188 | needRefresh = True | |
189 | shape.Select(False, dc) | |
190 | if needRefresh: | |
191 | self._canvas.Redraw(dc) | |
192 | ||
193 | self.SetPropertyModel(None) | |
194 | ||
195 | if len(self.GetSelection()) == 0: | |
196 | self.SetPropertyShape(None) | |
197 | ||
198 | ||
199 | ||
200 | def OnLeftDoubleClick(self, event): | |
201 | propertyService = wx.GetApp().GetService(PropertyService.PropertyService) | |
202 | if propertyService: | |
203 | propertyService.ShowWindow() | |
204 | ||
205 | ||
206 | def OnLeftDrag(self, event): | |
207 | # draw lasso for group select | |
208 | if self._pt1 and event.LeftIsDown(): # we are in middle of lasso selection | |
209 | self.EraseRubberBand() | |
210 | ||
211 | dc = wx.ClientDC(self._canvas) | |
212 | self._canvas.PrepareDC(dc) | |
213 | self._pt2 = event.GetLogicalPosition(dc) # this takes into account scrollbar offset | |
214 | self.DrawRubberBand() | |
215 | else: | |
216 | event.Skip() | |
217 | ||
218 | ||
219 | def OnLeftUp(self, event): | |
220 | # do group select | |
221 | if self._needEraseLasso: | |
222 | self.EraseRubberBand() | |
223 | ||
224 | dc = wx.ClientDC(self._canvas) | |
225 | self._canvas.PrepareDC(dc) | |
226 | x1, y1 = self._pt1 | |
227 | x2, y2 = event.GetLogicalPosition(dc) # this takes into account scrollbar offset | |
228 | ||
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 | |
232 | if x1 > x2: | |
233 | temp = x1 | |
234 | x1 = x2 | |
235 | x2 = temp | |
236 | if y1 > y2: | |
237 | temp = y1 | |
238 | y1 = y2 | |
239 | y2 = temp | |
240 | ||
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 | |
247 | if selected: | |
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) | |
252 | else: | |
253 | event.Skip() | |
254 | else: | |
255 | event.Skip() | |
256 | ||
257 | ||
258 | def EraseRubberBand(self): | |
259 | if self._needEraseLasso: | |
260 | self._needEraseLasso = False | |
261 | ||
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) | |
266 | dc.SetPen(pen) | |
267 | brush = wx.Brush(wx.Colour(255, 255, 255), wx.TRANSPARENT) | |
268 | dc.SetBrush(brush) | |
269 | dc.ResetBoundingBox() | |
270 | dc.BeginDrawing() | |
271 | ||
272 | x1, y1 = self._pt1 | |
273 | x2, y2 = self._pt2 | |
274 | ||
275 | # make sure x1 < x2 and y1 < y2 | |
276 | # this will make (x1, y1) = upper left corner | |
277 | if x1 > x2: | |
278 | temp = x1 | |
279 | x1 = x2 | |
280 | x2 = temp | |
281 | if y1 > y2: | |
282 | temp = y1 | |
283 | y1 = y2 | |
284 | y2 = temp | |
285 | ||
286 | # erase previous outline | |
287 | dc.SetClippingRegion(x1, y1, x2 - x1, y2 - y1) | |
288 | dc.DrawRectangle(x1, y1, x2 - x1, y2 - y1) | |
289 | dc.EndDrawing() | |
290 | ||
291 | ||
292 | def DrawRubberBand(self): | |
293 | self._needEraseLasso = True | |
294 | ||
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) | |
299 | dc.SetPen(pen) | |
300 | brush = wx.Brush(wx.Colour(255, 255, 255), wx.TRANSPARENT) | |
301 | dc.SetBrush(brush) | |
302 | dc.ResetBoundingBox() | |
303 | dc.BeginDrawing() | |
304 | ||
305 | x1, y1 = self._pt1 | |
306 | x2, y2 = self._pt2 | |
307 | ||
308 | # make sure x1 < x2 and y1 < y2 | |
309 | # this will make (x1, y1) = upper left corner | |
310 | if x1 > x2: | |
311 | temp = x1 | |
312 | x1 = x2 | |
313 | x2 = temp | |
314 | if y1 > y2: | |
315 | temp = y1 | |
316 | y1 = y2 | |
317 | y2 = temp | |
318 | ||
319 | # draw outline | |
320 | dc.SetClippingRegion(x1, y1, x2 - x1, y2 - y1) | |
321 | dc.DrawRectangle(x1, y1, x2 - x1, y2 - y1) | |
322 | dc.EndDrawing() | |
323 | ||
324 | ||
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 | |
328 | x = offset | |
329 | y = offset | |
330 | maxX = 700 # max distance to the right where we'll place tables | |
331 | noParkingSpot = True | |
332 | ||
333 | while noParkingSpot: | |
334 | point = self.isSpotOccupied(x, y, width, height) | |
335 | if point: | |
336 | x = point[0] + offset | |
337 | if x > maxX: | |
338 | x = offset | |
339 | y = point[1] + offset | |
340 | else: | |
341 | noParkingSpot = False | |
342 | ||
343 | return x, y | |
344 | ||
345 | ||
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 | |
349 | """ | |
350 | x2 = x + width | |
351 | y2 = y + height | |
352 | ||
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 | |
357 | continue | |
358 | ||
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 | |
363 | else: | |
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 | |
368 | ||
369 | other_x2 = other_x + other_width | |
370 | other_y2 = other_y + other_height | |
371 | # intersection check | |
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) | |
377 | return None | |
378 | ||
379 | ||
380 | #---------------------------------------------------------------------------- | |
381 | # Canvas methods | |
382 | #---------------------------------------------------------------------------- | |
383 | ||
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) | |
388 | shape.Move(dc, x, y) | |
389 | else: | |
390 | shape.SetDraggable(True, True) | |
391 | shape.SetCanvas(self._canvas) | |
392 | ||
393 | if x: | |
394 | shape.SetX(x) | |
395 | if y: | |
396 | shape.SetY(y) | |
397 | shape.SetCentreResize(False) | |
398 | if pen: | |
399 | shape.SetPen(pen) | |
400 | if brush: | |
401 | shape.SetBrush(brush) | |
402 | if text: | |
403 | shape.AddText(text) | |
2eeaec19 | 404 | shape.SetShadowMode(ogl.SHADOW_NONE) |
1f780e48 RD |
405 | self._diagram.AddShape(shape) |
406 | shape.Show(True) | |
407 | if not eventHandler: | |
408 | eventHandler = EditorCanvasShapeEvtHandler(self) | |
409 | eventHandler.SetShape(shape) | |
410 | eventHandler.SetPreviousHandler(shape.GetEventHandler()) | |
411 | shape.SetEventHandler(eventHandler) | |
412 | return shape | |
413 | ||
414 | ||
415 | def RemoveShape(self, model = None, shape = None): | |
416 | if not model and not shape: | |
417 | return | |
418 | ||
419 | if not shape: | |
420 | shape = self.GetShape(model) | |
421 | ||
422 | if shape: | |
423 | shape.Select(False) | |
2eeaec19 RD |
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()): | |
430 | obj.RemoveLine(line) | |
431 | self._diagram.RemoveShape(line) | |
432 | if line == shape: | |
433 | obj.RemoveLine(line) | |
434 | ||
435 | shape.RemoveFromCanvas(self._canvas) | |
1f780e48 | 436 | self._diagram.RemoveShape(shape) |
2eeaec19 RD |
437 | |
438 | ||
439 | def IsShapeContained(self, parent, shape): | |
440 | if parent == shape: | |
441 | return True | |
442 | elif shape.GetParent(): | |
443 | return self.IsShapeContained(parent, shape.GetParent()) | |
444 | ||
445 | return False | |
1f780e48 RD |
446 | |
447 | ||
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() | |
452 | newX = x + w / 2 | |
453 | newY = y + h / 2 | |
454 | changed = False | |
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 | |
461 | changed = True | |
462 | else: | |
463 | oldw, oldh = shape.GetBoundingBoxMax() | |
464 | oldx = shape.GetX() | |
465 | oldy = shape.GetY() | |
466 | if oldw != w or oldh != h or oldx != newX or oldy != newY: | |
467 | shape.SetSize(w, h) | |
468 | shape.SetX(newX) | |
469 | shape.SetY(newY) | |
470 | changed = True | |
471 | if changed: | |
472 | shape.ResetControlPoints() | |
473 | self._canvas.Refresh() | |
474 | break | |
475 | ||
476 | ||
477 | def GetShape(self, model): | |
478 | for shape in self._diagram.GetShapeList(): | |
479 | if hasattr(shape, "GetModel") and shape.GetModel() == model: | |
480 | return shape | |
481 | return None | |
482 | ||
483 | ||
484 | def GetSelection(self): | |
485 | return filter(lambda shape: shape.Selected(), self._diagram.GetShapeList()) | |
486 | ||
487 | ||
488 | def SetSelection(self, models, extendSelect = False): | |
489 | dc = wx.ClientDC(self._canvas) | |
490 | self._canvas.PrepareDC(dc) | |
491 | update = False | |
492 | if not isinstance(models, type([])) and not isinstance(models, type(())): | |
493 | models = [models] | |
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 | |
497 | if not extendSelect: | |
498 | shape.Select(False, dc) | |
499 | update = True | |
500 | elif not shape.Selected() and shape.GetModel() in models: # was not selected and in new list, so select | |
501 | shape.Select(True, dc) | |
502 | update = True | |
503 | elif extendSelect and shape.Selected() and shape.GetModel() in models: # was selected, but extend select means to deselect | |
504 | shape.Select(False, dc) | |
505 | update = True | |
506 | if update: | |
507 | self._canvas.Redraw(dc) | |
508 | ||
509 | ||
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()) | |
514 | else: | |
515 | self._diagram.RemoveShape(shape) | |
516 | self._diagram.AddShape(shape) | |
517 | ||
518 | ||
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()) | |
523 | else: | |
524 | self._diagram.RemoveShape(shape) | |
525 | self._diagram.InsertShape(shape) | |
526 | ||
527 | ||
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 | |
535 | ||
536 | if x >= scrollX*xUnit and x <= scrollX*xUnit + scrollW: # don't scroll if already visible | |
537 | x = -1 | |
538 | else: | |
539 | x = x/xUnit | |
540 | ||
541 | if y >= scrollY*yUnit and y <= scrollY*yUnit + scrollH: # don't scroll if already visible | |
542 | y = -1 | |
543 | else: | |
544 | y = y/yUnit | |
545 | ||
546 | self._canvas.Scroll(x, y) # in scroll units | |
547 | ||
548 | ||
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: | |
553 | return | |
554 | ||
555 | if shape == self._propShape: | |
556 | return | |
557 | ||
558 | if hasattr(shape, "GetPropertyShape"): | |
559 | shape = shape.GetPropertyShape() | |
560 | ||
561 | dc = wx.ClientDC(self._canvas) | |
562 | self._canvas.PrepareDC(dc) | |
563 | dc.BeginDrawing() | |
564 | ||
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) | |
571 | ||
572 | # set new selection | |
573 | self._propShape = shape | |
574 | ||
575 | # draw new selection | |
576 | if self._propShape and self._propShape in self._diagram.GetShapeList(): | |
6f1a3f9c RD |
577 | if self.HasFocus(): |
578 | self._propShape.SetBrush(SELECT_BRUSH) | |
579 | else: | |
580 | self._propShape.SetBrush(INACTIVE_SELECT_BRUSH) | |
1f780e48 RD |
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) | |
584 | ||
585 | dc.EndDrawing() | |
586 | ||
587 | ||
b792147d RD |
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: | |
592 | return | |
593 | ||
594 | if not self._propShape: | |
595 | return | |
596 | ||
597 | dc = wx.ClientDC(self._canvas) | |
598 | self._canvas.PrepareDC(dc) | |
599 | dc.BeginDrawing() | |
600 | ||
601 | # draw deactivated selection | |
602 | if self._propShape and self._propShape in self._diagram.GetShapeList(): | |
603 | if gotFocus: | |
604 | self._propShape.SetBrush(SELECT_BRUSH) | |
605 | else: | |
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) | |
610 | ||
611 | dc.EndDrawing() | |
612 | ||
613 | ||
1f780e48 RD |
614 | #---------------------------------------------------------------------------- |
615 | # Property Service methods | |
616 | #---------------------------------------------------------------------------- | |
617 | ||
618 | def GetPropertyModel(self): | |
619 | if hasattr(self, "_propModel"): | |
620 | return self._propModel | |
621 | return None | |
622 | ||
623 | ||
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: | |
628 | return | |
629 | ||
630 | if hasattr(self, "_propModel") and model == self._propModel: | |
631 | return | |
632 | ||
633 | self._propModel = model | |
634 | propertyService.LoadProperties(self._propModel, self.GetDocument()) | |
635 | ||
636 | ||
637 | class EditorCanvasShapeMixin: | |
638 | ||
639 | def GetModel(self): | |
640 | return self._model | |
641 | ||
642 | ||
643 | def SetModel(self, model): | |
644 | self._model = model | |
645 | ||
646 | ||
647 | class EditorCanvasShapeEvtHandler(ogl.ShapeEvtHandler): | |
648 | ||
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 | |
651 | resize the object. | |
652 | """ | |
653 | SHIFT_KEY = 1 | |
654 | CONTROL_KEY = 2 | |
655 | ||
656 | def __init__(self, view): | |
657 | ogl.ShapeEvtHandler.__init__(self) | |
658 | self._view = view | |
659 | ||
660 | ||
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() | |
665 | else: | |
666 | shape = shape.GetParent() | |
667 | if shape: | |
668 | model = shape.GetModel() | |
669 | ||
670 | self._view.SetSelection(model, keys == self.SHIFT_KEY or keys == self.CONTROL_KEY) | |
671 | self._view.SetPropertyShape(shape) | |
672 | self._view.SetPropertyModel(model) | |
673 | ||
674 | ||
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() | |
680 | else: | |
681 | parentShape = shape.GetParent() | |
682 | if parentShape: | |
683 | model = parentShape.GetModel() | |
684 | self._view.SetSelection(model, keys == self.SHIFT_KEY or keys == self.CONTROL_KEY) | |
685 | ||
686 | ||
26ee3a06 RD |
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): | |
690 | return False | |
691 | ||
692 | return ogl.ShapeEvtHandler.OnMovePre(self, dc, x, y, oldX, oldY, display) | |
693 | ||
694 | ||
1f780e48 | 695 | def OnMovePost(self, dc, x, y, oldX, oldY, display): |
26ee3a06 | 696 | """ Update the model's record of where the shape should be. Also enable redo/undo. """ |
1f780e48 RD |
697 | if x == oldX and y == oldY: |
698 | return | |
699 | if not self._view.GetDocument(): | |
700 | return | |
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) | |
714 | ||
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)) | |
717 | ||
718 | ||
719 | def Draw(self, dc): | |
720 | pass | |
721 | ||
722 | ||
723 | class EditorCanvasUpdateShapeBoundariesCommand(wx.lib.docview.Command): | |
724 | ||
725 | ||
726 | def __init__(self, canvasDocument, model, newbounds): | |
727 | wx.lib.docview.Command.__init__(self, canUndo = True) | |
728 | self._canvasDocument = canvasDocument | |
729 | self._model = model | |
730 | self._oldbounds = model.getEditorBounds() | |
731 | self._newbounds = newbounds | |
732 | ||
733 | ||
734 | def GetName(self): | |
735 | name = self._canvasDocument.GetNameForObject(self._model) | |
736 | if not name: | |
737 | name = "" | |
738 | print "ERROR: AbstractEditor.EditorCanvasUpdateShapeBoundariesCommand.GetName: unable to get name for ", self._model | |
739 | return _("Move/Resize %s") % name | |
740 | ||
741 | ||
742 | def Do(self): | |
743 | return self._canvasDocument.UpdateEditorBoundaries(self._model, self._newbounds) | |
744 | ||
745 | ||
746 | def Undo(self): | |
747 | return self._canvasDocument.UpdateEditorBoundaries(self._model, self._oldbounds) | |
748 |