[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
    

    //rectangleitem.cpp

    #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;
    }
    

    //main.cpp

    #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();
    }
    


  • Can anyone tell me what is going wrong in my above code and what I should change to make my custom graphics item resizeable & rotatable.



  • 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.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.