1# Copyright (C) 2008 Jeremy S. Sanders 2# Email: Jeremy Sanders <jeremy@jeremysanders.net> 3# 4# This program is free software; you can redistribute it and/or modify 5# it under the terms of the GNU General Public License as published by 6# the Free Software Foundation; either version 2 of the License, or 7# (at your option) any later version. 8# 9# This program is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12# GNU General Public License for more details. 13# 14# You should have received a copy of the GNU General Public License along 15# with this program; if not, write to the Free Software Foundation, Inc., 16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17############################################################################## 18 19""" 20Classes for moving widgets around 21 22Control items have a createGraphicsItem method which returns a graphics 23item to control the object 24""" 25 26from __future__ import division 27import math 28 29from ..compat import crange, czip 30from .. import qtall as qt 31from .. import document 32from .. import setting 33 34def _(text, disambiguation=None, context='controlgraph'): 35 """Translate text.""" 36 return qt.QCoreApplication.translate(context, text, disambiguation) 37 38############################################################################## 39 40class _ScaledShape: 41 """Mixing class for shapes which can return scaled positions. 42 43 The control items are plotted on a zoomed plot, so the raw 44 unscaled coordinates need to be scaled by params.cgscale to be in 45 the correct position. 46 47 We could scale everything to achieve this, but the line widths and 48 size of objects would be changed. 49 50 """ 51 52 def setScaledPos(self, x, y): 53 self.setPos(x*self.params.cgscale, y*self.params.cgscale) 54 55 def scaledPos(self): 56 return self.pos()/self.params.cgscale 57 58 def setScaledLine(self, x1, y1, x2, y2): 59 s = self.params.cgscale 60 self.setLine(x1*s, y1*s, x2*s, y2*s) 61 62 def setScaledLinePos(self, x1, y1, x2, y2): 63 s = self.params.cgscale 64 self.setPos(x1*s, y1*s) 65 self.setLine(0,0,(x2-x1)*s,(y2-y1)*s) 66 67 def setScaledRect(self, x, y, w, h): 68 s = self.params.cgscale 69 self.setRect(x*s, y*s, w*s, h*s) 70 71 def scaledX(self): 72 return self.x()/self.params.cgscale 73 74 def scaledY(self): 75 return self.y()/self.params.cgscale 76 77 def scaledRect(self): 78 r = self.rect() 79 s = self.params.cgscale 80 return qt.QRectF(r.left()/s, r.top()/s, r.width()/s, r.height()/s) 81 82############################################################################## 83 84class _ShapeCorner(qt.QGraphicsRectItem, _ScaledShape): 85 """Representing the corners of the rectangle.""" 86 def __init__(self, parent, params, rotator=False): 87 qt.QGraphicsRectItem.__init__(self, parent) 88 self.params = params 89 if rotator: 90 self.setBrush( qt.QBrush(setting.settingdb.color('cntrlline')) ) 91 self.setRect(-3, -3, 6, 6) 92 else: 93 self.setBrush(qt.QBrush(setting.settingdb.color('cntrlcorner')) ) 94 self.setRect(-5, -5, 10, 10) 95 self.setPen(qt.QPen(qt.Qt.NoPen)) 96 self.setFlag(qt.QGraphicsItem.ItemIsMovable) 97 self.setZValue(3.) 98 99 def mouseMoveEvent(self, event): 100 """Notify parent on move.""" 101 qt.QGraphicsRectItem.mouseMoveEvent(self, event) 102 self.parentItem().updateFromCorner(self, event) 103 104 def mouseReleaseEvent(self, event): 105 """Notify parent on unclicking.""" 106 qt.QGraphicsRectItem.mouseReleaseEvent(self, event) 107 self.parentItem().updateWidget() 108 109############################################################################## 110 111def controlLinePen(): 112 """Get pen for lines around shapes.""" 113 return qt.QPen(setting.settingdb.color('cntrlline'), 2, qt.Qt.DotLine) 114 115class _EdgeLine(qt.QGraphicsLineItem, _ScaledShape): 116 """Line used for edges of resizing box.""" 117 def __init__(self, parent, params, ismovable=True): 118 qt.QGraphicsLineItem.__init__(self, parent) 119 self.setPen(controlLinePen()) 120 self.setZValue(2.) 121 self.params = params 122 if ismovable: 123 self.setFlag(qt.QGraphicsItem.ItemIsMovable) 124 self.setCursor(qt.Qt.SizeAllCursor) 125 126 def mouseMoveEvent(self, event): 127 """Notify parent on move.""" 128 qt.QGraphicsLineItem.mouseMoveEvent(self, event) 129 self.parentItem().updateFromLine(self, self.scaledPos()) 130 131 def mouseReleaseEvent(self, event): 132 """Notify parent on unclicking.""" 133 qt.QGraphicsLineItem.mouseReleaseEvent(self, event) 134 self.parentItem().updateWidget() 135 136############################################################################## 137 138class ControlMarginBox(object): 139 def __init__(self, widget, posn, maxposn, painthelper, 140 ismovable = True, isresizable = True): 141 """Create control box item. 142 143 widget: widget this is controllng 144 posn: coordinates of box [x1, y1, x2, y2] 145 maxposn: coordinates of biggest possibe box 146 painthelper: painterhelper to get scaling from 147 ismovable: box can be moved 148 isresizable: box can be resized 149 """ 150 151 # save values 152 self.posn = posn 153 self.maxposn = maxposn 154 self.widget = widget 155 self.ismovable = ismovable 156 self.isresizable = isresizable 157 158 # we need these later to convert back to original units 159 self.document = painthelper.document 160 self.pagesize = painthelper.pagesize 161 self.cgscale = painthelper.cgscale 162 self.dpi = painthelper.dpi 163 164 def createGraphicsItem(self, parent): 165 return _GraphMarginBox(parent, self) 166 167 def setWidgetMargins(self): 168 """A helpful routine for setting widget margins after 169 moving or resizing. 170 171 This is called by the widget after receiving updateControlItem 172 """ 173 s = self.widget.settings 174 175 # get margins in pixels 176 left = self.posn[0] - self.maxposn[0] 177 right = self.maxposn[2] - self.posn[2] 178 top = self.posn[1] - self.maxposn[1] 179 bottom = self.maxposn[3] - self.posn[3] 180 181 # set up fake painthelper containing veusz scalings 182 helper = document.PaintHelper( 183 self.document, self.pagesize, 184 scaling=self.cgscale, dpi=self.dpi) 185 186 # convert to physical units 187 left = s.get('leftMargin').convertInverse(left, helper) 188 right = s.get('rightMargin').convertInverse(right, helper) 189 top = s.get('topMargin').convertInverse(top, helper) 190 bottom = s.get('bottomMargin').convertInverse(bottom, helper) 191 192 # modify widget margins 193 operations = ( 194 document.OperationSettingSet(s.get('leftMargin'), left), 195 document.OperationSettingSet(s.get('rightMargin'), right), 196 document.OperationSettingSet(s.get('topMargin'), top), 197 document.OperationSettingSet(s.get('bottomMargin'), bottom) 198 ) 199 self.widget.document.applyOperation( 200 document.OperationMultiple(operations, descr=_('resize margins'))) 201 202 def setPageSize(self): 203 """Helper for setting document/page widget size. 204 205 This is called by the widget after receiving updateControlItem 206 """ 207 s = self.widget.settings 208 209 # get margins in pixels 210 width = self.posn[2] - self.posn[0] 211 height = self.posn[3] - self.posn[1] 212 213 # set up fake painter containing veusz scalings 214 helper = document.PaintHelper( 215 self.document, self.pagesize, 216 scaling=self.cgscale, dpi=self.dpi) 217 218 # convert to physical units 219 width = s.get('width').convertInverse(width, helper) 220 height = s.get('height').convertInverse(height, helper) 221 222 # modify widget margins 223 operations = ( 224 document.OperationSettingSet(s.get('width'), width), 225 document.OperationSettingSet(s.get('height'), height), 226 ) 227 self.widget.document.applyOperation( 228 document.OperationMultiple(operations, descr=_('change page size'))) 229 230class _GraphMarginBox(qt.QGraphicsItem): 231 """A box which can be moved or resized. 232 233 Can automatically set margins or widget 234 """ 235 236 # posn coords of each corner 237 mapcornertoposn = ( (0, 1), (2, 1), (0, 3), (2, 3) ) 238 239 def __init__(self, parent, params): 240 """Create control box item.""" 241 242 qt.QGraphicsItem.__init__(self, parent) 243 self.params = params 244 245 self.setZValue(2.) 246 247 # create corners of box 248 self.corners = [_ShapeCorner(self, params) for i in crange(4)] 249 250 # lines connecting corners 251 self.lines = [ 252 _EdgeLine(self, params, ismovable=params.ismovable) 253 for i in crange(4)] 254 255 # hide corners if box is not resizable 256 if not params.isresizable: 257 for c in self.corners: 258 c.hide() 259 260 self.updateCornerPosns() 261 262 def updateCornerPosns(self): 263 """Update all corners from updated box.""" 264 265 par = self.params 266 pos = par.posn 267 # update cursors 268 self.corners[0].setCursor(qt.Qt.SizeFDiagCursor) 269 self.corners[1].setCursor(qt.Qt.SizeBDiagCursor) 270 self.corners[2].setCursor(qt.Qt.SizeBDiagCursor) 271 self.corners[3].setCursor(qt.Qt.SizeFDiagCursor) 272 273 # trim box to maximum size 274 pos[0] = max(pos[0], par.maxposn[0]) 275 pos[1] = max(pos[1], par.maxposn[1]) 276 pos[2] = min(pos[2], par.maxposn[2]) 277 pos[3] = min(pos[3], par.maxposn[3]) 278 279 # move corners 280 for corner, (xindex, yindex) in czip(self.corners, 281 self.mapcornertoposn): 282 corner.setScaledPos(pos[xindex], pos[yindex]) 283 284 # move lines 285 w, h = pos[2]-pos[0], pos[3]-pos[1] 286 self.lines[0].setScaledLinePos(pos[0], pos[1], pos[0]+w, pos[1]) 287 self.lines[1].setScaledLinePos(pos[2], pos[1], pos[2], pos[1]+h) 288 self.lines[2].setScaledLinePos(pos[2], pos[3], pos[2]-w, pos[3]) 289 self.lines[3].setScaledLinePos(pos[0], pos[3], pos[0], pos[3]-h) 290 291 def updateFromLine(self, line, thispos): 292 """Edge line of box was moved - update bounding box.""" 293 294 par = self.params 295 # need old coordinate to work out how far line has moved 296 try: 297 li = self.lines.index(line) 298 except ValueError: 299 return 300 ox = par.posn[ (0, 2, 2, 0)[li] ] 301 oy = par.posn[ (1, 1, 3, 3)[li] ] 302 303 # add on deltas to box coordinates 304 dx, dy = thispos.x()-ox, thispos.y()-oy 305 306 # make sure box can't be moved outside the allowed region 307 if dx > 0: 308 dx = min(dx, par.maxposn[2]-par.posn[2]) 309 else: 310 dx = -min(abs(dx), abs(par.maxposn[0]-par.posn[0])) 311 if dy > 0: 312 dy = min(dy, par.maxposn[3]-par.posn[3]) 313 else: 314 dy = -min(abs(dy), abs(par.maxposn[1]-par.posn[1])) 315 316 # move the box 317 par.posn[0] += dx 318 par.posn[1] += dy 319 par.posn[2] += dx 320 par.posn[3] += dy 321 322 # update corner coords and other line coordinates 323 self.updateCornerPosns() 324 325 def updateFromCorner(self, corner, event): 326 """Move corner of box to new position.""" 327 try: 328 index = self.corners.index(corner) 329 except ValueError: 330 return 331 332 pos = self.params.posn 333 pos[ self.mapcornertoposn[index][0] ] = corner.scaledX() 334 pos[ self.mapcornertoposn[index][1] ] = corner.scaledY() 335 336 # this is needed if the corners move past each other 337 if pos[0] > pos[2]: 338 # swap x 339 pos[0], pos[2] = pos[2], pos[0] 340 self.corners[0], self.corners[1] = self.corners[1], self.corners[0] 341 self.corners[2], self.corners[3] = self.corners[3], self.corners[2] 342 if pos[1] > pos[3]: 343 # swap y 344 pos[1], pos[3] = pos[3], pos[1] 345 self.corners[0], self.corners[2] = self.corners[2], self.corners[0] 346 self.corners[1], self.corners[3] = self.corners[3], self.corners[1] 347 348 self.updateCornerPosns() 349 350 def boundingRect(self): 351 return qt.QRectF(0, 0, 0, 0) 352 353 def paint(self, painter, option, widget): 354 pass 355 356 def updateWidget(self): 357 """Update widget margins.""" 358 self.params.widget.updateControlItem(self.params) 359 360############################################################################## 361 362class ControlResizableBox(object): 363 """Control a resizable box. 364 Item resizes centred around a position 365 """ 366 367 def __init__(self, widget, phelper, posn, dims, angle, allowrotate=False): 368 """Initialise with widget and boxbounds shape. 369 Rotation is allowed if allowrotate is set 370 """ 371 self.widget = widget 372 self.posn = posn 373 self.dims = dims 374 self.angle = angle 375 self.allowrotate = allowrotate 376 self.cgscale = phelper.cgscale 377 378 def createGraphicsItem(self, parent): 379 return _GraphResizableBox(parent, self) 380 381class _GraphResizableBox(qt.QGraphicsItem): 382 """Control a resizable box. 383 Item resizes centred around a position 384 """ 385 386 def __init__(self, parent, params): 387 """Initialise with widget and boxbounds shape. 388 Rotation is allowed if allowrotate is set 389 """ 390 391 qt.QGraphicsItem.__init__(self, parent) 392 self.params = params 393 394 # create child graphicsitem for each corner 395 self.corners = [_ShapeCorner(self, params) for i in crange(4)] 396 self.corners[0].setCursor(qt.Qt.SizeFDiagCursor) 397 self.corners[1].setCursor(qt.Qt.SizeBDiagCursor) 398 self.corners[2].setCursor(qt.Qt.SizeBDiagCursor) 399 self.corners[3].setCursor(qt.Qt.SizeFDiagCursor) 400 for c in self.corners: 401 c.setToolTip(_('Hold shift to resize symmetrically')) 402 403 # lines connecting corners 404 self.lines = [ 405 _EdgeLine(self, params, ismovable=True) for i in crange(4)] 406 407 # whether box is allowed to be rotated 408 self.rotator = None 409 if params.allowrotate: 410 self.rotator = _ShapeCorner(self, params, rotator=True) 411 self.rotator.setCursor(qt.Qt.CrossCursor) 412 413 self.updateCorners() 414 415 def updateFromCorner(self, corner, event): 416 """Take position and update corners.""" 417 418 par = self.params 419 420 x = corner.scaledX()-par.posn[0] 421 y = corner.scaledY()-par.posn[1] 422 423 if corner in self.corners: 424 # rotate position back 425 angle = -par.angle/180.*math.pi 426 s, c = math.sin(angle), math.cos(angle) 427 tx = x*c-y*s 428 ty = x*s+y*c 429 430 if event.modifiers() & qt.Qt.ShiftModifier: 431 # expand around centre 432 par.dims[0] = abs(tx*2) 433 par.dims[1] = abs(ty*2) 434 else: 435 # moved distances of corner point 436 mdx = par.dims[0]*0.5 - abs(tx) 437 mdy = par.dims[1]*0.5 - abs(ty) 438 439 # The direction to move the centre depends on which corner 440 # it is. This makes the other side of the box stay in the 441 # same place. 442 signx = 1 if tx<0 else -1 443 signy = 1 if ty<0 else -1 444 445 # compute how much to move box centre by 446 dx = 0.5*signx*mdx 447 dy = 0.5*signy*mdy 448 rdx = dx*c+dy*s # rotate forwards again 449 rdy = -dx*s+dy*c 450 451 par.posn[0] += rdx 452 par.posn[1] += rdy 453 par.dims[0] -= mdx 454 par.dims[1] -= mdy 455 456 elif corner is self.rotator: 457 # work out angle relative to centre of widget 458 angle = math.atan2(y, x) 459 # change to degrees from correct direction 460 par.angle = round((angle*(180/math.pi) + 90.) % 360, 2) 461 462 self.updateCorners() 463 464 def updateCorners(self): 465 """Update corners on size.""" 466 par = self.params 467 468 # update corners 469 angle = par.angle/180.*math.pi 470 s, c = math.sin(angle), math.cos(angle) 471 472 for corn, (xd, yd) in czip( 473 self.corners, ((-1, -1), (1, -1), (-1, 1), (1, 1))): 474 dx, dy = xd*par.dims[0]*0.5, yd*par.dims[1]*0.5 475 corn.setScaledPos( 476 dx*c-dy*s + par.posn[0], 477 dx*s+dy*c + par.posn[1]) 478 479 if self.rotator: 480 # set rotator position (constant distance) 481 dx, dy = 0, -par.dims[1]*0.5 482 nx = dx*c-dy*s 483 ny = dx*s+dy*c 484 self.rotator.setScaledPos(nx+par.posn[0], ny+par.posn[1]) 485 486 self.linepos = [] 487 corn = self.corners 488 for i, (ci1, ci2) in enumerate(((0, 1), (2, 0), (1, 3), (2, 3))): 489 pos1 = corn[ci1].scaledX(), corn[ci1].scaledY() 490 self.lines[i].setScaledLinePos( 491 pos1[0], pos1[1], 492 corn[ci2].scaledX(), corn[ci2].scaledY()) 493 self.linepos.append(pos1) 494 495 def updateFromLine(self, line, thispos): 496 """Edge line of box was moved - update bounding box.""" 497 498 # need old coordinate to work out how far line has moved 499 oldpos = self.linepos[self.lines.index(line)] 500 501 dx = line.scaledX() - oldpos[0] 502 dy = line.scaledY() - oldpos[1] 503 self.params.posn[0] += dx 504 self.params.posn[1] += dy 505 506 # update corner coords and other line coordinates 507 self.updateCorners() 508 509 def updateWidget(self): 510 """Tell the user the graphicsitem has been moved or resized.""" 511 self.params.widget.updateControlItem(self.params) 512 513 def boundingRect(self): 514 """Intentionally zero bounding rect.""" 515 return qt.QRectF(0, 0, 0, 0) 516 517 def paint(self, painter, option, widget): 518 """Intentionally empty painter.""" 519 520############################################################################## 521 522class ControlMovableBox(ControlMarginBox): 523 """Item for user display for controlling widget. 524 This is a dotted movable box with an optional "cross" where 525 the real position of the widget is 526 """ 527 528 def __init__(self, widget, posn, painthelper, crosspos=None): 529 ControlMarginBox.__init__(self, widget, posn, 530 [-10000, -10000, 10000, 10000], 531 painthelper, isresizable=False) 532 self.deltacrosspos = (crosspos[0] - self.posn[0], 533 crosspos[1] - self.posn[1]) 534 535 def createGraphicsItem(self, parent): 536 return _GraphMovableBox(parent, self) 537 538class _GraphMovableBox(_GraphMarginBox): 539 def __init__(self, parent, params): 540 _GraphMarginBox.__init__(self, parent, params) 541 self.cross = _ShapeCorner(self, params) 542 self.cross.setCursor(qt.Qt.SizeAllCursor) 543 self.updateCornerPosns() 544 545 def updateCornerPosns(self): 546 _GraphMarginBox.updateCornerPosns(self) 547 548 par = self.params 549 if hasattr(self, 'cross'): 550 # this fails if called before self.cross is initialised! 551 self.cross.setScaledPos( 552 par.deltacrosspos[0] + par.posn[0], 553 par.deltacrosspos[1] + par.posn[1]) 554 555 def updateFromCorner(self, corner, event): 556 if corner == self.cross: 557 # if cross moves, move whole box 558 par = self.params 559 cx, cy = self.cross.scaledX(), self.cross.scaledY() 560 dx = cx - (par.deltacrosspos[0] + par.posn[0]) 561 dy = cy - (par.deltacrosspos[1] + par.posn[1]) 562 563 par.posn[0] += dx 564 par.posn[1] += dy 565 par.posn[2] += dx 566 par.posn[3] += dy 567 self.updateCornerPosns() 568 else: 569 _GraphMarginBox.updateFromCorner(self, corner, event) 570 571############################################################################## 572 573class ControlLine(object): 574 """For controlling the position and ends of a line.""" 575 def __init__(self, widget, phelper, x1, y1, x2, y2): 576 self.widget = widget 577 self.line = x1, y1, x2, y2 578 self.cgscale = phelper.cgscale 579 580 def createGraphicsItem(self, parent): 581 return _GraphLine(parent, self) 582 583class _GraphLine(qt.QGraphicsLineItem, _ScaledShape): 584 """Represents the line as a graphics item.""" 585 def __init__(self, parent, params): 586 qt.QGraphicsLineItem.__init__(self, parent) 587 self.params = params 588 l = self.params.line 589 self.setScaledLine(l[0], l[1], l[2], l[3]) 590 self.setCursor(qt.Qt.SizeAllCursor) 591 self.setFlag(qt.QGraphicsItem.ItemIsMovable) 592 self.setPen(controlLinePen()) 593 self.setZValue(1.) 594 595 self.p0 = _ShapeCorner(self, params, rotator=True) 596 self.p0.setScaledPos(params.line[0], params.line[1]) 597 self.p0.setCursor(qt.Qt.CrossCursor) 598 self.p1 = _ShapeCorner(self, params, rotator=True) 599 self.p1.setScaledPos(params.line[2], params.line[3]) 600 self.p1.setCursor(qt.Qt.CrossCursor) 601 602 def updateFromCorner(self, corner, event): 603 """Take position and update ends of line.""" 604 c = (self.p0.scaledX(), self.p0.scaledY(), 605 self.p1.scaledX(), self.p1.scaledY()) 606 self.setScaledLine(*c) 607 608 def mouseReleaseEvent(self, event): 609 """If widget has moved, tell it.""" 610 qt.QGraphicsItem.mouseReleaseEvent(self, event) 611 self.updateWidget() 612 613 def updateWidget(self): 614 """Update caller with position and line positions.""" 615 616 x, y = self.scaledX(), self.scaledY() 617 pt1 = self.p0.scaledX()+x, self.p0.scaledY()+y 618 pt2 = self.p1.scaledX()+x, self.p1.scaledY()+y 619 620 self.params.widget.updateControlItem(self.params, pt1, pt2) 621 622############################################################################# 623 624class _AxisGraphicsLineItem(qt.QGraphicsLineItem, _ScaledShape): 625 def __init__(self, parent, params): 626 qt.QGraphicsLineItem.__init__(self, parent) 627 self.parent = parent 628 self.params = params 629 630 self.setPen(controlLinePen()) 631 self.setZValue(2.) 632 self.setFlag(qt.QGraphicsItem.ItemIsMovable) 633 634 def mouseReleaseEvent(self, event): 635 """Notify finished.""" 636 qt.QGraphicsLineItem.mouseReleaseEvent(self, event) 637 self.parent.updateWidget() 638 639 def mouseMoveEvent(self, event): 640 """Move the axis.""" 641 qt.QGraphicsLineItem.mouseMoveEvent(self, event) 642 self.parent.doLineUpdate() 643 644 645 646class ControlAxisLine(object): 647 """Controlling position of an axis.""" 648 649 def __init__(self, widget, painthelper, direction, 650 minpos, maxpos, axispos, maxposn): 651 self.widget = widget 652 self.direction = direction 653 if minpos > maxpos: 654 minpos, maxpos = maxpos, minpos 655 self.minpos = self.minzoom = self.minorig = minpos 656 self.maxpos = self.maxzoom = self.maxorig = maxpos 657 self.axisorigpos = self.axispos = axispos 658 self.maxposn = maxposn 659 self.cgscale = painthelper.cgscale 660 self.reset = None 661 662 def done_reset(self): 663 return self.reset is not None 664 665 def zoomed(self): 666 """Is this a zoom?""" 667 return self.minzoom != self.minorig or self.maxzoom != self.maxorig 668 669 def moved(self): 670 """Has axis moved?""" 671 return ( 672 self.minpos != self.minorig or self.maxpos != self.maxorig or 673 self.axisorigpos != self.axispos 674 ) 675 676 def createGraphicsItem(self, parent): 677 return _GraphAxisLine(parent, self) 678 679class _AxisRange(_ShapeCorner): 680 """A control item which allows double click for a reset.""" 681 682 def mouseDoubleClickEvent(self, event): 683 qt.QGraphicsRectItem.mouseDoubleClickEvent(self, event) 684 self.parentItem().resetRange(self) 685 686class _GraphAxisLine(qt.QGraphicsItem): 687 688 # cursors to use 689 curs = { 690 True: qt.Qt.SizeVerCursor, 691 False: qt.Qt.SizeHorCursor 692 } 693 curs_zoom = { 694 True: qt.Qt.SplitVCursor, 695 False: qt.Qt.SplitHCursor 696 } 697 698 def __init__(self, parent, params): 699 """Line is about to be shown.""" 700 qt.QGraphicsItem.__init__(self, parent) 701 self.params = params 702 self.pts = [ 703 _ShapeCorner(self, params), _ShapeCorner(self, params), 704 _AxisRange(self, params), _AxisRange(self, params) 705 ] 706 self.line = _AxisGraphicsLineItem(self, params) 707 708 # set cursors and tooltips for items 709 self.horz = (params.direction == 'horizontal') 710 for p in self.pts[0:2]: 711 p.setCursor(self.curs[not self.horz]) 712 p.setToolTip("Move axis ends") 713 for p in self.pts[2:]: 714 p.setCursor(self.curs_zoom[not self.horz]) 715 p.setToolTip("Change axis scale. Double click to reset end.") 716 self.line.setCursor( self.curs[self.horz] ) 717 self.line.setToolTip("Move axis position") 718 self.setZValue(2.) 719 720 self.updatePos() 721 722 def updatePos(self): 723 """Set ends of line and line positions from stored values.""" 724 par = self.params 725 scaling = par.cgscale 726 mxp = par.maxposn 727 728 def _clip(*args): 729 """Clip positions to bounds of box given coords.""" 730 par.minpos = max(par.minpos, mxp[args[0]]) 731 par.maxpos = min(par.maxpos, mxp[args[1]]) 732 par.axispos = max(par.axispos, mxp[args[2]]) 733 par.axispos = min(par.axispos, mxp[args[3]]) 734 735 # distance zoom boxes offset from axis 736 offset = 15/scaling 737 738 if self.horz: 739 _clip(0, 2, 1, 3) 740 741 # set positions 742 if par.zoomed(): 743 self.line.setScaledPos(par.minzoom, par.axispos) 744 self.line.setScaledLine(0, 0, par.maxzoom-par.minzoom, 0) 745 else: 746 self.line.setScaledPos(par.minpos, par.axispos) 747 self.line.setScaledLine(0, 0, par.maxpos-par.minpos, 0) 748 self.pts[0].setScaledPos(par.minpos, par.axispos) 749 self.pts[1].setScaledPos(par.maxpos, par.axispos) 750 self.pts[2].setScaledPos(par.minzoom, par.axispos-offset) 751 self.pts[3].setScaledPos(par.maxzoom, par.axispos-offset) 752 else: 753 _clip(1, 3, 0, 2) 754 755 # set positions 756 if par.zoomed(): 757 self.line.setScaledPos(par.axispos, par.minzoom) 758 self.line.setScaledLine(0, 0, 0, par.maxzoom-par.minzoom) 759 else: 760 self.line.setScaledPos(par.axispos, par.minpos) 761 self.line.setScaledLine(0, 0, 0, par.maxpos-par.minpos) 762 self.pts[0].setScaledPos(par.axispos, par.minpos) 763 self.pts[1].setScaledPos(par.axispos, par.maxpos) 764 self.pts[2].setScaledPos(par.axispos+offset, par.minzoom) 765 self.pts[3].setScaledPos(par.axispos+offset, par.maxzoom) 766 767 def updateFromCorner(self, corner, event): 768 """Ends of axis have moved, so update values.""" 769 770 par = self.params 771 pt = (corner.scaledY(), corner.scaledX())[self.horz] 772 # which end has moved? 773 if corner is self.pts[0]: 774 # horizonal or vertical axis? 775 par.minpos = pt 776 elif corner is self.pts[1]: 777 par.maxpos = pt 778 elif corner is self.pts[2]: 779 par.minzoom = pt 780 elif corner is self.pts[3]: 781 par.maxzoom = pt 782 783 # swap round end points if min > max 784 if par.minpos > par.maxpos: 785 par.minpos, par.maxpos = par.maxpos, par.minpos 786 self.pts[0], self.pts[1] = self.pts[1], self.pts[0] 787 788 self.updatePos() 789 790 def doLineUpdate(self): 791 """Line has moved, so update position.""" 792 if self.horz: 793 self.params.axispos = self.line.scaledY() 794 else: 795 self.params.axispos = self.line.scaledX() 796 self.updatePos() 797 798 def updateWidget(self): 799 """Tell widget to update.""" 800 self.params.widget.updateControlItem(self.params) 801 802 def boundingRect(self): 803 """Intentionally zero bounding rect.""" 804 return qt.QRectF(0, 0, 0, 0) 805 806 def paint(self, painter, option, widget): 807 """Intentionally empty painter.""" 808 809 def resetRange(self, corner): 810 """User wants to reset range.""" 811 812 if corner is self.pts[2]: 813 self.params.reset = 0 814 else: 815 self.params.reset = 1 816 self.updateWidget() 817