[Solved]Resizable & rotatable GraphicsItem
-
Re: [SOLVED] Resizing a rotated QGraphicsRectItem
I am trying to do an application in which I want to achieve the functionality of rotating and resizing the Graphics Item which is basically a Rectangle drawn using paint.
I have used following code to achieve this functionality.
This code is not working correctly when I rotate it and then resize it and then again try to rotate then the centre point will be jumped to another point.void CRoiUI::mouseMoveEvent(QGraphicsSceneMouseEvent * event) { if (m_IsResizing) { QPointF ptMouseMoveInItemsCoord = mapFromScene(event->scenePos()); switch (m_ResizeCorner) { case TOP_LEFT: if (this->scene()->sceneRect().contains(event->scenePos())) { m_RoiBoundingRect.setTopLeft(ptMouseMoveInItemsCoord); m_RoiBoundingRect = m_RoiBoundingRect.normalized(); } break; case TOP: if (this->scene()->sceneRect().contains(event->scenePos())) { m_RoiBoundingRect.setTop(ptMouseMoveInItemsCoord.y()); m_RoiBoundingRect = m_RoiBoundingRect.normalized(); } break; case TOP_RIGHT: if (this->scene()->sceneRect().contains(event->scenePos())) { m_RoiBoundingRect.setTopRight(ptMouseMoveInItemsCoord); m_RoiBoundingRect = m_RoiBoundingRect.normalized(); } break; case RIGHT: if (this->scene()->sceneRect().contains(event->scenePos())) { m_RoiBoundingRect.setRight(ptMouseMoveInItemsCoord.x()); m_RoiBoundingRect = m_RoiBoundingRect.normalized(); } break; case BOTTOM_RIGHT: if (this->scene()->sceneRect().contains(event->scenePos())) { m_RoiBoundingRect.setBottomRight(ptMouseMoveInItemsCoord); m_RoiBoundingRect = m_RoiBoundingRect.normalized(); } break; case BOTTOM: if (this->scene()->sceneRect().contains(event->scenePos())) { m_RoiBoundingRect.setBottom(ptMouseMoveInItemsCoord.y()); m_RoiBoundingRect = m_RoiBoundingRect.normalized(); } break; case BOTTOM_LEFT: if (this->scene()->sceneRect().contains(event->scenePos())) { m_RoiBoundingRect.setBottomLeft(ptMouseMoveInItemsCoord); m_RoiBoundingRect = m_RoiBoundingRect.normalized(); } break; case LEFT: if (this->scene()->sceneRect().contains(event->scenePos())) { m_RoiBoundingRect.setLeft(ptMouseMoveInItemsCoord.x()); m_RoiBoundingRect = m_RoiBoundingRect.normalized(); } break; case ROTATE: if (this->scene()->sceneRect().contains(event->scenePos())) { QLineF line(m_RoiBoundingRect.center(), ptMouseMoveInItemsCoord); double rotations = line.angle(QLineF(0, 0, 1, 0)); if (line.dy() <= 0) { rotations = 180.0 - rotations; } else { rotations = rotations - 180.0; } m_Angle = rotations; m_RoiBoundingRect = m_RoiBoundingRect.normalized(); setTransformOriginPoint(m_RoiBoundingRect.center()); setRotation(rotation() + m_Angle); } break; } prepareGeometryChange(); update(); } else { prepareGeometryChange(); update(); QGraphicsItem::mouseMoveEvent(event); } } //bounding rect QRectF CRoiUI::boundingRect() const { return m_RoiBoundingRect; }
Please let me know how this rotation issue can be fixed.
-
I can't quite put my finger on it, but changing the position of the boundingRect plus using rotation is bound to produce some interesting results. I would instead try to define a boundingRect which has the origin exactly where you want the item rotated, and move the item around using plain old "setPos".
-
Find my test code here.
//rectangleitem.h#ifndef RECTANGLEITEM_H #define RECTANGLEITEM_H #include <QGraphicsItem> enum ResizeCorners { TOP_LEFT, TOP, TOP_RIGHT, RIGHT, BOTTOM_RIGHT, BOTTOM, BOTTOM_LEFT, LEFT, ROTATE }; class RectangleItem : public QGraphicsItem { QRectF m_BoundingRect; QRectF m_ActualRect; //! Resizable handles around the shape QVector<QRectF> m_ResizeHandles; //! Arrow line used as rotation handle QLineF m_RotateLine; //! Arrow head QPolygonF m_AngleHandle; double m_Angle; bool m_IsResizing; bool m_MousePressed; ResizeCorners m_ResizeCorner; public: RectangleItem(); QRectF boundingRect() const; void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0); void mousePressEvent(QGraphicsSceneMouseEvent * event); void mouseMoveEvent(QGraphicsSceneMouseEvent * event); void mouseReleaseEvent(QGraphicsSceneMouseEvent * event); void hoverEnterEvent(QGraphicsSceneHoverEvent * event); void hoverLeaveEvent(QGraphicsSceneHoverEvent * event); void hoverMoveEvent(QGraphicsSceneHoverEvent * event); void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); bool mousePosOnHandles(QPointF pos); }; #endif // RECTANGLEITEM_H
#include "rectangleitem.h" #include <QPainter> #include <QGraphicsScene> #include <QGraphicsView> #include <QGraphicsSceneMouseEvent> #include <QDebug> #define PIE 3.1415926535897932384626433832795 RectangleItem::RectangleItem() { m_BoundingRect = QRectF(0, 0, 200, 100); m_ResizeHandles.fill(QRect(0, 0, 0, 0), 8); //initially empty handles m_MousePressed = false; m_IsResizing = false; setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemSendsGeometryChanges); setPos(100, 150); } QRectF RectangleItem::boundingRect() const { return m_BoundingRect; } void RectangleItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option) Q_UNUSED(widget) painter->fillRect(boundingRect(), QBrush(QColor(128, 128, 255, 128))); //resize handles double scale = this->scene()->views().at(0)->transform().m11(); //get current sace factor float rectSize = 4 / scale; //this is to maintain same size for resize handle rects QRectF handles(0, 0, rectSize, rectSize); QRectF m_CornerRect = m_BoundingRect.adjusted(2, 2, -2, -2); handles.moveCenter(m_CornerRect.topLeft()); //TopLeft m_ResizeHandles.replace(0, handles); handles.moveCenter(m_CornerRect.topRight()); //TopRight m_ResizeHandles.replace(2, handles); handles.moveCenter(m_CornerRect.bottomRight()); //BottomRight m_ResizeHandles.replace(4, handles); handles.moveCenter(m_CornerRect.bottomLeft()); //BottomLeft m_ResizeHandles.replace(6, handles); QPointF center(m_CornerRect.center().x(), m_CornerRect.top()); //Top handles.moveCenter(center); m_ResizeHandles.replace(1, handles); center = QPointF(m_CornerRect.right(), m_CornerRect.center().y()); //Right handles.moveCenter(center); m_ResizeHandles.replace(3, handles); center = QPointF(m_CornerRect.center().x(), m_CornerRect.bottom()); //Bottom handles.moveCenter(center); m_ResizeHandles.replace(5, handles); center = QPointF(m_CornerRect.left(), m_CornerRect.center().y()); //Left handles.moveCenter(center); m_ResizeHandles.replace(7, handles); //arrow line float size = m_CornerRect.width(); m_RotateLine.setP1(m_ResizeHandles.at(7).center()); m_RotateLine.setP2(QPointF(m_ResizeHandles.at(7).center().x() + (size / 4), m_ResizeHandles.at(7).center().y())); //angle handle qreal arrowSize = 6.0 / scale; QPointF point = m_RotateLine.p2(); double angle = ::acos(m_RotateLine.dx() / m_RotateLine.length()); if (m_RotateLine.dy() >= 0) angle = (2.0 * PIE) - angle; QPointF destArrowP1 = point + QPointF(sin(angle - PIE / 3.0) * arrowSize, cos(angle - PIE / 3.0) * arrowSize); QPointF destArrowP2 = point + QPointF(sin(angle - PIE + PIE / 3.0) * arrowSize , cos(angle - PIE + PIE / 3.0) * arrowSize); float points[] = { point.x(), point.y(), destArrowP1.x(), destArrowP1.y(), destArrowP2.x(), destArrowP2.y() }; m_AngleHandle = QPolygonF() << m_RotateLine.p2() << destArrowP1 << destArrowP2; QPen pens; pens.setCosmetic(true); //to maintain same width of pen across zoom levels //draw rect painter->setPen(pens); painter->drawRect(m_CornerRect); //draw arrow handle QPen arrowPen; arrowPen.setCosmetic(true); arrowPen.setColor(Qt::yellow); painter->setBrush(Qt::black); painter->setPen(arrowPen); painter->drawLine(m_RotateLine); painter->drawPolygon(m_AngleHandle); //draw resize handles pens.setColor(QColor(255, 255, 255)); painter->setBrush(Qt::black); painter->setPen(pens); painter->drawRects(m_ResizeHandles); } void RectangleItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { m_MousePressed = true; m_IsResizing = mousePosOnHandles(event->scenePos()); //to check event on corners or not if (m_IsResizing) { m_ActualRect = m_BoundingRect; } QGraphicsItem::mousePressEvent(event); } void RectangleItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (m_IsResizing) { QPointF ptMouseMoveInItemsCoord = mapFromScene(event->scenePos()); switch (m_ResizeCorner) { case TOP_LEFT: if (this->scene()->sceneRect().contains(event->scenePos())) { m_BoundingRect.setTopLeft(ptMouseMoveInItemsCoord); m_BoundingRect = m_BoundingRect.normalized(); qDebug() << "TOP_LEFT::transformOriginPoint()" << m_BoundingRect.center(); } break; case TOP: if (this->scene()->sceneRect().contains(event->scenePos())) { m_BoundingRect.setTop(ptMouseMoveInItemsCoord.y()); m_BoundingRect = m_BoundingRect.normalized(); qDebug() << "TOP::transformOriginPoint()" << m_BoundingRect.center(); } break; case TOP_RIGHT: if (this->scene()->sceneRect().contains(event->scenePos())) { m_BoundingRect.setTopRight(ptMouseMoveInItemsCoord); m_BoundingRect = m_BoundingRect.normalized(); qDebug() << "TOP_RIGHT::transformOriginPoint()" << m_BoundingRect.center(); } break; case RIGHT: if (this->scene()->sceneRect().contains(event->scenePos())) { m_BoundingRect.setRight(ptMouseMoveInItemsCoord.x()); m_BoundingRect = m_BoundingRect.normalized(); qDebug() << "RIGHT::transformOriginPoint()" << m_BoundingRect.center(); } break; case BOTTOM_RIGHT: if (this->scene()->sceneRect().contains(event->scenePos())) { m_BoundingRect.setBottomRight(ptMouseMoveInItemsCoord); m_BoundingRect = m_BoundingRect.normalized(); qDebug() << "BOTTOM_RIGHT::transformOriginPoint()" << m_BoundingRect.center(); } break; case BOTTOM: if (this->scene()->sceneRect().contains(event->scenePos())) { //m_BoundingRect.setBottom(event->pos().y()); m_BoundingRect.setBottom(ptMouseMoveInItemsCoord.y()); m_BoundingRect = m_BoundingRect.normalized(); qDebug() << "BOTTOM::transformOriginPoint()" << m_BoundingRect.center(); } break; case BOTTOM_LEFT: if (this->scene()->sceneRect().contains(event->scenePos())) { m_BoundingRect.setBottomLeft(ptMouseMoveInItemsCoord); m_BoundingRect = m_BoundingRect.normalized(); qDebug() << "BOTTOM_LEFT::transformOriginPoint()" << m_BoundingRect.center(); } break; case LEFT: if (this->scene()->sceneRect().contains(event->scenePos())) { m_BoundingRect.setLeft(ptMouseMoveInItemsCoord.x()); m_BoundingRect = m_BoundingRect.normalized(); qDebug() << "LEFT::transformOriginPoint()" << m_BoundingRect.center(); } break; case ROTATE: if (this->scene()->sceneRect().contains(event->scenePos())) { QLineF line(m_BoundingRect.center(), ptMouseMoveInItemsCoord); double rotations = line.angle(QLineF(0, 0, 1, 0)); if (line.dy() <= 0) { rotations = 180.0 - rotations; } else { rotations = rotations - 180.0; } m_Angle = rotations; m_BoundingRect = m_BoundingRect.normalized(); setTransformOriginPoint(m_BoundingRect.center()); setRotation(rotation() + m_Angle); qDebug() << "transformOriginPoint()" << transformOriginPoint(); qDebug() << "sceneBoundingRect()" << sceneBoundingRect(); } break; } qDebug() << "boundingRect()" << m_BoundingRect; prepareGeometryChange(); update(); } else { prepareGeometryChange(); update(); QGraphicsItem::mouseMoveEvent(event); } } void RectangleItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { m_MousePressed = false; m_IsResizing = false; prepareGeometryChange(); update(); QGraphicsItem::mouseReleaseEvent(event); } void RectangleItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { } void RectangleItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { } void RectangleItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { } void RectangleItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { } bool RectangleItem::mousePosOnHandles(QPointF pos) { bool resizable = false; int rem4Index = 8;// +(qRound(this->rotation()) / 45); if (mapToScene(m_ResizeHandles[(0 + rem4Index) % 8]).containsPoint(pos, Qt::WindingFill)) { m_ResizeCorner = TOP_LEFT; resizable = true; } else if (mapToScene(m_ResizeHandles[(1 + rem4Index) % 8]).containsPoint(pos, Qt::WindingFill)) { m_ResizeCorner = TOP; resizable = true; } else if (mapToScene(m_ResizeHandles[(2 + rem4Index) % 8]).containsPoint(pos, Qt::WindingFill)) { m_ResizeCorner = TOP_RIGHT; resizable = true; } else if (mapToScene(m_ResizeHandles[(3 + rem4Index) % 8]).containsPoint(pos, Qt::WindingFill)) { m_ResizeCorner = RIGHT; resizable = true; } else if (mapToScene(m_ResizeHandles[(4 + rem4Index) % 8]).containsPoint(pos, Qt::WindingFill)) { m_ResizeCorner = BOTTOM_RIGHT; resizable = true; } else if (mapToScene(m_ResizeHandles[(5 + rem4Index) % 8]).containsPoint(pos, Qt::WindingFill)) { m_ResizeCorner = BOTTOM; resizable = true; } else if (mapToScene(m_ResizeHandles[(6 + rem4Index) % 8]).containsPoint(pos, Qt::WindingFill)) { m_ResizeCorner = BOTTOM_LEFT; resizable = true; } else if (mapToScene(m_ResizeHandles[(7 + rem4Index) % 8]).containsPoint(pos, Qt::WindingFill)) { m_ResizeCorner = LEFT; resizable = true; } else if (mapToScene(m_AngleHandle).containsPoint(pos, Qt::WindingFill)) { m_ResizeCorner = ROTATE; resizable = true; } return resizable; }
#include "rectangleitem.h" #include <QApplication> #include <QGraphicsScene> #include <QGraphicsView> int main(int argc, char *argv[]) { QApplication a(argc, argv); QGraphicsScene scene; scene.addItem(new RectangleItem()); scene.setSceneRect(0, 0, 400, 400); QGraphicsView view(&scene); view.resize(420, 420); view.show(); return a.exec(); }
-
Finally I got help from Qt support and solved this issue.
We should not use setTransformOriginPoint() while performing the rotation instead we should calculate the new center point soon after we finished resizing and set it.
In mouseReleaseEvent() method after line m_IsResizing = false;
add these lines:m_IsResizing = false; if (m_ActualRect != m_BoundingRect) { // Rotating won't trigger this, only resizing. auto oldScenePos = scenePos(); setTransformOriginPoint(m_BoundingRect.center()); auto newScenePos = scenePos(); auto oldPos = pos(); setPos(oldPos.x() + (oldScenePos.x() - newScenePos.x()), oldPos.y() + (oldScenePos.y() - newScenePos.y())); }
I hope this will be helpful for someone who wants to implement a custom graphicsitem which can be rotatable as well as resizeable.