QGraphicsItem mouse drag to rotate

Series Article Directory

Simple use of QGraphicsItem primitives (1)
QGraphicsItem primitive drag drawing (2)
QGraphicsItem primitive rotation scaling and custom primitives (3)
QGraphicsItem mouse drags the primitive to zoom and stretch (4)

foreword

Continuing from the previous article, this article mainly explains how to rotate by dragging primitives with the mouse

1. Effect demonstration

Second, the process analysis

1. Mouse hover

When I select an element, when the mouse hovers near the rotation circle of the element, the corresponding mouse ico style needs to be displayed, but the mouse hover event trigger is composed of the following parts:
1) Primitive settings receive mouse hover events

    // Receive mouse hover events
    this->setAcceptHoverEvents(true);

2) The mouse hover entry and exit events of the primitive are determined by the boundingRect area, so it needs to be modified. We actually display the drawn m_rect, but only the area containing the rotating connection dotted line and the rotating circle, which is convenient for triggering the mouse hover event (there are Big guys with better solutions can leave a message in the comment area)

QRectF RectItem::boundingRect() const
{
    // Because the mouse hover entry event of the primitive is triggered in this area, but when the mouse is selected, the rotation button extended by the dotted line cannot trigger the mouse hover entry event, so special processing is required.
    // However, the actual drawing display is still based on the size of the primitive.

    // Set the drawing boundary of the primitive to be dAdjust pixels away from the primitive
    return  QRectF(m_rect.x(), m_rect.y() - (m_dLineLen + 2.0 * m_dCircleRadius),
                   m_rect.width(), m_rect.height() + (m_dLineLen + 2.0 * m_dCircleRadius)).
            adjusted(-m_dAdjust, -m_dAdjust, m_dAdjust, m_dAdjust);
}

3) Draw rectangular primitives and selected areas

void RectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    // Redraw function, draw rectangle
    Q_UNUSED(widget);

    // Empty rectangles are not drawn
    if(m_rect.isEmpty())
    {
        return;
    }

    // Set up brushes and brushes
    painter->setPen(QPen(Qt::black, 1));
    painter->setBrush(Qt::green);
    // draw rectangle
    painter->drawRect(m_rect);

    // Draw a virtual box when selected
    if (option->state & QStyle::State_Selected)
    {
        // Get the primitive drawing area
        QRectF rect = getRect();// this->boundingRect();

        // draw dashed box
        painter->setPen(QPen(option->palette.windowText(), 0, Qt::DashLine));
        painter->setBrush(Qt::NoBrush);

        // Sets the spacing of the dashed box from the drawing area, since brushes have width
        const qreal pad = painter->pen().widthF() / 2 + m_dAdjust;

        painter->drawRect(rect.adjusted(-pad, -pad, pad, pad));

        // Add a dotted line to connect a circle for rotation
        // draw lines
        painter->drawLine(rect.center().x(), rect.top() - m_dLineLen, rect.center().x(), rect.top());
        // draw circle
        painter->drawEllipse(rect.center().x() - m_dCircleRadius, rect.top() - m_dLineLen - 2.0 * m_dCircleRadius,
                             2.0 * m_dCircleRadius, 2.0 * m_dCircleRadius);
    }
}

2. Rotation processing

The code is as follows (example):

void RectItem::RotateRect(const QPointF &mousePos)
{
    // Set the center point as the origin
    QPointF originPos = this->getRect().center();
    // Extend two lines from the origin, the line connecting the point when the mouse is pressed and the point where the current mouse position is located
    QLineF p1 = QLineF(originPos, m_pressPos);
    QLineF p2 = QLineF(originPos, mousePos);
    // Rotation angle
    qreal dRotateAngle = p2.angleTo(p1);

    // Set the center of rotation
    this->setTransformOriginPoint(originPos);

	// Calculate the current rotation angle
    qreal dCurAngle = this->rotation() + dRotateAngle;
    while (dCurAngle > 360.0) {
        dCurAngle -= 360.0;
    }

    // Set the rotation angle
    this->setRotation(dCurAngle);

    // refresh display
    this->update();
}

3. After rotating the primitive, the mouse pulls up the floating icon

After the graphic element is rotated and then pulled up, when the mouse hovers into the pull-up area, the icon of the mouse will definitely change. I am lazy here and wrote a garbage algorithm. Friends who want to have a better experience can do it. Set the mouse icon as a picture, and achieve better effect by rotating the picture

void RectItem::RotateCursor(qreal dAngle, Qt::CursorShape eCursor)
{
    // The actual displayed mouse icon
    Qt::CursorShape eRealCursor = eCursor;

    // The rotation angle is between [0, 360°), only the change between [0, 180) needs to be considered

    while (dAngle > 180.0) {
        dAngle -= 180.0;
    }
    // [0, 30°) do not need to change

    if((0.0 <= dAngle && dAngle < 30.0) ||  (150.0 <= dAngle && dAngle < 180.0))
    {   // 1. If it is [0,30) or [150,180), do not process
        eRealCursor = eCursor;
    }
    else if((30.0 <= dAngle && dAngle < 60.0))
    {   // 2. If it is [30,60), then [lower left and upper right] -> [left and right], [upper left and right] -> [up and down]... and so on
        if(eCursor == Qt::SizeBDiagCursor)
        {
            eRealCursor = Qt::SizeHorCursor;
        }
        else if(eCursor == Qt::SizeBDiagCursor)
        {
            eRealCursor = Qt::SizeHorCursor;
        }
        else if(eCursor == Qt::SizeVerCursor)
        {
            eRealCursor = Qt::SizeBDiagCursor;
        }
        else if(eCursor == Qt::SizeHorCursor)
        {
            eRealCursor = Qt::SizeFDiagCursor;
        }
    }
    else if((60.0 <= dAngle && dAngle < 120.0))
    {   // 3. If it is [60,120), then [lower left and upper right] -> [upper left and right lower], [left and right] -> [up and down]... and so on
        if(eCursor == Qt::SizeBDiagCursor)
        {
            eRealCursor = Qt::SizeFDiagCursor;
        }
        else if(eCursor == Qt::SizeFDiagCursor)
        {
            eRealCursor = Qt::SizeBDiagCursor;
        }
        else if(eCursor == Qt::SizeVerCursor)
        {
            eRealCursor = Qt::SizeHorCursor;
        }
        else if(eCursor == Qt::SizeHorCursor)
        {
            eRealCursor = Qt::SizeVerCursor;
        }
    }
    else if((120.0 <= dAngle && dAngle < 150.0))
    {   // 3. If it is [120,150), then [lower left and upper right] -> [up and down], [upper left and right lower] -> [left and right]... and so on
        if(eCursor == Qt::SizeBDiagCursor)
        {
            eRealCursor = Qt::SizeVerCursor;
        }
        else if(eCursor == Qt::SizeFDiagCursor)
        {
            eRealCursor = Qt::SizeHorCursor;
        }
        else if(eCursor == Qt::SizeVerCursor)
        {
            eRealCursor = Qt::SizeFDiagCursor;
        }
        else if(eCursor == Qt::SizeHorCursor)
        {
            eRealCursor = Qt::SizeBDiagCursor;
        }
    }

    this->setCursor(eRealCursor);
}

4. Primitive displacement problem

For primitives with a rotation angle, after stretching, rotating the primitives again will cause displacement. For the specific principle, please refer to this article by another big guy: After QGraphicsItem is rotated, the coordinate change mechanism is analyzed , the writing is very detailed. If you want to see the phenomenon, just do not deal with it. We need to add the following code to deal with it after the mouse is pulled up.

void RectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    // Prevent the right mouse button to pop up the menu, left click to move
    if (event->button() == Qt::LeftButton && 	// left button pressed
            m_eMouseHandle != Mouse_None)  	// Only handle drag and lift
    {
        // To solve a rectangle with a rotation angle, after stretching and rotating again, the rotation center should still be the previous coordinate, and manually setting it as the center will cause a drift problem
        auto rr = this->getRect();
        auto angle = qDegreesToRadians(this->rotation());

        auto p1 = rr.center();
        auto origin = this->transformOriginPoint();
        QPointF p2 = QPointF(0, 0);

        p2.setX(origin.x() + qCos(angle)*(p1.x() - origin.x()) - qSin(angle)*(p1.y() - origin.y()));
        p2.setY(origin.y() + qSin(angle)*(p1.x() - origin.x()) + qCos(angle)*(p1.y() - origin.y()));

        auto diff = p1 - p2;

        this->setRect(rr.adjusted(-diff.x(), -diff.y(), -diff.x(), -diff.y()));
        this->setTransformOriginPoint(this->getRect().center());

        this->update();

        // Restore selection after dragging
        this->setSelected(true);
    }

    return QGraphicsItem::mouseReleaseEvent(event);
}

Summarize

I feel that there are many things to summarize. The next chapter will explain the coordinates of the primitives and the coordinates of the relative scene in detail!

Tags: Qt

Posted by alexszilagyi on Fri, 02 Sep 2022 04:12:25 +0530