Why do I have trouble changing mouse cursors in my implementation of an interactive QGraphicsView in Qt?
Asked Answered
B

2

1

I need to display MDI windows containing images in my application. I wanted to be able to drag-scroll the images using the right mouse button, zoom them using the mouse wheel, and also create polygon-shaped area-of-interest masks over them. To this end, I created a custom QGraphicsView-derived class (ImageView) which reimplements some mouse events. I also have a QGraphicsPixmapItem-derived class (ImageItem) for implementing hover events which update a cursor pixel-position indicator in the application's interface. Here's the outline (doesn't yet implement the mask polygons):

class ImageView : public QGraphicsView
{
    Q_OBJECT
public:
    ImageView(QWidget *parent) : QGraphicsView(parent), _pan(false), _panStartX(0), _panStartY(0), 
        _scale(1.2), _scene(NULL), _imgItem(NULL)
    {
        _scene = new QGraphicsScene(this); 
        _scene->setBackgroundBrush(Qt::lightGray);
        setScene(_scene);
        _imgItem = new ImageItem(this);
        _scene->addItem(_imgItem);
    }

    void showImage(const QString &path)
    {
        _imgItem->loadImage(path);
        setSceneRect(0, 0, _imgItem->imageWidth(), _imgItem->imageHeight()); 
    }

    void startAoi(AoiType type)
    {
        _imgItem->setAcceptHoverEvents(true);
        _imgItem->setCursor(Qt::CrossCursor);
        // explicit mouse tracking enable isn't necessary
    }

    void stopAoi()
    {
        _imgItem->unsetCursor();
        _imgItem->setAcceptHoverEvents(false);
    }

public slots:
    void zoomIn() { scale(_scale, _scale); }
    void zoomOut() { scale(1/_scale, 1/_scale); }

protected:
    void ImageView::mousePressEvent(QMouseEvent *event)
    {
        // enter pan mode; set flag, change cursor and store start position
        if (event->button() == Qt::RightButton)
        {
            _pan = true;
            _panStartX = event->x();
            _panStartY = event->y();
            setCursor(Qt::OpenHandCursor);
            // accept the event and skip the default implementation?
            event->accept();
            return;
        }

        // should do event accept here?
        event->ignore();
    }

    void ImageView::mouseReleaseEvent(QMouseEvent *event)
    {
        // leave pan mode on right button release; clear flag and restore cursor
        if (_pan && event->button() == Qt::RightButton)
        {
            _pan = false;
            unsetCursor();
            event->accept();
            return;
        }

        // in the future, left clicks will add vertices to a mask polygon
        event->ignore() // ?
    }

    void ImageView::mouseMoveEvent(QMouseEvent *event)
    {
        // pan-mode move; scroll image by appropriate amount
        if (_pan)
        {
            scrollBy(_panStartX - event->x(), _panStartY - event->y());
            _panStartX = event->x();
            _panStartY = event->y();
            event->accept();
            return;
        }

        // generic mouse move, hover events won't occur otherwise.
        QGraphicsView::mouseMoveEvent(event);
        // need to accept or ignore afterwards?
    }

    void ImageView::wheelEvent(QWheelEvent *event)
    {
        // disallow zooming while panning
        if (_pan)
        {
            event->ignore();
            return;
        }

        // handle mouse wheel zoom
        // perform scaling
        if (event->delta() > 0) zoomIn();
        else zoomOut();
        event->accept();
        return;
    }


    bool _pan;
    int _panStartX, _panStartY;
    const qreal _scale;
    QGraphicsScene *_scene;
    ImageItem *_imgItem;
};

class ImageItem : public QGraphicsPixmapItem
{
public:
    ImageItem(ImageView *view) : QGraphicsPixmapItem()
    {
    }

    void loadImage(const QString &path)
    {
        _pixmap.load(path);
        setPixmap(_pixmap);
    }

protected:
    virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event)
    {
        // update label with position in GUI here
    }

    QPixmap _pixmap;
};

My first problem is that everything works fine before I activate startAoi() in the view (connected to a button in the GUI which the user presses). Before that, panning works fine, with the mouse cursor changing into an open hand. After I activate the AOI mode, the cursor changes into a cross while over the image, and pressing and dragging the right button pans the view, but doesn't change the cursor, as if the image item's cursor takes precedence over the view's cursor. After I deactivate the AOI mode, the cross cursor disappears, hover events are no longer triggered, but this time while pan still works, I'm stuck with the plain arrow cursor while dragging.

EDIT: Basically, it looks like for a QGraphicsItem, unsetCursor() doesn't remove the cursor per se, but rather changes it to the standard arrow, which is persistent and overrides the parent view's cursor.

The other problem is I'm not sure I have the whole mouse processing laid out correctly. For example, I don't know if I should process the panning and zooming in the mouse events overrides of the view, the scene, or the image item. I figured since because they are global to the whole view (which will contain other objects in the future), this should belong in the top item - the view. Also, I'm not sure when I should call accept() and ignore() on the events, what this actually does, and when I should call the parent classes' implementations of mouse events. Any insight will be greatly appreciated.

Boneblack answered 10/3, 2011 at 1:23 Comment(0)
O
1

I have created a similar image manipulation application some months ago: image drag, zoom-in/out + different custom tools to draw (add) specific items on a QGraphicsView. I used only the setCursor() function - not the unsetCursor().

I created a timer and I set the cursor-shape inside the timer-event function according to a custom "Tool-Type" flag stored in my GraphicsView subclass and taking into account the current state of keyboard & mouse. I think this is more convenient as:

a) It allows you to set the cursor-shape even if the user does not move the mouse or click on a specific object - e.g. if a "zoom tool" is selected you can set the cursor to a (+) [zoom-in cursor]. If the user holds-down the Ctrl-Key you can set the cursor to a (-) [zoom-out cursor]

b) There is no need to store any cursor states: You just set the correct cursor-shape according to currently selected tool and the mouse/keyboard state.

c) It's the easiest way to make your cursor-shape to quickly respond to all keyboard/mouse/tool-change events

I hope this timer idea can help you to overcome all cursor-shape related problems. A timer with a 30 msec interval it will be just fine.

Opine answered 18/3, 2011 at 6:3 Comment(0)
A
1

Looks like you hit this bug:

https://bugreports.qt-project.org/browse/QTBUG-4190

Workaround is to set the cursor on the view rather than the item

Ankara answered 2/5, 2013 at 11:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.