Qt Graphics View Framework — темная сторона. Часть 2

    Начав свой путь, мы не останавливаемся и продолжаем изучать темные стороны документации. Где-то они могут быть характерны для всего Qt, а где-то присущи только Graphics View. Но так или иначе встреча с ними не всегда проходит безболезненно.

    Дело №3


    Мы хотим написать свой item, в качестве примера пишем следующий код
    MainWindow.h
    class MainWindow : public QWidget
    {
        Q_OBJECT
        
    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
    private:
        QGraphicsScene *m_scene;
        QGraphicsRectItem *m_rect;
        QGraphicsItem *m_cross;
        QGraphicsView * graphicsView;
    };
    

    MainWindow.cpp
    class CrossItem: public QGraphicsItem
    {
    
    public:
        QRectF boundingRect() const
        {
            return QRectF(0, 0, 30*scale(), 30*scale());
        }
        void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
        {
            Q_UNUSED(option) Q_UNUSED(widget);
            painter->setPen(QColor(Qt::red));
            painter->drawLine(10, 0, 20, 0);
                painter->drawLine(20*scale(), 0*scale(), 20*scale(), 10*scale());
                painter->drawLine(20, 10, 30, 10);
                painter->drawLine(30, 10, 30, 20);
                painter->drawLine(30, 20, 20, 20);
                painter->drawLine(20, 20, 20, 30);
            painter->drawLine(20, 30, 10, 30);
                painter->drawLine(10, 30, 10, 20);
                painter->drawLine(10, 20,  0, 20);
                painter->drawLine( 0, 20,  0, 10);
                painter->drawLine( 0, 10, 10, 10);
                painter->drawLine(10, 10, 10, 0);
        }
    };
    
    
    MainWindow::MainWindow(QWidget *parent) :
        QWidget(parent)
    {
        setLayout(new QGridLayout());
        graphicsView = new QGraphicsView();
            layout()->addWidget(graphicsView);
            graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
            graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        m_scene = new QGraphicsScene();
    
        m_cross = new CrossItem();
            m_scene->addItem(m_cross);
    //	  m_cross->setScale(2);
        m_rect = new QGraphicsRectItem(m_cross->boundingRect(), m_cross);
            m_scene->addItem(m_rect);
        m_scene->setSceneRect(m_rect->boundingRect());
        graphicsView->setScene(m_scene);
    }
    
    MainWindow::~MainWindow()
    {
    }
    

    Комплируем, запускаем, видим вписанный в крест квадрат. Раскоментируем строчку и пересоберем проект, чтобы после запуска увидеть все свои ошибки.
    Дело в том, что:
    painter->drawLine(20*scale(), 0*scale(), 20*scale(), 10*scale());
    

    и
    return QRectF(0, 0, 30*scale(), 30*scale());

    — горе от ума.

    Давайте рассмотрим как хранит QGraphicsItem данные о трансформациях:

    struct QGraphicsItemPrivate::TransformData
    {
        QTransform transform;
        qreal scale;
        qreal rotation;
        qreal xOrigin;
        qreal yOrigin;
        QList<QGraphicsTransform *> graphicsTransforms;
        bool onlyTransform;
    
        TransformData() :
            scale(1.0), rotation(0.0),
            xOrigin(0.0), yOrigin(0.0),
            onlyTransform(true)
        { }
    
        QTransform computedFullTransform(QTransform *postmultiplyTransform = 0) const
        {
            if (onlyTransform) {
                if (!postmultiplyTransform || postmultiplyTransform->isIdentity())
                    return transform;
                if (transform.isIdentity())
                    return *postmultiplyTransform;
                return transform * *postmultiplyTransform;
            }
    
            QTransform x(transform);
            if (!graphicsTransforms.isEmpty()) {
                QMatrix4x4 m;
                for (int i = 0; i < graphicsTransforms.size(); ++i)
                    graphicsTransforms.at(i)->applyTo(&m);
                x *= m.toTransform();
            }
            x.translate(xOrigin, yOrigin);
            x.rotate(rotation);
            x.scale(scale, scale);
            x.translate(-xOrigin, -yOrigin);
            if (postmultiplyTransform)
                x *= *postmultiplyTransform;
            return x;
        }
    };
    

    Все очень просто и бесхитростно, как мычание коровы на лугу.И собственно перед тем как рисовать сцена дергает метод computedFullTransform и, совершив еще пару шаманских действий, подсовывает это QPainter-у. Тут можем поставить себе на заметку, что если мы хотим делать что-то хитрое с формой нашего item-a, то нам следует использовать методы setScale, setPos, setRotation и setTransforms и более нам ничего не надо.

    Дело №4


    Хотим отслеживать перемещение мыши по item-у. Сразу предупреждаю — грабли здесь разложены буквально на каждом шагу. И били они меня очень больно и очень упорно. Но полный список мытарств описывать не буду. Остановлюсь только на ключевых моментах
    Перепишем Mainwindow.cpp следующим образом:
    #include "MainWindow.h"
    #include <QGridLayout>
    #include <QGraphicsSceneHoverEvent>
    #include <QDebug>
    class MyRect: public QGraphicsRectItem
    {
    public:
        MyRect(const QRectF &rect, QGraphicsItem *parent=0):QGraphicsRectItem(rect, parent){}
    protected:
        void hoverMoveEvent(QGraphicsSceneHoverEvent *event)
        {
            qDebug()<<"rect"<<event->pos();
            QGraphicsRectItem::hoverMoveEvent(event);
        }
    };
    
    class CrossItem: public QGraphicsItem
    {
    
    public:
        QRectF boundingRect() const
        {
            return QRectF(0, 0, 30, 30);
        }
        void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
        {
            Q_UNUSED(option) Q_UNUSED(widget);
            painter->setPen(QColor(Qt::red));
            painter->drawLine(10, 0, 20, 0);
                painter->drawLine(20, 0, 20, 10);
                painter->drawLine(20, 10, 30, 10);
                painter->drawLine(30, 10, 30, 20);
                painter->drawLine(30, 20, 20, 20);
                painter->drawLine(20, 20, 20, 30);
            painter->drawLine(20, 30, 10, 30);
                painter->drawLine(10, 30, 10, 20);
                painter->drawLine(10, 20,  0, 20);
                painter->drawLine( 0, 20,  0, 10);
                painter->drawLine( 0, 10, 10, 10);
                painter->drawLine(10, 10, 10, 0);
        }
    protected:
        void hoverMoveEvent(QGraphicsSceneHoverEvent *event)
        {
            qDebug()<<"cross"<<event->pos();
            QGraphicsItem::hoverMoveEvent(event);
        }
    };
    
    
    MainWindow::MainWindow(QWidget *parent) :
        QWidget(parent)
    {
        setLayout(new QGridLayout());
        graphicsView = new QGraphicsView();
            layout()->addWidget(graphicsView);
            graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
            graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        m_scene = new QGraphicsScene();
    
        m_cross = new CrossItem();
            m_rect = new MyRect(m_cross->boundingRect(), m_cross);
        m_cross->setAcceptHoverEvents(true);
            m_rect->setAcceptHoverEvents(true);
        m_scene->addItem(m_cross);
        m_scene->setSceneRect(m_rect->boundingRect());
        graphicsView->setScene(m_scene);
    }
    
    MainWindow::~MainWindow()
    {
    }
    

    Мы следуем общей логике, что события сцены — полные аналоги обычных событий. О чем нам радостно сообщает дока по QGraphicsSceneHoverEvent.

    Компилируем, запускаем и видим, что при перемещении мыши событие доходит только до прямоугольника. Лезем в доку, и где-то на задворках при описании зачухонного метода setAcceptHoverEvents читаем:

    Parent items receive hover enter events before their children, and leave events after their children. The parent does not receive a hover leave event if the cursor enters a child, though; the parent stays «hovered» until the cursor leaves its area, including its children's areas.

    If a parent item handles child events, it will receive hover move, drag move, and drop events as the cursor passes through its children, but it does not receive hover enter and hover leave, nor drag enter and drag leave events on behalf of its children.

    A QGraphicsWidget with window decorations will accept hover events regardless of the value of acceptHoverEvents().

    Итак, нам необходимо отслеживать события потомка. Осознав это и приняв во внимание прочее, ищем метод, который нам поможет. И такой находится, это setFiltersChildEvents(не путать схожим setHandlesChildEvents, на том пути нам славы не найти). Добавим его в конструктор:
    m_cross->setFiltersChildEvents(true);

    А также добавим фильтр в класс CrossItem:
        bool sceneEventFilter(QGraphicsItem *watched, QEvent *event)
        {
            if(event->type() == QEvent::GraphicsSceneHoverMove)
                qDebug()<<"cross"<<event;
            return false;
        }
    

    Компилируем, запускаем. Ура! Заработало!!!

    Итог


    К сожалению подвести конструктивный итог, как в прошлой статье сейчас у меня не получится. Скорее это недоумение — потратив значительные усилия на независимость item-а от всяких геометрических преобразований, разработчики Qt предложили нам очень странную систему доставки событий к item-ам. Хотя приведенный выше пример является скорее исключительным случаем, но одного взгляда на флаги в QGraphicsItem достаточно, чтоб сильно задуматься.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.