1# -*- coding: utf-8 -*- 2#---------------------------------------------------------------------------- 3# Name: basic.py 4# Purpose: The basic OGL shapes 5# 6# Author: Pierre Hjälm (from C++ original by Julian Smart) 7# 8# Created: 2004-05-08 9# Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart 10# Licence: wxWindows license 11# Tags: phoenix-port, unittest, py3-port, documented 12#---------------------------------------------------------------------------- 13""" 14The basic shapes for OGL 15""" 16 17import wx 18import math 19 20from .oglmisc import * 21 22DragOffsetX = 0.0 23DragOffsetY = 0.0 24 25 26def OGLInitialize(): 27 """Initialize OGL. 28 29 :note: This creates some pens and brushes that the OGL library uses. 30 It should be called after the app object has been created, but 31 before OGL is used. 32 """ 33 global WhiteBackgroundPen, WhiteBackgroundBrush, TransparentPen 34 global BlackForegroundPen, NormalFont 35 36 WhiteBackgroundPen = wx.Pen(wx.WHITE, 1, wx.PENSTYLE_SOLID) 37 WhiteBackgroundBrush = wx.Brush(wx.WHITE, wx.BRUSHSTYLE_SOLID) 38 39 TransparentPen = wx.Pen(wx.WHITE, 1, wx.PENSTYLE_TRANSPARENT) 40 BlackForegroundPen = wx.Pen(wx.BLACK, 1, wx.PENSTYLE_SOLID) 41 42 NormalFont = wx.Font(10, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) 43 44 45def OGLCleanUp(): 46 """not implemented???""" 47 pass 48 49 50class ShapeTextLine(object): 51 """The :class:`ShapeTextLine` class.""" 52 def __init__(self, the_x, the_y, the_line): 53 """ 54 Default class constructor. 55 56 :param int `the_x`: the x position 57 :param int `the_y`: the y position 58 :param str `the_line`: the text 59 60 """ 61 self._x = the_x 62 self._y = the_y 63 self._line = the_line 64 65 def GetX(self): 66 """Get the x position.""" 67 return self._x 68 69 def GetY(self): 70 """Get the y position.""" 71 return self._y 72 73 def SetX(self, x): 74 """ 75 Set the x position. 76 77 :param int `x`: the x position 78 79 """ 80 self._x = x 81 82 def SetY(self, y): 83 """ 84 Set the y position. 85 86 :param int `y`: the x position 87 88 """ 89 self._y = y 90 91 def SetText(self, text): 92 """ 93 Set the text. 94 95 :param str `text`: the text 96 97 """ 98 self._line = text 99 100 def GetText(self): 101 """Get the text.""" 102 return self._line 103 104 105class ShapeEvtHandler(object): 106 """The :class:`ShapeEvtHandler` class.""" 107 def __init__(self, prev = None, shape = None): 108 """ 109 Default class constructor. 110 111 :param `pref`: the previous event handler, an instance of 112 :class:`ShapeEvtHandler` ??? 113 :param `shape`: the shape, an instance of :class:`Shape` 114 115 """ 116 self._previousHandler = prev 117 self._handlerShape = shape 118 119 def SetShape(self, sh): 120 """ 121 Set associated shape 122 123 :param `sh`: the shape, an instance of :class:`Shape` 124 125 """ 126 self._handlerShape = sh 127 128 def GetShape(self): 129 """Get associated shape.""" 130 return self._handlerShape 131 132 def SetPreviousHandler(self, handler): 133 """ 134 Set previous event handler. 135 136 :param `handler`: the previous handler, an instance of 137 :class:`ShapeEvtHandler` ??? 138 139 """ 140 self._previousHandler = handler 141 142 def GetPreviousHandler(self): 143 """Get previous event handler.""" 144 return self._previousHandler 145 146 def OnDelete(self): 147 """The delete handler.""" 148 if self!=self.GetShape(): 149 del self 150 151 def OnDraw(self, dc): 152 """The draw handler.""" 153 if self._previousHandler: 154 self._previousHandler.OnDraw(dc) 155 156 def OnMoveLinks(self, dc): 157 """The move links handler.""" 158 if self._previousHandler: 159 self._previousHandler.OnMoveLinks(dc) 160 161 def OnMoveLink(self, dc, moveControlPoints = True): 162 """The move link handler.""" 163 if self._previousHandler: 164 self._previousHandler.OnMoveLink(dc, moveControlPoints) 165 166 def OnDrawContents(self, dc): 167 """The draw contents handler.""" 168 if self._previousHandler: 169 self._previousHandler.OnDrawContents(dc) 170 171 def OnDrawBranches(self, dc, erase = False): 172 """The draw branches handler.""" 173 if self._previousHandler: 174 self._previousHandler.OnDrawBranches(dc, erase = erase) 175 176 def OnSize(self, x, y): 177 """The size handler.""" 178 if self._previousHandler: 179 self._previousHandler.OnSize(x, y) 180 181 def OnMovePre(self, dc, x, y, old_x, old_y, display = True): 182 """The pre move handler.""" 183 if self._previousHandler: 184 return self._previousHandler.OnMovePre(dc, x, y, old_x, old_y, display) 185 else: 186 return True 187 188 def OnMovePost(self, dc, x, y, old_x, old_y, display = True): 189 """The post move handler.""" 190 if self._previousHandler: 191 return self._previousHandler.OnMovePost(dc, x, y, old_x, old_y, display) 192 else: 193 return True 194 195 def OnErase(self, dc): 196 """The erase handler.""" 197 if self._previousHandler: 198 self._previousHandler.OnErase(dc) 199 200 def OnEraseContents(self, dc): 201 """The erase contents handler.""" 202 if self._previousHandler: 203 self._previousHandler.OnEraseContents(dc) 204 205 def OnHighlight(self, dc): 206 """The highlight handler.""" 207 if self._previousHandler: 208 self._previousHandler.OnHighlight(dc) 209 210 def OnLeftClick(self, x, y, keys, attachment): 211 """The left click handler.""" 212 if self._previousHandler: 213 self._previousHandler.OnLeftClick(x, y, keys, attachment) 214 215 def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0): 216 """The left double click handler.""" 217 if self._previousHandler: 218 self._previousHandler.OnLeftDoubleClick(x, y, keys, attachment) 219 220 def OnRightClick(self, x, y, keys = 0, attachment = 0): 221 """The right click handler.""" 222 if self._previousHandler: 223 self._previousHandler.OnRightClick(x, y, keys, attachment) 224 225 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0): 226 """The drag left handler.""" 227 if self._previousHandler: 228 self._previousHandler.OnDragLeft(draw, x, y, keys, attachment) 229 230 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0): 231 """The begin drag left handler.""" 232 if self._previousHandler: 233 self._previousHandler.OnBeginDragLeft(x, y, keys, attachment) 234 235 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0): 236 """The end drag left handler.""" 237 if self._previousHandler: 238 self._previousHandler.OnEndDragLeft(x, y, keys, attachment) 239 240 def OnDragRight(self, draw, x, y, keys = 0, attachment = 0): 241 """The drag right handler.""" 242 if self._previousHandler: 243 self._previousHandler.OnDragRight(draw, x, y, keys, attachment) 244 245 def OnBeginDragRight(self, x, y, keys = 0, attachment = 0): 246 """The begin drag right handler.""" 247 if self._previousHandler: 248 self._previousHandler.OnBeginDragRight(x, y, keys, attachment) 249 250 def OnEndDragRight(self, x, y, keys = 0, attachment = 0): 251 """The end drag right handler.""" 252 if self._previousHandler: 253 self._previousHandler.OnEndDragRight(x, y, keys, attachment) 254 255 # Control points ('handles') redirect control to the actual shape, 256 # to make it easier to override sizing behaviour. 257 def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0): 258 """The sizing drag left handler.""" 259 if self._previousHandler: 260 self._previousHandler.OnSizingDragLeft(pt, draw, x, y, keys, attachment) 261 262 def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0): 263 """The sizing begin drag left handler.""" 264 if self._previousHandler: 265 self._previousHandler.OnSizingBeginDragLeft(pt, x, y, keys, attachment) 266 267 def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0): 268 """The sizing end drag left handler.""" 269 if self._previousHandler: 270 self._previousHandler.OnSizingEndDragLeft(pt, x, y, keys, attachment) 271 272 def OnBeginSize(self, w, h): 273 """not implemented???""" 274 pass 275 276 def OnEndSize(self, w, h): 277 """not implemented???""" 278 pass 279 280 def OnDrawOutline(self, dc, x, y, w, h): 281 """The drag outline handler.""" 282 if self._previousHandler: 283 self._previousHandler.OnDrawOutline(dc, x, y, w, h) 284 285 def OnDrawControlPoints(self, dc): 286 """The draw control points handler.""" 287 if self._previousHandler: 288 self._previousHandler.OnDrawControlPoints(dc) 289 290 def OnEraseControlPoints(self, dc): 291 """The erase control points handler.""" 292 if self._previousHandler: 293 self._previousHandler.OnEraseControlPoints(dc) 294 295 # Can override this to prevent or intercept line reordering. 296 def OnChangeAttachment(self, attachment, line, ordering): 297 """The change attachment handler.""" 298 if self._previousHandler: 299 self._previousHandler.OnChangeAttachment(attachment, line, ordering) 300 301 302class Shape(ShapeEvtHandler): 303 """ 304 The :class:`Shape` is the base class for OGL shapes. 305 306 The :class:`Shape` is the top-level, abstract object that all other objects 307 are derived from. All common functionality is represented by :class:`Shape` 308 members, and overriden members that appear in derived classes and have 309 behaviour as documented for :class:`Shape`, are not documented separately. 310 """ 311 GraphicsInSizeToContents = False 312 313 def __init__(self, canvas = None): 314 """ 315 Default class constructor. 316 317 :param `canvas`: an instance of :class:`~lib.ogl.Canvas` 318 319 """ 320 ShapeEvtHandler.__init__(self) 321 322 self._eventHandler = self 323 self.SetShape(self) 324 self._id = 0 325 self._formatted = False 326 self._canvas = canvas 327 self._xpos = 0.0 328 self._ypos = 0.0 329 self._pen = BlackForegroundPen 330 self._brush = wx.WHITE_BRUSH 331 self._font = NormalFont 332 self._textColour = wx.BLACK 333 self._textColourName = wx.BLACK 334 self._visible = False 335 self._selected = False 336 self._attachmentMode = ATTACHMENT_MODE_NONE 337 self._spaceAttachments = True 338 self._disableLabel = False 339 self._fixedWidth = False 340 self._fixedHeight = False 341 self._drawHandles = True 342 self._sensitivity = OP_ALL 343 self._draggable = True 344 self._parent = None 345 self._formatMode = FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT 346 self._shadowMode = SHADOW_NONE 347 self._shadowOffsetX = 6 348 self._shadowOffsetY = 6 349 self._shadowBrush = wx.BLACK_BRUSH 350 self._textMarginX = 5 351 self._textMarginY = 5 352 self._regionName = "0" 353 self._centreResize = True 354 self._maintainAspectRatio = False 355 self._highlighted = False 356 self._rotation = 0.0 357 self._branchNeckLength = 10 358 self._branchStemLength = 10 359 self._branchSpacing = 10 360 self._branchStyle = BRANCHING_ATTACHMENT_NORMAL 361 362 self._regions = [] 363 self._lines = [] 364 self._controlPoints = [] 365 self._attachmentPoints = [] 366 self._text = [] 367 self._children = [] 368 369 # Set up a default region. Much of the above will be put into 370 # the region eventually (the duplication is for compatibility) 371 region = ShapeRegion() 372 region.SetName("0") 373 region.SetFont(NormalFont) 374 region.SetFormatMode(FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT) 375 region.SetColour("BLACK") 376 self._regions.append(region) 377 378 def __str__(self): 379 return "<%s.%s>" % (self.__class__.__module__, self.__class__.__name__) 380 381 def GetClassName(self): 382 return str(self.__class__).split(".")[-1][:-2] 383 384 def Delete(self): 385 """ 386 Fully disconnect this shape from parents, children, the 387 canvas, etc. 388 """ 389 if self._parent: 390 self._parent.GetChildren().remove(self) 391 392 for child in self.GetChildren(): 393 child.Delete() 394 395 self.ClearText() 396 self.ClearRegions() 397 self.ClearAttachments() 398 399 self._handlerShape = None 400 401 if self._canvas: 402 self.RemoveFromCanvas(self._canvas) 403 404 if self.GetEventHandler(): 405 self.GetEventHandler().OnDelete() 406 self._eventHandler = None 407 408 def Draggable(self): 409 """ 410 Is shape draggable? 411 412 :returns: `True` if the shape may be dragged by the user. 413 """ 414 return True 415 416 def SetShape(self, sh): 417 """Set shape ??? 418 419 :param `sh`: an instance of :class:`Shape` 420 421 """ 422 self._handlerShape = sh 423 424 def GetCanvas(self): 425 """Get the internal canvas.""" 426 return self._canvas 427 428 def GetBranchStyle(self): 429 """Get the branch style.""" 430 return self._branchStyle 431 432 def GetRotation(self): 433 """Return the angle of rotation in radians.""" 434 return self._rotation 435 436 def SetRotation(self, rotation): 437 """Set rotation 438 439 :param int `rotation`: rotation 440 441 """ 442 self._rotation = rotation 443 444 def SetHighlight(self, hi, recurse = False): 445 """Set the highlight for a shape. Shape highlighting is unimplemented.""" 446 self._highlighted = hi 447 if recurse: 448 for shape in self._children: 449 shape.SetHighlight(hi, recurse) 450 451 def SetSensitivityFilter(self, sens = OP_ALL, recursive = False): 452 """ 453 Set the shape to be sensitive or insensitive to specific mouse 454 operations. 455 456 :param `sens`: is a bitlist of the following: 457 458 ========================== =================== 459 Mouse operation Description 460 ========================== =================== 461 `OP_CLICK_LEFT` left clicked 462 `OP_CLICK_RIGHT` right clicked 463 `OP_DRAG_LEFT` left drag 464 `OP_DRAG_RIGHT` right drag 465 `OP_ALL` all of the above 466 ========================== =================== 467 468 :param `recursive`: if `True` recurse through children 469 470 """ 471 self._draggable = sens & OP_DRAG_LEFT 472 473 self._sensitivity = sens 474 if recursive: 475 for shape in self._children: 476 shape.SetSensitivityFilter(sens, True) 477 478 def SetDraggable(self, drag, recursive = False): 479 """Set the shape to be draggable or not draggable. 480 481 :param `drag`: if `True` make shape draggable 482 :param `recursive`: if `True` recurse through children 483 484 """ 485 self._draggable = drag 486 if drag: 487 self._sensitivity |= OP_DRAG_LEFT 488 elif self._sensitivity & OP_DRAG_LEFT: 489 self._sensitivity -= OP_DRAG_LEFT 490 491 if recursive: 492 for shape in self._children: 493 shape.SetDraggable(drag, True) 494 495 def SetDrawHandles(self, drawH): 496 """ 497 Set the drawHandles flag for this shape and all descendants. 498 499 :param `drawH`: if `True` (the default), any handles (control points) 500 will be drawn. Otherwise, the handles will not be drawn. 501 502 """ 503 self._drawHandles = drawH 504 for shape in self._children: 505 shape.SetDrawHandles(drawH) 506 507 def SetShadowMode(self, mode, redraw = False): 508 """ 509 Set the shadow mode (whether a shadow is drawn or not). 510 511 :param `mode`: can be one of the following: 512 513 =============================== =========================== 514 Shadow mode Description 515 =============================== =========================== 516 `SHADOW_NONE` No shadow (the default) 517 `SHADOW_LEFT` Shadow on the left side 518 `SHADOW_RIGHT` Shadow on the right side 519 =============================== =========================== 520 521 """ 522 if redraw and self.GetCanvas(): 523 dc = wx.MemoryDC() 524 dc.SelectObject(self.GetCanvas().GetBuffer()) 525 self.GetCanvas().PrepareDC(dc) 526 self.Erase(dc) 527 self._shadowMode = mode 528 self.Draw(dc) 529 else: 530 self._shadowMode = mode 531 532 def GetShadowMode(self): 533 """Get the current shadow mode setting.""" 534 return self._shadowMode 535 536 def SetCanvas(self, theCanvas): 537 """ 538 Set the canvas, identical to Shape.Attach. 539 540 :param `theCanvas`: an instance of :class:`~lib.ogl.Canvas` 541 542 """ 543 self._canvas = theCanvas 544 for shape in self._children: 545 shape.SetCanvas(theCanvas) 546 547 def AddToCanvas(self, theCanvas, addAfter = None): 548 """ 549 Add the shape to the canvas's shape list. 550 551 :param `theCanvas`: an instance of :class:`~lib.ogl.Canvas` 552 :param `addAfter`: if non-NULL, will add the shape after this shape 553 554 """ 555 theCanvas.AddShape(self, addAfter) 556 557 lastImage = self 558 for object in self._children: 559 object.AddToCanvas(theCanvas, lastImage) 560 lastImage = object 561 562 def InsertInCanvas(self, theCanvas): 563 """ 564 Insert the shape at the front of the shape list of canvas. 565 566 :param `theCanvas`: an instance of :class:`~lib.ogl.Canvas` 567 568 """ 569 theCanvas.InsertShape(self) 570 571 lastImage = self 572 for object in self._children: 573 object.AddToCanvas(theCanvas, lastImage) 574 lastImage = object 575 576 def RemoveFromCanvas(self, theCanvas): 577 """ 578 Remove the shape from the canvas. 579 580 :param `theCanvas`: an instance of :class:`~lib.ogl.Canvas` 581 582 """ 583 if self.Selected(): 584 self.Select(False) 585 586 self._canvas = None 587 theCanvas.RemoveShape(self) 588 for object in self._children: 589 object.RemoveFromCanvas(theCanvas) 590 591 def ClearAttachments(self): 592 """Clear internal custom attachment point shapes (of class 593 :class:`~lib.ogl.AttachmentPoint`) 594 """ 595 self._attachmentPoints = [] 596 597 def ClearText(self, regionId = 0): 598 """ 599 Clear the text from the specified text region. 600 601 :param `regionId`: the region identifier 602 603 """ 604 if regionId == 0: 605 self._text = "" 606 if regionId < len(self._regions): 607 self._regions[regionId].ClearText() 608 609 def ClearRegions(self): 610 """Clear the ShapeRegions from the shape.""" 611 self._regions = [] 612 613 def AddRegion(self, region): 614 """Add a region to the shape.""" 615 self._regions.append(region) 616 617 def SetDefaultRegionSize(self): 618 """Set the default region to be consistent with the shape size.""" 619 if not self._regions: 620 return 621 w, h = self.GetBoundingBoxMax() 622 self._regions[0].SetSize(w, h) 623 624 def HitTest(self, x, y): 625 """ 626 Given a point on a canvas, returns `True` if the point was on the 627 shape, and returns the nearest attachment point and distance from 628 the given point and target. 629 630 :param `x`: the x position 631 :param `y`: the y position 632 633 """ 634 width, height = self.GetBoundingBoxMax() 635 if abs(width) < 4: 636 width = 4.0 637 if abs(height) < 4: 638 height = 4.0 639 640 width += 4 # Allowance for inaccurate mousing 641 height += 4 642 643 left = self._xpos - width / 2.0 644 top = self._ypos - height / 2.0 645 right = self._xpos + width / 2.0 646 bottom = self._ypos + height / 2.0 647 648 nearest_attachment = 0 649 650 # If within the bounding box, check the attachment points 651 # within the object. 652 if x >= left and x <= right and y >= top and y <= bottom: 653 n = self.GetNumberOfAttachments() 654 nearest = 999999 655 656 # GetAttachmentPosition[Edge] takes a logical attachment position, 657 # i.e. if it's rotated through 90%, position 0 is East-facing. 658 659 for i in range(n): 660 e = self.GetAttachmentPositionEdge(i) 661 if e: 662 xp, yp = e 663 l = math.sqrt(((xp - x) * (xp - x)) + (yp - y) * (yp - y)) 664 if l < nearest: 665 nearest = l 666 nearest_attachment = i 667 668 return nearest_attachment, nearest 669 return False 670 671 # Format a text string according to the region size, adding 672 # strings with positions to region text list 673 674 def FormatText(self, dc, s, i = 0): 675 """ 676 Reformat the given text region; defaults to formatting the 677 default region. 678 679 :param `dc`: the device contexr 680 :param str `s`: the text string 681 :param int `i`: the region identifier 682 683 """ 684 self.ClearText(i) 685 686 if not self._regions: 687 return 688 689 if i >= len(self._regions): 690 return 691 692 region = self._regions[i] 693 region._regionText = s 694 dc.SetFont(region.GetFont()) 695 696 w, h = region.GetSize() 697 698 stringList = FormatText(dc, s, (w - 2 * self._textMarginX), (h - 2 * self._textMarginY), region.GetFormatMode()) 699 for s in stringList: 700 line = ShapeTextLine(0.0, 0.0, s) 701 region.GetFormattedText().append(line) 702 703 actualW = w 704 actualH = h 705 # Don't try to resize an object with more than one image (this 706 # case should be dealt with by overriden handlers) 707 if (region.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS) and \ 708 len(region.GetFormattedText()) and \ 709 len(self._regions) == 1 and \ 710 not Shape.GraphicsInSizeToContents: 711 712 actualW, actualH = GetCentredTextExtent(dc, region.GetFormattedText()) 713 if actualW + 2 * self._textMarginX != w or actualH + 2 * self._textMarginY != h: 714 # If we are a descendant of a composite, must make sure 715 # the composite gets resized properly 716 717 topAncestor = self.GetTopAncestor() 718 if topAncestor != self: 719 Shape.GraphicsInSizeToContents = True 720 721 composite = topAncestor 722 composite.Erase(dc) 723 self.SetSize(actualW + 2 * self._textMarginX, actualH + 2 * self._textMarginY) 724 self.Move(dc, self._xpos, self._ypos) 725 composite.CalculateSize() 726 if composite.Selected(): 727 composite.DeleteControlPoints(dc) 728 composite.MakeControlPoints() 729 composite.MakeMandatoryControlPoints() 730 # Where infinite recursion might happen if we didn't stop it 731 composite.Draw(dc) 732 Shape.GraphicsInSizeToContents = False 733 else: 734 self.Erase(dc) 735 736 self.SetSize(actualW + 2 * self._textMarginX, actualH + 2 * self._textMarginY) 737 self.Move(dc, self._xpos, self._ypos) 738 self.EraseContents(dc) 739 CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, actualW - 2 * self._textMarginX, actualH - 2 * self._textMarginY, region.GetFormatMode()) 740 self._formatted = True 741 742 def Recentre(self, dc): 743 """ 744 Recentre (or other formatting) all the text regions for this shape. 745 """ 746 w, h = self.GetBoundingBoxMin() 747 for region in self._regions: 748 CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, w - 2 * self._textMarginX, h - 2 * self._textMarginY, region.GetFormatMode()) 749 750 def GetPerimeterPoint(self, x1, y1, x2, y2): 751 """ 752 Get the point at which the line from (x1, y1) to (x2, y2) hits 753 the shape. 754 755 :param `x1`: the x1 position 756 :param `y1`: the y1 position 757 :param `x2`: the x2 position 758 :param `y2`: the y2 position 759 760 :returns: `False` if the line doesn't hit the perimeter. 761 762 """ 763 return False 764 765 def SetPen(self, the_pen): 766 """Set the pen for drawing the shape's outline.""" 767 self._pen = the_pen 768 769 def SetBrush(self, the_brush): 770 """Set the brush for filling the shape's shape.""" 771 self._brush = the_brush 772 773 # Get the top - most (non-division) ancestor, or self 774 def GetTopAncestor(self): 775 """ 776 Return the top-most ancestor of this shape (the root of 777 the composite). 778 """ 779 if not self.GetParent(): 780 return self 781 782 if isinstance(self.GetParent(), DivisionShape): 783 return self 784 return self.GetParent().GetTopAncestor() 785 786 # Region functions 787 def SetFont(self, the_font, regionId = 0): 788 """ 789 Set the font for the specified text region. 790 791 :param `the_font`: an instance of :class:`wx.Font` ??? 792 :param `regionId`: the region identifier 793 794 """ 795 self._font = the_font 796 if regionId < len(self._regions): 797 self._regions[regionId].SetFont(the_font) 798 799 def GetFont(self, regionId = 0): 800 """ 801 Get the font for the specified text region. 802 803 :param `regionId`: the region identifier 804 805 """ 806 if regionId >= len(self._regions): 807 return None 808 return self._regions[regionId].GetFont() 809 810 def SetFormatMode(self, mode, regionId = 0): 811 """ 812 Set the format mode of the region. 813 814 :param `mode`: can be a bit list of the following 815 816 ============================== ============================== 817 Format mode Description 818 ============================== ============================== 819 `FORMAT_NONE` No formatting 820 `FORMAT_CENTRE_HORIZ` Horizontal centring 821 `FORMAT_CENTRE_VERT` Vertical centring 822 ============================== ============================== 823 824 :param `regionId`: the region identifier, default=0 825 826 """ 827 if regionId < len(self._regions): 828 self._regions[regionId].SetFormatMode(mode) 829 830 def GetFormatMode(self, regionId = 0): 831 """ 832 Get the format mode. 833 834 :param `regionId`: the region identifier, default=0 835 836 """ 837 if regionId >= len(self._regions): 838 return 0 839 return self._regions[regionId].GetFormatMode() 840 841 def SetTextColour(self, the_colour, regionId = 0): 842 """ 843 Set the colour for the specified text region. 844 845 :param str `the_colour`: a valid colour name, 846 see :class:`wx.ColourDatabase` 847 :param `regionId`: the region identifier 848 849 """ 850 self._textColour = wx.TheColourDatabase.Find(the_colour) 851 self._textColourName = the_colour 852 853 if regionId < len(self._regions): 854 self._regions[regionId].SetColour(the_colour) 855 856 def GetTextColour(self, regionId = 0): 857 """ 858 Get the colour for the specified text region. 859 860 :param `regionId`: the region identifier 861 862 """ 863 if regionId >= len(self._regions): 864 return "" 865 return self._regions[regionId].GetColour() 866 867 def SetRegionName(self, name, regionId = 0): 868 """ 869 Set the name for this region. 870 871 :param str `name`: the name to set 872 :param `regionId`: the region identifier 873 874 :note: The name for a region is unique within the scope of the whole 875 composite, whereas a region id is unique only for a single image. 876 877 """ 878 if regionId < len(self._regions): 879 self._regions[regionId].SetName(name) 880 881 def GetRegionName(self, regionId = 0): 882 """ 883 Get the region's name. 884 885 :param `regionId`: the region identifier 886 887 :note: A region's name can be used to uniquely determine a region within 888 an entire composite image hierarchy. See also 889 :meth:`~lib.ogl.Shape.SetRegionName`. 890 891 """ 892 if regionId >= len(self._regions): 893 return "" 894 return self._regions[regionId].GetName() 895 896 def GetRegionId(self, name): 897 """ 898 Get the region's identifier by name. 899 900 :param str `name`: the regions name 901 902 :note: This is not unique for within an entire composite, but is unique 903 for the image. 904 905 """ 906 for i, r in enumerate(self._regions): 907 if r.GetName() == name: 908 return i 909 return -1 910 911 # Name all _regions in all subimages recursively 912 def NameRegions(self, parentName=""): 913 """ 914 Make unique names for all the regions in a shape or composite shape. 915 916 :param str `parentName`: a prefix for the region names 917 918 """ 919 n = self.GetNumberOfTextRegions() 920 for i in range(n): 921 if parentName: 922 buff = parentName+"."+str(i) 923 else: 924 buff = str(i) 925 self.SetRegionName(buff, i) 926 927 for j, child in enumerate(self._children): 928 if parentName: 929 buff = parentName+"."+str(j) 930 else: 931 buff = str(j) 932 child.NameRegions(buff) 933 934 # Get a region by name, possibly looking recursively into composites 935 def FindRegion(self, name): 936 """ 937 Find the actual image ('this' if non-composite) and region id 938 for the given region name. 939 940 :param str `name`: the region name 941 942 """ 943 id = self.GetRegionId(name) 944 if id > -1: 945 return self, id 946 947 for child in self._children: 948 actualImage, regionId = child.FindRegion(name) 949 if actualImage: 950 return actualImage, regionId 951 952 return None, -1 953 954 # Finds all region names for this image (composite or simple). 955 def FindRegionNames(self): 956 """Get a list of all region names for this image (composite or simple).""" 957 list = [] 958 n = self.GetNumberOfTextRegions() 959 for i in range(n): 960 list.append(self.GetRegionName(i)) 961 962 for child in self._children: 963 list += child.FindRegionNames() 964 965 return list 966 967 def AssignNewIds(self): 968 """Assign new ids to this image and its children.""" 969 self._id = wx.NewIdRef() 970 for child in self._children: 971 child.AssignNewIds() 972 973 def OnDraw(self, dc): 974 """not implemented???""" 975 pass 976 977 def OnMoveLinks(self, dc): 978 """The move links handler.""" 979 # Want to set the ends of all attached links 980 # to point to / from this object 981 982 for line in self._lines: 983 line.GetEventHandler().OnMoveLink(dc) 984 985 def OnDrawContents(self, dc): 986 """The draw contents handler.""" 987 if not self._regions: 988 return 989 990 bound_x, bound_y = self.GetBoundingBoxMin() 991 992 if self._pen: 993 dc.SetPen(self._pen) 994 995 for region in self._regions: 996 if region.GetFont(): 997 dc.SetFont(region.GetFont()) 998 999 dc.SetTextForeground(region.GetActualColourObject()) 1000 dc.SetBackgroundMode(wx.TRANSPARENT) 1001 if not self._formatted: 1002 CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, bound_x - 2 * self._textMarginX, bound_y - 2 * self._textMarginY, region.GetFormatMode()) 1003 self._formatted = True 1004 1005 if not self.GetDisableLabel(): 1006 DrawFormattedText(dc, region.GetFormattedText(), self._xpos, self._ypos, bound_x - 2 * self._textMarginX, bound_y - 2 * self._textMarginY, region.GetFormatMode()) 1007 1008 def DrawContents(self, dc): 1009 """ 1010 Draw the internal graphic of the shape (such as text). 1011 1012 Do not override this function: override OnDrawContents, which 1013 is called by this function. 1014 """ 1015 self.GetEventHandler().OnDrawContents(dc) 1016 1017 def OnSize(self, x, y): 1018 """not implemented???""" 1019 pass 1020 1021 def OnMovePre(self, dc, x, y, old_x, old_y, display = True): 1022 return True 1023 1024 def OnErase(self, dc): 1025 """The erase handler.""" 1026 if not self._visible: 1027 return 1028 1029 # Erase links 1030 for line in self._lines: 1031 line.GetEventHandler().OnErase(dc) 1032 1033 self.GetEventHandler().OnEraseContents(dc) 1034 1035 def OnEraseContents(self, dc): 1036 """The erase contents handler.""" 1037 if not self._visible: 1038 return 1039 1040 xp, yp = self.GetX(), self.GetY() 1041 minX, minY = self.GetBoundingBoxMin() 1042 maxX, maxY = self.GetBoundingBoxMax() 1043 1044 topLeftX = xp - maxX / 2.0 - 2 1045 topLeftY = yp - maxY / 2.0 - 2 1046 1047 penWidth = 0 1048 if self._pen: 1049 penWidth = self._pen.GetWidth() 1050 1051 dc.SetPen(self.GetBackgroundPen()) 1052 dc.SetBrush(self.GetBackgroundBrush()) 1053 1054 dc.DrawRectangle(topLeftX - penWidth, topLeftY - penWidth, maxX + penWidth * 2 + 4, maxY + penWidth * 2 + 4) 1055 1056 def EraseLinks(self, dc, attachment = -1, recurse = False): 1057 """ 1058 Erase links attached to this shape, but do not repair damage 1059 caused to other shapes. 1060 1061 :param `dc`: the device context 1062 :param `attachment`: ??? 1063 :param `recurse`: if `True` recurse through the children 1064 1065 """ 1066 if not self._visible: 1067 return 1068 1069 for line in self._lines: 1070 if attachment == -1 or (line.GetTo() == self and line.GetAttachmentTo() == attachment or line.GetFrom() == self and line.GetAttachmentFrom() == attachment): 1071 line.GetEventHandler().OnErase(dc) 1072 1073 if recurse: 1074 for child in self._children: 1075 child.EraseLinks(dc, attachment, recurse) 1076 1077 def DrawLinks(self, dc, attachment = -1, recurse = False): 1078 """ 1079 Draws any lines linked to this shape. 1080 1081 :param `dc`: the device context 1082 :param `attachment`: ??? 1083 :param `recurse`: if `True` recurse through the children 1084 1085 """ 1086 if not self._visible: 1087 return 1088 1089 for line in self._lines: 1090 if attachment == -1 or (line.GetTo() == self and line.GetAttachmentTo() == attachment or line.GetFrom() == self and line.GetAttachmentFrom() == attachment): 1091 line.Draw(dc) 1092 1093 if recurse: 1094 for child in self._children: 1095 child.DrawLinks(dc, attachment, recurse) 1096 1097 # Returns TRUE if pt1 <= pt2 in the sense that one point comes before 1098 # another on an edge of the shape. 1099 # attachmentPoint is the attachment point (= side) in question. 1100 1101 # This is the default, rectangular implementation. 1102 def AttachmentSortTest(self, attachmentPoint, pt1, pt2): 1103 """ 1104 Return TRUE if pt1 is less than or equal to pt2, in the sense 1105 that one point comes before another on an edge of the shape. 1106 1107 attachment is the attachment point (side) in question. 1108 1109 This function is used in Shape.MoveLineToNewAttachment to determine 1110 the new line ordering. 1111 """ 1112 physicalAttachment = self.LogicalToPhysicalAttachment(attachmentPoint) 1113 if physicalAttachment in [0, 2]: 1114 return pt1[0] <= pt2[0] 1115 elif physicalAttachment in [1, 3]: 1116 return pt1[1] <= pt2[1] 1117 1118 return False 1119 1120 def MoveLineToNewAttachment(self, dc, to_move, x, y): 1121 """ 1122 Move the given line (which must already be attached to the shape) 1123 to a different attachment point on the shape, or a different order 1124 on the same attachment. 1125 1126 Calls Shape.AttachmentSortTest and then 1127 ShapeEvtHandler.OnChangeAttachment. 1128 """ 1129 if self.GetAttachmentMode() == ATTACHMENT_MODE_NONE: 1130 return False 1131 1132 # Is (x, y) on this object? If so, find the new attachment point 1133 # the user has moved the point to 1134 hit = self.HitTest(x, y) 1135 if not hit: 1136 return False 1137 1138 newAttachment, distance = hit 1139 1140 self.EraseLinks(dc) 1141 1142 if to_move.GetTo() == self: 1143 oldAttachment = to_move.GetAttachmentTo() 1144 else: 1145 oldAttachment = to_move.GetAttachmentFrom() 1146 1147 # The links in a new ordering 1148 # First, add all links to the new list 1149 newOrdering = self._lines[:] 1150 1151 # Delete the line object from the list of links; we're going to move 1152 # it to another position in the list 1153 del newOrdering[newOrdering.index(to_move)] 1154 1155 old_x = -99999.9 1156 old_y = -99999.9 1157 1158 found = False 1159 1160 for line in newOrdering: 1161 if line.GetTo() == self and oldAttachment == line.GetAttachmentTo() or \ 1162 line.GetFrom() == self and oldAttachment == line.GetAttachmentFrom(): 1163 startX, startY, endX, endY = line.GetEnds() 1164 if line.GetTo() == self: 1165 xp = endX 1166 yp = endY 1167 else: 1168 xp = startX 1169 yp = startY 1170 1171 thisPoint = wx.RealPoint(xp, yp) 1172 lastPoint = wx.RealPoint(old_x, old_y) 1173 newPoint = wx.RealPoint(x, y) 1174 1175 if self.AttachmentSortTest(newAttachment, newPoint, thisPoint) and self.AttachmentSortTest(newAttachment, lastPoint, newPoint): 1176 found = True 1177 newOrdering.insert(newOrdering.index(line), to_move) 1178 1179 old_x = xp 1180 old_y = yp 1181 if found: 1182 break 1183 1184 if not found: 1185 newOrdering.append(to_move) 1186 1187 self.GetEventHandler().OnChangeAttachment(newAttachment, to_move, newOrdering) 1188 return True 1189 1190 def OnChangeAttachment(self, attachment, line, ordering): 1191 """Change attachment handler.""" 1192 if line.GetTo() == self: 1193 line.SetAttachmentTo(attachment) 1194 else: 1195 line.SetAttachmentFrom(attachment) 1196 1197 self.ApplyAttachmentOrdering(ordering) 1198 1199 dc = wx.MemoryDC() 1200 dc.SelectObject(self.GetCanvas().GetBuffer()) 1201 self.GetCanvas().PrepareDC(dc) 1202 self.MoveLinks(dc) 1203 1204 if not self.GetCanvas().GetQuickEditMode(): 1205 self.GetCanvas().Redraw(dc) 1206 1207 # Reorders the lines according to the given list 1208 def ApplyAttachmentOrdering(self, linesToSort): 1209 """ 1210 Apply the line ordering in linesToSort to the shape, to reorder 1211 the way lines are attached. 1212 """ 1213 linesStore = self._lines[:] 1214 1215 self._lines = [] 1216 1217 for line in linesToSort: 1218 if line in linesStore: 1219 del linesStore[linesStore.index(line)] 1220 self._lines.append(line) 1221 1222 # Now add any lines that haven't been listed in linesToSort 1223 self._lines += linesStore 1224 1225 def SortLines(self, attachment, linesToSort): 1226 """ 1227 Reorder the lines coming into the node image at this attachment 1228 position, in the order in which they appear in linesToSort. 1229 1230 Any remaining lines not in the list will be added to the end. 1231 """ 1232 # This is a temporary store of all the lines at this attachment 1233 # point. We'll tick them off as we've processed them. 1234 linesAtThisAttachment = [] 1235 1236 for line in self._lines[:]: 1237 if line.GetTo() == self and line.GetAttachmentTo() == attachment or \ 1238 line.GetFrom() == self and line.GetAttachmentFrom() == attachment: 1239 linesAtThisAttachment.append(line) 1240 del self._lines[self._lines.index(line)] 1241 1242 for line in linesToSort: 1243 if line in linesAtThisAttachment: 1244 # Done this one 1245 del linesAtThisAttachment[linesAtThisAttachment.index(line)] 1246 self._lines.append(line) 1247 1248 # Now add any lines that haven't been listed in linesToSort 1249 self._lines += linesAtThisAttachment 1250 1251 def OnHighlight(self, dc): 1252 """not implemented???""" 1253 pass 1254 1255 def OnLeftClick(self, x, y, keys = 0, attachment = 0): 1256 """The left click handler.""" 1257 if self._sensitivity & OP_CLICK_LEFT != OP_CLICK_LEFT: 1258 if self._parent: 1259 attachment, dist = self._parent.HitTest(x, y) 1260 self._parent.GetEventHandler().OnLeftClick(x, y, keys, attachment) 1261 1262 def OnRightClick(self, x, y, keys = 0, attachment = 0): 1263 """The right click handler.""" 1264 if self._sensitivity & OP_CLICK_RIGHT != OP_CLICK_RIGHT: 1265 attachment, dist = self._parent.HitTest(x, y) 1266 self._parent.GetEventHandler().OnRightClick(x, y, keys, attachment) 1267 1268 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0): 1269 """The drag left handler.""" 1270 if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT: 1271 if self._parent: 1272 hit = self._parent.HitTest(x, y) 1273 if hit: 1274 attachment, dist = hit 1275 self._parent.GetEventHandler().OnDragLeft(draw, x, y, keys, attachment) 1276 return 1277 1278 dc = wx.ClientDC(self.GetCanvas()) 1279 self.GetCanvas().PrepareDC(dc) 1280 dc.SetLogicalFunction(OGLRBLF) 1281 1282 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT) 1283 1284 dc.SetPen(dottedPen) 1285 dc.SetBrush(wx.TRANSPARENT_BRUSH) 1286 1287 xx = x + DragOffsetX 1288 yy = y + DragOffsetY 1289 1290 xx, yy = self._canvas.Snap(xx, yy) 1291 w, h = self.GetBoundingBoxMax() 1292 self.GetEventHandler().OnDrawOutline(dc, xx, yy, w, h) 1293 1294 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0): 1295 """The begin drag left handler.""" 1296 global DragOffsetX, DragOffsetY 1297 1298 if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT: 1299 if self._parent: 1300 hit = self._parent.HitTest(x, y) 1301 if hit: 1302 attachment, dist = hit 1303 self._parent.GetEventHandler().OnBeginDragLeft(x, y, keys, attachment) 1304 return 1305 1306 DragOffsetX = self._xpos - x 1307 DragOffsetY = self._ypos - y 1308 1309 dc = wx.ClientDC(self.GetCanvas()) 1310 self.GetCanvas().PrepareDC(dc) 1311 dc.SetLogicalFunction(OGLRBLF) 1312 1313 # New policy: don't erase shape until end of drag. 1314 # self.Erase(dc) 1315 xx = x + DragOffsetX 1316 yy = y + DragOffsetY 1317 xx, yy = self._canvas.Snap(xx, yy) 1318 1319 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT) 1320 dc.SetPen(dottedPen) 1321 dc.SetBrush(wx.TRANSPARENT_BRUSH) 1322 1323 w, h = self.GetBoundingBoxMax() 1324 self.GetEventHandler().OnDrawOutline(dc, xx, yy, w, h) 1325 self._canvas.CaptureMouse() 1326 1327 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0): 1328 """The end drag left handler.""" 1329 if self._canvas.HasCapture(): 1330 self._canvas.ReleaseMouse() 1331 if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT: 1332 if self._parent: 1333 hit = self._parent.HitTest(x, y) 1334 if hit: 1335 attachment, dist = hit 1336 self._parent.GetEventHandler().OnEndDragLeft(x, y, keys, attachment) 1337 return 1338 1339 dc = wx.ClientDC(self.GetCanvas()) 1340 self.GetCanvas().PrepareDC(dc) 1341 dc.SetLogicalFunction(wx.COPY) 1342 1343 xx = x + DragOffsetX 1344 yy = y + DragOffsetY 1345 xx, yy = self._canvas.Snap(xx, yy) 1346 1347 # New policy: erase shape at end of drag. 1348 self.Erase(dc) 1349 1350 self.Move(dc, xx, yy) 1351 if self._canvas and not self._canvas.GetQuickEditMode(): 1352 self._canvas.Redraw(dc) 1353 1354 def OnDragRight(self, draw, x, y, keys = 0, attachment = 0): 1355 """The drag right handler.""" 1356 if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT: 1357 if self._parent: 1358 attachment, dist = self._parent.HitTest(x, y) 1359 self._parent.GetEventHandler().OnDragRight(draw, x, y, keys, attachment) 1360 return 1361 1362 def OnBeginDragRight(self, x, y, keys = 0, attachment = 0): 1363 """The begin drag right handler.""" 1364 if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT: 1365 if self._parent: 1366 attachment, dist = self._parent.HitTest(x, y) 1367 self._parent.GetEventHandler().OnBeginDragRight(x, y, keys, attachment) 1368 return 1369 1370 def OnEndDragRight(self, x, y, keys = 0, attachment = 0): 1371 """The end drag right handler.""" 1372 if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT: 1373 if self._parent: 1374 attachment, dist = self._parent.HitTest(x, y) 1375 self._parent.GetEventHandler().OnEndDragRight(x, y, keys, attachment) 1376 return 1377 1378 def OnDrawOutline(self, dc, x, y, w, h): 1379 """The draw outline handler.""" 1380 points = [[x - w / 2.0, y - h / 2.0], 1381 [x + w / 2.0, y - h / 2.0], 1382 [x + w / 2.0, y + h / 2.0], 1383 [x - w / 2.0, y + h / 2.0], 1384 [x - w / 2.0, y - h / 2.0], 1385 ] 1386 1387 dc.DrawLines(points) 1388 1389 def Attach(self, can): 1390 """Set the shape's internal canvas pointer to point to the given canvas.""" 1391 self._canvas = can 1392 1393 def Detach(self): 1394 """Disassociates the shape from its canvas.""" 1395 self._canvas = None 1396 1397 def Move(self, dc, x, y, display = True): 1398 """ 1399 Move the shape to the given position. 1400 1401 :param `dc`: the device context 1402 :param `x`: the x position 1403 :param `y`: the y position 1404 :param `display`: if `True` redraw 1405 1406 """ 1407 old_x = self._xpos 1408 old_y = self._ypos 1409 1410 if not self.GetEventHandler().OnMovePre(dc, x, y, old_x, old_y, display): 1411 return 1412 1413 self._xpos, self._ypos = x, y 1414 1415 self.ResetControlPoints() 1416 1417 if display: 1418 self.Draw(dc) 1419 1420 self.MoveLinks(dc) 1421 1422 self.GetEventHandler().OnMovePost(dc, x, y, old_x, old_y, display) 1423 1424 def MoveLinks(self, dc): 1425 """Redraw all the lines attached to the shape.""" 1426 self.GetEventHandler().OnMoveLinks(dc) 1427 1428 def Draw(self, dc): 1429 """ 1430 Draw the whole shape and any lines attached to it. 1431 1432 Do not override this function: override OnDraw, which is called 1433 by this function. 1434 """ 1435 if self._visible: 1436 self.GetEventHandler().OnDraw(dc) 1437 self.GetEventHandler().OnDrawContents(dc) 1438 self.GetEventHandler().OnDrawControlPoints(dc) 1439 self.GetEventHandler().OnDrawBranches(dc) 1440 1441 def Flash(self): 1442 """Flash the shape.""" 1443 if self.GetCanvas(): 1444 dc = wx.MemoryDC() 1445 dc.SelectObject(self.GetCanvas().GetBuffer()) 1446 self.GetCanvas().PrepareDC(dc) 1447 dc.SetLogicalFunction(OGLRBLF) 1448 self.Draw(dc) 1449 dc.SetLogicalFunction(wx.COPY) 1450 self.Draw(dc) 1451 1452 def Show(self, show): 1453 """Set a flag indicating whether the shape should be drawn.""" 1454 self._visible = show 1455 for child in self._children: 1456 child.Show(show) 1457 1458 def Erase(self, dc): 1459 """ 1460 Erase the shape. 1461 1462 Does not repair damage caused to other shapes. 1463 """ 1464 self.GetEventHandler().OnErase(dc) 1465 self.GetEventHandler().OnEraseControlPoints(dc) 1466 self.GetEventHandler().OnDrawBranches(dc, erase = True) 1467 1468 def EraseContents(self, dc): 1469 """ 1470 Erase the shape contents, that is, the area within the shape's 1471 minimum bounding box. 1472 """ 1473 self.GetEventHandler().OnEraseContents(dc) 1474 1475 def AddText(self, string): 1476 """Add a line of text to the shape's default text region.""" 1477 if not self._regions: 1478 return 1479 1480 region = self._regions[0] 1481 #region.ClearText() 1482 new_line = ShapeTextLine(0, 0, string) 1483 text = region.GetFormattedText() 1484 text.append(new_line) 1485 1486 self._formatted = False 1487 1488 def SetSize(self, x, y, recursive = True): 1489 """Set the shape's size. 1490 1491 :param `x`: the x position 1492 :param `y`: the y position 1493 :param `recursive`: not used 1494 1495 """ 1496 self.SetAttachmentSize(x, y) 1497 self.SetDefaultRegionSize() 1498 1499 def SetAttachmentSize(self, w, h): 1500 """ 1501 Set the attachment size. 1502 1503 :param `w`: width 1504 :param `h`: height 1505 1506 """ 1507 width, height = self.GetBoundingBoxMin() 1508 if width == 0: 1509 scaleX = 1.0 1510 else: 1511 scaleX = float(w) / width 1512 if height == 0: 1513 scaleY = 1.0 1514 else: 1515 scaleY = float(h) / height 1516 1517 for point in self._attachmentPoints: 1518 point._x = point._x * scaleX 1519 point._y = point._y * scaleY 1520 1521 # Add line FROM this object 1522 def AddLine(self, line, other, attachFrom = 0, attachTo = 0, positionFrom = -1, positionTo = -1): 1523 """ 1524 Add a line between this shape and the given other shape, at the 1525 specified attachment points. 1526 1527 :param `line`: the line an instance of :class:`~lib.ogl.LineShape` 1528 :param `other`: the other shape, an instance of :class:`Shape` 1529 :param `attachFrom`: the attachment from point ??? 1530 :param `attachTo`: the attachment to point ??? 1531 :param `positionFrom`: the from position 1532 :param `positionTo`: the to position 1533 1534 :note: The position in the list of lines at each end can also be 1535 specified, so that the line will be drawn at a particular point on 1536 its attachment point. 1537 1538 """ 1539 if positionFrom == -1: 1540 if not line in self._lines: 1541 self._lines.append(line) 1542 else: 1543 # Don't preserve old ordering if we have new ordering instructions 1544 try: 1545 self._lines.remove(line) 1546 except ValueError: 1547 pass 1548 if positionFrom < len(self._lines): 1549 self._lines.insert(positionFrom, line) 1550 else: 1551 self._lines.append(line) 1552 1553 if positionTo == -1: 1554 if not other in other._lines: 1555 other._lines.append(line) 1556 else: 1557 # Don't preserve old ordering if we have new ordering instructions 1558 try: 1559 other._lines.remove(line) 1560 except ValueError: 1561 pass 1562 if positionTo < len(other._lines): 1563 other._lines.insert(positionTo, line) 1564 else: 1565 other._lines.append(line) 1566 1567 line.SetFrom(self) 1568 line.SetTo(other) 1569 line.SetAttachments(attachFrom, attachTo) 1570 1571 dc = wx.MemoryDC() 1572 dc.SelectObject(self.GetCanvas().GetBuffer()) 1573 self.GetCanvas().PrepareDC(dc) 1574 self.MoveLinks(dc) 1575 1576 def RemoveLine(self, line): 1577 """ 1578 Remove the given line from the shape's list of attached lines. 1579 1580 :param `line`: an instance of :class:`~lib.ogl.LineShape` 1581 1582 """ 1583 if line.GetFrom() == self: 1584 line.GetTo()._lines.remove(line) 1585 else: 1586 line.GetFrom()._lines.remove(line) 1587 1588 self._lines.remove(line) 1589 1590 # Default - make 6 control points 1591 def MakeControlPoints(self): 1592 """ 1593 Make a list of control points (draggable handles) appropriate to 1594 the shape. 1595 """ 1596 maxX, maxY = self.GetBoundingBoxMax() 1597 minX, minY = self.GetBoundingBoxMin() 1598 1599 widthMin = minX + CONTROL_POINT_SIZE + 2 1600 heightMin = minY + CONTROL_POINT_SIZE + 2 1601 1602 # Offsets from main object 1603 top = -heightMin / 2.0 1604 bottom = heightMin / 2.0 + (maxY - minY) 1605 left = -widthMin / 2.0 1606 right = widthMin / 2.0 + (maxX - minX) 1607 1608 control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, left, top, CONTROL_POINT_DIAGONAL) 1609 self._canvas.AddShape(control) 1610 self._controlPoints.append(control) 1611 1612 control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, 0, top, CONTROL_POINT_VERTICAL) 1613 self._canvas.AddShape(control) 1614 self._controlPoints.append(control) 1615 1616 control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, right, top, CONTROL_POINT_DIAGONAL) 1617 self._canvas.AddShape(control) 1618 self._controlPoints.append(control) 1619 1620 control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, right, 0, CONTROL_POINT_HORIZONTAL) 1621 self._canvas.AddShape(control) 1622 self._controlPoints.append(control) 1623 1624 control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, right, bottom, CONTROL_POINT_DIAGONAL) 1625 self._canvas.AddShape(control) 1626 self._controlPoints.append(control) 1627 1628 control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, 0, bottom, CONTROL_POINT_VERTICAL) 1629 self._canvas.AddShape(control) 1630 self._controlPoints.append(control) 1631 1632 control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, left, bottom, CONTROL_POINT_DIAGONAL) 1633 self._canvas.AddShape(control) 1634 self._controlPoints.append(control) 1635 1636 control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, left, 0, CONTROL_POINT_HORIZONTAL) 1637 self._canvas.AddShape(control) 1638 self._controlPoints.append(control) 1639 1640 def MakeMandatoryControlPoints(self): 1641 """ 1642 Make the mandatory control points. 1643 1644 For example, the control point on a dividing line should appear even 1645 if the divided rectangle shape's handles should not appear (because 1646 it is the child of a composite, and children are not resizable). 1647 """ 1648 for child in self._children: 1649 child.MakeMandatoryControlPoints() 1650 1651 def ResetMandatoryControlPoints(self): 1652 """Reset the mandatory control points.""" 1653 for child in self._children: 1654 child.ResetMandatoryControlPoints() 1655 1656 def ResetControlPoints(self): 1657 """ 1658 Reset the positions of the control points (for instance when the 1659 shape's shape has changed). 1660 """ 1661 self.ResetMandatoryControlPoints() 1662 1663 if len(self._controlPoints) == 0: 1664 return 1665 1666 maxX, maxY = self.GetBoundingBoxMax() 1667 minX, minY = self.GetBoundingBoxMin() 1668 1669 widthMin = minX + CONTROL_POINT_SIZE + 2 1670 heightMin = minY + CONTROL_POINT_SIZE + 2 1671 1672 # Offsets from main object 1673 top = -heightMin / 2.0 1674 bottom = heightMin / 2.0 + (maxY - minY) 1675 left = -widthMin / 2.0 1676 right = widthMin / 2.0 + (maxX - minX) 1677 1678 self._controlPoints[0]._xoffset = left 1679 self._controlPoints[0]._yoffset = top 1680 1681 self._controlPoints[1]._xoffset = 0 1682 self._controlPoints[1]._yoffset = top 1683 1684 self._controlPoints[2]._xoffset = right 1685 self._controlPoints[2]._yoffset = top 1686 1687 self._controlPoints[3]._xoffset = right 1688 self._controlPoints[3]._yoffset = 0 1689 1690 self._controlPoints[4]._xoffset = right 1691 self._controlPoints[4]._yoffset = bottom 1692 1693 self._controlPoints[5]._xoffset = 0 1694 self._controlPoints[5]._yoffset = bottom 1695 1696 self._controlPoints[6]._xoffset = left 1697 self._controlPoints[6]._yoffset = bottom 1698 1699 self._controlPoints[7]._xoffset = left 1700 self._controlPoints[7]._yoffset = 0 1701 1702 def DeleteControlPoints(self, dc = None): 1703 """ 1704 Delete the control points (or handles) for the shape. 1705 1706 Does not redraw the shape. 1707 """ 1708 for control in self._controlPoints[:]: 1709 if dc: 1710 control.GetEventHandler().OnErase(dc) 1711 control.Delete() 1712 self._controlPoints.remove(control) 1713 self._controlPoints = [] 1714 1715 # Children of divisions are contained objects, 1716 # so stop here 1717 if not isinstance(self, DivisionShape): 1718 for child in self._children: 1719 child.DeleteControlPoints(dc) 1720 1721 def OnDrawControlPoints(self, dc): 1722 """The draw control points handler.""" 1723 if not self._drawHandles: 1724 return 1725 1726 dc.SetBrush(wx.BLACK_BRUSH) 1727 dc.SetPen(wx.BLACK_PEN) 1728 1729 for control in self._controlPoints: 1730 control.Draw(dc) 1731 1732 # Children of divisions are contained objects, 1733 # so stop here. 1734 # This test bypasses the type facility for speed 1735 # (critical when drawing) 1736 1737 if not isinstance(self, DivisionShape): 1738 for child in self._children: 1739 child.GetEventHandler().OnDrawControlPoints(dc) 1740 1741 def OnEraseControlPoints(self, dc): 1742 """The erase control points handler.""" 1743 for control in self._controlPoints: 1744 control.Erase(dc) 1745 1746 if not isinstance(self, DivisionShape): 1747 for child in self._children: 1748 child.GetEventHandler().OnEraseControlPoints(dc) 1749 1750 def Select(self, select, dc = None): 1751 """ 1752 Select or deselect the given shape, drawing or erasing control points 1753 (handles) as necessary. 1754 1755 :param `select`: `True` to select 1756 :param `dc`: the device context 1757 1758 """ 1759 self._selected = select 1760 if select: 1761 self.MakeControlPoints() 1762 # Children of divisions are contained objects, 1763 # so stop here 1764 if not isinstance(self, DivisionShape): 1765 for child in self._children: 1766 child.MakeMandatoryControlPoints() 1767 if dc: 1768 self.GetEventHandler().OnDrawControlPoints(dc) 1769 else: 1770 self.DeleteControlPoints(dc) 1771 if not isinstance(self, DivisionShape): 1772 for child in self._children: 1773 child.DeleteControlPoints(dc) 1774 1775 def Selected(self): 1776 """`True` if the shape is currently selected.""" 1777 return self._selected 1778 1779 def AncestorSelected(self): 1780 """`True` if the shape's ancestor is currently selected.""" 1781 if self._selected: 1782 return True 1783 if not self.GetParent(): 1784 return False 1785 return self.GetParent().AncestorSelected() 1786 1787 def GetNumberOfAttachments(self): 1788 """Get the number of attachment points for this shape.""" 1789 # Should return the MAXIMUM attachment point id here, 1790 # so higher-level functions can iterate through all attachments, 1791 # even if they're not contiguous. 1792 1793 if len(self._attachmentPoints) == 0: 1794 return 4 1795 else: 1796 maxN = 3 1797 for point in self._attachmentPoints: 1798 if point._id > maxN: 1799 maxN = point._id 1800 return maxN + 1 1801 1802 def AttachmentIsValid(self, attachment): 1803 """`True` if attachment is a valid attachment point.""" 1804 if len(self._attachmentPoints) == 0: 1805 return attachment in range(4) 1806 1807 for point in self._attachmentPoints: 1808 if point._id == attachment: 1809 return True 1810 return False 1811 1812 def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None): 1813 """ 1814 Get the position at which the given attachment point should be drawn. 1815 1816 :param `attachment`: the attachment ??? 1817 :param `nth`: get nth attachment ??? 1818 :param `no_arcs`: ??? 1819 :param `line`: ??? 1820 1821 If attachment isn't found among the attachment points of the shape, 1822 returns None. 1823 """ 1824 if self._attachmentMode == ATTACHMENT_MODE_NONE: 1825 return self._xpos, self._ypos 1826 elif self._attachmentMode == ATTACHMENT_MODE_BRANCHING: 1827 pt, stemPt = self.GetBranchingAttachmentPoint(attachment, nth) 1828 return pt[0], pt[1] 1829 elif self._attachmentMode == ATTACHMENT_MODE_EDGE: 1830 if len(self._attachmentPoints): 1831 for point in self._attachmentPoints: 1832 if point._id == attachment: 1833 return self._xpos + point._x, self._ypos + point._y 1834 return None 1835 else: 1836 # Assume is rectangular 1837 w, h = self.GetBoundingBoxMax() 1838 top = self._ypos + h / 2.0 1839 bottom = self._ypos - h / 2.0 1840 left = self._xpos - w / 2.0 1841 right = self._xpos + w / 2.0 1842 1843 # wtf? 1844 line and line.IsEnd(self) 1845 1846 physicalAttachment = self.LogicalToPhysicalAttachment(attachment) 1847 1848 # Simplified code 1849 if physicalAttachment == 0: 1850 pt = self.CalcSimpleAttachment((left, bottom), (right, bottom), nth, no_arcs, line) 1851 elif physicalAttachment == 1: 1852 pt = self.CalcSimpleAttachment((right, bottom), (right, top), nth, no_arcs, line) 1853 elif physicalAttachment == 2: 1854 pt = self.CalcSimpleAttachment((left, top), (right, top), nth, no_arcs, line) 1855 elif physicalAttachment == 3: 1856 pt = self.CalcSimpleAttachment((left, bottom), (left, top), nth, no_arcs, line) 1857 else: 1858 return None 1859 return pt[0], pt[1] 1860 return None 1861 1862 def GetBoundingBoxMax(self): 1863 """ 1864 Get the maximum bounding box for the shape, taking into account 1865 external features such as shadows. 1866 """ 1867 ww, hh = self.GetBoundingBoxMin() 1868 if self._shadowMode != SHADOW_NONE: 1869 ww += self._shadowOffsetX 1870 hh += self._shadowOffsetY 1871 return ww, hh 1872 1873 def GetBoundingBoxMin(self): 1874 """ 1875 Get the minimum bounding box for the shape, that defines the area 1876 available for drawing the contents (such as text). 1877 1878 Must be overridden. 1879 """ 1880 return 0, 0 1881 1882 def HasDescendant(self, image): 1883 """ 1884 Is image a descendant of this composite. 1885 1886 :param `image`: the image, is this a shape??? 1887 :returns: `True` if it is a descendant 1888 1889 """ 1890 if image == self: 1891 return True 1892 for child in self._children: 1893 if child.HasDescendant(image): 1894 return True 1895 return False 1896 1897 # Assuming the attachment lies along a vertical or horizontal line, 1898 # calculate the position on that point. 1899 def CalcSimpleAttachment(self, pt1, pt2, nth, noArcs, line): 1900 """ 1901 Assuming the attachment lies along a vertical or horizontal line, 1902 calculate the position on that point. 1903 1904 :param `pt1`: The first point of the line repesenting the edge of 1905 the shape 1906 :param `pt2`: The second point of the line representing the edge of 1907 the shape 1908 :param `nth`: The position on the edge (for example there may be 6 1909 lines at this attachment point, and this may be the 2nd line. 1910 :param `noArcs`: The number of lines at this edge. 1911 :param `line`: The line shape. 1912 1913 :note: This function expects the line to be either vertical or horizontal, 1914 and determines which. 1915 1916 """ 1917 isEnd = line and line.IsEnd(self) 1918 1919 # Are we horizontal or vertical? 1920 isHorizontal = RoughlyEqual(pt1[1], pt2[1]) 1921 1922 if isHorizontal: 1923 if pt1[0] > pt2[0]: 1924 firstPoint = pt2 1925 secondPoint = pt1 1926 else: 1927 firstPoint = pt1 1928 secondPoint = pt2 1929 1930 if self._spaceAttachments: 1931 if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE: 1932 # Align line according to the next handle along 1933 point = line.GetNextControlPoint(self) 1934 if point[0] < firstPoint[0]: 1935 x = firstPoint[0] 1936 elif point[0] > secondPoint[0]: 1937 x = secondPoint[0] 1938 else: 1939 x = point[0] 1940 else: 1941 x = firstPoint[0] + (nth + 1) * (secondPoint[0] - firstPoint[0]) / (noArcs + 1.0) 1942 else: 1943 x = (secondPoint[0] - firstPoint[0]) / 2.0 # Midpoint 1944 y = pt1[1] 1945 else: 1946 assert RoughlyEqual(pt1[0], pt2[0]) 1947 1948 if pt1[1] > pt2[1]: 1949 firstPoint = pt2 1950 secondPoint = pt1 1951 else: 1952 firstPoint = pt1 1953 secondPoint = pt2 1954 1955 if self._spaceAttachments: 1956 if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE: 1957 # Align line according to the next handle along 1958 point = line.GetNextControlPoint(self) 1959 if point[1] < firstPoint[1]: 1960 y = firstPoint[1] 1961 elif point[1] > secondPoint[1]: 1962 y = secondPoint[1] 1963 else: 1964 y = point[1] 1965 else: 1966 y = firstPoint[1] + (nth + 1) * (secondPoint[1] - firstPoint[1]) / (noArcs + 1.0) 1967 else: 1968 y = (secondPoint[1] - firstPoint[1]) / 2.0 # Midpoint 1969 x = pt1[0] 1970 1971 return x, y 1972 1973 def GetLinePosition(self, line): 1974 """ 1975 Get the zero-based position of line in the list of lines 1976 for this shape. 1977 1978 :param `line`: line to find position for 1979 1980 """ 1981 try: 1982 return self._lines.index(line) 1983 except: 1984 return 0 1985 1986 1987 # |________| 1988 # | <- root 1989 # | <- neck 1990 # shoulder1 ->---------<- shoulder2 1991 # | | | | | 1992 # <- branching attachment point N-1 1993 1994 def GetBranchingAttachmentInfo(self, attachment): 1995 """ 1996 Get information about where branching connections go. 1997 1998 :param `attachment`: ??? 1999 :returns: `False` if there are no lines at this attachment. 2000 """ 2001 physicalAttachment = self.LogicalToPhysicalAttachment(attachment) 2002 2003 # Number of lines at this attachment 2004 lineCount = self.GetAttachmentLineCount(attachment) 2005 2006 if not lineCount: 2007 return False 2008 2009 totalBranchLength = self._branchSpacing * (lineCount - 1) 2010 root = self.GetBranchingAttachmentRoot(attachment) 2011 2012 neck = wx.RealPoint() 2013 shoulder1 = wx.RealPoint() 2014 shoulder2 = wx.RealPoint() 2015 2016 # Assume that we have attachment points 0 to 3: top, right, bottom, left 2017 if physicalAttachment == 0: 2018 neck[0] = self.GetX() 2019 neck[1] = root[1] - self._branchNeckLength 2020 2021 shoulder1[0] = root[0] - totalBranchLength / 2.0 2022 shoulder2[0] = root[0] + totalBranchLength / 2.0 2023 2024 shoulder1[1] = neck[1] 2025 shoulder2[1] = neck[1] 2026 elif physicalAttachment == 1: 2027 neck[0] = root[0] + self._branchNeckLength 2028 neck[1] = root[1] 2029 2030 shoulder1[0] = neck[0] 2031 shoulder2[0] = neck[0] 2032 2033 shoulder1[1] = neck[1] - totalBranchLength / 2.0 2034 shoulder1[1] = neck[1] + totalBranchLength / 2.0 2035 elif physicalAttachment == 2: 2036 neck[0] = self.GetX() 2037 neck[1] = root[1] + self._branchNeckLength 2038 2039 shoulder1[0] = root[0] - totalBranchLength / 2.0 2040 shoulder2[0] = root[0] + totalBranchLength / 2.0 2041 2042 shoulder1[1] = neck[1] 2043 shoulder2[1] = neck[1] 2044 elif physicalAttachment == 3: 2045 neck[0] = root[0] - self._branchNeckLength 2046 neck[1] = root[1] 2047 2048 shoulder1[0] = neck[0] 2049 shoulder2[0] = neck[0] 2050 2051 shoulder1[1] = neck[1] - totalBranchLength / 2.0 2052 shoulder2[1] = neck[1] + totalBranchLength / 2.0 2053 else: 2054 raise ValueError("Unrecognised attachment point in GetBranchingAttachmentInfo") 2055 return root, neck, shoulder1, shoulder2 2056 2057 def GetBranchingAttachmentPoint(self, attachment, n): 2058 """ 2059 Get branching attachment point. 2060 2061 :param `attachment`: ??? 2062 :param `n`: ??? 2063 2064 """ 2065 physicalAttachment = self.LogicalToPhysicalAttachment(attachment) 2066 2067 root, neck, shoulder1, shoulder2 = self.GetBranchingAttachmentInfo(attachment) 2068 pt = wx.RealPoint() 2069 stemPt = wx.RealPoint() 2070 2071 if physicalAttachment == 0: 2072 pt[1] = neck[1] - self._branchStemLength 2073 pt[0] = shoulder1[0] + n * self._branchSpacing 2074 2075 stemPt[0] = pt[0] 2076 stemPt[1] = neck[1] 2077 elif physicalAttachment == 2: 2078 pt[1] = neck[1] + self._branchStemLength 2079 pt[0] = shoulder1[0] + n * self._branchStemLength 2080 2081 stemPt[0] = pt[0] 2082 stemPt[1] = neck[1] 2083 elif physicalAttachment == 1: 2084 pt[0] = neck[0] + self._branchStemLength 2085 pt[1] = shoulder1[1] + n * self._branchSpacing 2086 2087 stemPt[0] = neck[0] 2088 stemPt[1] = pt[1] 2089 elif physicalAttachment == 3: 2090 pt[0] = neck[0] - self._branchStemLength 2091 pt[1] = shoulder1[1] + n * self._branchSpacing 2092 2093 stemPt[0] = neck[0] 2094 stemPt[1] = pt[1] 2095 else: 2096 raise ValueError("Unrecognised attachment point in GetBranchingAttachmentPoint") 2097 2098 return pt, stemPt 2099 2100 def GetAttachmentLineCount(self, attachment): 2101 """ 2102 Get the number of lines at this attachment position. 2103 2104 :param `attachment`: ??? 2105 :returns: the count of lines at this position 2106 2107 """ 2108 count = 0 2109 for lineShape in self._lines: 2110 if lineShape.GetFrom() == self and lineShape.GetAttachmentFrom() == attachment: 2111 count += 1 2112 elif lineShape.GetTo() == self and lineShape.GetAttachmentTo() == attachment: 2113 count += 1 2114 return count 2115 2116 def GetBranchingAttachmentRoot(self, attachment): 2117 """ 2118 Get the root point at the given attachment. 2119 2120 :param `attachment`: ??? 2121 2122 """ 2123 physicalAttachment = self.LogicalToPhysicalAttachment(attachment) 2124 2125 root = wx.RealPoint() 2126 2127 width, height = self.GetBoundingBoxMax() 2128 2129 # Assume that we have attachment points 0 to 3: top, right, bottom, left 2130 if physicalAttachment == 0: 2131 root[0] = self.GetX() 2132 root[1] = self.GetY() - height / 2.0 2133 elif physicalAttachment == 1: 2134 root[0] = self.GetX() + width / 2.0 2135 root[1] = self.GetY() 2136 elif physicalAttachment == 2: 2137 root[0] = self.GetX() 2138 root[1] = self.GetY() + height / 2.0 2139 elif physicalAttachment == 3: 2140 root[0] = self.GetX() - width / 2.0 2141 root[1] = self.GetY() 2142 else: 2143 raise ValueError("Unrecognised attachment point in GetBranchingAttachmentRoot") 2144 2145 return root 2146 2147 # Draw or erase the branches (not the actual arcs though) 2148 def OnDrawBranchesAttachment(self, dc, attachment, erase = False): 2149 """The draw branches attachment handler.""" 2150 count = self.GetAttachmentLineCount(attachment) 2151 if count == 0: 2152 return 2153 2154 root, neck, shoulder1, shoulder2 = self.GetBranchingAttachmentInfo(attachment) 2155 2156 if erase: 2157 dc.SetPen(wx.WHITE_PEN) 2158 dc.SetBrush(wx.WHITE_BRUSH) 2159 else: 2160 dc.SetPen(wx.BLACK_PEN) 2161 dc.SetBrush(wx.BLACK_BRUSH) 2162 2163 # Draw neck 2164 dc.DrawLine(root[0], root[1], neck[0], neck[1]) 2165 2166 if count > 1: 2167 # Draw shoulder-to-shoulder line 2168 dc.DrawLine(shoulder1[0], shoulder1[1], shoulder2[0], shoulder2[1]) 2169 # Draw all the little branches 2170 for i in range(count): 2171 pt, stemPt = self.GetBranchingAttachmentPoint(attachment, i) 2172 dc.DrawLine(stemPt[0], stemPt[1], pt[0], pt[1]) 2173 2174 if self.GetBranchStyle() & BRANCHING_ATTACHMENT_BLOB and count > 1: 2175 blobSize = 6.0 2176 dc.DrawEllipse(stemPt[0] - blobSize / 2.0, stemPt[1] - blobSize / 2.0, blobSize, blobSize) 2177 2178 def OnDrawBranches(self, dc, erase = False): 2179 """The draw branches handler.""" 2180 if self._attachmentMode != ATTACHMENT_MODE_BRANCHING: 2181 return 2182 for i in range(self.GetNumberOfAttachments()): 2183 self.OnDrawBranchesAttachment(dc, i, erase) 2184 2185 def GetAttachmentPositionEdge(self, attachment, nth = 0, no_arcs = 1, line = None): 2186 """ 2187 Only get the attachment position at the _edge_ of the shape, 2188 ignoring branching mode. This is used e.g. to indicate the edge of 2189 interest, not the point on the attachment branch. 2190 2191 :param `attachment`: the attachment ??? 2192 :param `nth`: get nth attachment ??? 2193 :param `no_arcs`: ??? 2194 :param `line`: ??? 2195 2196 """ 2197 oldMode = self._attachmentMode 2198 2199 # Calculate as if to edge, not branch 2200 if self._attachmentMode == ATTACHMENT_MODE_BRANCHING: 2201 self._attachmentMode = ATTACHMENT_MODE_EDGE 2202 res = self.GetAttachmentPosition(attachment, nth, no_arcs, line) 2203 self._attachmentMode = oldMode 2204 2205 return res 2206 2207 def PhysicalToLogicalAttachment(self, physicalAttachment): 2208 """ 2209 Rotate the standard attachment point from physical 2210 (0 is always North) to logical (0 -> 1 if rotated by 90 degrees) 2211 2212 :param `physicalAttachment`: ??? 2213 2214 """ 2215 if RoughlyEqual(self.GetRotation(), 0): 2216 i = physicalAttachment 2217 elif RoughlyEqual(self.GetRotation(), math.pi / 2.0): 2218 i = physicalAttachment - 1 2219 elif RoughlyEqual(self.GetRotation(), math.pi): 2220 i = physicalAttachment - 2 2221 elif RoughlyEqual(self.GetRotation(), 3 * math.pi / 2.0): 2222 i = physicalAttachment - 3 2223 else: 2224 # Can't handle -- assume the same 2225 return physicalAttachment 2226 2227 if i < 0: 2228 i += 4 2229 2230 return i 2231 2232 def LogicalToPhysicalAttachment(self, logicalAttachment): 2233 """ 2234 Rotate the standard attachment point from logical 2235 to physical (0 is always North). 2236 2237 :param `logicalAttachment`: ??? 2238 2239 """ 2240 if RoughlyEqual(self.GetRotation(), 0): 2241 i = logicalAttachment 2242 elif RoughlyEqual(self.GetRotation(), math.pi / 2.0): 2243 i = logicalAttachment + 1 2244 elif RoughlyEqual(self.GetRotation(), math.pi): 2245 i = logicalAttachment + 2 2246 elif RoughlyEqual(self.GetRotation(), 3 * math.pi / 2.0): 2247 i = logicalAttachment + 3 2248 else: 2249 return logicalAttachment 2250 2251 if i > 3: 2252 i -= 4 2253 2254 return i 2255 2256 def Rotate(self, x, y, theta): 2257 """ 2258 Rotate about the given axis by the given amount in radians. 2259 2260 :param `x`: the x position 2261 :param `y`: the y position 2262 :param `theta`: the theta 2263 2264 """ 2265 self._rotation = theta 2266 if self._rotation < 0: 2267 self._rotation += 2 * math.pi 2268 elif self._rotation > 2 * math.pi: 2269 self._rotation -= 2 * math.pi 2270 2271 def GetBackgroundPen(self): 2272 """Return pen of the right colour for the background.""" 2273 if self.GetCanvas(): 2274 return wx.Pen(self.GetCanvas().GetBackgroundColour(), 1, wx.PENSTYLE_SOLID) 2275 return WhiteBackgroundPen 2276 2277 def GetBackgroundBrush(self): 2278 """Return brush of the right colour for the background.""" 2279 if self.GetCanvas(): 2280 return wx.Brush(self.GetCanvas().GetBackgroundColour(), wx.BRUSHSTYLE_SOLID) 2281 return WhiteBackgroundBrush 2282 2283 def GetX(self): 2284 """Get the x position of the centre of the shape.""" 2285 return self._xpos 2286 2287 def GetY(self): 2288 """Get the y position of the centre of the shape.""" 2289 return self._ypos 2290 2291 def SetX(self, x): 2292 """ 2293 Set the x position of the shape. 2294 2295 :param `x`: the x position 2296 """ 2297 self._xpos = x 2298 2299 def SetY(self, y): 2300 """ 2301 Set the y position of the shape. 2302 2303 :param `y`: the y position 2304 2305 """ 2306 self._ypos = y 2307 2308 def GetParent(self): 2309 """Get the parent of this shape, if it is part of a composite.""" 2310 return self._parent 2311 2312 def SetParent(self, p): 2313 """Set the parent 2314 2315 :param `p`: the parent 2316 2317 """ 2318 self._parent = p 2319 2320 def GetChildren(self): 2321 """Get the list of children for this shape.""" 2322 return self._children 2323 2324 def GetDrawHandles(self): 2325 """Get the list of drawhandles.""" 2326 return self._drawHandles 2327 2328 def GetEventHandler(self): 2329 """Get the event handler for this shape.""" 2330 return self._eventHandler 2331 2332 def SetEventHandler(self, handler): 2333 """Set the event handler for this shape. 2334 2335 :param `handler`: an instance of :class:`ShapeEvtHandler` 2336 2337 """ 2338 self._eventHandler = handler 2339 2340 def Recompute(self): 2341 """ 2342 Recomputes any constraints associated with the shape. 2343 2344 Normally applicable to CompositeShapes only, but harmless for 2345 other classes of Shape. 2346 """ 2347 return True 2348 2349 def IsHighlighted(self): 2350 """ 2351 `True` if the shape is highlighted. Shape highlighting is unimplemented. 2352 """ 2353 return self._highlighted 2354 2355 def GetSensitivityFilter(self): 2356 """ 2357 Get the sensitivity filter, a bitlist of values. 2358 2359 See :meth:`Shape.SetSensitivityFilter` 2360 2361 """ 2362 return self._sensitivity 2363 2364 def SetFixedSize(self, x, y): 2365 """ 2366 Set the shape to be fixed size. 2367 2368 :param `x`: the width 2369 :param `y`: the height 2370 2371 """ 2372 self._fixedWidth = x 2373 self._fixedHeight = y 2374 2375 def GetFixedSize(self): 2376 """ 2377 Return flags indicating whether the shape is of fixed size in 2378 either direction. 2379 """ 2380 return self._fixedWidth, self._fixedHeight 2381 2382 def GetFixedWidth(self): 2383 """`True` if the shape cannot be resized in the horizontal plane.""" 2384 return self._fixedWidth 2385 2386 def GetFixedHeight(self): 2387 """`True` if the shape cannot be resized in the vertical plane.""" 2388 return self._fixedHeight 2389 2390 def SetSpaceAttachments(self, sp): 2391 """ 2392 Indicate whether lines should be spaced out evenly at the point 2393 they touch the node. 2394 2395 :param `sp`: if `True` space out evently, else they should join at a 2396 single point. 2397 2398 """ 2399 self._spaceAttachments = sp 2400 2401 def GetSpaceAttachments(self): 2402 """ 2403 Get whether lines should be spaced out evenly at the point they 2404 touch the node (True), or whether they should join at a single point 2405 (False). 2406 """ 2407 return self._spaceAttachments 2408 2409 def SetCentreResize(self, cr): 2410 """ 2411 Specify whether the shape is to be resized from the centre (the 2412 centre stands still) or from the corner or side being dragged (the 2413 other corner or side stands still). 2414 """ 2415 self._centreResize = cr 2416 2417 def GetCentreResize(self): 2418 """ 2419 `True` if the shape is to be resized from the centre (the centre stands 2420 still), or `False` if from the corner or side being dragged (the other 2421 corner or side stands still) 2422 2423 """ 2424 return self._centreResize 2425 2426 def SetMaintainAspectRatio(self, ar): 2427 """ 2428 Set whether a shape that resizes should not change the aspect ratio 2429 (width and height should be in the original proportion). 2430 2431 """ 2432 self._maintainAspectRatio = ar 2433 2434 def GetMaintainAspectRatio(self): 2435 """`True` if shape keeps aspect ratio during resize.""" 2436 return self._maintainAspectRatio 2437 2438 def GetLines(self): 2439 """Return the list of lines connected to this shape.""" 2440 return self._lines 2441 2442 def SetDisableLabel(self, flag): 2443 """Set flag to `True` to stop the default region being shown.""" 2444 self._disableLabel = flag 2445 2446 def GetDisableLabel(self): 2447 """`True` if the default region will not be shown, `False` otherwise.""" 2448 return self._disableLabel 2449 2450 def SetAttachmentMode(self, mode): 2451 """ 2452 Set the attachment mode. 2453 2454 :param `mode`: if `True` attachment points will be significant when 2455 drawing lines to and from this shape. If `False` lines will be drawn 2456 as if to the centre of the shape. 2457 2458 """ 2459 self._attachmentMode = mode 2460 2461 def GetAttachmentMode(self): 2462 """ 2463 Get the attachment mode. 2464 2465 See :meth:`Shape.SetAttachmentMode` 2466 """ 2467 return self._attachmentMode 2468 2469 def SetId(self, i): 2470 """Set the integer identifier for this shape.""" 2471 self._id = i 2472 2473 def GetId(self): 2474 """Get the integer identifier for this shape.""" 2475 return self._id 2476 2477 def IsShown(self): 2478 """ 2479 `True` if the shape is in a visible state, `False` otherwise. 2480 2481 :note: That this has nothing to do with whether the window is hidden 2482 or the shape has scrolled off the canvas; it refers to the internal 2483 visibility flag. 2484 2485 """ 2486 return self._visible 2487 2488 def GetPen(self): 2489 """Get the pen used for drawing the shape's outline.""" 2490 return self._pen 2491 2492 def GetBrush(self): 2493 """Get the brush used for filling the shape.""" 2494 return self._brush 2495 2496 def GetNumberOfTextRegions(self): 2497 """Get the number of text regions for this shape.""" 2498 return len(self._regions) 2499 2500 def GetRegions(self): 2501 """Get the list of ShapeRegions.""" 2502 return self._regions 2503 2504 # Control points ('handles') redirect control to the actual shape, to 2505 # make it easier to override sizing behaviour. 2506 def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0): 2507 """The sizing drag left handler.""" 2508 bound_x, bound_y = self.GetBoundingBoxMin() 2509 2510 dc = wx.MemoryDC() 2511 dc.SelectObject(self.GetCanvas().GetBuffer()) 2512 self.GetCanvas().PrepareDC(dc) 2513 dc.SetLogicalFunction(OGLRBLF) 2514 2515 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT) 2516 dc.SetPen(dottedPen) 2517 dc.SetBrush(wx.TRANSPARENT_BRUSH) 2518 2519 if self.GetCentreResize(): 2520 # Maintain the same centre point 2521 new_width = 2.0 * abs(x - self.GetX()) 2522 new_height = 2.0 * abs(y - self.GetY()) 2523 2524 # Constrain sizing according to what control point you're dragging 2525 if pt._type == CONTROL_POINT_HORIZONTAL: 2526 if self.GetMaintainAspectRatio(): 2527 new_height = bound_y * (new_width / bound_x) 2528 else: 2529 new_height = bound_y 2530 elif pt._type == CONTROL_POINT_VERTICAL: 2531 if self.GetMaintainAspectRatio(): 2532 new_width = bound_x * (new_height / bound_y) 2533 else: 2534 new_width = bound_x 2535 elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT): 2536 new_height = bound_y * (new_width / bound_x) 2537 2538 if self.GetFixedWidth(): 2539 new_width = bound_x 2540 2541 if self.GetFixedHeight(): 2542 new_height = bound_y 2543 2544 pt._controlPointDragEndWidth = new_width 2545 pt._controlPointDragEndHeight = new_height 2546 2547 self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), new_width, new_height) 2548 else: 2549 # Don't maintain the same centre point 2550 newX1 = min(pt._controlPointDragStartX, x) 2551 newY1 = min(pt._controlPointDragStartY, y) 2552 newX2 = max(pt._controlPointDragStartX, x) 2553 newY2 = max(pt._controlPointDragStartY, y) 2554 if pt._type == CONTROL_POINT_HORIZONTAL: 2555 newY1 = pt._controlPointDragStartY 2556 newY2 = newY1 + pt._controlPointDragStartHeight 2557 elif pt._type == CONTROL_POINT_VERTICAL: 2558 newX1 = pt._controlPointDragStartX 2559 newX2 = newX1 + pt._controlPointDragStartWidth 2560 elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT or self.GetMaintainAspectRatio()): 2561 newH = (newX2 - newX1) * (float(pt._controlPointDragStartHeight) / pt._controlPointDragStartWidth) 2562 if self.GetY() > pt._controlPointDragStartY: 2563 newY2 = newY1 + newH 2564 else: 2565 newY1 = newY2 - newH 2566 2567 newWidth = float(newX2 - newX1) 2568 newHeight = float(newY2 - newY1) 2569 2570 if pt._type == CONTROL_POINT_VERTICAL and self.GetMaintainAspectRatio(): 2571 newWidth = bound_x * (newHeight / bound_y) 2572 2573 if pt._type == CONTROL_POINT_HORIZONTAL and self.GetMaintainAspectRatio(): 2574 newHeight = bound_y * (newWidth / bound_x) 2575 2576 pt._controlPointDragPosX = newX1 + newWidth / 2.0 2577 pt._controlPointDragPosY = newY1 + newHeight / 2.0 2578 if self.GetFixedWidth(): 2579 newWidth = bound_x 2580 2581 if self.GetFixedHeight(): 2582 newHeight = bound_y 2583 2584 pt._controlPointDragEndWidth = newWidth 2585 pt._controlPointDragEndHeight = newHeight 2586 self.GetEventHandler().OnDrawOutline(dc, pt._controlPointDragPosX, pt._controlPointDragPosY, newWidth, newHeight) 2587 2588 def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0): 2589 """The sizing begin drag left handler.""" 2590 self._canvas.CaptureMouse() 2591 2592 dc = wx.MemoryDC() 2593 dc.SelectObject(self.GetCanvas().GetBuffer()) 2594 self.GetCanvas().PrepareDC(dc) 2595 dc.SetLogicalFunction(OGLRBLF) 2596 2597 bound_x, bound_y = self.GetBoundingBoxMin() 2598 self.GetEventHandler().OnBeginSize(bound_x, bound_y) 2599 2600 # Choose the 'opposite corner' of the object as the stationary 2601 # point in case this is non-centring resizing. 2602 if pt.GetX() < self.GetX(): 2603 pt._controlPointDragStartX = self.GetX() + bound_x / 2.0 2604 else: 2605 pt._controlPointDragStartX = self.GetX() - bound_x / 2.0 2606 2607 if pt.GetY() < self.GetY(): 2608 pt._controlPointDragStartY = self.GetY() + bound_y / 2.0 2609 else: 2610 pt._controlPointDragStartY = self.GetY() - bound_y / 2.0 2611 2612 if pt._type == CONTROL_POINT_HORIZONTAL: 2613 pt._controlPointDragStartY = self.GetY() - bound_y / 2.0 2614 elif pt._type == CONTROL_POINT_VERTICAL: 2615 pt._controlPointDragStartX = self.GetX() - bound_x / 2.0 2616 2617 # We may require the old width and height 2618 pt._controlPointDragStartWidth = bound_x 2619 pt._controlPointDragStartHeight = bound_y 2620 2621 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT) 2622 dc.SetPen(dottedPen) 2623 dc.SetBrush(wx.TRANSPARENT_BRUSH) 2624 2625 if self.GetCentreResize(): 2626 new_width = 2.0 * abs(x - self.GetX()) 2627 new_height = 2.0 * abs(y - self.GetY()) 2628 2629 # Constrain sizing according to what control point you're dragging 2630 if pt._type == CONTROL_POINT_HORIZONTAL: 2631 if self.GetMaintainAspectRatio(): 2632 new_height = bound_y * (new_width / bound_x) 2633 else: 2634 new_height = bound_y 2635 elif pt._type == CONTROL_POINT_VERTICAL: 2636 if self.GetMaintainAspectRatio(): 2637 new_width = bound_x * (new_height / bound_y) 2638 else: 2639 new_width = bound_x 2640 elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT): 2641 new_height = bound_y * (new_width / bound_x) 2642 2643 if self.GetFixedWidth(): 2644 new_width = bound_x 2645 2646 if self.GetFixedHeight(): 2647 new_height = bound_y 2648 2649 pt._controlPointDragEndWidth = new_width 2650 pt._controlPointDragEndHeight = new_height 2651 self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), new_width, new_height) 2652 else: 2653 # Don't maintain the same centre point 2654 newX1 = min(pt._controlPointDragStartX, x) 2655 newY1 = min(pt._controlPointDragStartY, y) 2656 newX2 = max(pt._controlPointDragStartX, x) 2657 newY2 = max(pt._controlPointDragStartY, y) 2658 if pt._type == CONTROL_POINT_HORIZONTAL: 2659 newY1 = pt._controlPointDragStartY 2660 newY2 = newY1 + pt._controlPointDragStartHeight 2661 elif pt._type == CONTROL_POINT_VERTICAL: 2662 newX1 = pt._controlPointDragStartX 2663 newX2 = newX1 + pt._controlPointDragStartWidth 2664 elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT or self.GetMaintainAspectRatio()): 2665 newH = (newX2 - newX1) * (float(pt._controlPointDragStartHeight) / pt._controlPointDragStartWidth) 2666 if pt.GetY() > pt._controlPointDragStartY: 2667 newY2 = newY1 + newH 2668 else: 2669 newY1 = newY2 - newH 2670 2671 newWidth = float(newX2 - newX1) 2672 newHeight = float(newY2 - newY1) 2673 2674 if pt._type == CONTROL_POINT_VERTICAL and self.GetMaintainAspectRatio(): 2675 newWidth = bound_x * (newHeight / bound_y) 2676 2677 if pt._type == CONTROL_POINT_HORIZONTAL and self.GetMaintainAspectRatio(): 2678 newHeight = bound_y * (newWidth / bound_x) 2679 2680 pt._controlPointDragPosX = newX1 + newWidth / 2.0 2681 pt._controlPointDragPosY = newY1 + newHeight / 2.0 2682 if self.GetFixedWidth(): 2683 newWidth = bound_x 2684 2685 if self.GetFixedHeight(): 2686 newHeight = bound_y 2687 2688 pt._controlPointDragEndWidth = newWidth 2689 pt._controlPointDragEndHeight = newHeight 2690 self.GetEventHandler().OnDrawOutline(dc, pt._controlPointDragPosX, pt._controlPointDragPosY, newWidth, newHeight) 2691 2692 def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0): 2693 """The sizing end drag left handler.""" 2694 dc = wx.MemoryDC() 2695 dc.SelectObject(self.GetCanvas().GetBuffer()) 2696 self.GetCanvas().PrepareDC(dc) 2697 2698 if self._canvas.HasCapture(): 2699 self._canvas.ReleaseMouse() 2700 dc.SetLogicalFunction(wx.COPY) 2701 self.Recompute() 2702 self.ResetControlPoints() 2703 2704 self.Erase(dc) 2705 2706 self.SetSize(pt._controlPointDragEndWidth, pt._controlPointDragEndHeight) 2707 2708 # The next operation could destroy this control point (it does for 2709 # label objects, via formatting the text), so save all values we're 2710 # going to use, or we'll be accessing garbage. 2711 2712 #return 2713 2714 if self.GetCentreResize(): 2715 self.Move(dc, self.GetX(), self.GetY()) 2716 else: 2717 self.Move(dc, pt._controlPointDragPosX, pt._controlPointDragPosY) 2718 2719 # Recursively redraw links if we have a composite 2720 if len(self.GetChildren()): 2721 self.DrawLinks(dc, -1, True) 2722 2723 width, height = self.GetBoundingBoxMax() 2724 self.GetEventHandler().OnEndSize(width, height) 2725 2726 if not self._canvas.GetQuickEditMode() and pt._eraseObject: 2727 self._canvas.Redraw(dc) 2728 2729 2730class RectangleShape(Shape): 2731 """ 2732 The :class:`wx.RectangleShape` class has rounded or square corners. 2733 """ 2734 def __init__(self, w = 0.0, h = 0.0): 2735 """ 2736 Default class constructor 2737 2738 :param float `w`: the width 2739 :param float `h`: the height 2740 2741 """ 2742 Shape.__init__(self) 2743 self._width = w 2744 self._height = h 2745 self._cornerRadius = 0.0 2746 self.SetDefaultRegionSize() 2747 2748 def OnDraw(self, dc): 2749 """The draw handler.""" 2750 x1 = self._xpos - self._width / 2.0 2751 y1 = self._ypos - self._height / 2.0 2752 2753 if self._shadowMode != SHADOW_NONE: 2754 if self._shadowBrush: 2755 dc.SetBrush(self._shadowBrush) 2756 dc.SetPen(TransparentPen) 2757 2758 if self._cornerRadius: 2759 dc.DrawRoundedRectangle(x1 + self._shadowOffsetX, y1 + self._shadowOffsetY, self._width, self._height, self._cornerRadius) 2760 else: 2761 dc.DrawRectangle(x1 + self._shadowOffsetX, y1 + self._shadowOffsetY, self._width, self._height) 2762 2763 if self._pen: 2764 if self._pen.GetWidth() == 0: 2765 dc.SetPen(TransparentPen) 2766 else: 2767 dc.SetPen(self._pen) 2768 if self._brush: 2769 dc.SetBrush(self._brush) 2770 2771 if self._cornerRadius: 2772 dc.DrawRoundedRectangle(x1, y1, self._width, self._height, self._cornerRadius) 2773 else: 2774 dc.DrawRectangle(x1, y1, self._width, self._height) 2775 2776 def GetBoundingBoxMin(self): 2777 """Get the bounding box minimum.""" 2778 return self._width, self._height 2779 2780 def SetSize(self, x, y, recursive = False): 2781 """ 2782 Set the size. 2783 2784 :param `x`: the width 2785 :param `y`: the height 2786 :param `recursive`: not used 2787 2788 """ 2789 self.SetAttachmentSize(x, y) 2790 self._width = max(x, 1) 2791 self._height = max(y, 1) 2792 self.SetDefaultRegionSize() 2793 2794 def GetCornerRadius(self): 2795 """Get the radius of the rectangle's rounded corners.""" 2796 return self._cornerRadius 2797 2798 def SetCornerRadius(self, rad): 2799 """ 2800 Set the radius of the rectangle's rounded corners. 2801 2802 :param `rad`: If the radius is zero, a non-rounded rectangle will be 2803 drawn. If the radius is negative, the value is the proportion of the 2804 smaller dimension of the rectangle. 2805 2806 """ 2807 self._cornerRadius = rad 2808 2809 # Assume (x1, y1) is centre of box (most generally, line end at box) 2810 def GetPerimeterPoint(self, x1, y1, x2, y2): 2811 """ 2812 Get the perimeter point. 2813 2814 :param `x1`: ??? 2815 :param `y1`: ??? 2816 :param `x2`: ??? 2817 :param `y2`: ??? 2818 2819 """ 2820 bound_x, bound_y = self.GetBoundingBoxMax() 2821 return FindEndForBox(bound_x, bound_y, self._xpos, self._ypos, x2, y2) 2822 2823 def GetWidth(self): 2824 """Get the width.""" 2825 return self._width 2826 2827 def GetHeight(self): 2828 """Get the height.""" 2829 return self._height 2830 2831 def SetWidth(self, w): 2832 """ 2833 Set the width. 2834 2835 :param `w`: width to be set 2836 2837 """ 2838 self._width = w 2839 2840 def SetHeight(self, h): 2841 """ 2842 Set the heigth. 2843 2844 :param `h`: heigth to be set 2845 2846 """ 2847 self._height = h 2848 2849 2850class PolygonShape(Shape): 2851 """ 2852 The :class:`PolygonShape` class shape is defined by a number of points 2853 passed to the object's constructor. It can be used to create new shapes 2854 such as diamonds and triangles. 2855 """ 2856 def __init__(self): 2857 """ 2858 Default class constructor 2859 2860 Does not follow above statement, should it? or is Create called 2861 automagically? 2862 2863 """ 2864 Shape.__init__(self) 2865 2866 self._points = None 2867 self._originalPoints = None 2868 2869 def Create(self, the_points = None): 2870 """ 2871 Takes a list of :class:`wx.Points` or tuples; each point is an offset 2872 from the centre. 2873 """ 2874 self.ClearPoints() 2875 2876 if not the_points: 2877 self._originalPoints = [] 2878 self._points = [] 2879 else: 2880 self._originalPoints = the_points 2881 2882 # Duplicate the list of points 2883 self._points = [] 2884 for point in the_points: 2885 new_point = wx.Point(point[0], point[1]) 2886 self._points.append(new_point) 2887 self.CalculateBoundingBox() 2888 self._originalWidth = self._boundWidth 2889 self._originalHeight = self._boundHeight 2890 self.SetDefaultRegionSize() 2891 2892 def ClearPoints(self): 2893 """Clear the points.""" 2894 self._points = [] 2895 self._originalPoints = [] 2896 2897 # Width and height. Centre of object is centre of box 2898 def GetBoundingBoxMin(self): 2899 """Get minimum bounding box.""" 2900 return self._boundWidth, self._boundHeight 2901 2902 def GetPoints(self): 2903 """Return the internal list of polygon vertices.""" 2904 return self._points 2905 2906 def GetOriginalPoints(self): 2907 """Get the original points.""" 2908 return self._originalPoints 2909 2910 def GetOriginalWidth(self): 2911 """Get the original width.""" 2912 return self._originalWidth 2913 2914 def GetOriginalHeight(self): 2915 """Get the original height.""" 2916 return self._originalHeight 2917 2918 def SetOriginalWidth(self, w): 2919 """ 2920 Set the original width. 2921 2922 :param `w`: the width 2923 2924 """ 2925 self._originalWidth = w 2926 2927 def SetOriginalHeight(self, h): 2928 """ 2929 Set the original height. 2930 2931 :param `w`: the height 2932 2933 """ 2934 self._originalHeight = h 2935 2936 def CalculateBoundingBox(self): 2937 """Calculate the bounding box.""" 2938 left = 10000 2939 right = -10000 2940 top = 10000 2941 bottom = -10000 2942 2943 for point in self._points: 2944 if point[0] < left: 2945 left = point[0] 2946 if point[0] > right: 2947 right = point[0] 2948 2949 if point[1] < top: 2950 top = point[1] 2951 if point[1] > bottom: 2952 bottom = point[1] 2953 2954 self._boundWidth = right - left 2955 self._boundHeight = bottom - top 2956 2957 def CalculatePolygonCentre(self): 2958 """ 2959 Recalculates the centre of the polygon, and 2960 readjusts the point offsets accordingly. 2961 Necessary since the centre of the polygon 2962 is expected to be the real centre of the bounding 2963 box. 2964 """ 2965 left = 10000 2966 right = -10000 2967 top = 10000 2968 bottom = -10000 2969 2970 for point in self._points: 2971 if point[0] < left: 2972 left = point[0] 2973 if point[0] > right: 2974 right = point[0] 2975 2976 if point[1] < top: 2977 top = point[1] 2978 if point[1] > bottom: 2979 bottom = point[1] 2980 2981 bwidth = right - left 2982 bheight = bottom - top 2983 2984 newCentreX = left + bwidth / 2.0 2985 newCentreY = top + bheight / 2.0 2986 2987 for i in range(len(self._points)): 2988 self._points[i] = self._points[i][0] - newCentreX, self._points[i][1] - newCentreY 2989 self._xpos += newCentreX 2990 self._ypos += newCentreY 2991 2992 def HitTest(self, x, y): 2993 """Hit text 2994 2995 :param `x`: the x position 2996 :param `y`: the y position 2997 2998 """ 2999 # Imagine four lines radiating from this point. If all of these lines 3000 # hit the polygon, we're inside it, otherwise we're not. Obviously 3001 # we'd need more radiating lines to be sure of correct results for 3002 # very strange (concave) shapes. 3003 endPointsX = [x, x + 1000, x, x - 1000] 3004 endPointsY = [y - 1000, y, y + 1000, y] 3005 3006 xpoints = [] 3007 ypoints = [] 3008 3009 for point in self._points: 3010 xpoints.append(point[0] + self._xpos) 3011 ypoints.append(point[1] + self._ypos) 3012 3013 # We assume it's inside the polygon UNLESS one or more 3014 # lines don't hit the outline. 3015 isContained = True 3016 3017 for i in range(4): 3018 if not PolylineHitTest(xpoints, ypoints, x, y, endPointsX[i], endPointsY[i]): 3019 isContained = False 3020 3021 if not isContained: 3022 return False 3023 3024 nearest_attachment = 0 3025 3026 # If a hit, check the attachment points within the object 3027 nearest = 999999 3028 3029 for i in range(self.GetNumberOfAttachments()): 3030 e = self.GetAttachmentPositionEdge(i) 3031 if e: 3032 xp, yp = e 3033 l = math.sqrt((xp - x) * (xp - x) + (yp - y) * (yp - y)) 3034 if l < nearest: 3035 nearest = l 3036 nearest_attachment = i 3037 3038 return nearest_attachment, nearest 3039 3040 # Really need to be able to reset the shape! Otherwise, if the 3041 # points ever go to zero, we've lost it, and can't resize. 3042 def SetSize(self, new_width, new_height, recursive = True): 3043 """ 3044 Set the size 3045 3046 :param `new_width`: the width 3047 :param `new_height`: the height 3048 :param `recursive`: not used 3049 3050 """ 3051 self.SetAttachmentSize(new_width, new_height) 3052 3053 # Multiply all points by proportion of new size to old size 3054 x_proportion = abs(float(new_width) / self._originalWidth) 3055 y_proportion = abs(float(new_height) / self._originalHeight) 3056 3057 for i in range(max(len(self._points), len(self._originalPoints))): 3058 self._points[i] = wx.Point(self._originalPoints[i][0] * x_proportion, self._originalPoints[i][1] * y_proportion) 3059 3060 self._boundWidth = abs(new_width) 3061 self._boundHeight = abs(new_height) 3062 self.SetDefaultRegionSize() 3063 3064 # Make the original points the same as the working points 3065 def UpdateOriginalPoints(self): 3066 """ 3067 If we've changed the shape, must make the original points match the 3068 working points with this function. 3069 3070 """ 3071 self._originalPoints = [] 3072 3073 for point in self._points: 3074 original_point = wx.RealPoint(point[0], point[1]) 3075 self._originalPoints.append(original_point) 3076 3077 self.CalculateBoundingBox() 3078 self._originalWidth = self._boundWidth 3079 self._originalHeight = self._boundHeight 3080 3081 def AddPolygonPoint(self, pos): 3082 """ 3083 Add a control point after the given point. 3084 3085 :param `pos`: position of point 3086 3087 """ 3088 try: 3089 firstPoint = self._points[pos] 3090 except ValueError: 3091 firstPoint = self._points[0] 3092 3093 try: 3094 secondPoint = self._points[pos + 1] 3095 except ValueError: 3096 secondPoint = self._points[0] 3097 3098 x = (secondPoint[0] - firstPoint[0]) / 2.0 + firstPoint[0] 3099 y = (secondPoint[1] - firstPoint[1]) / 2.0 + firstPoint[1] 3100 point = wx.RealPoint(x, y) 3101 3102 if pos >= len(self._points) - 1: 3103 self._points.append(point) 3104 else: 3105 self._points.insert(pos + 1, point) 3106 3107 self.UpdateOriginalPoints() 3108 3109 if self._selected: 3110 self.DeleteControlPoints() 3111 self.MakeControlPoints() 3112 3113 def DeletePolygonPoint(self, pos): 3114 """ 3115 3116 Delete the given control point. 3117 3118 :param `pos`: position of point 3119 3120 """ 3121 if pos < len(self._points): 3122 del self._points[pos] 3123 self.UpdateOriginalPoints() 3124 if self._selected: 3125 self.DeleteControlPoints() 3126 self.MakeControlPoints() 3127 3128 # Assume (x1, y1) is centre of box (most generally, line end at box) 3129 def GetPerimeterPoint(self, x1, y1, x2, y2): 3130 """ 3131 Get the perimeter point. 3132 3133 :param `x1`: the x1 position 3134 :param `y1`: the y1 position 3135 :param `x2`: the x2 position 3136 :param `y2`: the y2 position 3137 3138 """ 3139 # First check for situation where the line is vertical, 3140 # and we would want to connect to a point on that vertical -- 3141 # oglFindEndForPolyline can't cope with this (the arrow 3142 # gets drawn to the wrong place). 3143 if self._attachmentMode == ATTACHMENT_MODE_NONE and x1 == x2: 3144 # Look for the point we'd be connecting to. This is 3145 # a heuristic... 3146 for point in self._points: 3147 if point[0] == 0: 3148 if y2 > y1 and point[1] > 0: 3149 return point[0] + self._xpos, point[1] + self._ypos 3150 elif y2 < y1 and point[1] < 0: 3151 return point[0] + self._xpos, point[1] + self._ypos 3152 3153 xpoints = [] 3154 ypoints = [] 3155 for point in self._points: 3156 xpoints.append(point[0] + self._xpos) 3157 ypoints.append(point[1] + self._ypos) 3158 3159 return FindEndForPolyline(xpoints, ypoints, x1, y1, x2, y2) 3160 3161 def OnDraw(self, dc): 3162 """The draw handler.""" 3163 if self._shadowMode != SHADOW_NONE: 3164 if self._shadowBrush: 3165 dc.SetBrush(self._shadowBrush) 3166 dc.SetPen(TransparentPen) 3167 3168 dc.DrawPolygon(self._points, self._xpos + self._shadowOffsetX, self._ypos, self._shadowOffsetY) 3169 3170 if self._pen: 3171 if self._pen.GetWidth() == 0: 3172 dc.SetPen(TransparentPen) 3173 else: 3174 dc.SetPen(self._pen) 3175 if self._brush: 3176 dc.SetBrush(self._brush) 3177 dc.DrawPolygon(self._points, self._xpos, self._ypos) 3178 3179 def OnDrawOutline(self, dc, x, y, w, h): 3180 """The draw outline handler.""" 3181 dc.SetBrush(wx.TRANSPARENT_BRUSH) 3182 # Multiply all points by proportion of new size to old size 3183 x_proportion = abs(float(w) / self._originalWidth) 3184 y_proportion = abs(float(h) / self._originalHeight) 3185 3186 intPoints = [] 3187 for point in self._originalPoints: 3188 intPoints.append(wx.Point(x_proportion * point[0], y_proportion * point[1])) 3189 dc.DrawPolygon(intPoints, x, y) 3190 3191 # Make as many control points as there are vertices 3192 def MakeControlPoints(self): 3193 """Make control points.""" 3194 for point in self._points: 3195 control = PolygonControlPoint(self._canvas, self, CONTROL_POINT_SIZE, point, point[0], point[1]) 3196 self._canvas.AddShape(control) 3197 self._controlPoints.append(control) 3198 3199 def ResetControlPoints(self): 3200 """Reset control points.""" 3201 for i in range(min(len(self._points), len(self._controlPoints))): 3202 point = self._points[i] 3203 self._controlPoints[i]._xoffset = point[0] 3204 self._controlPoints[i]._yoffset = point[1] 3205 self._controlPoints[i].polygonVertex = point 3206 3207 def GetNumberOfAttachments(self): 3208 """Get number of attachments.""" 3209 maxN = max(len(self._points) - 1, 0) 3210 for point in self._attachmentPoints: 3211 if point._id > maxN: 3212 maxN = point._id 3213 return maxN + 1 3214 3215 def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None): 3216 """ 3217 Get attachment position. 3218 3219 :param `attachment`: the attachment ??? 3220 :param `nth`: get nth attachment ??? 3221 :param `no_arcs`: ??? 3222 :param `line`: ??? 3223 3224 """ 3225 if self._attachmentMode == ATTACHMENT_MODE_EDGE and self._points and attachment < len(self._points): 3226 point = self._points[0] 3227 return point[0] + self._xpos, point[1] + self._ypos 3228 return Shape.GetAttachmentPosition(self, attachment, nth, no_arcs, line) 3229 3230 def AttachmentIsValid(self, attachment): 3231 """ 3232 Is attachment valid? 3233 3234 :param `attachment`: ??? 3235 3236 """ 3237 if not self._points: 3238 return False 3239 3240 if attachment >= 0 and attachment < len(self._points): 3241 return True 3242 3243 for point in self._attachmentPoints: 3244 if point._id == attachment: 3245 return True 3246 3247 return False 3248 3249 def Rotate(self, x, y, theta): 3250 """ 3251 Rotate about the given axis by the given amount in radians. 3252 3253 :param `x`: the x position 3254 :param `y`: the y position 3255 :param `theta`: the theta 3256 3257 """ 3258 3259 actualTheta = theta - self._rotation 3260 3261 # Rotate attachment points 3262 sinTheta = math.sin(actualTheta) 3263 cosTheta = math.cos(actualTheta) 3264 3265 for point in self._attachmentPoints: 3266 x1 = point._x 3267 y1 = point._y 3268 3269 point._x = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta 3270 point._y = x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta 3271 3272 for i in range(len(self._points)): 3273 x1, y1 = self._points[i] 3274 3275 self._points[i] = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta, x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta 3276 3277 for i in range(len(self._originalPoints)): 3278 x1, y1 = self._originalPoints[i] 3279 3280 self._originalPoints[i] = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta, x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta 3281 3282 # Added by Pierre Hjälm. If we don't do this the outline will be 3283 # the wrong size. Hopefully it won't have any ill effects. 3284 self.UpdateOriginalPoints() 3285 3286 self._rotation = theta 3287 3288 self.CalculatePolygonCentre() 3289 self.CalculateBoundingBox() 3290 self.ResetControlPoints() 3291 3292 # Control points ('handles') redirect control to the actual shape, to 3293 # make it easier to override sizing behaviour. 3294 def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0): 3295 """The sizing drag left handler.""" 3296 dc = wx.MemoryDC() 3297 dc.SelectObject(self.GetCanvas().GetBuffer()) 3298 self.GetCanvas().PrepareDC(dc) 3299 dc.SetLogicalFunction(OGLRBLF) 3300 3301 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT) 3302 dc.SetPen(dottedPen) 3303 dc.SetBrush(wx.TRANSPARENT_BRUSH) 3304 3305 # Code for CTRL-drag in C++ version commented out 3306 3307 pt.CalculateNewSize(x, y) 3308 3309 self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), pt.GetNewSize()[0], pt.GetNewSize()[1]) 3310 3311 def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0): 3312 """The sizing begin drag left handler.""" 3313 dc = wx.MemoryDC() 3314 dc.SelectObject(self.GetCanvas().GetBuffer()) 3315 self.GetCanvas().PrepareDC(dc) 3316 dc.SetLogicalFunction(OGLRBLF) 3317 3318 self.Erase(dc) 3319 3320 bound_x, bound_y = self.GetBoundingBoxMin() 3321 3322 dist = math.sqrt((x - self.GetX()) * (x - self.GetX()) + (y - self.GetY()) * (y - self.GetY())) 3323 3324 pt._originalDistance = dist 3325 pt._originalSize[0] = bound_x 3326 pt._originalSize[1] = bound_y 3327 3328 if pt._originalDistance == 0: 3329 pt._originalDistance = 0.0001 3330 3331 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT) 3332 dc.SetPen(dottedPen) 3333 dc.SetBrush(wx.TRANSPARENT_BRUSH) 3334 3335 # Code for CTRL-drag in C++ version commented out 3336 3337 pt.CalculateNewSize(x, y) 3338 3339 self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), pt.GetNewSize()[0], pt.GetNewSize()[1]) 3340 3341 self._canvas.CaptureMouse() 3342 3343 def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0): 3344 """The sizing end drag left handler.""" 3345 dc = wx.MemoryDC() 3346 dc.SelectObject(self.GetCanvas().GetBuffer()) 3347 self.GetCanvas().PrepareDC(dc) 3348 3349 if self._canvas.HasCapture(): 3350 self._canvas.ReleaseMouse() 3351 dc.SetLogicalFunction(wx.COPY) 3352 3353 # If we're changing shape, must reset the original points 3354 if keys & KEY_CTRL: 3355 self.CalculateBoundingBox() 3356 self.CalculatePolygonCentre() 3357 else: 3358 self.SetSize(pt.GetNewSize()[0], pt.GetNewSize()[1]) 3359 3360 self.Recompute() 3361 self.ResetControlPoints() 3362 self.Move(dc, self.GetX(), self.GetY()) 3363 if not self._canvas.GetQuickEditMode(): 3364 self._canvas.Redraw(dc) 3365 3366 3367class EllipseShape(Shape): 3368 """ 3369 The :class:`EllipseShape` class behaves similarly to the 3370 :class`RectangleShape` but is elliptical. 3371 3372 """ 3373 def __init__(self, w, h): 3374 """ 3375 Default class constructor 3376 3377 :param `w`: the width 3378 :param `h`: the height 3379 3380 """ 3381 Shape.__init__(self) 3382 self._width = w 3383 self._height = h 3384 self.SetDefaultRegionSize() 3385 3386 def GetBoundingBoxMin(self): 3387 """Get the minimum bounding box.""" 3388 return self._width, self._height 3389 3390 def GetPerimeterPoint(self, x1, y1, x2, y2): 3391 """ 3392 Get the perimeter point. 3393 3394 :param `x1`: the x1 position 3395 :param `y1`: the y1 position 3396 :param `x2`: the x2 position 3397 :param `y2`: the y2 position 3398 3399 """ 3400 bound_x, bound_y = self.GetBoundingBoxMax() 3401 3402 return DrawArcToEllipse(self._xpos, self._ypos, bound_x, bound_y, x2, y2, x1, y1) 3403 3404 def GetWidth(self): 3405 """Get the width.""" 3406 return self._width 3407 3408 def GetHeight(self): 3409 """Get the height.""" 3410 return self._height 3411 3412 def SetWidth(self, w): 3413 """ 3414 Set the width. 3415 3416 :param `w`: the width 3417 3418 """ 3419 self._width = w 3420 3421 def SetHeight(self, h): 3422 """ 3423 Set the height. 3424 3425 :param `h`: the height 3426 3427 """ 3428 self._height = h 3429 3430 def OnDraw(self, dc): 3431 """The draw handler.""" 3432 if self._shadowMode != SHADOW_NONE: 3433 if self._shadowBrush: 3434 dc.SetBrush(self._shadowBrush) 3435 dc.SetPen(TransparentPen) 3436 dc.DrawEllipse(self._xpos - self.GetWidth() / 2.0 + self._shadowOffsetX, 3437 self._ypos - self.GetHeight() / 2.0 + self._shadowOffsetY, 3438 self.GetWidth(), self.GetHeight()) 3439 3440 if self._pen: 3441 if self._pen.GetWidth() == 0: 3442 dc.SetPen(TransparentPen) 3443 else: 3444 dc.SetPen(self._pen) 3445 if self._brush: 3446 dc.SetBrush(self._brush) 3447 dc.DrawEllipse(self._xpos - self.GetWidth() / 2.0, self._ypos - self.GetHeight() / 2.0, self.GetWidth(), self.GetHeight()) 3448 3449 def SetSize(self, x, y, recursive = True): 3450 """ 3451 Set the size. 3452 3453 :param `x`: the width 3454 :param `y`: the height 3455 :recursive: not used 3456 3457 """ 3458 self.SetAttachmentSize(x, y) 3459 self._width = x 3460 self._height = y 3461 self.SetDefaultRegionSize() 3462 3463 def GetNumberOfAttachments(self): 3464 """Get number of attachments.""" 3465 return Shape.GetNumberOfAttachments(self) 3466 3467 # There are 4 attachment points on an ellipse - 0 = top, 1 = right, 3468 # 2 = bottom, 3 = left. 3469 def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None): 3470 """ 3471 Get attachment position. 3472 3473 :param `attachment`: the attachment ??? 3474 :param `nth`: get nth attachment ??? 3475 :param `no_arcs`: ??? 3476 :param `line`: ??? 3477 3478 """ 3479 if self._attachmentMode == ATTACHMENT_MODE_BRANCHING: 3480 return Shape.GetAttachmentPosition(self, attachment, nth, no_arcs, line) 3481 3482 if self._attachmentMode != ATTACHMENT_MODE_NONE: 3483 top = self._ypos + self._height / 2.0 3484 bottom = self._ypos - self._height / 2.0 3485 left = self._xpos - self._width / 2.0 3486 right = self._xpos + self._width / 2.0 3487 3488 physicalAttachment = self.LogicalToPhysicalAttachment(attachment) 3489 3490 if physicalAttachment == 0: 3491 if self._spaceAttachments: 3492 x = left + (nth + 1) * self._width / (no_arcs + 1.0) 3493 else: 3494 x = self._xpos 3495 y = top 3496 # We now have the point on the bounding box: but get the point 3497 # on the ellipse by imagining a vertical line from 3498 # (x, self._ypos - self._height - 500) to (x, self._ypos) intersecting 3499 # the ellipse. 3500 3501 return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, x, self._ypos - self._height - 500, x, self._ypos) 3502 elif physicalAttachment == 1: 3503 x = right 3504 if self._spaceAttachments: 3505 y = bottom + (nth + 1) * self._height / (no_arcs + 1.0) 3506 else: 3507 y = self._ypos 3508 return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, self._xpos + self._width + 500, y, self._xpos, y) 3509 elif physicalAttachment == 2: 3510 if self._spaceAttachments: 3511 x = left + (nth + 1) * self._width / (no_arcs + 1.0) 3512 else: 3513 x = self._xpos 3514 y = bottom 3515 return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, x, self._ypos + self._height + 500, x, self._ypos) 3516 elif physicalAttachment == 3: 3517 x = left 3518 if self._spaceAttachments: 3519 y = bottom + (nth + 1) * self._height / (no_arcs + 1.0) 3520 else: 3521 y = self._ypos 3522 return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, self._xpos - self._width - 500, y, self._xpos, y) 3523 else: 3524 return Shape.GetAttachmentPosition(self, attachment, x, y, nth, no_arcs, line) 3525 else: 3526 return self._xpos, self._ypos 3527 3528 3529class CircleShape(EllipseShape): 3530 """ 3531 The :class:`CircleShape` class is an :class:`EllipseShape` whose width 3532 and height are the same. 3533 """ 3534 def __init__(self, diameter): 3535 """ 3536 Default class constructor 3537 3538 :param `diameter`: the diameter 3539 3540 """ 3541 EllipseShape.__init__(self, diameter, diameter) 3542 self.SetMaintainAspectRatio(True) 3543 3544 def GetPerimeterPoint(self, x1, y1, x2, y2): 3545 """ 3546 Get the perimeter point. 3547 3548 :param `x1`: ??? 3549 :param `y1`: ??? 3550 :param `x2`: ??? 3551 :param `y2`: ??? 3552 :returns: ??? 3553 3554 """ 3555 return FindEndForCircle(self._width / 2.0, self._xpos, self._ypos, x2, y2) 3556 3557 3558class TextShape(RectangleShape): 3559 """ 3560 The :class:`TextShape` class like :class:`wx.RectangleShape` but only the 3561 text is displayed. 3562 """ 3563 def __init__(self, width, height): 3564 """ 3565 Default class constructor 3566 3567 :param `width`: the width 3568 :param `height`: the height 3569 3570 """ 3571 RectangleShape.__init__(self, width, height) 3572 3573 def OnDraw(self, dc): 3574 """not implemented???""" 3575 pass 3576 3577 3578class ShapeRegion(object): 3579 """The :class:`ShapeRegion` class.""" 3580 def __init__(self, region = None): 3581 """ 3582 Default class constructor 3583 3584 :param `region`: a parent region or None??? 3585 3586 """ 3587 if region: 3588 self._regionText = region._regionText 3589 self._regionName = region._regionName 3590 self._textColour = region._textColour 3591 3592 self._font = region._font 3593 self._minHeight = region._minHeight 3594 self._minWidth = region._minWidth 3595 self._width = region._width 3596 self._height = region._height 3597 self._x = region._x 3598 self._y = region._y 3599 3600 self._regionProportionX = region._regionProportionX 3601 self._regionProportionY = region._regionProportionY 3602 self._formatMode = region._formatMode 3603 self._actualColourObject = region._actualColourObject 3604 self._actualPenObject = None 3605 self._penStyle = region._penStyle 3606 self._penColour = region._penColour 3607 3608 self.ClearText() 3609 for line in region._formattedText: 3610 new_line = ShapeTextLine(line.GetX(), line.GetY(), line.GetText()) 3611 self._formattedText.append(new_line) 3612 else: 3613 self._regionText = "" 3614 self._font = NormalFont 3615 self._minHeight = 5.0 3616 self._minWidth = 5.0 3617 self._width = 0.0 3618 self._height = 0.0 3619 self._x = 0.0 3620 self._y = 0.0 3621 3622 self._regionProportionX = -1.0 3623 self._regionProportionY = -1.0 3624 self._formatMode = FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT 3625 self._regionName = "" 3626 self._textColour = "BLACK" 3627 self._penColour = "BLACK" 3628 self._penStyle = wx.PENSTYLE_SOLID 3629 self._actualColourObject = wx.TheColourDatabase.Find("BLACK") 3630 self._actualPenObject = None 3631 3632 self._formattedText = [] 3633 3634 def ClearText(self): 3635 """Clear the text.""" 3636 self._formattedText = [] 3637 3638 def SetFont(self, f): 3639 """ 3640 Set the font. 3641 3642 :param `f`: an instance of :class:`wx.Font` 3643 """ 3644 self._font = f 3645 3646 def SetMinSize(self, w, h): 3647 """ 3648 Set the minumum size. 3649 3650 :param `w`: the minimum width 3651 :Param `h`: the minimum height 3652 3653 """ 3654 self._minWidth = w 3655 self._minHeight = h 3656 3657 def SetSize(self, w, h): 3658 """ 3659 Set the size. 3660 3661 :param `w`: the width 3662 :Param `h`: the jeight 3663 3664 """ 3665 self._width = w 3666 self._height = h 3667 3668 def SetPosition(self, xp, yp): 3669 """ 3670 Set the position. 3671 3672 :param `xp`: the x position 3673 :Param `yp`: the y position 3674 3675 """ 3676 self._x = xp 3677 self._y = yp 3678 3679 def SetProportions(self, xp, yp): 3680 """ 3681 Set the proportions. 3682 3683 :param `xp`: the x region proportion 3684 :Param `yp`: the y region proportion 3685 3686 """ 3687 self._regionProportionX = xp 3688 self._regionProportionY = yp 3689 3690 def SetFormatMode(self, mode): 3691 """ 3692 Set the format mode of the region. 3693 3694 :param `mode`: can be a bit list of the following 3695 3696 ============================== ============================== 3697 Format mode Description 3698 ============================== ============================== 3699 `FORMAT_NONE` No formatting 3700 `FORMAT_CENTRE_HORIZ` Horizontal centring 3701 `FORMAT_CENTRE_VERT` Vertical centring 3702 ============================== ============================== 3703 3704 """ 3705 self._formatMode = mode 3706 3707 def SetColour(self, col): 3708 """ 3709 Set the colour. 3710 3711 :param str `col`: a valid colour name, 3712 see :class:`wx.ColourDatabase` 3713 3714 """ 3715 self._textColour = col 3716 self._actualColourObject = col 3717 3718 def GetActualColourObject(self): 3719 """Get the actual colour object from the :class:`wx.ColourDatabase`.""" 3720 self._actualColourObject = wx.TheColourDatabase.Find(self.GetColour()) 3721 return self._actualColourObject 3722 3723 def SetPenColour(self, col): 3724 """ 3725 Set the pen colour. 3726 3727 :param str `col`: a valid colour name, 3728 see :class:`wx.ColourDatabase` 3729 3730 """ 3731 self._penColour = col 3732 self._actualPenObject = None 3733 3734 def GetActualPen(self): 3735 """ 3736 Get actual pen. 3737 3738 :note: Returns NULL if the pen is invisible 3739 (different to pen being transparent; indicates that 3740 region boundary should not be drawn.) 3741 3742 """ 3743 if self._actualPenObject: 3744 return self._actualPenObject 3745 3746 if not self._penColour: 3747 return None 3748 if self._penColour=="Invisible": 3749 return None 3750 self._actualPenObject = wx.Pen(self._penColour, 1, self._penStyle) 3751 return self._actualPenObject 3752 3753 def SetText(self, s): 3754 """ 3755 Set the text. 3756 3757 :param str `s`: the text 3758 3759 """ 3760 self._regionText = s 3761 3762 def SetName(self, s): 3763 """ 3764 Set the name. 3765 3766 :param str `s`: the name 3767 3768 """ 3769 self._regionName = s 3770 3771 def GetText(self): 3772 """Get the text.""" 3773 return self._regionText 3774 3775 def GetFont(self): 3776 """Get the font.""" 3777 return self._font 3778 3779 def GetMinSize(self): 3780 """Get the minimum size.""" 3781 return self._minWidth, self._minHeight 3782 3783 def GetProportion(self): 3784 """Get the proportion.""" 3785 return self._regionProportionX, self._regionProportionY 3786 3787 def GetSize(self): 3788 """Get the size.""" 3789 return self._width, self._height 3790 3791 def GetPosition(self): 3792 """Get the position.""" 3793 return self._x, self._y 3794 3795 def GetFormatMode(self): 3796 """Get the format mode.""" 3797 return self._formatMode 3798 3799 def GetName(self): 3800 """Get the name.""" 3801 return self._regionName 3802 3803 def GetColour(self): 3804 """Get the colour.""" 3805 return self._textColour 3806 3807 def GetFormattedText(self): 3808 """Get the formatted text.""" 3809 return self._formattedText 3810 3811 def GetPenColour(self): 3812 """Get the pen colour""" 3813 return self._penColour 3814 3815 def GetPenStyle(self): 3816 """Get the pen style.""" 3817 return self._penStyle 3818 3819 def SetPenStyle(self, style): 3820 """ 3821 Set the pen style. 3822 3823 :param `style`: the style, see :class:`wx.Pen` 3824 3825 """ 3826 self._penStyle = style 3827 self._actualPenObject = None 3828 3829 def GetWidth(self): 3830 """Get the width.""" 3831 return self._width 3832 3833 def GetHeight(self): 3834 """Get the height.""" 3835 return self._height 3836 3837 3838class ControlPoint(RectangleShape): 3839 """The :class:`wx.ControlPoint` class.""" 3840 def __init__(self, theCanvas, object, size, the_xoffset, the_yoffset, the_type): 3841 """ 3842 Default class constructor 3843 3844 :param `theCanvas`: a :class:`~lib.ogl.Canvas` 3845 :param `object`: the shape, instance of :class:`~lib.ogl.Shape` 3846 :param float `size`: the size 3847 :param float `the_xoffset`: the x position 3848 :param float `the_yoffset`: the y position 3849 :param int `the_type`: one of the following types ??? 3850 3851 ======================================== ================================== 3852 Control point type Description 3853 ======================================== ================================== 3854 `CONTROL_POINT_VERTICAL` Vertical 3855 `CONTROL_POINT_HORIZONTAL` Horizontal 3856 `CONTROL_POINT_DIAGONAL` Diagonal 3857 ======================================== ================================== 3858 3859 """ 3860 RectangleShape.__init__(self, size, size) 3861 3862 self._canvas = theCanvas 3863 self._shape = object 3864 self._xoffset = the_xoffset 3865 self._yoffset = the_yoffset 3866 self._type = the_type 3867 self.SetPen(BlackForegroundPen) 3868 self.SetBrush(wx.BLACK_BRUSH) 3869 self._oldCursor = None 3870 self._visible = True 3871 self._eraseObject = True 3872 3873 # Don't even attempt to draw any text - waste of time 3874 def OnDrawContents(self, dc): 3875 """not implemented???""" 3876 pass 3877 3878 def OnDraw(self, dc): 3879 """The draw handler.""" 3880 self._xpos = self._shape.GetX() + self._xoffset 3881 self._ypos = self._shape.GetY() + self._yoffset 3882 RectangleShape.OnDraw(self, dc) 3883 3884 def OnErase(self, dc): 3885 """The erase handler.""" 3886 RectangleShape.OnErase(self, dc) 3887 3888 # Implement resizing of canvas object 3889 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0): 3890 """The drag left handler.""" 3891 self._shape.GetEventHandler().OnSizingDragLeft(self, draw, x, y, keys, attachment) 3892 3893 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0): 3894 """The begin drag left handler.""" 3895 self._shape.GetEventHandler().OnSizingBeginDragLeft(self, x, y, keys, attachment) 3896 3897 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0): 3898 """The end drag left handler.""" 3899 self._shape.GetEventHandler().OnSizingEndDragLeft(self, x, y, keys, attachment) 3900 3901 def GetNumberOfAttachments(self): 3902 """Get the number of attachments.""" 3903 return 1 3904 3905 def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None): 3906 """ 3907 Get the attachment position. 3908 3909 :param `attachment`: the attachment ??? 3910 :param `nth`: get nth attachment ??? 3911 :param `no_arcs`: ??? 3912 :param `line`: ??? 3913 3914 """ 3915 return self._xpos, self._ypos 3916 3917 def SetEraseObject(self, er): 3918 """ 3919 Set the erase object ??? 3920 3921 :param `er`: the object 3922 3923 """ 3924 self._eraseObject = er 3925 3926 3927class PolygonControlPoint(ControlPoint): 3928 """The :class:`PolygonControlPoint` class.""" 3929 def __init__(self, theCanvas, object, size, vertex, the_xoffset, the_yoffset): 3930 """ 3931 Default class constructor 3932 3933 :param `theCanvas`: a :class:`~lib.ogl.Canvas` 3934 :param `object`: the shape, instance of :class:`~lib.ogl.Shape` 3935 :param float `size`: the size 3936 :param `vertext`: the vertex 3937 :param float `the_xoffset`: the x position 3938 :param float `the_yoffset`: the y position 3939 :param int `the_type`: one of the following types ??? 3940 3941 ======================================== ================================== 3942 Control point type Description 3943 ======================================== ================================== 3944 `CONTROL_POINT_VERTICAL` Vertical 3945 `CONTROL_POINT_HORIZONTAL` Horizontal 3946 `CONTROL_POINT_DIAGONAL` Diagonal 3947 ======================================== ================================== 3948 3949 """ 3950 ControlPoint.__init__(self, theCanvas, object, size, the_xoffset, the_yoffset, 0) 3951 self._polygonVertex = vertex 3952 self._originalDistance = 0.0 3953 self._newSize = wx.RealPoint() 3954 self._originalSize = wx.RealPoint() 3955 3956 def GetNewSize(self): 3957 """Get the new size.""" 3958 return self._newSize 3959 3960 def CalculateNewSize(self, x, y): 3961 """ 3962 Calculate what new size would be, at end of resize. 3963 3964 :param `x`: x ??? 3965 :param `y`: y ??? 3966 3967 """ 3968 bound_x, bound_y = self.GetShape().GetBoundingBoxMax() 3969 dist = math.sqrt((x - self._shape.GetX()) * (x - self._shape.GetX()) + (y - self._shape.GetY()) * (y - self._shape.GetY())) 3970 3971 self._newSize[0] = dist / self._originalDistance * self._originalSize[0] 3972 self._newSize[1] = dist / self._originalDistance * self._originalSize[1] 3973 3974 # Implement resizing polygon or moving the vertex 3975 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0): 3976 """The drag left handler.""" 3977 self._shape.GetEventHandler().OnSizingDragLeft(self, draw, x, y, keys, attachment) 3978 3979 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0): 3980 """The begin drag left handler.""" 3981 self._shape.GetEventHandler().OnSizingBeginDragLeft(self, x, y, keys, attachment) 3982 3983 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0): 3984 """The end drag left handler.""" 3985 self._shape.GetEventHandler().OnSizingEndDragLeft(self, x, y, keys, attachment) 3986 3987from .canvas import * 3988from .lines import * 3989from .composit import * 3990