How to keep transformations of child item independent from parenitem?
-
@jeremy_k @Asperamanca Well these items are "handles" so when the parent is clicked they shall apear at the corners of the paremt. Aswell as when the parent is moved or transformed they are supposed to stay in the corner an be rotated with the parent, but they shall NOT be scaled with the parent.
-
@StudentScripter said in How to keep transformations of child item independent from parenitem?:
they are supposed to stay in the corner an be rotated with the parent, but they shall NOT be scaled with the parent.
The fact that you want some parent transformations but not others presumably rules out blanket use of
QGraphicsItem::ItemIgnoresTransformations
.Not something I have done but, depending on how it all works/whether this is doable, you might have to do something like either (a) allow them to accept parent transformations but then reset their scale or (b) ignore parent transformations but then copy parent transformation matrix, reset its scale element and then apply it? One thing I am not sure about is: when you want to sometimes apply scale (on parent) and sometimes not (on children) does the difference in scaling mean that the coordinate transformations like position are now "wrong" e.g. relative to parent? I can sort of imagine how the handles, rotated and moved per the parent, do not come out quite in the right corner position given that scaling is different?
There are a couple of hits Googling for
qgraphicsitem handles
you might like to check to see whether anyone has asked your type of question where they need to deal with transformations. -
@JonB Well thanks Jon, the ideas you stated could be an option, but i have to test it.
Regarding googling in the past six months i've read nearly every post or article on qgraphicsitem and handles or transformation, and figured that all examples i could find won't fit my needs. I've build/ tried to replicate around 15 different variations, they are still rotting in my scrapped folder now. xd Meanwhile teaming up with some insides of my professor i figured out a solution atleast in mathematical terms, but the sizing problem was the one thing i still struggelt with on implementation.
Regarding the matrix as said i have to check which approach would be viable. Im also currently trying detaching the handles from being child items directly but positioning them independently on my calculation, but the results aren't as smooth so far than with set parent.
-
@StudentScripter said in How to keep transformations of child item independent from parenitem?:
Im also currently trying detaching the handles from being child items directly but positioning them independently on my calculation, but the results aren't as smooth so far than with set parent.
That's the correct approach IMO. Making the Handle items children of the items they are handles to is clearly the wrong structure. It makes more sense to invert that, and have the Handle item the parent. Or perhaps better, use association rather than aggregation.
I have implemented this kind of thing using a HandlesSelection item as parent of the representation item, dealing with item selection and movement, without any rendering issues.
-
@KenAppleby-0 Sounds great, could you maybe share a snippet of how you implemented it? (Obviously only if it's no problem)
Anyway i would like to know how you handle it? With setPos() and than update the scene by scene()->update()? -
I had forgotten the details, but having revisited the code ... I used a contentless graphics item to be the parent of both the selectable item and the object drawing the handles or selection. Here's an outline of the code:
// This is the graphics object that paints the visual selection around the item. // It could be a QGraphicsItem subclass. I used a QGraphicsObject because it can be more easily animated. class SelectionGraphicsObject : public QGraphicsObject { public: SelectionGraphicsObject(QGraphicsItem * parent, QGraphicsItem * item); // item is the selectable // paint function uses the item's scene bounding rect and other graphical parameters. void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget =nullptr) override; }; // A contentless parent class for both the selectable item and the SelectionGraphicsObject. class SelectableGraphicsItem : public QGraphicsRectItem { public: SelectableGraphicsItem(QGraphicsItem * parent, QGraphicsItem * item, QColor selectionColour, int id); void setSelected(bool selected); protected: QGraphicsItem * mItem{ nullptr }; // the selectable item SelectionGraphicsObject * mSelectionItem{ nullptr }; // the graphics object painting the selection. }; SelectableGraphicsItem::SelectableGraphicsItem(QGraphicsItem * parent, QGraphicsItem * item) : QGraphicsRectItem{ parent }, mItem{ item } { mItem->setParentItem(this); mSelectionItem = new SelectionGraphicsObject{ this, item, selectionColour }; mSelectionItem->setVisible(false); mSelectionItem->setFlag(QGraphicsItem::ItemIgnoresTransformations); setFlag(QGraphicsItem::ItemHasNoContents); } void SelectableGraphicsItem::setSelected(bool selected) { mSelectionItem->setVisible(selected); update(); }
-
@KenAppleby-0 First of all thanks, but from the example you provided i still don't get how you size the selection item correctly in case the underlying item was transformed by scaling, rotating ... as it appears to me in this case your selection item would be not fitting to the underneath item cause it ignore all transforms?
Am i right? Or wasn't this just not needed in your case?Best regards
-
This post is deleted!
-
@StudentScripter said in How to keep transformations of child item independent from parenitem?:
@jeremy_k @Asperamanca Well these items are "handles" so when the parent is clicked they shall apear at the corners of the paremt. Aswell as when the parent is moved or transformed they are supposed to stay in the corner an be rotated with the parent, but they shall NOT be scaled with the parent.
This sounds like the two items are siblings, with a common parent. Only transformations that should be applied to both are performed on the common parent. Using a QGraphicsItemGroup might save a little code.
-
Below is code that I can share. It's a complete example which perhaps does what you need. The void HandlesItem::paint() function is where the graphics transformations are done. It's rather long-winded but it at least shows what's required. It draws a painter path around the model items and deals with scaling and rotation of the model items as well as scaling of the graphics view.
I'm sorry there is so much code: it's mostly the test framework. It uses mouse wheel events + modifiers to set the scale and rotation of items and the view.
// main.cpp #include <QGraphicsRectItem> #include <QGraphicsRectItem> #include <QRandomGenerator> #include <QPainter> // A graphics path item that paints a selection graphic rectangle around a model item. class HandlesItem : public QGraphicsPathItem { public: HandlesItem(QGraphicsItem * modelItem, QGraphicsItem * parent =nullptr); void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget =nullptr) override; void setViewScale(double scale); protected: QGraphicsItem * mModelItem{ nullptr }; }; // A contentless graphics item that acts as a proxy for a model item and manages a HandlesItem for it. // The model item position must be set by setting the Proxy item position. // The model item scale and rotation must be set by the setItemScale() and setItemRotation() functions. // Model items and HandleItems are assumed to transform about their center points. class Proxy : public QGraphicsRectItem { public: Proxy(QGraphicsItem * modelItem, QGraphicsItem * parent =nullptr); void setSelected(bool selected); bool isSelected() const; void setItemRotation(double angle); void setItemScale(double scale); void setViewScale(double scale); // call to set the HandleItem's pen width according to the view scale. protected: QGraphicsItem * mModelItem{ nullptr }; HandlesItem * mHandlesItem{ nullptr }; bool mSelected{ false }; }; // -------------------------------------- HandlesItem::HandlesItem(QGraphicsItem * modelItem, QGraphicsItem * parent) : QGraphicsPathItem{ parent }, mModelItem{ modelItem } { setViewScale(1.0); setTransformOriginPoint(mModelItem->boundingRect().center()); } void HandlesItem::setViewScale(double scale) { QPen pen{ Qt::yellow }; pen.setWidthF(4.0/scale); setPen(pen); } void HandlesItem::paint(QPainter * painter, [[ maybe_unused ]] const QStyleOptionGraphicsItem * option, [[ maybe_unused ]] QWidget * widget) { const double scale{ mModelItem->scale() }; const double margin{ 8.0 / scale }; QRectF r{ mModelItem->boundingRect().adjusted(-margin, -margin, margin, margin) }; const QPointF tl{ r.topLeft() }, tr{ r.topRight() }, br{ r.bottomRight() }, bl{ r.bottomLeft() }; QTransform transform; transform.translate(r.center().x(), r.center().y()); transform.scale(mModelItem->scale(), mModelItem->scale()); transform.rotate(mModelItem->rotation()); transform.translate(-r.center().x(), -r.center().y()); const QPointF tlt{ transform.map(tl) }, trt{ transform.map(tr) }, brt{ transform.map(br) }, blt{ transform.map(bl) }; QPainterPath path; path.moveTo(tlt); path.lineTo(trt); path.lineTo(brt); path.lineTo(blt); path.lineTo(tlt); setPath(path); QGraphicsPathItem::paint(painter, option, widget); } Proxy::Proxy(QGraphicsItem * modelItem, QGraphicsItem * parent) : QGraphicsRectItem{ parent }, mModelItem{ modelItem } { mModelItem->setPos(0, 0); // the model item is positioned by the Proxy. mModelItem->setParentItem(this); setFlag(QGraphicsItem::ItemHasNoContents); mHandlesItem = new HandlesItem{ modelItem, this }; setSelected(false); } void Proxy::setSelected(bool selected) { mSelected = selected; mHandlesItem->setVisible(mSelected); update(); } bool Proxy::isSelected() const { return mSelected; } void Proxy::setItemRotation(double angle) { if (mModelItem) { mModelItem->setTransformOriginPoint(mModelItem->boundingRect().center()); mModelItem->setRotation(mModelItem->rotation() + angle); } } void Proxy::setItemScale(double scale) { if (mModelItem) { mModelItem->setTransformOriginPoint(mModelItem->boundingRect().center()); mModelItem->setScale(mModelItem->scale() * scale); } } void Proxy::setViewScale(double scale) { mHandlesItem->setViewScale(scale); } // The remaining code is a QGraphicsView and QGraphicsScene test framework for the above. #include <QGraphicsView> class GraphicsView : public QGraphicsView { public: GraphicsView(QWidget * parent); void initialise(QGraphicsScene& scene); protected: void mousePressEvent(QMouseEvent * event) override; void mouseMoveEvent(QMouseEvent * event) override; void mouseReleaseEvent(QMouseEvent *) override; void wheelEvent(QWheelEvent *) override; void setSelection(Proxy * item); void initialiseScene(); void setViewTransform(); QTransform makeTransform() const; QList<Proxy *> mItems; Proxy * mSelectedItem{ nullptr }; Proxy * mDragging{ nullptr }; QPointF mMoveScenePoint; double mScale{ 1.0 }; double mRotation{ 0.0 }; }; #include <QMainWindow> #include <QGraphicsScene> #include <QBoxLayout> class HandlesMainWindow : public QMainWindow { public: HandlesMainWindow(QWidget * parent =nullptr); protected: QGraphicsScene mScene; }; HandlesMainWindow::HandlesMainWindow(QWidget * parent) : QMainWindow(parent) { QWidget * centralwidget; QVBoxLayout * verticalLayout; GraphicsView * graphicsView; resize(532, 377); centralwidget = new QWidget(this); centralwidget->setObjectName("centralwidget"); verticalLayout = new QVBoxLayout(centralwidget); verticalLayout->setObjectName("verticalLayout"); graphicsView = new GraphicsView(centralwidget); graphicsView->setObjectName("graphicsView"); verticalLayout->addWidget(graphicsView); setCentralWidget(centralwidget); graphicsView->initialise(mScene); } #include <QMouseEvent> #include <QWheelEvent> namespace { QRandomGenerator& rng() { static QRandomGenerator sRng; return sRng; } QColor randomColor() { return QColor{ rng().bounded(64, 255), rng().bounded(64, 255), rng().bounded(64, 255) }; } QPointF randomPoint() { return QPointF{ 1.0*rng().bounded(20, 400), 1.0*rng().bounded(20, 400) }; } QRectF randomRect() { return QRectF{ 0.0, 0.0, 1.0*rng().bounded(20, 100), 1.0*rng().bounded(20, 100) }; } } GraphicsView::GraphicsView(QWidget * parent) : QGraphicsView{ parent } { setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setRenderHint(QPainter::Antialiasing); setTransformationAnchor(ViewportAnchor::NoAnchor); } void GraphicsView::initialise(QGraphicsScene& scene) { setScene(&scene); initialiseScene(); } void GraphicsView::mousePressEvent(QMouseEvent * event) { const QPoint p{ event->pos() }; QGraphicsItem * item{ itemAt(p) }; if (item) { mDragging = qgraphicsitem_cast<Proxy *>(item->parentItem()); mMoveScenePoint = mapToScene(p); } else { mDragging = nullptr; } } void GraphicsView::mouseMoveEvent(QMouseEvent * event) { const QPoint p{ event->pos() }; if (mDragging) { const QPointF sp{ mapToScene(p) }; mDragging->moveBy(sp.x() - mMoveScenePoint.x(), sp.y() - mMoveScenePoint.y()); mMoveScenePoint = sp; } } void GraphicsView::mouseReleaseEvent(QMouseEvent * event) { mDragging = nullptr; const QPoint p{ event->pos() }; QGraphicsItem * item{ itemAt(p) }; if (item) { setSelection(qgraphicsitem_cast<Proxy *>(item->parentItem())); } else { setSelection(nullptr); } } void GraphicsView::wheelEvent(QWheelEvent * event) { if (event->modifiers() == Qt::NoModifier) { double deltaScale{ 1.0 }; QPoint p = event->angleDelta(); if (p.y() > 0) { deltaScale = 1.1; } else if (p.y() < 0) { deltaScale = 1.0/1.1; } event->accept(); if (mSelectedItem) { mSelectedItem->setItemScale(deltaScale); } else { mScale = mScale * deltaScale; setViewTransform(); } } else if (event->modifiers() == Qt::ControlModifier){ QPoint p = event->angleDelta(); double rotation{ 0.0 }; if (p.y() > 0) { rotation = 2.0; } else if (p.y() < 0) { rotation = -2.0; } event->accept(); if (mSelectedItem) { mSelectedItem->setItemRotation(rotation); } else { mRotation += rotation; setViewTransform(); } } } void GraphicsView::setSelection(Proxy * item) { mSelectedItem = item; if (item) { item->setSelected(true); } for (auto i : mItems) { if (not (i == item)) { i->setSelected(false); } } } void GraphicsView::setViewTransform() { setTransform(makeTransform()); for (auto i : mItems) { i->setViewScale(mScale); } } QTransform GraphicsView::makeTransform() const { QTransform result; const QPointF center{ rect().center() }; result.translate(center.x(), center.y()); result.scale(mScale, mScale); result.rotate(mRotation); result.translate(-center.x(), -center.y()); return result; } void GraphicsView::initialiseScene() { scene()->setBackgroundBrush(QBrush{ Qt::cyan }); for (int i = 0; i < 20; i++) { QGraphicsRectItem * item{ new QGraphicsRectItem(::randomRect()) }; item->setBrush(QBrush{ ::randomColor() }); Proxy * proxy{ new Proxy{ item } }; proxy->setPos(::randomPoint()); scene()->addItem(proxy); mItems << proxy; } } #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); HandlesMainWindow w; w.show(); return a.exec(); }