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