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.