QT QGraphicsView странное поведение setCursor

Задача. Есть кастомный GraphicsView (наследник QGraphicsView). Хочу три режима:

  • Select - объекты сцены (items) выбираются и перемещаются мышью, эта возможность подсвечивается курсором "палец" присвоенном объектам. Собственно это штатный режим GraphicsView.
  • Zoom - рамка выделения мышью показывается и после отпускания мыши делает fitInView(...), но мышь с объектами не взаимодействует. Курсор всегда "линза" и на сцене и на объектах;
  • Hand move - мышь сдвигает сцену и не взаимодействует с объектами. Курсор рука.

Кроме этого, есть масштабирование колесом мыши, которое должно работать во всех режимах.

Для отключения воздействия мыши на объекты есть функция setInteractive(false), но она работает через блокировку событий мыши, из-за этого:

  • отключается нужная мне для масштабирования рамка (режим Zoom);
  • глючит масштабирование колесом.

Поэтому я перегружаю эту функцию так:

// Перегрузка отключения интерактивности
void  GraphicsView::setInteractive(bool on){
    QList<QGraphicsItem *> listItems = items();
    foreach (QGraphicsItem *item, listItems) {
        item->setFlag(QGraphicsItem::ItemIsSelectable, on);
        item->setFlag(QGraphicsItem::ItemIsMovable, on);
        if (on)
            item->setCursor(Qt::PointingHandCursor);
        else
            item->setCursor(cursor());
    }
};

т.е. пробегаюсь по списку items и:

  • отключаю/включаю им флаги ItemIsMovable, ItemIsSelectable;
  • переключаю курсор на "палец" если режим интерактивный или на взятый из GraphicsView если режим Zoom или Hand move.

Сами режимы переключаю слотами, реагирующими на меню и ToolBar:

  • Zoom - устанавливает свой курсор "линза", флаг режима и отключает интерактивность:
void GraphicsView::setZoomMode(bool on){
    if (on)
        setCursor(*cursorZoom);
    zoomMode = on;  // флаг - используется в событиях мыши
    setInteractive(!on);
    // ::QGraphicsView::setInteractive(!on); // Отключает нужную мне рамку выделения
    qDebug() << "Zoom mode - " << on;
};
  • Select - соответствует штатному интерактивному режиму QGraphicsView, поэтому только устанавливает курсор:
void GraphicsView::setSelectMode(bool on){
    if (on)
        setCursor(Qt::ArrowCursor);
    qDebug() << "Select mode - " << on;
};
  • Hand move - отключает интерактивность и настраивается штатными режимами QGraphicsView. Курсор в них уже встроен.
void GraphicsView::setHandMoveMode(bool on){
    if (on)
        setDragMode(QGraphicsView::ScrollHandDrag);
    else
        setDragMode(QGraphicsView::RubberBandDrag);
    setInteractive(!on);
    // ::QGraphicsView::setInteractive(!on); // Глючит масштабирование колесом
    qDebug() << "Hand move mode - " << on;
};

qDebug подтверждает что переключение режимов происходит как задумано - там сюрпризов нет.

В целом всё работает, но есть странные приколы с курсорами:

  • Если сначала выбрать режим Hand move, а потом переключиться на Zoom или Select, то соответствующие им курсоры отображаются правильно. А если не трогать Hand move и переключаться между Zoom и Select то 50х50 - могут переключиться, а могут нет. Если у вас вдруг всё переключается, то перезапустите программу и не трогайте Hand move. После его использования вероятность этого глюка сильно снижается, но не до нуля.
  • Когда курсор Zoom или Select не переключился, то он правильно отображается на полосах прокрутки и на объекте (!), который вообще то берёт свой курсор из GraphicsView(!) Т.е. он на самом деле установился, но почему-то не отображается на GraphicsView. invalidateScene(); не помогло.
  • В режиме Hand move курсор "рука" всегда правильно отображается на GraphicsView, но неправильно отображается на объекте.

Что это за цирк такой и как с ним бороться?

Проект здесь.


PS: этот проект можно использовать как пример:

  • Радиокнопок в меню и ToolBar (QActionGroup в С++ программе, хотя сами QAction созданы в Дизайнере. На мой взгляд так намного удобнее и нагляднее чем весь UI делать только через дизайнер или только через С++ и уж точно лучше ручной правки xml в форме дизайнера).
  • Подключения своего курсора из png файла. Формат png поддерживает прозрачность и поэтому не требуется маска. Встраивание через файл ресурсов позволяет встроить курсор в исполняемый файл и не таскать его рядом.
  • Кастомного GraphicsView (см. также).
  • Масштабирования GraphicsView колесом мыши и рамкой выделения. Здесь важно отметить что сдвиг экрана и соответственно масштабирование от положения мыши работают в пределах полос прокрутки. Если диапазон полос прокрутки не позволяет двигать экран, то сдвиг рукой невозможен, а масштабирование происходит от центра. Это особенность QT, там так задумано. Поэтому делайте сцену достаточно большого размера - именно её размер определяет диапазон полос прокрутки.

Исправленный проект без глюков курсора.


Ответы (1 шт):

Автор решения: needKVAS

Белый фон, который вы видите, на самом деле не является виджетом QGraphicsView. Это его QGraphicsView::viewport(). И именно на него setDragMode устанавливает новый курсор, а курсор основного виджета остаётся неизменным. Всё наблюдаемое вами поведение исходит из этого.

Если вы будете устанавливать курсор на viewport, ваш результат будет ближе к желаемому:

viewport()->setCursor(*cursorZoom);
viewport()->setCursor(Qt::ArrowCursor);

И получать курсор соответственно нужно так:

item->setCursor(viewport()->cursor());
→ Ссылка