Ignore:
Timestamp:
10/29/12 15:08:52 (18 months ago)
Author:
Ales Erjavec <ales.erjavec@…>
Branch:
default
Message:

Refactored GraphicsPathObject and ControlPoint/Rect/Line into two new modules.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • Orange/OrangeCanvas/canvas/items/annotationitem.py

    r11151 r11160  
    33 
    44from PyQt4.QtGui import ( 
    5     QGraphicsItem, QGraphicsObject, QGraphicsPathItem, QGraphicsWidget, 
     5    QGraphicsItem, QGraphicsPathItem, QGraphicsWidget, 
    66    QGraphicsTextItem, QPainterPath, QPainterPathStroker, 
    7     QPen, QBrush, QPolygonF 
     7    QPolygonF 
    88) 
    99 
    1010from PyQt4.QtCore import ( 
    11     Qt, QPointF, QSizeF, QRectF, QLineF, QMargins, QEvent, QVariant 
     11    Qt, QSizeF, QRectF, QLineF, QEvent 
    1212) 
    1313 
    14 from PyQt4.QtCore import pyqtSignal as Signal, pyqtProperty as Property 
     14from PyQt4.QtCore import pyqtSignal as Signal 
    1515 
    1616log = logging.getLogger(__name__) 
    1717 
    18  
    19 class GraphicsPathObject(QGraphicsObject): 
    20     """A QGraphicsObject subclass implementing an interface similar to 
    21     QGraphicsPathItem. 
    22  
    23     """ 
    24     def __init__(self, parent=None, **kwargs): 
    25         QGraphicsObject.__init__(self, parent, **kwargs) 
    26  
    27         self.__boundingRect = None 
    28         self.__path = QPainterPath() 
    29         self.__brush = QBrush(Qt.NoBrush) 
    30         self.__pen = QPen() 
    31  
    32     def setPath(self, path): 
    33         """Set the path shape for the object point. 
    34         """ 
    35         if not isinstance(path, QPainterPath): 
    36             raise TypeError("%r, 'QPainterPath' expected" % type(path)) 
    37  
    38         if self.__path != path: 
    39             self.prepareGeometryChange() 
    40             self.__path = path 
    41             self.__boundingRect = None 
    42             self.update() 
    43  
    44     def path(self): 
    45         return self.__path 
    46  
    47     def setBrush(self, brush): 
    48         if not isinstance(brush, QBrush): 
    49             brush = QBrush(brush) 
    50  
    51         if self.__brush != brush: 
    52             self.__brush = brush 
    53             self.update() 
    54  
    55     def brush(self): 
    56         return self.__brush 
    57  
    58     def setPen(self, pen): 
    59         if not isinstance(pen, QPen): 
    60             pen = QPen(pen) 
    61  
    62         if self.__pen != pen: 
    63             self.prepareGeometryChange() 
    64             self.__pen = pen 
    65             self.__boundingRect = None 
    66             self.update() 
    67  
    68     def pen(self): 
    69         return self.__pen 
    70  
    71     def paint(self, painter, option, widget=None): 
    72         if self.__path.isEmpty(): 
    73             return 
    74  
    75         painter.save() 
    76         painter.setPen(self.pen()) 
    77         painter.setBrush(self.brush()) 
    78         painter.drawPath(self.path()) 
    79         painter.restore() 
    80  
    81     def boundingRect(self): 
    82         if self.__boundingRect is None: 
    83             br = self.__path.controlPointRect() 
    84             pen_w = self.__pen.widthF() 
    85             self.__boundingRect = br.adjusted(-pen_w, -pen_w, pen_w, pen_w) 
    86  
    87         return self.__boundingRect 
    88  
    89     def shape(self): 
    90         return shapeForPath(self.__path, self.__pen) 
    91  
    92  
    93 def shapeForPath(path, pen): 
    94     """Create a QPainterPath shape from the path drawn with pen. 
    95     """ 
    96     stroker = QPainterPathStroker() 
    97     stroker.setWidth(max(pen.width(), 1)) 
    98     shape = stroker.createStroke(path) 
    99     shape.addPath(path) 
    100     return shape 
    101  
    102  
    103 class ControlPoint(GraphicsPathObject): 
    104     """A control point for annotations in the canvas. 
    105     """ 
    106     Free = 0 
    107  
    108     Left, Top, Right, Bottom, Center = 1, 2, 4, 8, 16 
    109  
    110     TopLeft = Top | Left 
    111     TopRight = Top | Right 
    112     BottomRight = Bottom | Right 
    113     BottomLeft = Bottom | Left 
    114  
    115     posChanged = Signal(QPointF) 
    116  
    117     def __init__(self, parent=None, anchor=0, **kwargs): 
    118         GraphicsPathObject.__init__(self, parent, **kwargs) 
    119         self.setFlag(QGraphicsItem.ItemIsMovable) 
    120         self.setAcceptedMouseButtons(Qt.LeftButton) 
    121  
    122         self.__posEmitted = self.pos()  # Last emitted position 
    123         self.xChanged.connect(self.__emitPosChanged) 
    124         self.yChanged.connect(self.__emitPosChanged) 
    125  
    126         self.__constraint = 0 
    127         self.__constraintFunc = None 
    128         self.__anchor = 0 
    129         self.setAnchor(anchor) 
    130  
    131         path = QPainterPath() 
    132         path.addEllipse(QRectF(-4, -4, 8, 8)) 
    133         self.setPath(path) 
    134  
    135         self.setBrush(QBrush(Qt.lightGray, Qt.SolidPattern)) 
    136  
    137     def setAnchor(self, anchor): 
    138         """Set anchor position 
    139         """ 
    140         self.__anchor = anchor 
    141  
    142     def anchor(self): 
    143         return self.__anchor 
    144  
    145     def mousePressEvent(self, event): 
    146         if event.button() == Qt.LeftButton: 
    147             # Enable ItemPositionChange (and pos constraint) only when 
    148             # this is the mouse grabber item 
    149             self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) 
    150         return GraphicsPathObject.mousePressEvent(self, event) 
    151  
    152     def mouseReleaseEvent(self, event): 
    153         if event.button() == Qt.LeftButton: 
    154             self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, False) 
    155         return GraphicsPathObject.mouseReleaseEvent(self, event) 
    156  
    157     def itemChange(self, change, value): 
    158  
    159         if change == QGraphicsItem.ItemPositionChange: 
    160             pos = value.toPointF() 
    161             newpos = self.constrain(pos) 
    162             return QVariant(newpos) 
    163  
    164         return GraphicsPathObject.itemChange(self, change, value) 
    165  
    166     def __emitPosChanged(self, *args): 
    167         # Emit the posChanged signal if the current pos is different 
    168         # from the last emitted pos 
    169         pos = self.pos() 
    170         if pos != self.__posEmitted: 
    171             self.posChanged.emit(pos) 
    172             self.__posEmitted = pos 
    173  
    174     def hasConstraint(self): 
    175         return self.__constraintFunc is not None or self.__constraint != 0 
    176  
    177     def setConstraint(self, constraint): 
    178         """Set the constraint for the point (Qt.Vertical Qt.Horizontal or 0) 
    179  
    180         .. note:: Clears the constraintFunc if it was previously set 
    181  
    182         """ 
    183         if self.__constraint != constraint: 
    184             self.__constraint = constraint 
    185  
    186         self.__constraintFunc = None 
    187  
    188     def constrain(self, pos): 
    189         """Constrain the pos. 
    190         """ 
    191         if self.__constraintFunc: 
    192             return self.__constraintFunc(pos) 
    193         elif self.__constraint == Qt.Vertical: 
    194             return QPointF(self.pos().x(), pos.y()) 
    195         elif self.__constraint == Qt.Horizontal: 
    196             return QPointF(pos.x(), self.pos().y()) 
    197         else: 
    198             return pos 
    199  
    200     def setConstraintFunc(self, func): 
    201         if self.__constraintFunc != func: 
    202             self.__constraintFunc = func 
    203  
    204  
    205 class ControlPointRect(QGraphicsObject): 
    206     Free = 0 
    207     KeepAspectRatio = 1 
    208     KeepCenter = 2 
    209  
    210     rectChanged = Signal(QRectF) 
    211     rectEdited = Signal(QRectF) 
    212  
    213     def __init__(self, parent=None, rect=None, constraints=0, **kwargs): 
    214         QGraphicsObject.__init__(self, parent, **kwargs) 
    215         self.setFlag(QGraphicsItem.ItemHasNoContents) 
    216  
    217         self.__rect = rect if rect is not None else QRectF() 
    218         self.__margins = QMargins() 
    219         points = \ 
    220             [ControlPoint(self, ControlPoint.Left), 
    221              ControlPoint(self, ControlPoint.Top), 
    222              ControlPoint(self, ControlPoint.TopLeft), 
    223              ControlPoint(self, ControlPoint.Right), 
    224              ControlPoint(self, ControlPoint.TopRight), 
    225              ControlPoint(self, ControlPoint.Bottom), 
    226              ControlPoint(self, ControlPoint.BottomLeft), 
    227              ControlPoint(self, ControlPoint.BottomRight) 
    228              ] 
    229         assert(points == sorted(points, key=lambda p: p.anchor())) 
    230  
    231         self.__points = dict((p.anchor(), p) for p in points) 
    232  
    233         if self.scene(): 
    234             self.__installFilter() 
    235  
    236         self.controlPoint(ControlPoint.Top).setConstraint(Qt.Vertical) 
    237         self.controlPoint(ControlPoint.Bottom).setConstraint(Qt.Vertical) 
    238         self.controlPoint(ControlPoint.Left).setConstraint(Qt.Horizontal) 
    239         self.controlPoint(ControlPoint.Right).setConstraint(Qt.Horizontal) 
    240  
    241         self.__constraints = constraints 
    242         self.__activeControl = None 
    243  
    244         self.__pointsLayout() 
    245  
    246     def controlPoint(self, anchor): 
    247         """Return the anchor point at anchor position if not set. 
    248         """ 
    249         return self.__points.get(anchor) 
    250  
    251     def setRect(self, rect): 
    252         if self.__rect != rect: 
    253             self.__rect = rect 
    254             self.__pointsLayout() 
    255             self.prepareGeometryChange() 
    256             self.rectChanged.emit(rect) 
    257  
    258     def rect(self): 
    259         """Return the control rect 
    260         """ 
    261         # Return the rect normalized. During the control point move the 
    262         # rect can change to an invalid size, but the layout must still 
    263         # know to which point does an unnormalized rect side belong. 
    264         return self.__rect.normalized() 
    265  
    266     rect_ = Property(QRectF, fget=rect, fset=setRect, user=True) 
    267  
    268     def setControlMargins(self, *margins): 
    269         """Set the controls points on the margins around `rect` 
    270         """ 
    271         if len(margins) > 1: 
    272             margins = QMargins(*margins) 
    273         else: 
    274             margins = margins[0] 
    275             if isinstance(margins, int): 
    276                 margins = QMargins(margins, margins, margins, margins) 
    277  
    278         if self.__margins != margins: 
    279             self.__margins = margins 
    280             self.__pointsLayout() 
    281  
    282     def controlMargins(self): 
    283         return self.__margins 
    284  
    285     def setConstraints(self, constraints): 
    286         pass 
    287  
    288     def itemChange(self, change, value): 
    289         if change == QGraphicsItem.ItemSceneHasChanged and self.scene(): 
    290             self.__installFilter() 
    291  
    292         return QGraphicsObject.itemChange(self, change, value) 
    293  
    294     def sceneEventFilter(self, obj, event): 
    295         try: 
    296             if isinstance(obj, ControlPoint): 
    297                 etype = event.type() 
    298                 if etype == QEvent.GraphicsSceneMousePress and \ 
    299                         event.button() == Qt.LeftButton: 
    300                     self.__setActiveControl(obj) 
    301  
    302                 elif etype == QEvent.GraphicsSceneMouseRelease and \ 
    303                         event.button() == Qt.LeftButton: 
    304                     self.__setActiveControl(None) 
    305  
    306         except Exception: 
    307             log.error("Error in 'ControlPointRect.sceneEventFilter'", 
    308                       exc_info=True) 
    309  
    310         return QGraphicsObject.sceneEventFilter(self, obj, event) 
    311  
    312     def __installFilter(self): 
    313         # Install filters on the control points. 
    314         try: 
    315             for p in self.__points.values(): 
    316                 p.installSceneEventFilter(self) 
    317         except Exception: 
    318             log.error("Error in ControlPointRect.__installFilter", 
    319                       exc_info=True) 
    320  
    321     def __pointsLayout(self): 
    322         """Layout the control points 
    323         """ 
    324         rect = self.__rect 
    325         margins = self.__margins 
    326         rect = rect.adjusted(-margins.left(), -margins.top(), 
    327                              margins.right(), margins.bottom()) 
    328         center = rect.center() 
    329         cx, cy = center.x(), center.y() 
    330         left, top, right, bottom = \ 
    331                 rect.left(), rect.top(), rect.right(), rect.bottom() 
    332  
    333         self.controlPoint(ControlPoint.Left).setPos(left, cy) 
    334         self.controlPoint(ControlPoint.Right).setPos(right, cy) 
    335         self.controlPoint(ControlPoint.Top).setPos(cx, top) 
    336         self.controlPoint(ControlPoint.Bottom).setPos(cx, bottom) 
    337  
    338         self.controlPoint(ControlPoint.TopLeft).setPos(left, top) 
    339         self.controlPoint(ControlPoint.TopRight).setPos(right, top) 
    340         self.controlPoint(ControlPoint.BottomLeft).setPos(left, bottom) 
    341         self.controlPoint(ControlPoint.BottomRight).setPos(right, bottom) 
    342  
    343     def __setActiveControl(self, control): 
    344         if self.__activeControl != control: 
    345             if self.__activeControl is not None: 
    346                 self.__activeControl.posChanged.disconnect( 
    347                     self.__activeControlMoved 
    348                 ) 
    349  
    350             self.__activeControl = control 
    351  
    352             if control is not None: 
    353                 control.posChanged.connect(self.__activeControlMoved) 
    354  
    355     def __activeControlMoved(self, pos): 
    356         # The active control point has moved, update the control 
    357         # rectangle 
    358         control = self.__activeControl 
    359         pos = control.pos() 
    360         rect = QRectF(self.__rect) 
    361         margins = self.__margins 
    362  
    363         # TODO: keyboard modifiers and constraints. 
    364  
    365         anchor = control.anchor() 
    366         if anchor & ControlPoint.Top: 
    367             rect.setTop(pos.y() + margins.top()) 
    368         elif anchor & ControlPoint.Bottom: 
    369             rect.setBottom(pos.y() - margins.bottom()) 
    370  
    371         if anchor & ControlPoint.Left: 
    372             rect.setLeft(pos.x() + margins.left()) 
    373         elif anchor & ControlPoint.Right: 
    374             rect.setRight(pos.x() - margins.right()) 
    375  
    376         changed = self.__rect != rect 
    377  
    378         self.blockSignals(True) 
    379         self.setRect(rect) 
    380         self.blockSignals(False) 
    381  
    382         if changed: 
    383             self.rectEdited.emit(rect) 
    384  
    385     def boundingRect(self): 
    386         return QRectF() 
    387  
    388  
    389 class ControlPointLine(QGraphicsObject): 
    390  
    391     lineChanged = Signal(QLineF) 
    392     lineEdited = Signal(QLineF) 
    393  
    394     def __init__(self, parent=None, **kwargs): 
    395         QGraphicsObject.__init__(self, parent, **kwargs) 
    396         self.setFlag(QGraphicsItem.ItemHasNoContents) 
    397  
    398         self.__line = QLineF() 
    399         self.__points = \ 
    400             [ControlPoint(self, ControlPoint.TopLeft),  # TopLeft is line start 
    401              ControlPoint(self, ControlPoint.BottomRight)  # line end 
    402              ] 
    403  
    404         self.__activeControl = None 
    405  
    406         if self.scene(): 
    407             self.__installFilter() 
    408  
    409     def setLine(self, line): 
    410         if not isinstance(line, QLineF): 
    411             raise TypeError() 
    412  
    413         if line != self.__line: 
    414             self.__line = line 
    415             self.__pointsLayout() 
    416             self.lineChanged.emit(line) 
    417  
    418     def line(self): 
    419         return self.__line 
    420  
    421     def __installFilter(self): 
    422         for p in self.__points: 
    423             p.installSceneEventFilter(self) 
    424  
    425     def itemChange(self, change, value): 
    426         if change == QGraphicsItem.ItemSceneHasChanged: 
    427             if self.scene(): 
    428                 self.__installFilter() 
    429         return QGraphicsObject.itemChange(self, change, value) 
    430  
    431     def sceneEventFilter(self, obj, event): 
    432         try: 
    433             if isinstance(obj, ControlPoint): 
    434                 etype = event.type() 
    435                 if etype == QEvent.GraphicsSceneMousePress: 
    436                     self.__setActiveControl(obj) 
    437                 elif etype == QEvent.GraphicsSceneMouseRelease: 
    438                     self.__setActiveControl(None) 
    439  
    440             return QGraphicsObject.sceneEventFilter(self, obj, event) 
    441         except Exception: 
    442             log.error("", exc_info=True) 
    443  
    444     def __pointsLayout(self): 
    445         self.__points[0].setPos(self.__line.p1()) 
    446         self.__points[1].setPos(self.__line.p2()) 
    447  
    448     def __setActiveControl(self, control): 
    449         if self.__activeControl != control: 
    450             if self.__activeControl is not None: 
    451                 self.__activeControl.posChanged.disconnect( 
    452                     self.__activeControlMoved 
    453                 ) 
    454  
    455             self.__activeControl = control 
    456  
    457             if control is not None: 
    458                 control.posChanged.connect(self.__activeControlMoved) 
    459  
    460     def __activeControlMoved(self, pos): 
    461         line = QLineF(self.__line) 
    462         control = self.__activeControl 
    463         if control.anchor() == ControlPoint.TopLeft: 
    464             line.setP1(pos) 
    465         elif control.anchor() == ControlPoint.BottomRight: 
    466             line.setP2(pos) 
    467  
    468         if self.__line != line: 
    469             self.blockSignals(True) 
    470             self.setLine(line) 
    471             self.blockSignals(False) 
    472             self.lineEdited.emit(line) 
    473  
    474     def boundingRect(self): 
    475         return QRectF() 
     18from .graphicspathobject import GraphicsPathObject 
     19from .controlpoints import ControlPointLine, ControlPointRect 
    47620 
    47721 
Note: See TracChangeset for help on using the changeset viewer.