class ObjectWidget(QWidget): """The base class for all circuit object widgets. It implements a lot of basic functionality, including selecting, dragging, connection/disconnection and attribute storage. Each widget has a number of anchor points where connections can be made. Subclasses must set these up in their constructors, by changing the properties self._anchorPoints, self._connections (set it to empty lists initially) and self._maxConnections. See NodeWidget and friends below for examples of subclasses. SIGNALS: selected() - emitted when this object is selected unselected() connectionsChanged() clicked(int, QPoint) - emitted when the object is clicked, with distance from nearest anchor and which anchor it is. moved(QPoint) - emitted when the object moves. flipped() - emitted when the object flips orientation """ def __init__(self, parent=None): QWidget.__init__(self, parent) self._orientation = 'h' self._isSelected = False self._isDragging = False self._anchorPoints = [(0,0)] self._connections = {self._anchorPoints[0]: []} self._maxConnections = {self._anchorPoints[0]: 0} self._attributes = {'nickname': ''} # attribute model self._attrmodel = ObjectAttributeModel(self) # slot connections QObject.connect(self, SIGNAL("selected()"), self, SLOT("update()")) QObject.connect(self, SIGNAL("unselected()"), self, SLOT("update()")) def __getitem__(self, attr): """Get an attribute of the object.""" return self._attributes[attr] def __setitem__(self, attr, value): """Set an attribute of the object.""" self._attributes[attr] = value def attributes(self): """Returns the list of available attributes. attributes() -> list<string> """ return self._attributes.keys() def anchorPoints(self): """Returns the list of available anchorpoints. anchorPoints() -> list<tup> """ return self._anchorPoints def connections(self, ancPoint=None): """If given an anchor point, this functionr returns a list of connections associated with that anchor point. Otherwise, it will return all connections associated with this object. connections are of the form: list<tuple<object, anchor>, tuple<object,anchor>> connections([tup]) -> list<connections> """ if ancPoint: return [[(self, ancPoint), (other, other.anchorOf(self))] for other in self._connections[ancPoint]] else: cons = [] for ap in self._anchorPoints: cons = cons + self.connections(ap) return cons def anchorOf(self, other): """Gets the anchor point that connects this object to other. anchorOf(object) -> QPoint """ for x in self._connections: if other in self._connections[x]: return x return None def canConnect(self, ancPoint): """Is there space for another connection on the given anchor point? canConnect(QPoint) -> bool """ return (len(self._connections[ancPoint]) < self._maxConnections[ancPoint] or self._maxConnections[ancPoint] == -1) def connectTo(self, ancPoint, object): """Connects this object to another via the given anchor point, if possible, in a one-way fashion. connectTo(QPoint, object) -> void """ if self.canConnect(ancPoint): self._connections[ancPoint].append(object) self.emit(SIGNAL("connectionsChanged()")) def connect(self, ancPoint, object, otherAncPoint): """Connects this object to another via the two given anchors, if possible, in a two-way fashion. connect(QPoint, object ,QPoint) -> void """ if self.canConnect(ancPoint) and object.canConnect(otherAncPoint): self.connectTo(ancPoint, object) object.connectTo(otherAncPoint, self) def disconnectFrom(self, ancPoint, object): """Destroys one end of a connection towards object via anchor point.""" if object in self._connections[ancPoint]: self._connections[ancPoint].remove(object) self.emit(SIGNAL("connectionsChanged()")) def disconnect(self, ancPoint, object, otherAncPoint): """Destroys an entire connection towards object's otherAncPoint via this object's ancPoint.""" self.disconnectFrom(ancPoint, object) object.disconnectFrom(otherAncPoint, self) def mouseDoubleClickEvent(self, event): """Handles a mouse double click by opening an attribute dialog""" self._attrdlg = AttributeDialog(self._attrmodel) self._attrdlg.show() def mousePressEvent(self, event): """Handles a mouse press event on the object widget. Prepares to start the drag-movement process, and attempts to select the widget.""" self._isDragging = True self._dragLastPos = QPoint(event.globalX(), event.globalY()) self._dragStartPos = QPoint(self.x(), self.y()) # find nearest anchor point to where the user clicked anchorPoints = [QPoint(x,y) for x,y in self._anchorPoints] dragPoint = QPoint(event.x(), event.y()) leastDist = (dragPoint - anchorPoints[0]).manhattanLength() leastPoint = anchorPoints[0] for p in anchorPoints: dist = (dragPoint - p).manhattanLength() if dist < leastDist: leastDist = dist leastPoint = p self._dragClosestAnchor = leastPoint # emit the Qt signal self.emit(SIGNAL("clicked(int,QPoint)"), leastDist, leastPoint) #self.parent().selectMe(self) def mouseMoveEvent(self, event): """Handles mouse movement on the widget. If we're in drag mode, ie a button is held down, the widget will move by snapped intervals.""" if self._isDragging: curPos = QPoint(self.x(), self.y()) newPos = self._dragStartPos + (event.globalPos() - self._dragLastPos) snapPos = self.parent().snap(newPos) self.move(snapPos) self.emit(SIGNAL("moved(QPoint)"), snapPos) #self.lastPos = event.globalPos() def mouseReleaseEvent(self, event): self._isDragging = False def flip(self): """Flips the object's orientation over.""" self._orientation = {'h': 'v', 'v': 'h'}[self._orientation] self.update() self.emit(SIGNAL("flipped()")) def orientation(self): """Returns the object's present orientation. orientation() -> char (one of 'h' or 'v') """ return self._orientation def selected(self): """Returns True if the object is selected, otherwise False.""" return self._isSelected def select(self): """Sets this object as selected.""" self._isSelected = True self.emit(SIGNAL("selected()")) def unselect(self): """Sets this object as unselected.""" self._isSelected = False self.emit(SIGNAL("unselected()")) def drawSelectedOutline(self, painter): """A convenience function for subclasses to paint a red box around the widget when it is selected. drawSelectedOutline(QPainter) -> void """ if self._isSelected: painter.setPen(Qt.red) painter.drawRect(QRect(0,0,self.width(), self.height()))
def mouseDoubleClickEvent(self, event): """Handles a mouse double click by opening an attribute dialog""" self._attrdlg = AttributeDialog(self._attrmodel) self._attrdlg.show()