1# -*- coding: utf-8 -*- 2#---------------------------------------------------------------------------- 3# Name: oglmisc.py 4# Purpose: Miscellaneous OGL support functions 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""" 14Miscellaneous support functions for OGL. 15 16params marked with '???' need review! 17""" 18import math 19 20import wx 21 22# Control point types 23# Rectangle and most other shapes 24CONTROL_POINT_VERTICAL = 1 25CONTROL_POINT_HORIZONTAL = 2 26CONTROL_POINT_DIAGONAL = 3 27 28# Line 29CONTROL_POINT_ENDPOINT_TO = 4 30CONTROL_POINT_ENDPOINT_FROM = 5 31CONTROL_POINT_LINE = 6 32 33# Types of formatting: can be combined in a bit list 34FORMAT_NONE = 0 # Left justification 35FORMAT_CENTRE_HORIZ = 1 # Centre horizontally 36FORMAT_CENTRE_VERT = 2 # Centre vertically 37FORMAT_SIZE_TO_CONTENTS = 4 # Resize shape to contents 38 39# Attachment modes 40ATTACHMENT_MODE_NONE, ATTACHMENT_MODE_EDGE, ATTACHMENT_MODE_BRANCHING = 0, 1, 2 41 42# Shadow mode 43SHADOW_NONE, SHADOW_LEFT, SHADOW_RIGHT = 0, 1, 2 44 45OP_CLICK_LEFT, OP_CLICK_RIGHT, OP_DRAG_LEFT, OP_DRAG_RIGHT = 1, 2, 4, 8 46OP_ALL = OP_CLICK_LEFT | OP_CLICK_RIGHT | OP_DRAG_LEFT | OP_DRAG_RIGHT 47 48# Sub-modes for branching attachment mode 49BRANCHING_ATTACHMENT_NORMAL = 1 50BRANCHING_ATTACHMENT_BLOB = 2 51 52# logical function to use when drawing rubberband boxes, etc. 53OGLRBLF = wx.INVERT 54 55CONTROL_POINT_SIZE = 6 56 57# Types of arrowhead 58# (i) Built-in 59ARROW_HOLLOW_CIRCLE = 1 60ARROW_FILLED_CIRCLE = 2 61ARROW_ARROW = 3 62ARROW_SINGLE_OBLIQUE = 4 63ARROW_DOUBLE_OBLIQUE = 5 64# (ii) Custom 65ARROW_METAFILE = 20 66 67# Position of arrow on line 68ARROW_POSITION_START = 0 69ARROW_POSITION_END = 1 70ARROW_POSITION_MIDDLE = 2 71 72# Line alignment flags 73# Vertical by default 74LINE_ALIGNMENT_HORIZ = 1 75LINE_ALIGNMENT_VERT = 0 76LINE_ALIGNMENT_TO_NEXT_HANDLE = 2 77LINE_ALIGNMENT_NONE = 0 78 79# was defined in canvas and in composit 80KEY_SHIFT = 1 81KEY_CTRL = 2 82 83 84 85def FormatText(dc, text, width, height, formatMode): 86 """ 87 Format a text 88 89 :param `dc`: the :class:`wx.MemoryDC` 90 :param `text`: the text to format 91 :param `width`: the width of the box??? 92 :param `height`: the height of the box??? it is not used in the code! 93 :param `formatMode`: one of the format modes, can be combined in a bit list 94 95 ======================================== ================================== 96 Format mode name Description 97 ======================================== ================================== 98 `FORMAT_NONE` Left justification 99 `FORMAT_CENTRE_HORIZ` Centre horizontally 100 `FORMAT_CENTRE_VERT` Centre vertically 101 `FORMAT_SIZE_TO_CONTENTS` Resize shape to contents 102 ======================================== ================================== 103 104 :returns: a list of strings fitting in the box 105 106 """ 107 i = 0 108 word = "" 109 word_list = [] 110 end_word = False 111 new_line = False 112 while i < len(text): 113 if text[i] == "%": 114 i += 1 115 if i == len(text): 116 word += "%" 117 else: 118 if text[i] == "n": 119 new_line = True 120 end_word = True 121 i += 1 122 else: 123 word += "%" + text[i] 124 i += 1 125 elif text[i] in ["\012","\015"]: 126 new_line = True 127 end_word = True 128 i += 1 129 elif text[i] == " ": 130 end_word = True 131 i += 1 132 else: 133 word += text[i] 134 i += 1 135 136 if i == len(text): 137 end_word = True 138 139 if end_word: 140 word_list.append(word) 141 word = "" 142 end_word = False 143 if new_line: 144 word_list.append(None) 145 new_line = False 146 147 # Now, make a list of strings which can fit in the box 148 string_list = [] 149 buffer = "" 150 for s in word_list: 151 oldBuffer = buffer 152 if s is None: 153 # FORCE NEW LINE 154 if len(buffer) > 0: 155 string_list.append(buffer) 156 buffer = "" 157 else: 158 if len(buffer): 159 buffer += " " 160 buffer += s 161 x, y = dc.GetTextExtent(buffer) 162 163 # Don't fit within the bounding box if we're fitting 164 # shape to contents 165 if (x > width) and not (formatMode & FORMAT_SIZE_TO_CONTENTS): 166 # Deal with first word being wider than box 167 if len(oldBuffer): 168 string_list.append(oldBuffer) 169 buffer = s 170 if len(buffer): 171 string_list.append(buffer) 172 173 return string_list 174 175 176def GetCentredTextExtent(dc, text_list, xpos=0, ypos=0, width=0, height=0): 177 """ 178 Get the centred text extend 179 180 :param `dc`: the :class:`wx.MemoryDC` 181 :param `text_list`: a list of text lines 182 :param `xpos`: unused 183 :param `ypos`: unused 184 :param `width`: unused 185 :param `height`: unused 186 187 :returns: maximum width and the height 188 189 """ 190 if not text_list: 191 return 0, 0 192 193 max_width = 0 194 for line in text_list: 195 current_width, char_height = dc.GetTextExtent(line.GetText()) 196 if current_width > max_width: 197 max_width = current_width 198 199 return max_width, len(text_list) * char_height 200 201 202def CentreText(dc, text_list, xpos, ypos, width, height, formatMode): 203 """ 204 Centre a text 205 206 :param `dc`: the :class:`wx.MemoryDC` 207 :param `text_list`: a list of texts 208 :param `xpos`: the x position 209 :param `ypos`: the y position 210 :param `width`: the width of the box??? 211 :param `height`: the height of the box??? 212 :param `formatMode`: one of the format modes, can be combined in a bit list 213 214 ======================================== ================================== 215 Format mode name Description 216 ======================================== ================================== 217 `FORMAT_NONE` Left justification 218 `FORMAT_CENTRE_HORIZ` Centre horizontally 219 `FORMAT_CENTRE_VERT` Centre vertically 220 `FORMAT_SIZE_TO_CONTENTS` Resize shape to contents 221 ======================================== ================================== 222 223 """ 224 if not text_list: 225 return 226 227 # First, get maximum dimensions of box enclosing text 228 char_height = 0 229 max_width = 0 230 current_width = 0 231 232 # Store text extents for speed 233 widths = [] 234 for line in text_list: 235 current_width, char_height = dc.GetTextExtent(line.GetText()) 236 widths.append(current_width) 237 if current_width > max_width: 238 max_width = current_width 239 240 max_height = len(text_list) * char_height 241 242 if formatMode & FORMAT_CENTRE_VERT: 243 if max_height < height: 244 yoffset = ypos - height / 2.0 + (height - max_height) / 2.0 245 else: 246 yoffset = ypos - height / 2.0 247 yOffset = ypos 248 else: 249 yoffset = 0.0 250 yOffset = 0.0 251 252 if formatMode & FORMAT_CENTRE_HORIZ: 253 xoffset = xpos - width / 2.0 254 xOffset = xpos 255 else: 256 xoffset = 0.0 257 xOffset = 0.0 258 259 for i, line in enumerate(text_list): 260 if formatMode & FORMAT_CENTRE_HORIZ and widths[i] < width: 261 x = (width - widths[i]) / 2.0 + xoffset 262 else: 263 x = xoffset 264 y = i * char_height + yoffset 265 266 line.SetX(x - xOffset) 267 line.SetY(y - yOffset) 268 269 270def DrawFormattedText(dc, text_list, xpos, ypos, width, height, formatMode): 271 """ 272 Draw formated text 273 274 :param `dc`: the :class:`wx.MemoryDC` 275 :param `text_list`: a list of texts 276 :param `xpos`: the x position 277 :param `ypos`: the y position 278 :param `width`: the width of the box??? 279 :param `height`: the height of the box??? 280 :param `formatMode`: one of the format modes, can be combined in a bit list 281 282 ======================================== ================================== 283 Format mode name Description 284 ======================================== ================================== 285 `FORMAT_NONE` Left justification 286 `FORMAT_CENTRE_HORIZ` Centre horizontally 287 `FORMAT_CENTRE_VERT` Centre vertically 288 `FORMAT_SIZE_TO_CONTENTS` Resize shape to contents 289 ======================================== ================================== 290 291 """ 292 if formatMode & FORMAT_CENTRE_HORIZ: 293 xoffset = xpos 294 else: 295 xoffset = xpos - width / 2.0 296 297 if formatMode & FORMAT_CENTRE_VERT: 298 yoffset = ypos 299 else: 300 yoffset = ypos - height / 2.0 301 302 # +1 to allow for rounding errors 303 dc.SetClippingRegion(xpos - width / 2.0, ypos - height / 2.0, width + 1, height + 1) 304 305 for line in text_list: 306 dc.DrawText(line.GetText(), xoffset + line.GetX(), yoffset + line.GetY()) 307 308 dc.DestroyClippingRegion() 309 310 311def RoughlyEqual(val1, val2, tol=0.00001): 312 """ 313 Check if values are roughtly equal 314 315 :param `val1`: the first value to check 316 :param `val2`: the second value to check 317 :param `tol`: the tolerance, defaults to 0.00001 318 319 :returns: True or False 320 321 """ 322 return val1 < (val2 + tol) and val1 > (val2 - tol) and \ 323 val2 < (val1 + tol) and val2 > (val1 - tol) 324 325 326def FindEndForBox(width, height, x1, y1, x2, y2): 327 """ 328 Find the end for a box 329 330 :param `width`: the width of the box 331 :param `height`: the height of the box 332 :param `x1`: x1 position 333 :param `y1`: y1 position 334 :param `x2`: x2 position 335 :param `y2`: y2 position 336 337 :returns: the end position 338 339 """ 340 xvec = [x1 - width / 2.0, x1 - width / 2.0, x1 + width / 2.0, x1 + width / 2.0, x1 - width / 2.0] 341 yvec = [y1 - height / 2.0, y1 + height / 2.0, y1 + height / 2.0, y1 - height / 2.0, y1 - height / 2.0] 342 343 return FindEndForPolyline(xvec, yvec, x2, y2, x1, y1) 344 345 346def CheckLineIntersection(x1, y1, x2, y2, x3, y3, x4, y4): 347 """ 348 Check for line intersection 349 350 :param `x1`: x1 position 351 :param `y1`: y1 position 352 :param `x2`: x2 position 353 :param `y2`: y2 position 354 :param `x3`: x3 position 355 :param `y3`: y3 position 356 :param `x4`: x4 position 357 :param `y4`: y4 position 358 359 :returns: a lenght ratio and a k line??? 360 361 """ 362 denominator_term = (y4 - y3) * (x2 - x1) - (y2 - y1) * (x4 - x3) 363 numerator_term = (x3 - x1) * (y4 - y3) + (x4 - x3) * (y1 - y3) 364 365 length_ratio = 1.0 366 k_line = 1.0 367 368 # Check for parallel lines 369 if denominator_term < 0.005 and denominator_term > -0.005: 370 line_constant = -1.0 371 else: 372 line_constant = float(numerator_term) / denominator_term 373 374 # Check for intersection 375 if line_constant < 1.0 and line_constant > 0.0: 376 # Now must check that other line hits 377 if (y4 - y3) < 0.005 and (y4 - y3) > -0.005: 378 k_line = (x1 - x3 + line_constant * (x2 - x1)) / (x4 - x3) 379 else: 380 k_line = (y1 - y3 + line_constant * (y2 - y1)) / (y4 - y3) 381 if k_line >= 0 and k_line < 1: 382 length_ratio = line_constant 383 else: 384 k_line = 1 385 386 return length_ratio, k_line 387 388 389def FindEndForPolyline(xvec, yvec, x1, y1, x2, y2): 390 """ 391 Find the end for a polyline 392 393 :param `xvec`: x vector ??? 394 :param `yvec`: y vector ??? 395 :param `x1`: x1 position 396 :param `y1`: y1 position 397 :param `x2`: x2 position 398 :param `y2`: y2 position 399 400 :returns: the end position 401 402 """ 403 lastx = xvec[0] 404 lasty = yvec[0] 405 406 min_ratio = 1.0 407 408 for i in range(1, len(xvec)): 409 line_ratio, other_ratio = CheckLineIntersection(x1, y1, x2, y2, lastx, lasty, xvec[i], yvec[i]) 410 lastx = xvec[i] 411 lasty = yvec[i] 412 413 if line_ratio < min_ratio: 414 min_ratio = line_ratio 415 416 # Do last (implicit) line if last and first doubles are not identical 417 if not (xvec[0] == lastx and yvec[0] == lasty): 418 line_ratio, other_ratio = CheckLineIntersection(x1, y1, x2, y2, lastx, lasty, xvec[0], yvec[0]) 419 if line_ratio < min_ratio: 420 min_ratio = line_ratio 421 422 return x1 + (x2 - x1) * min_ratio, y1 + (y2 - y1) * min_ratio 423 424 425def PolylineHitTest(xvec, yvec, x1, y1, x2, y2): 426 """ 427 Hittest for a polyline 428 429 :param `xvec`: x vector ??? 430 :param `yvec`: y vector ??? 431 :param `x1`: x1 position 432 :param `y1`: y1 position 433 :param `x2`: x2 position 434 :param `y2`: y2 position 435 436 :returns: True or False 437 438 """ 439 isAHit = False 440 lastx = xvec[0] 441 lasty = yvec[0] 442 443 min_ratio = 1.0 444 445 for i in range(1, len(xvec)): 446 line_ratio, other_ratio = CheckLineIntersection(x1, y1, x2, y2, lastx, lasty, xvec[i], yvec[i]) 447 if line_ratio != 1.0: 448 isAHit = True 449 lastx = xvec[i] 450 lasty = yvec[i] 451 452 if line_ratio < min_ratio: 453 min_ratio = line_ratio 454 455 # Do last (implicit) line if last and first doubles are not identical 456 if not (xvec[0] == lastx and yvec[0] == lasty): 457 line_ratio, other_ratio = CheckLineIntersection(x1, y1, x2, y2, lastx, lasty, xvec[0], yvec[0]) 458 if line_ratio != 1.0: 459 isAHit = True 460 461 return isAHit 462 463 464def GraphicsStraightenLine(point1, point2): 465 """ 466 Straighten a line in graphics 467 468 :param `point1`: a point list??? 469 :param `point2`: a point list??? 470 471 """ 472 dx = point2[0] - point1[0] 473 dy = point2[1] - point1[1] 474 475 if dx == 0: 476 return 477 elif abs(float(dy) / dx) > 1: 478 point2[0] = point1[0] 479 else: 480 point2[1] = point1[1] 481 482 483def GetPointOnLine(x1, y1, x2, y2, length): 484 """ 485 Get point on a line 486 487 :param `x1`: x1 position 488 :param `y1`: y1 position 489 :param `x2`: x2 position 490 :param `y2`: y2 position 491 :param `length`: length ??? 492 493 :returns: point on line 494 495 """ 496 l = math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) 497 if l < 0.01: 498 l = 0.01 499 500 i_bar = (x2 - x1) / l 501 j_bar = (y2 - y1) / l 502 503 return -length * i_bar + x2, -length * j_bar + y2 504 505 506def GetArrowPoints(x1, y1, x2, y2, length, width): 507 """ 508 Get point on arrow 509 510 :param `x1`: x1 position 511 :param `y1`: y1 position 512 :param `x2`: x2 position 513 :param `y2`: y2 position 514 :param `length`: length ??? 515 :param `width`: width ??? 516 517 :returns: point on line 518 519 """ 520 l = math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) 521 522 if l < 0.01: 523 l = 0.01 524 525 i_bar = (x2 - x1) / l 526 j_bar = (y2 - y1) / l 527 528 x3 = -length * i_bar + x2 529 y3 = -length * j_bar + y2 530 531 return x2, y2, width * -j_bar + x3, width * i_bar + y3, -width * -j_bar + x3, -width * i_bar + y3 532 533 534def DrawArcToEllipse(x1, y1, width1, height1, x2, y2, x3, y3): 535 """ 536 Draw arc to ellipse 537 538 :param `x1`: x1 position 539 :param `y1`: y1 position 540 :param `width1`: width 541 :param `height1`: height 542 :param `x2`: x2 position 543 :param `y2`: y2 position 544 :param `x3`: x3 position 545 :param `y3`: y3 position 546 547 :returns: ellipse points ??? 548 549 """ 550 a1 = width1 / 2.0 551 b1 = height1 / 2.0 552 553 # Check that x2 != x3 554 if abs(x2 - x3) < 0.05: 555 x4 = x2 556 if y3 > y2: 557 y4 = y1 - math.sqrt((b1 * b1 - (((x2 - x1) * (x2 - x1)) * (b1 * b1) / (a1 * a1)))) 558 else: 559 y4 = y1 + math.sqrt((b1 * b1 - (((x2 - x1) * (x2 - x1)) * (b1 * b1) / (a1 * a1)))) 560 return x4, y4 561 562 # Calculate the x and y coordinates of the point where arc intersects ellipse 563 A = (1 / (a1 * a1)) 564 B = ((y3 - y2) * (y3 - y2)) / ((x3 - x2) * (x3 - x2) * b1 * b1) 565 C = (2 * (y3 - y2) * (y2 - y1)) / ((x3 - x2) * b1 * b1) 566 D = ((y2 - y1) * (y2 - y1)) / (b1 * b1) 567 E = (A + B) 568 F = (C - (2 * A * x1) - (2 * B * x2)) 569 G = ((A * x1 * x1) + (B * x2 * x2) - (C * x2) + D - 1) 570 H = (float(y3 - y2) / (x3 - x2)) 571 K = ((F * F) - (4 * E * G)) 572 573 if K >= 0: 574 # In this case the line intersects the ellipse, so calculate intersection 575 if x2 >= x1: 576 ellipse1_x = ((F * -1) + math.sqrt(K)) / (2 * E) 577 ellipse1_y = ((H * (ellipse1_x - x2)) + y2) 578 else: 579 ellipse1_x = (((F * -1) - math.sqrt(K)) / (2 * E)) 580 ellipse1_y = ((H * (ellipse1_x - x2)) + y2) 581 else: 582 # in this case, arc does not intersect ellipse, so just draw arc 583 ellipse1_x = x3 584 ellipse1_y = y3 585 586 return ellipse1_x, ellipse1_y 587 588 589def FindEndForCircle(radius, x1, y1, x2, y2): 590 """ 591 Find end for a circle 592 593 :param `radius`: radius 594 :param `x1`: x1 position 595 :param `y1`: y1 position 596 :param `x2`: x2 position 597 :param `y2`: y2 position 598 599 :returns: end position 600 601 """ 602 H = math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) 603 604 if H == 0: 605 return x1, y1 606 else: 607 return radius * (x2 - x1) / H + x1, radius * (y2 - y1) / H + y1 608