Qt: Proper Method to Implement Panning(Drag) - c++

QGraphicsview has a method setDragMode(ScrollHandDrag) to enable panning with the left mouse click. But I wanted to enable panning when the mouse wheel is clicked(the middle button) and created the following custom implementation to pan:
//Within a custom derived class of QGraphicsView
//pan is true when wheel is clicked and false when released
//last Pos is defined somewhere else in the class
void GridView::mouseMoveEvent(QMouseEvent *event){
if(pan){
QPointF currentPos = event->pos();
QPointF relPos = currentPos - lastPos;
lastPos = currentPos;
//this is what creates the panning effect
translate(relPos.x(), relPos.y());
}
QGraphicsView::mouseMoveEvent(event);
}
This works fine for the most part. But for example, if I scale the identity matrix by 1,000,000 this method fails and stops panning (as if the view got stuck). This problem doesn't arise when I use setDragMode()
What would be the proper custom implementation of setDragMode() so it is enabled by the wheel click?

This works for me... it tricks Qt into thinking the user is pressing the left mouse button when he's really pressing the middle one.
void GridView :: mousePressEvent(QMouseEvent * e)
{
if (e->button() == MidButton)
{
QMouseEvent fake(e->type(), e->pos(), LeftButton, LeftButton, e->modifiers());
QGraphicsView::mousePressEvent(&fake);
}
else QGraphicsView::mousePressEvent(e);
}
void GridView :: mouseReleaseEvent(QMouseEvent * e)
{
if (e->button() == MidButton)
{
QMouseEvent fake(e->type(), e->pos(), LeftButton, LeftButton, e->modifiers());
QGraphicsView::mouseReleaseEvent(&fake);
}
else QGraphicsView::mouseReleaseEvent(e);
}

Related

How to disable scrolling in QGraphicsView?

I am trying to implement mouse wheel zoom in/out. It works BUT when I zoom in/out, the image is getting smaller AND scrolls up/down at the same time with zoom function. Look like events exist at the same time and work together.
I cannot find how to disable scrolling with mouse wheel. May be there is a way to make possible scrolling only with mouse cursor (by clicking on scrollbar).
I was overriding the main method of mouse wheel but it was causing the effect I wrote above
void MainWindow::wheelEvent( QWheelEvent* event )
Solved by using event filter. The code below provides zoom in/out with holding ctrl button.
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::GraphicsSceneWheel)
{
ui->GV_image->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
double scaleFactor = 1.15;
bool ok = QApplication::keyboardModifiers() & Qt::ControlModifier;
if (ok)
{
QGraphicsSceneWheelEvent *scrollevent = static_cast<QGraphicsSceneWheelEvent *>(event);
if (scrollevent->delta() > 0)
{
ui->GV_image->scale(scaleFactor, scaleFactor);
}
else
{
ui->GV_image->scale(1/scaleFactor, 1/scaleFactor);
}
}
event->accept();
return true;
}
return false;
}
Put this line into your constructor or other your init function
QGraphicsView *GV_image;
...
ui->GV_image->scene()->installEventFilter(this);

QGestureEvent for mouse

I write desktop application that works with map,
and I want to react on pan and long press events.
It is possible to use QGestureEvent on Qt/Linux/X11 with ordinary mouse?
I took Qt gesture example, it works on tablet,
but not reaction on press left mouse button and move (I expect that application recognizes it as tap or swipe event).
Then I added to Qt gesture example app.setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents, true); at main and such code to imagewidget.cpp:
void ImageWidget::mousePressEvent(QMouseEvent *e)
{
e->ignore();
}
void ImageWidget::mouseReleaseEvent(QMouseEvent *e)
{
e->ignore();
}
void ImageWidget::mouseMoveEvent(QMouseEvent *e)
{
e->ignore();
}
this code still works on tablet, but again no reaction on mouse on Linux/X11.
Any way to enable qgesture on linux/x11, should I write my own gesture recognition
for mouse?
The official way to make gestures out of mouse events in Qt is deriving from the QGestureRecognizer class, which allows to listen to relevant mouse events, set gesture properties accordingly, then trigger the gesture (or cancel it).
Here follows an example for pan gestures only, just to give an idea of what has to be done.
Have a QGestureRecognizer subclass like this:
#include <QGestureRecognizer>
#include <QPointF>
class PanGestureRecognizer : public QGestureRecognizer
{
QPointF startpoint;
bool panning;
public:
PanGestureRecognizer() : panning(false){}
QGesture *create(QObject *target);
Result recognize(QGesture *state, QObject *watched, QEvent *event);
};
The create method has been overridden to return a new instance of our gesture of interest:
QGesture *PanGestureRecognizer::create(QObject *target)
{
return new QPanGesture();
}
The recognize method override is the core of our recognizer class, where events are passed in, gesture properties set, gesture events triggered:
QGestureRecognizer::Result PanGestureRecognizer::recognize(QGesture *state, QObject *, QEvent *event)
{
QMouseEvent * mouse = dynamic_cast<QMouseEvent*>(event);
if(mouse != 0)
{
if(mouse->type() == QMouseEvent::MouseButtonPress)
{
QPanGesture * gesture = dynamic_cast<QPanGesture*>(state);
if(gesture != 0)
{
panning = true;
startpoint = mouse->pos();
gesture->setLastOffset(QPointF());
gesture->setOffset(QPointF());
return TriggerGesture;
}
}
if(panning && (mouse->type() == QMouseEvent::MouseMove))
{
QPanGesture * gesture = dynamic_cast<QPanGesture*>(state);
if(gesture != 0)
{
gesture->setLastOffset(gesture->offset());
gesture->setOffset(mouse->pos() - startpoint);
return TriggerGesture;
}
}
if(mouse->type() == QMouseEvent::MouseButtonRelease)
{
QPanGesture * gesture = dynamic_cast<QPanGesture*>(state);
if(gesture != 0)
{
QPointF endpoint = mouse->pos();
if(startpoint == endpoint)
{
return CancelGesture;
}
panning = false;
gesture->setLastOffset(gesture->offset());
gesture->setOffset(mouse->pos() - startpoint);
return FinishGesture;
}
}
if(mouse->type() == QMouseEvent::MouseButtonDblClick)
{
panning = false;
return CancelGesture;
}
return Ignore;
}
}
Basically, we track mouse events, updating a couple of properties of our own (panning and startpoint) and the passed in gesture properties as well. For each mouse event type, we also return a QGestureRecognizer::Result . All other events are discarded (the method returns Ignore).
This code can be tested with the Image Gestures Example, though: just add the class to the project and this line in the ImageWidget constructor:
QGestureRecognizer::registerRecognizer(new PanGestureRecognizer());
This should let the user grab the picture and move it around, using a mouse.
Look into this image widget gestures example. (search for mouseDoubleClickEvent)
http://doc.qt.io/qt-5/qtwidgets-gestures-imagegestures-imagewidget-cpp.html
Based on that you need to reimplement the required mouse events.
MyWidget::MyWidget() {
---
---
}
bool MyWidget::event(QEvent *ev)
{
---
---
}
void MyWidget::mouseReleaseEvent(QMouseEvent *event)
{
}
void MyWidget::mouseMoveEvent(QMouseEvent *event)
{
}
And declare those two functions in header
void mouseReleaseEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);

Qt: accept mouseRelease only when there was no drag and drop

I have a subclass of QGraphicsView that should accept two kinds of mouse events: drag and drop for scrolling and simple clicks for item selection/highlight.
So I use
setDragMode(QGraphicsView::ScrollHandDrag);
to enable scrolling the view with the "Hand". And I have a function like this:
void GraphView::mouseReleaseEvent(QMouseEvent* e)
{
if (e->button() == Qt::LeftButton)
emit leftClicked(mapToScene(e->pos()));
else
emit rightClicked(mapToScene(e->pos()));
QGraphicsView::mouseReleaseEvent(e);
}
which creates signal whenever the user clicks on the scene.
However, the problem is: when I stop dragging and release the mouse button, the mouseReleaseEvent function is called, and if the cursor happens to be over some element of the scene, it will get highlighted.
How can I changed the mouseReleaseEvent function so that the signals are created only if there was no previous drag of the mouse?
If you use mousePress and mouseMove in combination with mouseRelease, then you can determine what mouse action the user just performed.
If you have mousePress then mouseRelease, then it must be a simple click.
If you have mousePress, mouseMove, and then mouseRelease, then it must be a drag.
The Qt documentation contains an example of interpreting combinations of mouse events in action in a scribbling program.
http://doc.qt.io/qt-4.8/qt-widgets-scribble-example.html
You can extend the principle to something like this:
private bool MyWidget::dragging = false;
private bool MyWidget::pressed = false;
void MyWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
pressed=true;
}
QGraphicsView::mousePressEvent(event)
}
void MyWidget::mouseMoveEvent(QMouseEvent *event)
{
if ((event->buttons() & Qt::LeftButton) && pressed)
{
dragging=true;
}
QGraphicsView::mouseMoveEvent(event)
}
void MyWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton && pressed) {
pressed = false;
if (dragging)
{
dragging = false;
}
else
{
// plain mouse click
// do something here
}
}
QGraphicsView::mouseReleaseEvent(event)
}
Note that this does not address edge cases where a user's mouse action is performed only partially inside the widget. I must also admit that I am relatively new to Qt and have not yet used ScrollHandDrag, but this is how one would go about identifying a certain combination of mouse events.

Qt how to check which mouse button is pressed

I have problems in PySide while trying to determine which mouse button is pressed in event function. I need it in particular for ignoring mouse move event, because it's doing job on both mouse buttons, left and right.
I want to ignore mouse move event if the right button on scene is pressed. Any help?
All of mouse events have two methods (button and buttons) to determine which of buttons are pressed. But for only move event the documentation says:
Note that the returned value is always Qt::NoButton for mouse move events.
for mouseMoveEvent you should use buttons method.
void mouseMoveEvent(QMouseEvent *e)
{
if(e->buttons() == Qt::RightButton)
qDebug() << "Only right button";
}
In order to ignore move events you need to do this work in eventFilter of course.
QApplication::mouseButtons() will return the status of mouseButton,
so, you can get the status of mouse in KeyPressEvent.
you can check, which mouse button is pressed via Qt::RightButton. Sorry for c++ code, but i hope, you would understand idea anyway:
void mousePressEvent(QMouseEvent *event)
{
if (event->button()==Qt::RightButton){
qDebug() << "right button is pressed
}
}
You could use a boolean:
void mousePressEvent(QMouseEvent *event)
{
if (event->button()==Qt::RightButton){
qDebug() << "right button is pressed
pressed=true; //<-----
}
}
and on mouseMoveEvent
void GLWidget::mouseMoveEvent(QMouseEvent *event)
{
float dx = event->x() - lastPos.x(); // where lastpos is a QPoint member
float dy = event->y() - lastPos.y();
if (dx<0) dx=-dx;
if (dy<0) dy=-dy;
if (event->buttons() & Qt::LeftButton) { //if you have MOVEd
...do something
}
if (event->buttons() & Qt::RightButton) {
if (pressed==true) return;
else{
...do
}
}
}
On mouserelease you have to set pressed=false; ( "pressed" must be a member of the class)
Hope it helps,let me know

how to pan images in QGraphicsView

I am currently able to load my image into a grahpics scene, and then again into a QGraphicsViewer.
I am able to implement a zoom feature by dtecting a QEvent::Wheel and then calling the graphicsViews's scale() function.
However, I can't seem to figure out how to get the pan functionality working. I basically want to detect when a mouse has clicked down on the image, and then move the image left, right, up or down along with the mouse.
As of right now, I basically have a MouseFilter class that is detecting events, and doing different things depending on the event type. I attached that listener to the QGraphicsView object
In case someone is wondering how to do it on their own, it's actually quite simple. Here's the code from my app:
class ImageView : public QGraphicsView
{
public:
ImageView(QWidget *parent);
~ImageView();
private:
virtual void mouseMoveEvent(QMouseEvent *event);
virtual void mousePressEvent(QMouseEvent *event);
virtual void mouseReleaseEvent(QMouseEvent *event);
bool _pan;
int _panStartX, _panStartY;
};
You need to store the start position of the drag, for example like this (I used the right button):
void ImageView::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::RightButton)
{
_pan = true;
_panStartX = event->x();
_panStartY = event->y();
setCursor(Qt::ClosedHandCursor);
event->accept();
return;
}
event->ignore();
}
Also, you need to clear the flag and restore the cursor once the button is released:
void ImageView::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::RightButton)
{
_pan = false;
setCursor(Qt::ArrowCursor);
event->accept();
return;
}
event->ignore();
}
To actually manage the drag, you need to override the mouse move event. QGraphicsView inherits a QAbstractScrollArea, and its scrollbars are easily accessible. You also need to update the pan position:
void ImageView::mouseMoveEvent(QMouseEvent *event)
{
if (_pan)
{
horizontalScrollBar()->setValue(horizontalScrollBar()->value() - (event->x() - _panStartX));
verticalScrollBar()->setValue(verticalScrollBar()->value() - (event->y() - _panStartY));
_panStartX = event->x();
_panStartY = event->y();
event->accept();
return;
}
event->ignore();
}
QGraphicsView has build-in mouse-panning support. Set correct DragMode and it will handle the rest. You do need the enable scroll bars for that to work.
neuviemeporte solution requires to subclass a QGraphicsView.
Another working drag implementation can be obtained without subclassing the view using eventFilter. If you don't need to customize other behaviors of the QGraphicsView, this technique will save you some work.
Let's say your GUI logic is maintained by a QMainWindow subclass and the QGraphicsView & QGraphicsScene are declared as a private members of this subclass. You would have to implement the eventFilter function as follows:
bool MyMainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == scene && event->type() == Event::GraphicsSceneMouseMove)
{
QGraphicsSceneMouseEvent *m = static_cast<QGraphicsSceneMouseEvent*>(event);
if (m->buttons() & Qt::MiddleButton)
{
QPointF delta = m->lastScreenPos() - m->screenPos();
int newX = view->horizontalScrollBar()->value() + delta.x();
int newY = view->verticalScrollBar()->value() + delta.y();
view->horizontalScrollBar()->setValue(newX);
view->verticalScrollBar()->setValue(newY);
return true;
}
}
return QMainWindow::eventFilter(obj, event);
}
To filter events from the QGraphicsScene, you'll have to install MyMainWindow as an eventFilter of the scene. Perhaps you could do this in the same function where you setup your GUI.
void MyMainWindow::setupGUI()
{
// along with other GUI stuff...
scene->installEventFilter(this);
}
You can extend this idea to replace the cursor with the drag "hand" as previously shown.