2014-04-11

Mouse cursor centered zooming in QGraphicsView

Zooming into and out of a QGraphicsView so that the position under the mouse cursor remains fixed is easy, right? Sadly, it isn't. I leave my best working approach here for you to enjoy and hopefully save you some time.

Before coming up with this, I browsed the interwebs and tried a few other things. I always ended up with not-working code or a slight drift while repeatedly zooming.

void GraphicsView::wheelEvent(QWheelEvent* e) {
        if ((e->modifiers()&Qt::ControlModifier) == Qt::ControlModifier
                        && e->angleDelta().x() == 0) {

                QPoint  pos  = e->pos();
                QPointF posf = this->mapToScene(pos);

                double by;
                double angle = e->angleDelta().y();

                if      (angle > 0) { by = 1 + ( angle / 360 * 0.1); }
                else if (angle < 0) { by = 1 - (-angle / 360 * 0.1); }
                else                { by = 1; }

                this->scale(by, by);

                double w = this->viewport()->width();
                double h = this->viewport()->height();

                double wf = this->mapToScene(QPoint(w-1, 0)).x()
                                - this->mapToScene(QPoint(0,0)).x();
                double hf = this->mapToScene(QPoint(0, h-1)).y()
                                - this->mapToScene(QPoint(0,0)).y();

                double lf = posf.x() - pos.x() * wf / w;
                double tf = posf.y() - pos.y() * hf / h;

                /* try to set viewport properly */
                this->ensureVisible(lf, tf, wf, hf, 0, 0);

                QPointF newPos = this->mapToScene(pos);

                /* readjust according to the still remaining offset/drift
                 * I don't know how to do this any other way */
                this->ensureVisible(QRectF(QPointF(lf, tf) - newPos + posf,
                                QSizeF(wf, hf)), 0, 0);

                e->accept();
        }

        if ((e->modifiers()&Qt::ControlModifier) != Qt::ControlModifier) {
                /* no scrolling while control is held */
                QGraphicsView::wheelEvent(e);
        }

}

Any Tipps to make this shorter/better are very much appreciated.

4 comments:

  1. have you tried setTransformationAnchor(QGraphicsView::AnchorUnderMouse) ?

    ReplyDelete
  2. Thanks for this article! I was having exactly the same issue. Interestingly, the code to ensureVisible() had no effect. What worked for me is in GraphicsView::wheelEvent(): 1) call setTransformationAnchor(QGraphicsView::AnchorUnderMouse), 2) call e->accept() and DO NOT call QGraphicsView::wheelEvent(), like your code does. Not doing #2 caused the view to pan undesirably, causing the same point to drift from under the cursor so the next wheel event centered a different point.

    ReplyDelete