Search code examples
qt5qgraphicsitem

Painting over QGraphicsItem without scaling


I have a custom class derived from the QGraphicsRectItem. All I need is to draw a frame over this item and some block on the bottom right. The width of the frame and size of the block are fixed, but the item can be scaled using QGraphicsItem::setTransform function. The problem is that when I try to map the bounding rect of the item to the view, I get an inaccurate output rect and this rect gets out of the bounds of the item. Please, look at the code below:

#ifndef TEST_H
#define TEST_H

#include "ui_Test.h"

#include <QGraphicsRectItem>

class MyItem : public QGraphicsRectItem
{
public:
    explicit MyItem(QGraphicsItem *parent = Q_NULLPTR);

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = Q_NULLPTR) Q_DECL_OVERRIDE;

private:
    void drawVolumeIndicator(QPainter *painter);
};

class Test : public QMainWindow, private Ui::TestClass
{
    Q_OBJECT

public:
    explicit Test(QWidget *parent = Q_NULLPTR);
};

#endif // TEST_H
#include "Test.h"

#include <QGraphicsScene>
#include <QGraphicsView>

MyItem::MyItem(QGraphicsItem *parent)
    : QGraphicsRectItem(parent)
{}

void MyItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    QGraphicsRectItem::paint(painter, option, widget);

    drawVolumeIndicator(painter);
}

void MyItem::drawVolumeIndicator(QPainter *painter)
{
    static const auto blockColor = QColor(QStringLiteral("#0097a7"));
    static const auto frameColor = QColor(QStringLiteral("#660097a7"));

    static Q_CONSTEXPR auto blockSize = QSize(12, 40);
    static Q_CONSTEXPR auto blockMargin = 2;
    static Q_CONSTEXPR auto frameWidth = 4;

    const auto outputRect = painter->transform().mapRect(boundingRect());

    painter->save();
    painter->resetTransform();

    // draw block
    const auto x = outputRect.right() - frameWidth - blockMargin - blockSize.width();
    const auto y = outputRect.bottom() - frameWidth - blockMargin - blockSize.height();
    painter->fillRect(QRect(QPoint(x, y), blockSize), blockColor);

    // draw frame
    painter->setBrush(Qt::transparent);
    painter->setPen(QPen(frameColor, frameWidth, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
    painter->drawRect(outputRect);

    painter->restore();
}

Test::Test(QWidget *parent)
    : QMainWindow(parent)
{
    setupUi(this);

    resize(200, 400);

    auto item = new MyItem;
    item->setBrush(Qt::lightGray);
    item->setPen(QPen(Qt::transparent));
    item->setRect(0, 0, 100, 100);

    // set scale for the item
    QTransform transform;
    transform.scale(1, 3);
    item->setTransform(transform);

    auto scene = new QGraphicsScene;
    scene->addItem(item);

    auto view = new QGraphicsView;
    view->setScene(scene);

    QMainWindow::setCentralWidget(view);
}

Here's how it looks without scaling:
without scaling

And here's how it looks with scaling:
with scaling


Solution

  • The solution is to set the null pen for the item, and also to adjust the output rect when drawing the frame. So, finally, my code looks like this:

    #include "Test.h"
    
    #include <QDebug>
    #include <QGraphicsScene>
    #include <QGraphicsView>
    
    MyItem::MyItem(QGraphicsItem *parent)
        : QGraphicsRectItem(parent)
    {}
    
    void MyItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
        QGraphicsRectItem::paint(painter, option, widget);
    
        drawVolumeIndicator(painter);
    }
    
    void MyItem::drawVolumeIndicator(QPainter *painter)
    {
        static const auto blockColor = QColor(QStringLiteral("#0097a7"));
        static const auto frameColor = QColor(QStringLiteral("#660097a7"));
    
        static Q_CONSTEXPR auto blockSize = QSize(12, 40);
        static Q_CONSTEXPR auto blockMargin = 2;
        static Q_CONSTEXPR auto frameWidth = 4;
        static Q_CONSTEXPR auto halfFrameWidth = frameWidth / 2;
    
        const auto outputRect = painter->transform().mapRect(boundingRect());
    
        painter->save();
        painter->resetTransform();
    
        // draw block
        const auto x = outputRect.right() - frameWidth - blockMargin - blockSize.width();
        const auto y = outputRect.bottom() - frameWidth - blockMargin - blockSize.height();
        painter->fillRect(QRect(QPoint(x, y), blockSize), blockColor);
    
        // draw frame
        painter->setBrush(Qt::transparent);
        painter->setPen(QPen(frameColor, frameWidth, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
        painter->drawRect(outputRect.adjusted(halfFrameWidth, halfFrameWidth, -halfFrameWidth, -halfFrameWidth));
    
        painter->restore();
    }
    
    Test::Test(QWidget *parent)
        : QMainWindow(parent)
    {
        setupUi(this);
    
        resize(200, 400);
    
        auto item = new MyItem;
        item->setBrush(Qt::lightGray);
        item->setPen(Qt::NoPen);
        item->setRect(0, 0, 100, 100);
    
        // set scale for the item
        QTransform transform;
        transform.scale(1, 3);
        item->setTransform(transform);
    
        auto scene = new QGraphicsScene;
        scene->addItem(item);
    
        auto view = new QGraphicsView;
        view->setScene(scene);
    
        QMainWindow::setCentralWidget(view);
    }