4

I am currently developing on an image viewer application. In this application I have a so called "pan-zoom" feature. This means that, when holding a certain mouse button, the user can zoom the image by panning forth and back.

It works fine, but as the feature is used, the mouse (naturally) moves up and down on the screen and will at some point reach the screen borders, which will make it stop. Instead I would like a behaviour where the mouse remains stationary and only the image magnification changes.

I tried to achieve this by invoking QCursor::setPos inside the QWidget::mouseMoveEvent and reset the mouse to the initial position after I have processed the move. It works so far as that the mouse is staying nearly stationary (it's wiggling forth and back). However, this will cause the mouse move event to be called again effectively annulling the adjustment I just made. This will result in a "wiggling" effect. Every adjustment will immediately be reversed.

Here is a code snipped, so you get an idea of what I am doing:

void ImageView::mouseMoveEvent(QMouseEvent *e) {
    //some code
    if (_panZooming) {
        //some code here

        //doesn't work as expected because it invokes this event again
        QCursor::setPos(mapToGlobal(_initialMousePosition.toPoint()));
    }
}

Is there a way to prevent the mouse move event to happen when using QCursor::setPos?

jww
  • 97,681
  • 90
  • 411
  • 885
bweber
  • 3,772
  • 3
  • 32
  • 57
  • `QCursor::setPos()` will cause the `mouseMoveEvent()`, of cause. Maybe you want to check if the cursor reach to the border of the screen, for example, if the cursor is on the top border of screen, keep zooming in/out no matter the cursor is moving or not. – helsinki Nov 11 '15 at 14:43
  • But how shall I obtain a delta then? The delta is calculated by the difference of the mouse's y coordinate at the initial position to the position it currently is. If the user keeps on moving upwards but the mouse no longer moves because it is on the edge of the screen, then there won't be any more difference in y-coordinate. – bweber Nov 11 '15 at 15:17
  • 1
    you can get the desktopwidget cursor position and hide the actual cursor for better user experience and on mouse release reset mouse position – Rafael Fontes Nov 11 '15 at 15:40

3 Answers3

1

I would have a flag to disable the event with will be false by default.

inside the event check if flag is false, then perform the zoom operation, set flag to true and reset cursor.

then the event will be called again and the flag will be true, so you set flag to false and you will be ready to handle the next event.

You just have to make sure you dont have two or more calls to the mouse event firing from the actual mouse before receiving the event from the setCursor call.

Rafael Fontes
  • 1,195
  • 11
  • 19
  • That probably would work, but somehow I would like a solution that's a bit more..."neat". This way I just might miss other mouse move events. – bweber Nov 11 '15 at 15:18
1

Assuming you're not calling the base class mouseMoveEvent, you should accept the event to mark it as being handled. By default, they're accepted when you re-implement the event, but it's clearer to be explicit. Call e->accept( ).

It's also recommended that if you handle any of the mouse events, you should handle all, with the possible exception of mouse double click.

Here's an example of keeping the mouse still, though on OS X there's an occasional flicker which appears to be due to how Qt is handling the events

class MyWidget : public QWidget
{
    void mousePressEvent(QMouseEvent* e)
    {
        m_pos = e->globalPos();
        m_lastPos = m_pos;
        QWidget::mousePressEvent(e);
    }

    void mouseMoveEvent(QMouseEvent* e)
    {
       // Calculate  relative zoom factor
       // scaled down ( / 10 ) for image zooming

        m_zoomFactor += ((float)e->globalPos().y() - m_lastPos.y()) / 10;

        QCursor::setPos(m_pos);
        m_lastPos = m_pos;
        e->accept();

        qDebug() << m_zoomFactor << endl;
    }

    void mouseReleaseEvent(QMouseEvent* e)
    {
        QWidget::mouseReleaseEvent(e);
    }

private:
    QPoint m_pos;
    QPoint m_lastPos;

    float m_zoomFactor = 0; // C++ 11 initialisation
};

If you're not bothered at keeping the mouse stationary, take out the QCursor::setPos call and this will still receive move events when the cursor is outside the widget, whilst the mouse button is held down.

However, it may be a better user experience hiding the cursor when zooming.

Community
  • 1
  • 1
TheDarkKnight
  • 27,181
  • 6
  • 55
  • 85
  • Hm..that doesn't really change anything. I also don't understand why it should. I mean, the cursor is moved by the operating system. My program just reacts to that move event. Accepting the event will not prevent the mouse from moving. – bweber Nov 11 '15 at 15:23
  • I do want to hide the cursor anyway. My problem also is not that the mouse is not keeping still, actually it is (that part works). I also know, that I will still receive mouse move events when I am outside the widget. The problem I want to prevent is what happens when I reach the edge of the screen. Then the mouse will no longer move, thus the y-coordinate of the mouse position will no longer change. Since I calculate the zoom-delta in the mouse move event out of that very y-coordinate, no further magnification will happen because the y coordinate stays the same. – bweber Nov 11 '15 at 16:43
  • Now, I tried to circumvent this by resetting the mouse to the initial position after every moues move event. However, this is handled as if the user would have moved the mouse back, and triggers a new mouse move event, effectively annulling the last move. This is what I have to prevent somehow. – bweber Nov 11 '15 at 16:45
  • If you keep the cursor at the same position, but hide it, the cursor will not go outside of the window border. So, what's the problem? Is it calculating the delta to use for the zoom because calling `QCursor::setPos()` is creating another move event? – TheDarkKnight Nov 11 '15 at 17:02
  • Yes. Basically this is what happens: Mouse moves +5px > mouse move event happens and zoom is increased by value equivalent to +5px > now cursor is 5px further up than initially > call `QCursor::setPos(initialPos)` > cursor is moved to inital position which is -5px > mouse move event happens and zoom is decreased by value equivalent to those -5px > everything is at start again. – bweber Nov 11 '15 at 18:25
  • It's all relative! Now I understand your issue; I've updated the code example, which calculates a zoom factor, based on the movement of the y-axis of the mouse. – TheDarkKnight Nov 12 '15 at 09:28
  • I tried it like that and it actually works. However, the zooming itself then isn't as smooth any more, as it was before. Instead, it's kind of jaggy. That is probably because too many mouse events are skipped inbetween. I think I'll have to look for another way or leave it as it is. – bweber Nov 15 '15 at 10:42
  • "It actually works" - great, then this answers your initial question. I suspect that the movement isn't smooth due to other factors, but may be linked to the rate in which you update the zoom factor. If it's still a problem, I suggest asking a separate question on SO to discuss this and include the code of how you update and scale the image. – TheDarkKnight Nov 16 '15 at 09:29
  • Well, with the inital implementation it works smooth. So the zoom implementation itself is fine, I think. I'll mark your answer as accepted, although I decided not to implement it like that because of aforementioned issues. – bweber Nov 16 '15 at 11:38
0

Don't use event->pos() in mouse events, use QCursor::pos() intead and check if it changed. Like this:

void MyWidget::mousePressEvent(QMouseEvent *)
{
    mPrevPos=QCursor::pos();
    mMoving=false;
}

void MyWidget::mouseMoveEvent(QMouseEvent *)
{
    auto cursorPos=QCursor::pos();
    if(mPressedPos==cursorPos){
        return;
    }
    if(!mMoving
        && (cursorPos-mPrevPos).manhattanLength()>QApplication::startDragDistance()){
        mMoving=true;
    }
    if(mMoving){
        auto diff=cursorPos-mPrevPos;

        // move something using diff

        QCursor::setPos(mPrevPos);
    }
}

void MyWidget::mouseReleaseEvent(QMouseEvent *)
{
    mMoving=false;
}
void MyWidget::leaveEvent(QEvent *)
{
    mMoving=false;
}