Derek
Derek

Reputation: 11905

Scale image directly under mouse in Qt 4

Similar to google.com/maps, I would like it so that if my mouse is over a QGraphicsItem, then when I move the wheelmouse forward and back, the area of the image under the mouse is what is centered in my QGraphicsView.

Can I accomplish this in the wheelEvent method of the custom QGraphicsIte I have created?

I have some code like this

update();
qreal factor = 1.2;
if (event->delta() < 0)
  factor = 1.0 / factor;
scale(factor, factor);
scaleFactor *=factor;
this->scene()->setSceneRect(0,0,this->boundingRect().width(), this->boundingRect().height());

What can I add to this to give the desired effect?

As an example, in google maps, if you hold your mouse over utah, and keep zooming in with the wheel mouse, eventually Utah is the only thing left in the viewport.

Upvotes: 0

Views: 2296

Answers (3)

handle
handle

Reputation: 6369

Also see setTransformationAnchor( QGraphicsView::AnchorUnderMouse );

Upvotes: 1

ekerner
ekerner

Reputation: 5848

Th equation is like ,,,

margin = -half_overflow + (((center_offset - click_offset) * resize_ratio) - (center_offset - click_offset));

... and you have to do it for x and y.

Below Ill paste in some code which does it but keep in mind that this code deals with square images, so I use width for both width and height. For you to use this on rectangle images you will need to also grab the height and use that on all Y calculations where I have width, as well as setting all of vars which I am using like content_width etc. But the algorithm are all there and you can work it out from the code ...

        zoomChart: function(e)
        {
                var chart_max = 2048;
                var zoom_max = egapp.user.options.zoom_multiplier || 2; // >= 2
                var chart_bounds = $('#egapp_chart_bounds');
                var chart_imgs = chart_bounds.find('img');
                var width = chart_imgs.width();
                if (width == chart_bounds.width()) { // zoom in
                        // margin = -half_overflow + (((center_offset - click_offset) * resize_ratio) - (center_offset - click_offset));
                        chart_bounds.removeClass('egapp_zoom_in');
                        if (width == chart_max)
                                return;
                        chart_bounds.addClass('egapp_zoom_out');
                        var new_width = parseInt(width * zoom_max);
                        if (new_width > chart_max)
                                new_width = chart_max;
                        var ratio = new_width / width;
                        var moveX = moveY = -((new_width - width) / 2);
                        var chart_offset = chart_bounds.offset();
                        var offsetX = (chart_offset.left + (width / 2)) - e.pageX;
                        var offsetY = (chart_offset.top + (width / 2)) - e.pageY;
                        moveX += parseInt((offsetX * ratio) - offsetX);
                        moveY += parseInt((offsetY * ratio) - offsetY);
                        chart_imgs.animate({width: new_width+'px', height: new_width+'px', marginLeft: moveX+'px', marginTop: moveY+'px'}, 'fast');
                        chart_bounds.addClass('egapp_zoom_out');
                }
                else { // zoom out
                        var new_width = egapp.content_width - 10;
                        chart_imgs.animate({width: new_width+'px', height: new_width+'px', marginLeft: 0, marginTop: 0}, 'fast');
                        chart_bounds.removeClass('egapp_zoom_out').addClass('egapp_zoom_in');
                }
        },

The HTML is something like ...

<div id="egapp_chart_bounds" style="width: 608px; height: 608px;" class="egapp_zoom_in">
    <img id="egapp_timeline_chart" src="some.image" class="egapp_chart" style="width: 608px; height: 608px; margin-left: 0px; margin-top: 0px;">
    <img id="egapp_expression_chart_9" src="overlay.image" class="egapp_chart_overlay" style="width: 608px; height: 608px;">
</div>

And the CSS is like ...

#egapp_chart_bounds{
        overflow: hidden;
}
.egapp_chart, .egapp_chart_overlay{
        position: absolute;
}

.egapp_zoom_in{
        cursor: -moz-zoom-in;
        cursor: -webkit-zoom-in;
        cursor: zoom-in;
}
.egapp_zoom_out{
        cursor: -moz-zoom-out;
        cursor: -webkit-zoom-out;
        cursor: zoom-out;
}

Upvotes: 0

Stefan Monov
Stefan Monov

Reputation: 11732

Let's rephrase your aim in a more direct way. On the wheel event:

  1. The scene should zoom in/out
  2. The scene location under the mouse should remain under the mouse

You've implemented (1) with the scale call. What remains is (2). To scroll the view to a certain location, you need to do:

horizontalScrollbar().setValue(x);
verticalScrollbar().setValue(y);

This scrolls the scene so (x, y) is at the top-left corner of the view. But you want (x, y) to be at the mouse position (in relation to the QGraphicsView). So you need to scroll to (x-mx, y-my) where (mx, my) is the mouse position in relation to the QGraphicsView and (x, y) is the scene position that was under the mouse before the wheel event.

This is untested and may be wrong in the details (for example in the exact behavior of QScrollBar::setValue) but the math concept is correct so it should be enough for you to get it working.

Upvotes: 0

Related Questions