5 days. I have been trying to center a freakin QGraphicsItem hierarchy. They made this hard.
-
I have tried:
- setTransformationOriginPoint
- Translating the child item by boundingRect().center()
- setTransform()
- A million other things (5 days!!)
I have a child QGraphicsObject (a control point) part of a GraphicsSet item, which is a child of a Line item.
The best I've gotten it is this: Every time I press an item with the mouse, it's lower/upper/left/right corner (depends on control point positions) becomes center or in other words that corner shifts to the mouse's position. Which is what i want, except I want the mouse cursor to end up on the item's boundingRect's center, not a corner!
Don't give me this, moveBy, setPos, setTransform crap. It does not work!
from PyQt5.QtWidgets import (QApplication, QMainWindow, QGraphicsScene, QGraphicsView, QGraphicsObject, QGraphicsSceneEvent) from PyQt5.QtGui import QBrush, QPen, QColor, QPainterPathStroker from PyQt5.QtCore import QPointF, Qt, QRectF, pyqtSignal, QEvent import sys from PyQt5.QtCore import QObject, pyqtBoundSignal, QLineF import inspect class PickleableSignal(QObject): """ Wrapper class for a single signal, returned by __getattribute__ method of SignalHelper. Supports, lambda, delegates, disconnection by string or by another reference / copy of the lambda or delegate. Supports pickling of everything using the dill library. For pickling to work, sender must be pickleable on its own in the usual way. """ Handle, Slot = range(2) def __init__(self, sender=None, signal=None, init=None): assert(isinstance(sender, SignalPickler)) # Requires inheritance to work super().__init__() if init in [None, True]: self._sender = sender self._signal = signal self._slotInfo = {} # Keyed by string representing lambda or delegate name def __setstate__(self, data): """ Standard pickle method design. Includes slot info and connection state. """ self.__init__(init=False) self._sender = data['sender'] self._signal = data['signal'] self._slotInfo = {} # PyQt5 is not pickle aware so we must reconnect the signals after unpickling and/or copying. for slot_id, (handle, slot) in data['slot info'].items(): self.connect(slot) def __getstate__(self): """ Standard pickle method design. Includes slot info. """ return { 'sender' : self._sender, 'signal' : self._signal, 'slot info' : self._slotInfo, } def __deepcopy__(self, memo): """ Standard deepcopy method design. Includes slot info and connection state. """ from copy import deepcopy copy = type(self)(init=False) memo[id(self)] = copy copy._sender = deepcopy(self._sender, memo) copy._signal = self._signal # OK, since we treat _signal as an immutable string. copy._slotInfo = {} # Of course we need to create this copies own slot connections. for slot_id, (handle, slot) in self._slotInfo.items(): copy.connect(slot) return copy def connect(self, slot): """ Allows a given slot signature to be connected once and only once. Returns the slot id to use for disconnecting; alternatively pass in the exact same lambda or delegate and that should do the trick. Returns None if that same lambda or delegate has already been added. """ if not self._sender.isSignalRegistered(self): self._sender.registerSignal(self) slot_id = self.slotID(slot) if slot_id not in self._slotInfo: self._slotInfo[slot_id] = (self._sender.pyqtSignal(self._signal).connect(slot), slot) return slot_id return None def emit(self, *args): self._sender.pyqtSignal(self._signal).emit(*args) def disconnect(self, slot): """ Pass in a string for the delegate / lambda or the delegate lambda itself and we'll generate the canonical id ourself. """ if not isinstance(slot, str): slot = self.slotID(slot) handle = self._slotInfo[slot][self.Handle] self._sender.pyqtSignal(self._signal).disconnect(handle) del self._slotInfo[slot] # Don't forget to delete its entry def signalName(self): return self._signal def senderObj(self): return self._sender def slot(self, slot_id): return self._slotInfo[slot_id][self.Slot] def slotID(self, slot): """ This is how we generated an identifier string given a slot. """ return inspect.getsource(slot).strip() def __contains__(self, slot): if not isinstance(slot, str): slot = self.slotID(slot) return slot in self._slotInfo class SignalPickler: """ A mixin class that helps connect / disconnect slots to member signals and pickle them (using dill). It should go "GraphicsBase(QGraphicsObject, SignalPickler)" as far as base class ordering goes. I've never found the other way around to work without compiler error or unexplicable runtime crash. The same goes for other classes. Qt's raw class first, then your mixin or QObject-derived class. """ def __init__(self): self._signals = {} def __setstate__(self, data): """ Subclasses must call this class's __setstate__ in order to pickle signals. """ self.__init__() self._signals = data['signals'] def __getstate__(self): """ Subclasses must also call this class's __getstate__ in order to pickle signals. """ return { 'signals' : self._signals } def __deepcopy__(self, memo): """ Want your objects / widgets copy & pasteable? Then also call this from a subclass's __deepcopy__ method. """ copy = type(self)() memo[id(self)] = copy copy._signals = deepcopy(self._signals, memo) return copy def __getattribute__(self, attr): """ Internally, this is what SignalPickler does. Every time you ask for a signal from the sender object, for example: `sender.positionChanged.connect(foo)`, instead of returning sender.positionChanged it will return a SignalPickler object wrapping it. """ res = super().__getattribute__(attr) if isinstance(res, pyqtBoundSignal): return PickleableSignal(sender=self, signal=attr) return res def registerSignal(self, signal): """ For internal use only, usually. """ self._signals[signal.signalName()] = signal def isSignalRegistered(self, signal): """ For internal use only, usually. """ return signal.signalName() in self._signals def pyqtSignal(self, name): return super().__getattribute__(name) class Graphics(QGraphicsObject, SignalPickler): anythingHasChanged = pyqtSignal(str) changeToAnything = pyqtSignal(str) positionChange = pyqtSignal(QPointF) positionHasChanged = pyqtSignal(QPointF) parentChange = pyqtSignal(QObject) parentHasChanged = pyqtSignal(QObject) childAdded = pyqtSignal(QObject) childRemoved = pyqtSignal(QObject) brushChange = pyqtSignal(QBrush) brushHasChanged = pyqtSignal(QBrush) penChange = pyqtSignal(QPen) penHasChanged = pyqtSignal(QPen) def __init__(self, pen=None, brush=None, children=None, parent=None, init=None): if init in [None, True]: super().__init__() super().__init__() if pen is None: pen = QPen(QColor(255, 0, 0), 1.0) if brush is None: brush = QBrush(QColor(255, 0, 0)) self._brush = brush self._pen = pen self.setParentItem(parent) self.setFlags(self.ItemIsFocusable | self.ItemIsMovable | self.ItemIsSelectable | self.ItemSendsGeometryChanges | self.ItemSendsScenePositionChanges) self._surpressSigs = False self._prevBrush = self._brush self._prevPen = self._pen self._prevParent = parent self._prevPos = self.pos() if children is None: children = [] for child in children: if child: child.setParentItem(self) def boundingRect(self): return self.childrenBoundingRect() def itemChange(self, change, value): if change == self.ItemParentChange: self._prevParent = self.parentItem() self.emitChange(self.parentChange, 'parentItem', value) elif change == self.ItemParentHasChanged: self.emitHasChanged(self.parentHasChanged, 'parentItem', value) elif change == self.ItemPositionChange: self._prevPos = value self.emitChange(self.positionChange, 'pos', value) elif change == self.ItemPositionHasChanged: self.emitHasChanged(self.positionHasChanged, 'pos', value) elif change == self.ItemChildAddedChange: self.emitHasChanged(self.childAdded, 'childItems', value) elif change == self.ItemChildRemovedChange: self.emitHasChanged(self.childRemoved, 'childItems', value) elif change == self.ItemSceneChange: if self.scene(): self.removedFromSceneEvent() elif change == self.ItemSceneHasChanged: if self.scene(): self.addedToSceneEvent() return super().itemChange(change, value) def setBrush(self, QBrush): self.emitChange(self.brushChange, 'QBrush', QBrush) self._prevBrush = self._brush self._brush = QBrush self.update() self.emitHasChanged(self.brushHasChanged, 'QBrush', QBrush) def QBrush(self): return self._brush def setPen(self, pen): self.emitChange(self.penChange, 'pen', pen) self._prevPen = self._pen self._pen = pen self.update() self.emitHasChanged(self.penHasChanged, 'pen', pen) def pen(self): return self._pen def setSignalsSurpressed(self, en): if self._surpressSigs != en: self._surpressSigs = en for child in self.childItems(): child.setSignalsSurpressed(en) def signalsSurpressed(self): return self._surpressSigs def emitChange(self, sig, member, *args): if not self._surpressSigs: sig.emit(*args) self.changeToAnything.emit(member) def emitHasChanged(self, sig, member, *args): if not self._surpressSigs: sig.emit(*args) self.anythingHasChanged.emit(member) def paint(self, painter, option, widget): from geom_tools import paintSelectionShape if self.isSelected(): paintSelectionShape(painter, self) painter.setRenderHints(painter.HighQualityAntialiasing | painter.Antialiasing) def childPosHasChangedEvent(self, child): pass def childPosChangeEvent(self, child): pass def addedToSceneEvent(self): """ Do here init things that can only be done once added to the scene. """ parent = self.parentItem() if isinstance(parent, Graphics): self.installEventFilter(parent) for child in self.childItems(): child.addedToSceneEvent() def removedFromSceneEvent(self): """ Undo things done in the above method. """ parent = self.parentItem() if isinstance(parent, Graphics): self.removeEventFilter(parent) for child in self.childItems(): child.removedFromSceneEvent() def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.setPos(self.mapToParent(event.pos())) super().mousePressEvent(event) from collections import OrderedDict class GraphicsSet(Graphics): """ Stores graphics items naturally by id() but they're also part of the same movable group. They are also kept ordered (order of element addition). """ elementPosHasChanged = pyqtSignal(Graphics) elementPosChange = pyqtSignal(Graphics) def __init__(self, count=None, type=None, parent=None, init=None, *args, **kwargs): if init in [None, True]: super().__init__(parent=parent) self._set = OrderedDict() if count is not None and type is not None: for k in range(0, count): obj = type(*args, **kwargs) self.add(obj) def boundingRect(self): return self.childrenBoundingRect() def add(self, obj): self._set[id(obj)] = obj obj.setParentItem(self) def remove(self, obj): del self._set[id(obj)] obj.setParentItem(None) def elements(self): return self._set.values() def odict(self): return self._set def __iter__(self): return iter(self._set) def __len__(self): return len(self._set) #def paint(self, painter, option, widget): #from PyQt5.QtGui import QColor, QPen #painter.setPen(QPen(QColor(0, 0, 255), 2.0)) #painter.drawPoint(self.pos()) #super().paint(painter, option, widget) def childPosChangeEvent(self, child): self.elementPosChange.emit(child) def childPosHasChangedEvent(self, child): self.elementPosHasChanged.emit(child) class Rectangle(Graphics): rectangleChange = pyqtSignal(Graphics, QRectF) rectangleHasChanged = pyqtSignal(Graphics, QRectF) def __init__(self, x=None, y=None, w=None, h=None, pen=None, QBrush=None, children=None, parent=None, init=None): if init in [None, True]: super().__init__(pen, QBrush, children, parent) if x is None: x = 0 if y is None: y = 0 if w is None: w = 10 if h is None: h = 5 self.setPos(QPointF(x, y)) rect = QRectF(x, y, w, h) self._rect = rect.translated(-w/2, -h/2) def __setstate__(self, data): super().__setstate__(data) self.setRect(data['rect']) self.__init__(init=False) def __getstate__(self): data = super().__getstate__() data['rect'] = self.rect() return data def __deepcopy__(self, memo): copy = super().__deepcopy__(memo) copy.setRect(self.rect()) return copy def boundingRect(self): w = self.pen().widthF() / 2 return self.rect().adjusted(-w, -w, w, w) def shape(self): path = QPainterPath() w = self.pen().widthF() / 2 path.addRect(self.rect().adjusted(-w, -w, w, w)) return path def paint(self, painter, option, widget): super().paint(painter, option, widget) painter.setPen(self.pen()) painter.setBrush(self.QBrush()) painter.drawRect(self.rect()) def rect(self): return self._rect def setRect(self, rect): rect = rect.translated(-rect.width()/2, -rect.height()/2) if self._rect != rect: self.emitChange(self.rectangleChange, 'rect', rect) self._rect = rect self.update() self.emitHasChanged(self.rectangleHasChanged, 'rect', rect) from PyQt5.QtGui import QPainterPath class Ellipse(Rectangle): def __init__(self, x=None, y=None, w=None, h=None, pen=None, QBrush=None, children=None, parent=None, init=None): if init in [None, True]: super().__init__(x, y, w, h, pen, QBrush) def shape(self): path = QPainterPath() w = self.pen().widthF() / 2 path.addEllipse(self.rect().adjusted(-w, -w, w, w)) return path def paint(self, painter, option, widget): Graphics.paint(self, painter, option, widget) painter.setPen(self.pen()) painter.setBrush(self.QBrush()) painter.drawEllipse(self.rect()) class Point(Ellipse): def __init__(self, x=None, y=None, diam=None, pen=None, brush=None, children=None, parent=None, init=None): if init in [None, True]: return super().__init__(x, y, diam, diam, pen, brush, children, parent) class Line(Graphics): def __init__(self, points=None, diam=None, pen=None, point_pen=None, point_brush=None, parent=None, init=None): if points is None: points = GraphicsSet(count=2, type=Point, diam=diam, pen=point_pen, brush=point_brush) if init in [None, True]: super().__init__(pen, children=[points], parent=parent) self.controlPoints = points self.controlPoints.elementPosHasChanged.connect(self.controlPointPosChangedEvent) self.setFiltersChildEvents(True) def __setstate__(self, data): super().__setstate__(data) self._points = data['points'] self.__init__() def __getstate__(self): return { 'parent' : self.parentItem(), 'pos' : self.pos(), 'control points' : self.controlPoints, } def __deepcopy__(self, memo): copy = deepcopy(super(), memo) copy.controlPoints = deepcopy(self.controlPoints, memo) return copy def line(self): points = self.pointPos() return QLineF(points[0], points[-1]) def shape(self): path = QPainterPath() line = self.line() path.moveTo(line.p1()) path.lineTo(line.p2()) stroker = QPainterPathStroker(QPen(Qt.black, self.pen().widthF())) return stroker.createStroke(path) def paint(self, painter, option, widget): super().paint(painter, option, widget) if __debug__: painter.setPen(self.pen()) painter.drawRect(self.boundingRect()) painter.setPen(self.pen()) painter.setBrush(self.QBrush()) painter.drawLine(self.line()) def controlPointPosChangedEvent(self, point): #""" #Ensures that self.line().center() == self.pos() always. #""" #child = self.controlPoints ##self.controlPoints.setPos(self.controlPoints.pos() - self.setSignalsSurpressed(True) #parent = self.parentItem() transform = QTransform() transform.translate(-self.boundingRect().center().x(), -self.boundingRect().center().y()) self.setTransform(transform) self.setSignalsSurpressed(False) ## When a control point position has changed, we should repaint the line: self.updateScene() def pointPos(self): return [self.controlPoints.mapToParent(elem.pos()) for elem in self.controlPoints.elements()] def p1(self): return self.pointPos()[0] def p2(self): return self.pointPos()[-1] def setP1(self, p): p = self.controlPoints.mapFromParent(p) if self.p1() != p: self.controlPoints.elements()[0].setPos(p) def setP2(self, p): p = self.controlPoints.mapFromParent(p) if self.p2() != p: self.controlPoints.elements()[-1].setPos(p) #def sceneEventFilter(self, watched, event): #if watched is self.controlPoints: #if event.type() == QEvent.GraphicsSceneMouseMove: #if self.scene(): #item = self.scene().itemAt(event.scenePos(), QTransform()) #if item and item in self.controlPoints: #return True #self.setSignalsSurpressed(True) #delta = event.pos() #delta = self.mapToParent(delta) #self.setPos(delta) #self.controlPoints.setPos(-self.boundingRect().center()) #self.setSignalsSurpressed(False) #return True #return False def mousePressEvent(self, event): super().mousePressEvent(event) self.setSignalsSurpressed(True) #self.setPos(event.pos()) #self.controlPoints.setPos(-self.boundingRect().center()) rect = self.controlPoints.boundingRect() trans = QTransform() trans.translate(-self.boundingRect().center().x(), -self.boundingRect().center().y()) self.controlPoints.setTransform(trans) self.setSignalsSurpressed(False) if __name__ == '__main__': app = QApplication([]) window = QMainWindow() window.view = QGraphicsView() window.scene = QGraphicsScene() window.view.setScene(window.scene) window.show() window.setCentralWidget(window.view) item = Line() window.scene.addItem(item) sys.exit(app.exec_())
-
I have tried:
- setTransformationOriginPoint
- Translating the child item by boundingRect().center()
- setTransform()
- A million other things (5 days!!)
I have a child QGraphicsObject (a control point) part of a GraphicsSet item, which is a child of a Line item.
The best I've gotten it is this: Every time I press an item with the mouse, it's lower/upper/left/right corner (depends on control point positions) becomes center or in other words that corner shifts to the mouse's position. Which is what i want, except I want the mouse cursor to end up on the item's boundingRect's center, not a corner!
Don't give me this, moveBy, setPos, setTransform crap. It does not work!
from PyQt5.QtWidgets import (QApplication, QMainWindow, QGraphicsScene, QGraphicsView, QGraphicsObject, QGraphicsSceneEvent) from PyQt5.QtGui import QBrush, QPen, QColor, QPainterPathStroker from PyQt5.QtCore import QPointF, Qt, QRectF, pyqtSignal, QEvent import sys from PyQt5.QtCore import QObject, pyqtBoundSignal, QLineF import inspect class PickleableSignal(QObject): """ Wrapper class for a single signal, returned by __getattribute__ method of SignalHelper. Supports, lambda, delegates, disconnection by string or by another reference / copy of the lambda or delegate. Supports pickling of everything using the dill library. For pickling to work, sender must be pickleable on its own in the usual way. """ Handle, Slot = range(2) def __init__(self, sender=None, signal=None, init=None): assert(isinstance(sender, SignalPickler)) # Requires inheritance to work super().__init__() if init in [None, True]: self._sender = sender self._signal = signal self._slotInfo = {} # Keyed by string representing lambda or delegate name def __setstate__(self, data): """ Standard pickle method design. Includes slot info and connection state. """ self.__init__(init=False) self._sender = data['sender'] self._signal = data['signal'] self._slotInfo = {} # PyQt5 is not pickle aware so we must reconnect the signals after unpickling and/or copying. for slot_id, (handle, slot) in data['slot info'].items(): self.connect(slot) def __getstate__(self): """ Standard pickle method design. Includes slot info. """ return { 'sender' : self._sender, 'signal' : self._signal, 'slot info' : self._slotInfo, } def __deepcopy__(self, memo): """ Standard deepcopy method design. Includes slot info and connection state. """ from copy import deepcopy copy = type(self)(init=False) memo[id(self)] = copy copy._sender = deepcopy(self._sender, memo) copy._signal = self._signal # OK, since we treat _signal as an immutable string. copy._slotInfo = {} # Of course we need to create this copies own slot connections. for slot_id, (handle, slot) in self._slotInfo.items(): copy.connect(slot) return copy def connect(self, slot): """ Allows a given slot signature to be connected once and only once. Returns the slot id to use for disconnecting; alternatively pass in the exact same lambda or delegate and that should do the trick. Returns None if that same lambda or delegate has already been added. """ if not self._sender.isSignalRegistered(self): self._sender.registerSignal(self) slot_id = self.slotID(slot) if slot_id not in self._slotInfo: self._slotInfo[slot_id] = (self._sender.pyqtSignal(self._signal).connect(slot), slot) return slot_id return None def emit(self, *args): self._sender.pyqtSignal(self._signal).emit(*args) def disconnect(self, slot): """ Pass in a string for the delegate / lambda or the delegate lambda itself and we'll generate the canonical id ourself. """ if not isinstance(slot, str): slot = self.slotID(slot) handle = self._slotInfo[slot][self.Handle] self._sender.pyqtSignal(self._signal).disconnect(handle) del self._slotInfo[slot] # Don't forget to delete its entry def signalName(self): return self._signal def senderObj(self): return self._sender def slot(self, slot_id): return self._slotInfo[slot_id][self.Slot] def slotID(self, slot): """ This is how we generated an identifier string given a slot. """ return inspect.getsource(slot).strip() def __contains__(self, slot): if not isinstance(slot, str): slot = self.slotID(slot) return slot in self._slotInfo class SignalPickler: """ A mixin class that helps connect / disconnect slots to member signals and pickle them (using dill). It should go "GraphicsBase(QGraphicsObject, SignalPickler)" as far as base class ordering goes. I've never found the other way around to work without compiler error or unexplicable runtime crash. The same goes for other classes. Qt's raw class first, then your mixin or QObject-derived class. """ def __init__(self): self._signals = {} def __setstate__(self, data): """ Subclasses must call this class's __setstate__ in order to pickle signals. """ self.__init__() self._signals = data['signals'] def __getstate__(self): """ Subclasses must also call this class's __getstate__ in order to pickle signals. """ return { 'signals' : self._signals } def __deepcopy__(self, memo): """ Want your objects / widgets copy & pasteable? Then also call this from a subclass's __deepcopy__ method. """ copy = type(self)() memo[id(self)] = copy copy._signals = deepcopy(self._signals, memo) return copy def __getattribute__(self, attr): """ Internally, this is what SignalPickler does. Every time you ask for a signal from the sender object, for example: `sender.positionChanged.connect(foo)`, instead of returning sender.positionChanged it will return a SignalPickler object wrapping it. """ res = super().__getattribute__(attr) if isinstance(res, pyqtBoundSignal): return PickleableSignal(sender=self, signal=attr) return res def registerSignal(self, signal): """ For internal use only, usually. """ self._signals[signal.signalName()] = signal def isSignalRegistered(self, signal): """ For internal use only, usually. """ return signal.signalName() in self._signals def pyqtSignal(self, name): return super().__getattribute__(name) class Graphics(QGraphicsObject, SignalPickler): anythingHasChanged = pyqtSignal(str) changeToAnything = pyqtSignal(str) positionChange = pyqtSignal(QPointF) positionHasChanged = pyqtSignal(QPointF) parentChange = pyqtSignal(QObject) parentHasChanged = pyqtSignal(QObject) childAdded = pyqtSignal(QObject) childRemoved = pyqtSignal(QObject) brushChange = pyqtSignal(QBrush) brushHasChanged = pyqtSignal(QBrush) penChange = pyqtSignal(QPen) penHasChanged = pyqtSignal(QPen) def __init__(self, pen=None, brush=None, children=None, parent=None, init=None): if init in [None, True]: super().__init__() super().__init__() if pen is None: pen = QPen(QColor(255, 0, 0), 1.0) if brush is None: brush = QBrush(QColor(255, 0, 0)) self._brush = brush self._pen = pen self.setParentItem(parent) self.setFlags(self.ItemIsFocusable | self.ItemIsMovable | self.ItemIsSelectable | self.ItemSendsGeometryChanges | self.ItemSendsScenePositionChanges) self._surpressSigs = False self._prevBrush = self._brush self._prevPen = self._pen self._prevParent = parent self._prevPos = self.pos() if children is None: children = [] for child in children: if child: child.setParentItem(self) def boundingRect(self): return self.childrenBoundingRect() def itemChange(self, change, value): if change == self.ItemParentChange: self._prevParent = self.parentItem() self.emitChange(self.parentChange, 'parentItem', value) elif change == self.ItemParentHasChanged: self.emitHasChanged(self.parentHasChanged, 'parentItem', value) elif change == self.ItemPositionChange: self._prevPos = value self.emitChange(self.positionChange, 'pos', value) elif change == self.ItemPositionHasChanged: self.emitHasChanged(self.positionHasChanged, 'pos', value) elif change == self.ItemChildAddedChange: self.emitHasChanged(self.childAdded, 'childItems', value) elif change == self.ItemChildRemovedChange: self.emitHasChanged(self.childRemoved, 'childItems', value) elif change == self.ItemSceneChange: if self.scene(): self.removedFromSceneEvent() elif change == self.ItemSceneHasChanged: if self.scene(): self.addedToSceneEvent() return super().itemChange(change, value) def setBrush(self, QBrush): self.emitChange(self.brushChange, 'QBrush', QBrush) self._prevBrush = self._brush self._brush = QBrush self.update() self.emitHasChanged(self.brushHasChanged, 'QBrush', QBrush) def QBrush(self): return self._brush def setPen(self, pen): self.emitChange(self.penChange, 'pen', pen) self._prevPen = self._pen self._pen = pen self.update() self.emitHasChanged(self.penHasChanged, 'pen', pen) def pen(self): return self._pen def setSignalsSurpressed(self, en): if self._surpressSigs != en: self._surpressSigs = en for child in self.childItems(): child.setSignalsSurpressed(en) def signalsSurpressed(self): return self._surpressSigs def emitChange(self, sig, member, *args): if not self._surpressSigs: sig.emit(*args) self.changeToAnything.emit(member) def emitHasChanged(self, sig, member, *args): if not self._surpressSigs: sig.emit(*args) self.anythingHasChanged.emit(member) def paint(self, painter, option, widget): from geom_tools import paintSelectionShape if self.isSelected(): paintSelectionShape(painter, self) painter.setRenderHints(painter.HighQualityAntialiasing | painter.Antialiasing) def childPosHasChangedEvent(self, child): pass def childPosChangeEvent(self, child): pass def addedToSceneEvent(self): """ Do here init things that can only be done once added to the scene. """ parent = self.parentItem() if isinstance(parent, Graphics): self.installEventFilter(parent) for child in self.childItems(): child.addedToSceneEvent() def removedFromSceneEvent(self): """ Undo things done in the above method. """ parent = self.parentItem() if isinstance(parent, Graphics): self.removeEventFilter(parent) for child in self.childItems(): child.removedFromSceneEvent() def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.setPos(self.mapToParent(event.pos())) super().mousePressEvent(event) from collections import OrderedDict class GraphicsSet(Graphics): """ Stores graphics items naturally by id() but they're also part of the same movable group. They are also kept ordered (order of element addition). """ elementPosHasChanged = pyqtSignal(Graphics) elementPosChange = pyqtSignal(Graphics) def __init__(self, count=None, type=None, parent=None, init=None, *args, **kwargs): if init in [None, True]: super().__init__(parent=parent) self._set = OrderedDict() if count is not None and type is not None: for k in range(0, count): obj = type(*args, **kwargs) self.add(obj) def boundingRect(self): return self.childrenBoundingRect() def add(self, obj): self._set[id(obj)] = obj obj.setParentItem(self) def remove(self, obj): del self._set[id(obj)] obj.setParentItem(None) def elements(self): return self._set.values() def odict(self): return self._set def __iter__(self): return iter(self._set) def __len__(self): return len(self._set) #def paint(self, painter, option, widget): #from PyQt5.QtGui import QColor, QPen #painter.setPen(QPen(QColor(0, 0, 255), 2.0)) #painter.drawPoint(self.pos()) #super().paint(painter, option, widget) def childPosChangeEvent(self, child): self.elementPosChange.emit(child) def childPosHasChangedEvent(self, child): self.elementPosHasChanged.emit(child) class Rectangle(Graphics): rectangleChange = pyqtSignal(Graphics, QRectF) rectangleHasChanged = pyqtSignal(Graphics, QRectF) def __init__(self, x=None, y=None, w=None, h=None, pen=None, QBrush=None, children=None, parent=None, init=None): if init in [None, True]: super().__init__(pen, QBrush, children, parent) if x is None: x = 0 if y is None: y = 0 if w is None: w = 10 if h is None: h = 5 self.setPos(QPointF(x, y)) rect = QRectF(x, y, w, h) self._rect = rect.translated(-w/2, -h/2) def __setstate__(self, data): super().__setstate__(data) self.setRect(data['rect']) self.__init__(init=False) def __getstate__(self): data = super().__getstate__() data['rect'] = self.rect() return data def __deepcopy__(self, memo): copy = super().__deepcopy__(memo) copy.setRect(self.rect()) return copy def boundingRect(self): w = self.pen().widthF() / 2 return self.rect().adjusted(-w, -w, w, w) def shape(self): path = QPainterPath() w = self.pen().widthF() / 2 path.addRect(self.rect().adjusted(-w, -w, w, w)) return path def paint(self, painter, option, widget): super().paint(painter, option, widget) painter.setPen(self.pen()) painter.setBrush(self.QBrush()) painter.drawRect(self.rect()) def rect(self): return self._rect def setRect(self, rect): rect = rect.translated(-rect.width()/2, -rect.height()/2) if self._rect != rect: self.emitChange(self.rectangleChange, 'rect', rect) self._rect = rect self.update() self.emitHasChanged(self.rectangleHasChanged, 'rect', rect) from PyQt5.QtGui import QPainterPath class Ellipse(Rectangle): def __init__(self, x=None, y=None, w=None, h=None, pen=None, QBrush=None, children=None, parent=None, init=None): if init in [None, True]: super().__init__(x, y, w, h, pen, QBrush) def shape(self): path = QPainterPath() w = self.pen().widthF() / 2 path.addEllipse(self.rect().adjusted(-w, -w, w, w)) return path def paint(self, painter, option, widget): Graphics.paint(self, painter, option, widget) painter.setPen(self.pen()) painter.setBrush(self.QBrush()) painter.drawEllipse(self.rect()) class Point(Ellipse): def __init__(self, x=None, y=None, diam=None, pen=None, brush=None, children=None, parent=None, init=None): if init in [None, True]: return super().__init__(x, y, diam, diam, pen, brush, children, parent) class Line(Graphics): def __init__(self, points=None, diam=None, pen=None, point_pen=None, point_brush=None, parent=None, init=None): if points is None: points = GraphicsSet(count=2, type=Point, diam=diam, pen=point_pen, brush=point_brush) if init in [None, True]: super().__init__(pen, children=[points], parent=parent) self.controlPoints = points self.controlPoints.elementPosHasChanged.connect(self.controlPointPosChangedEvent) self.setFiltersChildEvents(True) def __setstate__(self, data): super().__setstate__(data) self._points = data['points'] self.__init__() def __getstate__(self): return { 'parent' : self.parentItem(), 'pos' : self.pos(), 'control points' : self.controlPoints, } def __deepcopy__(self, memo): copy = deepcopy(super(), memo) copy.controlPoints = deepcopy(self.controlPoints, memo) return copy def line(self): points = self.pointPos() return QLineF(points[0], points[-1]) def shape(self): path = QPainterPath() line = self.line() path.moveTo(line.p1()) path.lineTo(line.p2()) stroker = QPainterPathStroker(QPen(Qt.black, self.pen().widthF())) return stroker.createStroke(path) def paint(self, painter, option, widget): super().paint(painter, option, widget) if __debug__: painter.setPen(self.pen()) painter.drawRect(self.boundingRect()) painter.setPen(self.pen()) painter.setBrush(self.QBrush()) painter.drawLine(self.line()) def controlPointPosChangedEvent(self, point): #""" #Ensures that self.line().center() == self.pos() always. #""" #child = self.controlPoints ##self.controlPoints.setPos(self.controlPoints.pos() - self.setSignalsSurpressed(True) #parent = self.parentItem() transform = QTransform() transform.translate(-self.boundingRect().center().x(), -self.boundingRect().center().y()) self.setTransform(transform) self.setSignalsSurpressed(False) ## When a control point position has changed, we should repaint the line: self.updateScene() def pointPos(self): return [self.controlPoints.mapToParent(elem.pos()) for elem in self.controlPoints.elements()] def p1(self): return self.pointPos()[0] def p2(self): return self.pointPos()[-1] def setP1(self, p): p = self.controlPoints.mapFromParent(p) if self.p1() != p: self.controlPoints.elements()[0].setPos(p) def setP2(self, p): p = self.controlPoints.mapFromParent(p) if self.p2() != p: self.controlPoints.elements()[-1].setPos(p) #def sceneEventFilter(self, watched, event): #if watched is self.controlPoints: #if event.type() == QEvent.GraphicsSceneMouseMove: #if self.scene(): #item = self.scene().itemAt(event.scenePos(), QTransform()) #if item and item in self.controlPoints: #return True #self.setSignalsSurpressed(True) #delta = event.pos() #delta = self.mapToParent(delta) #self.setPos(delta) #self.controlPoints.setPos(-self.boundingRect().center()) #self.setSignalsSurpressed(False) #return True #return False def mousePressEvent(self, event): super().mousePressEvent(event) self.setSignalsSurpressed(True) #self.setPos(event.pos()) #self.controlPoints.setPos(-self.boundingRect().center()) rect = self.controlPoints.boundingRect() trans = QTransform() trans.translate(-self.boundingRect().center().x(), -self.boundingRect().center().y()) self.controlPoints.setTransform(trans) self.setSignalsSurpressed(False) if __name__ == '__main__': app = QApplication([]) window = QMainWindow() window.view = QGraphicsView() window.scene = QGraphicsScene() window.view.setScene(window.scene) window.show() window.setCentralWidget(window.view) item = Line() window.scene.addItem(item) sys.exit(app.exec_())
@enjoysmath said in 5 days. I have been trying to center a freakin QGraphicsItem hierarchy. They made this hard.:
Don't give me this, moveBy, setPos, setTransform crap. It does not work!
when it doesn't work, you are doing it wrong, obviously.
Easiest would be when you show some code. -
I posted the code. I put it all in one file since I couldn't upload a document zip. Run it, or let me know if it doesn't immediately run. Firs spread the control points in the view. Then try moving the rectangle itself by selecting any part of the rectangle that's not at a control point. The lower / upper / left or right corner gets sucked into the mouse cursor position. I'd like the same to happen but with the center of the boundingRect(), not the corner!
I have tried at least 100 "permutations" on how it would work from my perfect understanding of the coordinate transformation system. Much of them failed to have an effect on the corner thing. And when they really suck, the whole scene just blows up or the item jets off or starts shaking abrubtly. Why is it so hard to do this? That makes no sense to me.
-
@enjoysmath said in 5 days. I have been trying to center a freakin QGraphicsItem hierarchy. They made this hard.:
Don't give me this, moveBy, setPos, setTransform crap. It does not work!
when it doesn't work, you are doing it wrong, obviously.
Easiest would be when you show some code.How is the code above?
-
How is the code above?
@enjoysmath
sry i am not very good with Python. Maybe someone else can help here?
When i spot something i let you know. -
@enjoysmath
sry i am not very good with Python. Maybe someone else can help here?
When i spot something i let you know.@raven-worx Thanks. Anything you code in C++ I can convert to Python easily. I'm familiar with Python mostly, but have toiled in C++ / QtCreator.
This problem is not specific to Python, I am 100% confident that you will find the same graphics item "features" in an analogous C++ version.
-
@enjoysmath
sry i am not very good with Python. Maybe someone else can help here?
When i spot something i let you know.@raven-worx In other words, given a graphics item hierarchy : A -> B -> C, where -> is "is a parent of", how would you let C move freely (control points), where B's boundingRect() is the minimal bounding rect of all control points C in B, and I want to keep B's boundingRect() appearing centered on A's position?
Hold on... that just gave me an idea.
K, stuck in same boat. Here is a Pain3D png of what I want for the control point drag action.
By "origin" in the image I mean
pos()
-
@raven-worx In other words, given a graphics item hierarchy : A -> B -> C, where -> is "is a parent of", how would you let C move freely (control points), where B's boundingRect() is the minimal bounding rect of all control points C in B, and I want to keep B's boundingRect() appearing centered on A's position?
Hold on... that just gave me an idea.
K, stuck in same boat. Here is a Pain3D png of what I want for the control point drag action.
By "origin" in the image I mean
pos()
The (0,0) point inside a child is the same (0,0) point inside your parent.
Noticed, when you are trying to centralize an object in a
graphicsview
, you have to translate a half to left and top to keep it on center:
Rect(-5,-5,10,10) => Centralized Rect with Width and Height = 10 on position (0,0).By what i understand, you are trying to resize the parent by child position B -> C. I had a similiar problem in an old project. See an example.
I am not sure how you can achieve this goal on PyQt but i can give you an example on Cpp.
.h file
class MovableCircle : public QGraphicsObject { Q_OBJECT public: explicit MovableCircle(QGraphicsItem *parent = 0); private: QRectF boundingRect() const; QPainterPath shape() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); void mouseMoveEvent(QGraphicsSceneMouseEvent *event); void mousePressEvent(QGraphicsSceneMouseEvent *event); void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); QPointF _shiftMouseCoords; signals: void circleMoved(); }; class BoundedRect : public QGraphicsObject { Q_OBJECT public: BoundedRect(QGraphicsItem *parent = 0); QRectF boundingRect() const; private: void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); MovableCircle *_topLeftCircle, *_topRightCircle, *_bottomLeftCircle, *_bottomRightCircle; QSizeF _size; };
.cpp file
#include "moveitem.h" #include <QtMath> ////////////////////// /// Movable Circle ////////////////////// MovableCircle::MovableCircle(QGraphicsItem *parent) : QGraphicsObject(parent) { setFlag(ItemClipsToShape, true); setCursor(QCursor(Qt::PointingHandCursor)); } QRectF MovableCircle::boundingRect() const { qreal adjust = 0.5; return QRectF(-5 - adjust, -5 - adjust, 10 + adjust, 10 + adjust); } QPainterPath MovableCircle::shape() const { QPainterPath path; qreal adjust = 0.5; path.addEllipse(-5 - adjust, -5 - adjust, 10 + adjust, 10 + adjust); return path; } void MovableCircle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); painter->setBrush(QBrush(Qt::red)); painter->setPen(QPen(Qt::black)); painter->drawEllipse(-5, -5, 10, 10); } void MovableCircle::mousePressEvent(QGraphicsSceneMouseEvent *event) { _shiftMouseCoords = this->pos() - mapToScene(event->pos()); this->setCursor(QCursor(Qt::ClosedHandCursor)); } void MovableCircle::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { setPos(mapToScene(event->pos() + _shiftMouseCoords)); emit circleMoved(); } void MovableCircle::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { Q_UNUSED(event); this->setCursor(QCursor(Qt::PointingHandCursor)); } ////////////////////// /// Bounded Class ////////////////////// BoundedRect::BoundedRect(QGraphicsItem *parent) : QGraphicsObject(parent), _size(180, 180) // Using the Construtor to setup te variable { setFlag(QGraphicsItem::ItemIsMovable, true); // Top Left _topLeftCircle = new MovableCircle(this); _topLeftCircle->setPos(-_size.width()/2, -_size.width()/2); // Top Right _topRightCircle = new MovableCircle(this); _topRightCircle->setPos(_size.width()/2, -_size.width()/2); // Bottom Left _bottomLeftCircle = new MovableCircle(this); _bottomLeftCircle->setPos(-_size.width()/2, _size.width()/2); // Bottom Right _bottomRightCircle = new MovableCircle(this); _bottomRightCircle->setPos(_size.width()/2, _size.width()/2); // Signals // If a delimiter point has been moved, so force the item to redraw connect(_topLeftCircle, &MovableCircle::circleMoved, this, [this](){ _bottomLeftCircle->setPos(_topLeftCircle->pos().x(), _bottomLeftCircle->pos().y()); _topRightCircle->setPos(_topRightCircle->pos().x(), _topLeftCircle->pos().y()); update(); // force to Repaint }); connect(_topRightCircle, &MovableCircle::circleMoved, this, [this](){ _topLeftCircle->setPos(_topLeftCircle->pos().x(), _topRightCircle->pos().y()); _bottomRightCircle->setPos(_topRightCircle->pos().x(), _bottomRightCircle->pos().y()); update(); // force to Repaint }); connect(_bottomLeftCircle, &MovableCircle::circleMoved, this, [this](){ _topLeftCircle->setPos(_bottomLeftCircle->pos().x(), _topLeftCircle->pos().y()); _bottomRightCircle->setPos(_bottomRightCircle->pos().x(), _bottomLeftCircle->pos().y()); update(); // force to Repaint }); connect(_bottomRightCircle, &MovableCircle::circleMoved, this, [this](){ _bottomLeftCircle->setPos(_bottomLeftCircle->pos().x(), _bottomRightCircle->pos().y()); _topRightCircle->setPos(_bottomRightCircle->pos().x(), _topRightCircle->pos().y()); update(); // force to Repaint }); } QRectF BoundedRect::boundingRect() const { // Calculate the Bouding Rect by 4 Limit Points qreal distX = sqrt(pow(_topLeftCircle->x() - _topRightCircle->x(),2) + pow(_topLeftCircle->y() - _topRightCircle->y(),2)); // eucledian distance qreal distY = sqrt(pow(_topLeftCircle->x() - _bottomLeftCircle->x(),2) + pow(_topLeftCircle->y() - _bottomLeftCircle->y(),2)); // eucledian distance return QRectF(qMin(_topLeftCircle->pos().x(), _topRightCircle->pos().x()) , qMin(_topLeftCircle->pos().y(), _bottomLeftCircle->pos().y()), distX, distY); } void BoundedRect::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); painter->setBrush(QBrush(Qt::blue)); painter->setPen(QPen(Qt::black)); painter->drawRect(boundingRect()); // draw by boundingRect }
-
The (0,0) point inside a child is the same (0,0) point inside your parent.
Noticed, when you are trying to centralize an object in a
graphicsview
, you have to translate a half to left and top to keep it on center:
Rect(-5,-5,10,10) => Centralized Rect with Width and Height = 10 on position (0,0).By what i understand, you are trying to resize the parent by child position B -> C. I had a similiar problem in an old project. See an example.
I am not sure how you can achieve this goal on PyQt but i can give you an example on Cpp.
.h file
class MovableCircle : public QGraphicsObject { Q_OBJECT public: explicit MovableCircle(QGraphicsItem *parent = 0); private: QRectF boundingRect() const; QPainterPath shape() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); void mouseMoveEvent(QGraphicsSceneMouseEvent *event); void mousePressEvent(QGraphicsSceneMouseEvent *event); void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); QPointF _shiftMouseCoords; signals: void circleMoved(); }; class BoundedRect : public QGraphicsObject { Q_OBJECT public: BoundedRect(QGraphicsItem *parent = 0); QRectF boundingRect() const; private: void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); MovableCircle *_topLeftCircle, *_topRightCircle, *_bottomLeftCircle, *_bottomRightCircle; QSizeF _size; };
.cpp file
#include "moveitem.h" #include <QtMath> ////////////////////// /// Movable Circle ////////////////////// MovableCircle::MovableCircle(QGraphicsItem *parent) : QGraphicsObject(parent) { setFlag(ItemClipsToShape, true); setCursor(QCursor(Qt::PointingHandCursor)); } QRectF MovableCircle::boundingRect() const { qreal adjust = 0.5; return QRectF(-5 - adjust, -5 - adjust, 10 + adjust, 10 + adjust); } QPainterPath MovableCircle::shape() const { QPainterPath path; qreal adjust = 0.5; path.addEllipse(-5 - adjust, -5 - adjust, 10 + adjust, 10 + adjust); return path; } void MovableCircle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); painter->setBrush(QBrush(Qt::red)); painter->setPen(QPen(Qt::black)); painter->drawEllipse(-5, -5, 10, 10); } void MovableCircle::mousePressEvent(QGraphicsSceneMouseEvent *event) { _shiftMouseCoords = this->pos() - mapToScene(event->pos()); this->setCursor(QCursor(Qt::ClosedHandCursor)); } void MovableCircle::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { setPos(mapToScene(event->pos() + _shiftMouseCoords)); emit circleMoved(); } void MovableCircle::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { Q_UNUSED(event); this->setCursor(QCursor(Qt::PointingHandCursor)); } ////////////////////// /// Bounded Class ////////////////////// BoundedRect::BoundedRect(QGraphicsItem *parent) : QGraphicsObject(parent), _size(180, 180) // Using the Construtor to setup te variable { setFlag(QGraphicsItem::ItemIsMovable, true); // Top Left _topLeftCircle = new MovableCircle(this); _topLeftCircle->setPos(-_size.width()/2, -_size.width()/2); // Top Right _topRightCircle = new MovableCircle(this); _topRightCircle->setPos(_size.width()/2, -_size.width()/2); // Bottom Left _bottomLeftCircle = new MovableCircle(this); _bottomLeftCircle->setPos(-_size.width()/2, _size.width()/2); // Bottom Right _bottomRightCircle = new MovableCircle(this); _bottomRightCircle->setPos(_size.width()/2, _size.width()/2); // Signals // If a delimiter point has been moved, so force the item to redraw connect(_topLeftCircle, &MovableCircle::circleMoved, this, [this](){ _bottomLeftCircle->setPos(_topLeftCircle->pos().x(), _bottomLeftCircle->pos().y()); _topRightCircle->setPos(_topRightCircle->pos().x(), _topLeftCircle->pos().y()); update(); // force to Repaint }); connect(_topRightCircle, &MovableCircle::circleMoved, this, [this](){ _topLeftCircle->setPos(_topLeftCircle->pos().x(), _topRightCircle->pos().y()); _bottomRightCircle->setPos(_topRightCircle->pos().x(), _bottomRightCircle->pos().y()); update(); // force to Repaint }); connect(_bottomLeftCircle, &MovableCircle::circleMoved, this, [this](){ _topLeftCircle->setPos(_bottomLeftCircle->pos().x(), _topLeftCircle->pos().y()); _bottomRightCircle->setPos(_bottomRightCircle->pos().x(), _bottomLeftCircle->pos().y()); update(); // force to Repaint }); connect(_bottomRightCircle, &MovableCircle::circleMoved, this, [this](){ _bottomLeftCircle->setPos(_bottomLeftCircle->pos().x(), _bottomRightCircle->pos().y()); _topRightCircle->setPos(_bottomRightCircle->pos().x(), _topRightCircle->pos().y()); update(); // force to Repaint }); } QRectF BoundedRect::boundingRect() const { // Calculate the Bouding Rect by 4 Limit Points qreal distX = sqrt(pow(_topLeftCircle->x() - _topRightCircle->x(),2) + pow(_topLeftCircle->y() - _topRightCircle->y(),2)); // eucledian distance qreal distY = sqrt(pow(_topLeftCircle->x() - _bottomLeftCircle->x(),2) + pow(_topLeftCircle->y() - _bottomLeftCircle->y(),2)); // eucledian distance return QRectF(qMin(_topLeftCircle->pos().x(), _topRightCircle->pos().x()) , qMin(_topLeftCircle->pos().y(), _bottomLeftCircle->pos().y()), distX, distY); } void BoundedRect::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); painter->setBrush(QBrush(Qt::blue)); painter->setPen(QPen(Qt::black)); painter->drawRect(boundingRect()); // draw by boundingRect }
Thank you, I can definitely look through and understand your code.
-
The (0,0) point inside a child is the same (0,0) point inside your parent.
Noticed, when you are trying to centralize an object in a
graphicsview
, you have to translate a half to left and top to keep it on center:
Rect(-5,-5,10,10) => Centralized Rect with Width and Height = 10 on position (0,0).By what i understand, you are trying to resize the parent by child position B -> C. I had a similiar problem in an old project. See an example.
I am not sure how you can achieve this goal on PyQt but i can give you an example on Cpp.
.h file
class MovableCircle : public QGraphicsObject { Q_OBJECT public: explicit MovableCircle(QGraphicsItem *parent = 0); private: QRectF boundingRect() const; QPainterPath shape() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); void mouseMoveEvent(QGraphicsSceneMouseEvent *event); void mousePressEvent(QGraphicsSceneMouseEvent *event); void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); QPointF _shiftMouseCoords; signals: void circleMoved(); }; class BoundedRect : public QGraphicsObject { Q_OBJECT public: BoundedRect(QGraphicsItem *parent = 0); QRectF boundingRect() const; private: void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); MovableCircle *_topLeftCircle, *_topRightCircle, *_bottomLeftCircle, *_bottomRightCircle; QSizeF _size; };
.cpp file
#include "moveitem.h" #include <QtMath> ////////////////////// /// Movable Circle ////////////////////// MovableCircle::MovableCircle(QGraphicsItem *parent) : QGraphicsObject(parent) { setFlag(ItemClipsToShape, true); setCursor(QCursor(Qt::PointingHandCursor)); } QRectF MovableCircle::boundingRect() const { qreal adjust = 0.5; return QRectF(-5 - adjust, -5 - adjust, 10 + adjust, 10 + adjust); } QPainterPath MovableCircle::shape() const { QPainterPath path; qreal adjust = 0.5; path.addEllipse(-5 - adjust, -5 - adjust, 10 + adjust, 10 + adjust); return path; } void MovableCircle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); painter->setBrush(QBrush(Qt::red)); painter->setPen(QPen(Qt::black)); painter->drawEllipse(-5, -5, 10, 10); } void MovableCircle::mousePressEvent(QGraphicsSceneMouseEvent *event) { _shiftMouseCoords = this->pos() - mapToScene(event->pos()); this->setCursor(QCursor(Qt::ClosedHandCursor)); } void MovableCircle::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { setPos(mapToScene(event->pos() + _shiftMouseCoords)); emit circleMoved(); } void MovableCircle::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { Q_UNUSED(event); this->setCursor(QCursor(Qt::PointingHandCursor)); } ////////////////////// /// Bounded Class ////////////////////// BoundedRect::BoundedRect(QGraphicsItem *parent) : QGraphicsObject(parent), _size(180, 180) // Using the Construtor to setup te variable { setFlag(QGraphicsItem::ItemIsMovable, true); // Top Left _topLeftCircle = new MovableCircle(this); _topLeftCircle->setPos(-_size.width()/2, -_size.width()/2); // Top Right _topRightCircle = new MovableCircle(this); _topRightCircle->setPos(_size.width()/2, -_size.width()/2); // Bottom Left _bottomLeftCircle = new MovableCircle(this); _bottomLeftCircle->setPos(-_size.width()/2, _size.width()/2); // Bottom Right _bottomRightCircle = new MovableCircle(this); _bottomRightCircle->setPos(_size.width()/2, _size.width()/2); // Signals // If a delimiter point has been moved, so force the item to redraw connect(_topLeftCircle, &MovableCircle::circleMoved, this, [this](){ _bottomLeftCircle->setPos(_topLeftCircle->pos().x(), _bottomLeftCircle->pos().y()); _topRightCircle->setPos(_topRightCircle->pos().x(), _topLeftCircle->pos().y()); update(); // force to Repaint }); connect(_topRightCircle, &MovableCircle::circleMoved, this, [this](){ _topLeftCircle->setPos(_topLeftCircle->pos().x(), _topRightCircle->pos().y()); _bottomRightCircle->setPos(_topRightCircle->pos().x(), _bottomRightCircle->pos().y()); update(); // force to Repaint }); connect(_bottomLeftCircle, &MovableCircle::circleMoved, this, [this](){ _topLeftCircle->setPos(_bottomLeftCircle->pos().x(), _topLeftCircle->pos().y()); _bottomRightCircle->setPos(_bottomRightCircle->pos().x(), _bottomLeftCircle->pos().y()); update(); // force to Repaint }); connect(_bottomRightCircle, &MovableCircle::circleMoved, this, [this](){ _bottomLeftCircle->setPos(_bottomLeftCircle->pos().x(), _bottomRightCircle->pos().y()); _topRightCircle->setPos(_bottomRightCircle->pos().x(), _topRightCircle->pos().y()); update(); // force to Repaint }); } QRectF BoundedRect::boundingRect() const { // Calculate the Bouding Rect by 4 Limit Points qreal distX = sqrt(pow(_topLeftCircle->x() - _topRightCircle->x(),2) + pow(_topLeftCircle->y() - _topRightCircle->y(),2)); // eucledian distance qreal distY = sqrt(pow(_topLeftCircle->x() - _bottomLeftCircle->x(),2) + pow(_topLeftCircle->y() - _bottomLeftCircle->y(),2)); // eucledian distance return QRectF(qMin(_topLeftCircle->pos().x(), _topRightCircle->pos().x()) , qMin(_topLeftCircle->pos().y(), _bottomLeftCircle->pos().y()), distX, distY); } void BoundedRect::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); painter->setBrush(QBrush(Qt::blue)); painter->setPen(QPen(Qt::black)); painter->drawRect(boundingRect()); // draw by boundingRect }
Sadly, this doesn't resolve my issues. I'm still troubleshooting it.
Remember, I want pressing on the items to center the item on the mouse cursor.
-
Sadly, this doesn't resolve my issues. I'm still troubleshooting it.
Remember, I want pressing on the items to center the item on the mouse cursor.
@enjoysmath
We need more information...- Your image is very polluated and confused
- Try to describe what you pretent to achieve with this manipulation and maybe it can be clearier to understand.
-
The (0,0) point inside a child is the same (0,0) point inside your parent.
Noticed, when you are trying to centralize an object in a
graphicsview
, you have to translate a half to left and top to keep it on center:
Rect(-5,-5,10,10) => Centralized Rect with Width and Height = 10 on position (0,0).By what i understand, you are trying to resize the parent by child position B -> C. I had a similiar problem in an old project. See an example.
I am not sure how you can achieve this goal on PyQt but i can give you an example on Cpp.
.h file
class MovableCircle : public QGraphicsObject { Q_OBJECT public: explicit MovableCircle(QGraphicsItem *parent = 0); private: QRectF boundingRect() const; QPainterPath shape() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); void mouseMoveEvent(QGraphicsSceneMouseEvent *event); void mousePressEvent(QGraphicsSceneMouseEvent *event); void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); QPointF _shiftMouseCoords; signals: void circleMoved(); }; class BoundedRect : public QGraphicsObject { Q_OBJECT public: BoundedRect(QGraphicsItem *parent = 0); QRectF boundingRect() const; private: void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); MovableCircle *_topLeftCircle, *_topRightCircle, *_bottomLeftCircle, *_bottomRightCircle; QSizeF _size; };
.cpp file
#include "moveitem.h" #include <QtMath> ////////////////////// /// Movable Circle ////////////////////// MovableCircle::MovableCircle(QGraphicsItem *parent) : QGraphicsObject(parent) { setFlag(ItemClipsToShape, true); setCursor(QCursor(Qt::PointingHandCursor)); } QRectF MovableCircle::boundingRect() const { qreal adjust = 0.5; return QRectF(-5 - adjust, -5 - adjust, 10 + adjust, 10 + adjust); } QPainterPath MovableCircle::shape() const { QPainterPath path; qreal adjust = 0.5; path.addEllipse(-5 - adjust, -5 - adjust, 10 + adjust, 10 + adjust); return path; } void MovableCircle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); painter->setBrush(QBrush(Qt::red)); painter->setPen(QPen(Qt::black)); painter->drawEllipse(-5, -5, 10, 10); } void MovableCircle::mousePressEvent(QGraphicsSceneMouseEvent *event) { _shiftMouseCoords = this->pos() - mapToScene(event->pos()); this->setCursor(QCursor(Qt::ClosedHandCursor)); } void MovableCircle::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { setPos(mapToScene(event->pos() + _shiftMouseCoords)); emit circleMoved(); } void MovableCircle::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { Q_UNUSED(event); this->setCursor(QCursor(Qt::PointingHandCursor)); } ////////////////////// /// Bounded Class ////////////////////// BoundedRect::BoundedRect(QGraphicsItem *parent) : QGraphicsObject(parent), _size(180, 180) // Using the Construtor to setup te variable { setFlag(QGraphicsItem::ItemIsMovable, true); // Top Left _topLeftCircle = new MovableCircle(this); _topLeftCircle->setPos(-_size.width()/2, -_size.width()/2); // Top Right _topRightCircle = new MovableCircle(this); _topRightCircle->setPos(_size.width()/2, -_size.width()/2); // Bottom Left _bottomLeftCircle = new MovableCircle(this); _bottomLeftCircle->setPos(-_size.width()/2, _size.width()/2); // Bottom Right _bottomRightCircle = new MovableCircle(this); _bottomRightCircle->setPos(_size.width()/2, _size.width()/2); // Signals // If a delimiter point has been moved, so force the item to redraw connect(_topLeftCircle, &MovableCircle::circleMoved, this, [this](){ _bottomLeftCircle->setPos(_topLeftCircle->pos().x(), _bottomLeftCircle->pos().y()); _topRightCircle->setPos(_topRightCircle->pos().x(), _topLeftCircle->pos().y()); update(); // force to Repaint }); connect(_topRightCircle, &MovableCircle::circleMoved, this, [this](){ _topLeftCircle->setPos(_topLeftCircle->pos().x(), _topRightCircle->pos().y()); _bottomRightCircle->setPos(_topRightCircle->pos().x(), _bottomRightCircle->pos().y()); update(); // force to Repaint }); connect(_bottomLeftCircle, &MovableCircle::circleMoved, this, [this](){ _topLeftCircle->setPos(_bottomLeftCircle->pos().x(), _topLeftCircle->pos().y()); _bottomRightCircle->setPos(_bottomRightCircle->pos().x(), _bottomLeftCircle->pos().y()); update(); // force to Repaint }); connect(_bottomRightCircle, &MovableCircle::circleMoved, this, [this](){ _bottomLeftCircle->setPos(_bottomLeftCircle->pos().x(), _bottomRightCircle->pos().y()); _topRightCircle->setPos(_bottomRightCircle->pos().x(), _topRightCircle->pos().y()); update(); // force to Repaint }); } QRectF BoundedRect::boundingRect() const { // Calculate the Bouding Rect by 4 Limit Points qreal distX = sqrt(pow(_topLeftCircle->x() - _topRightCircle->x(),2) + pow(_topLeftCircle->y() - _topRightCircle->y(),2)); // eucledian distance qreal distY = sqrt(pow(_topLeftCircle->x() - _bottomLeftCircle->x(),2) + pow(_topLeftCircle->y() - _bottomLeftCircle->y(),2)); // eucledian distance return QRectF(qMin(_topLeftCircle->pos().x(), _topRightCircle->pos().x()) , qMin(_topLeftCircle->pos().y(), _bottomLeftCircle->pos().y()), distX, distY); } void BoundedRect::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); painter->setBrush(QBrush(Qt::blue)); painter->setPen(QPen(Qt::black)); painter->drawRect(boundingRect()); // draw by boundingRect }
@KillerSmath Your example is great, exactly what i was looking for, but nevertheless, i noticed that this system breaks when there is rotation involved. I'm pretty new with all this but may you or anyone else can hint me the way on how get it working with rotation? Thanks alot beforehand. :)