I have a strange issue that I have been unable to determine the cause. Basically, I created a 2D view with pan and zoom functionality and a scene with items that can moved with grid snapping. To move an item in the scene I extended Scene::mousePressEvent to get a pointer to the item and Scene::mouseMoveEvent to keep the item tracked on the cursor. To drop the item, I used Scene::mousePressEvent again. To pan, I extended View::mousePressEvent, View::mouseReleaseEvent, and View::mouseMoveEvent and to zoom I extended View::wheelEvent.
Now for the symptoms:
I start the application with an item in the Scene. If I click and hold, then move the mouse, the item moves as intended. As soon as I release the mouse button, the item stops moving. I can click to drop and the item is placed according to the drop code in Scene::mousePressEvent. Try again and still the item only moves when the mouse button is pressed.
Then comes the strange part: If I use the mouse wheel to zoom the View, everything performs as expected after that event. The mouse is clicked to select an item, it moves as I move the mouse and drops when I click again.
So the obvious solution:
wheelEvent(new QWheelEvent(QPointF(0,0),0,Qt::NoButton,Qt::NoModifier));
called at the creation of the View and everything works fine. It calls the extended View::wheelEvent with no change to the view and before the scene is even created, but afterwards the programs behaves as expected.
So I'm here to see if any of the excellent Qt experts out there can explain this strange behavior. Any comments or direction are appreciated.
In case it helps, here is the View::wheelEvent override code. tform is a QTransform with which I maintain zoom. Also, I've tried with and without the call to the base method but there is no change in behavior.
void SchematicView::wheelEvent(QWheelEvent* event)
{
// Scale the view / do the zoom
double scaleFactor = 1.1;
if(event->delta() > 0 && tform.m11() < max_zoom) {
tform.scale(scaleFactor,scaleFactor);
} else if (event->delta() < 0 && tform.m11() > min_zoom){
tform.scale(1.0/scaleFactor,1.0/scaleFactor);
}
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
setTransform(tform);
QGraphicsView::wheelEvent(event);
}
Without a SSCCE to look at and test with, it's hard to say for sure, but what you're describing sounds a lot like your mouseMoveEvent() callback is only getting called when the mouse button is being held down during the move. That, in turn, sounds a lot like the expected behavior for mouseMoveEvent(), as documented in QWidget::mouseMoveEvent():
If mouse tracking is switched off, mouse move events only occur if a
mouse button is pressed while the mouse is being moved. If mouse
tracking is switched on, mouse move events occur even if no mouse
button is pressed.
If that is indeed the problem, then a call to setMouseTracking(true) may get you the behavior you are looking for.
On a broader level, note that there are easier ways of obtaining the behavior you are trying to implement -- for example, to allow the user to drag and drop items in a QGraphicsScene, all you really have to do is call setFlags(QGraphicsItem::ItemIsMovable) on any QGraphicsItems that you want the user to be able to drag around. No hand-coding of event handlers is necessary unless you are trying to obtain some non-standard behaviors.
Related
I'm using Qt 5.8 on Ubuntu, running an application which handles a pinch gesture to zoom in or out an image QWidget. The problem is that sometimes, just after a pinch gesture, when interacting with other components, the image jumps. Specifically this happens when the "First Finger you put in the screen is the First one that is Removed" (FFFR from now on). One could say that this happens half of the times the pinch is performed.
I observed, by looking to the event logs, that when FFFR happens, a mouseReleaseEvent does not, but it does if the fingers are removed from the screen the other way around. So I can force a mouseButtonRelease when the pinch gesture is finishing:
if (gesture->state() == Qt::GestureFinished)
{
QMouseEvent eve( (QEvent::MouseButtonRelease), QCursor::pos(),
Qt::LeftButton,
Qt::LeftButton,
Qt::NoModifier);
qApp->sendEvent(mGraphicsView,&eve);
}
This solves my issue of the image jumping, but I still have a problem since the application still does not respond the next time I interact with it. It seems as if the application is still controlled by the pinch gesture: if I try to press a button I need to touch the screen twice, the first time just to "free" the application. I also have a mouse: if after a FFFR I move the mouse, I see another mouseReleaseEvent happen (after the first one I cause with the code above) and then I can actually press a button with my first screen touch.
This is a very strange behavior that you can easily replicate with a code example from the Qt documentation: https://doc.qt.io/qt-5/qtwidgets-gestures-imagegestures-example.html
Compile and run, selecting a folder from the dialog containing JPG or PNG images. You can experiment with the implemented gestures (pan, swipe, rotate and scale). If you perform a double click the image restets to the original view. However, if you perform a two finger gesture doing FFFR, the first time you try a double click nothing happens. In contrast, by removing the fingers from the screen the other way the image resets with the first double click, as it should. If you don't have a mouse you can double tap the screen for the same effect.
---- UPDATE ----
I think this requires an update... I have realised that the pinch gesture is not responsible for this behavior. Using the Qt code example (provided in the link above) I inserted in the class ImageWidget an event filter for QGestures. I call installEventFilter(this) in the constructor and define:
bool ImageWidget::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::Gesture)
{
gestureEvent(static_cast<QGestureEvent*>(event));
return true;
}else{
return QObject::eventFilter(obj,event);
}
}
Also, I removed the original call to the gesture handler gestureEvent. So now I'm filtering QGestures for the image widget but still handling them. Yet if I do FFFR, the application hangs!
This is a known Qt bug. It has been reported here, using the same Qt documentation example to show this effect, only including two zoom in/out buttons to illustrate the problem better. And also here, giving some explanation of the problem. Any ideas of how to go around this bug?
On Windows 7 to be specific, while I don't think it matters.
We have all seen this issue in countless desktop applications, especially games that tend not to use OS-supplied controls: when the screen changes programmatically under a motionless mouse cursor (as opposed to user moving the cursor to a new widget), they go out of sync. Either the cursor does not change or the widget is not painted as it should be with the cursor inside it - obviously the widget's mouse enter event is not triggered. If you shake the mouse a bit without even leaving the widget, the thing fixes itself.
Sadly, Qt 5.7 shares this widespread problem. The first solution to come to mind is to move the mouse programmatically to (0, 0) and back by Windows means. However, it's not cross-platform(ish). Any better ideas?
I don't know if your question is still actual
You can override method underMouse() with following code:
bool MyWidget::underMouse()
{
return rect().contains(mapFromGlobal(QCursor::pos()));
}
Events moveEvent or resizeEvent in the rest panels will be definitely triggered when splitter resizes panel. All you need is check if widget is under mouse and then invoke enterEvent() manually
I found two solutions.
The first one is a simple hack: Call widget->hide() and then immediately widget->show(). This will reevaluate and update the widget's visual state depending on whether it is under mouse cursor or not, even if the cursor has not moved. But I would not recommend this solution because it might have some unwanted side effects. Though I have not encountered any yet.
The second solution is better because it does not look like a hack and it probably does not have any side effects:
widget->setAttribute(Qt::WA_UnderCursor, qApp->widgetAt(QCursor::pos()) == widget);
widget->update();
The code is assumed to be called from widget's parent. But you can adjust it and calle it from any other place. Note: it is better to use QApplication::widgetAt() than widget->rect().contains(), which is suggested in another answers, because in the latter case we would get false positives for widgets which are overlayed by other widgets.
What is actually complicated, is to find the place in code from where you should call this. Because there can be many sources of the widget's motion - moving withing its parent, moving of parent(s), resizing, resizing of parent(s), scrolling etc. This is probably the reason why this would be too complicated to implement this to standard Qt widget library. It would be probably a performance killer in some scenarios. (my guess)
Just to show my usage: I am moving a whole container widget which contains many child widgets. Subsequently one of the child widgets may get under the cursor after the container is moved. So I call:
containerWidget->move(dx, dy); // this moves the container
for (QWidget *child : containerWidget->findChildren<QWidget*>())
{
child->setAttribute(Qt::WA_UnderMouse, qApp->widgetAt(QCursor::pos()) == child);
child->update();
}
I'm trying to create a detachable type style widget, like in the way Chrome tabs are detachable (class is called Tab). I have everything working, except for a bug where sometimes (maybe 50% of the time), the Tab object never gets the mouse release event, and stops getting mouse move events.
Essentially, the detaching system works by allowing drags in the mouse press/move/release functions, just like normal. The mouseMoveEvent checks the total distance moved from the start, and if over a certain amount, will start the "detaching" process. The detaching process involves setting the parent widget to 0 (top level widget, undecorated window), so the Tab object is pretty much floating above everything, under the mouse, and continues to be dragged along with it until released.
I ran through all the QEvent items being delivered, and I found that when this issue occurs, the QEvent::MouseMove items (and all mouse events after this) are being sent to the TabBar (the Tab object's original parent). This occurs directly after calling setParent(0) on the Tab.
Basic mouse handling overview:
void Tab::mousePressEvent(*) {
[set up some boolean, start positions, etc]
}
void Tab::mouseMoveEvent(*) {
[track the updated position]
if (positionChange > STATIC_AMOUNT)
detachTab();
}
void Tab::mouseReleaseEvent(*) {
[return the Tab to its original position, and set the parent back to the TabBar]
}
void Tab::detachTab() {
QPoint mappedPos = mapToGlobal(0, 0);
setParent(0); //The loss of MouseMove events occurs when this returns.
move(mappedPos);
show();
raise();
}
Here are the events that the Tab object receives (first row is QEvent type, second is the name)
[Tab::detachTab() started]
[setParent(0) started]
QEvent::Hide
QEvent::Leave
qApp QEvent::MouseMove [ TabBar ] <-- now the TabBar is soaking up the mouse events
QEvent::HideToParent
QEvent::ParentAboutToChange
QEvent::ParentChange
[setParent(0) returned]
....
Summed up: my draggable QWidget loses QEvent::MouseMove and QEvent::MouseButtonRelease events after having its parent set to 0.
Any advice would be really appreciated!
A bit tricky workaround. I didn't test it, it's just an idea.
When your mouse hovers draggable part of a widget you may create topmost widget (let's call it Shade) with Qt::FramelessWindowHint (and possible with Qt::WA_TranslucentBackground). You may manipulate with Shade apperance via reimplementing paintEvent. For example - draw content of original widget, or draw some transparent preview, etc.
Then you may resize a Shade during dragging, to show user that widget will be detached. You will not loose mouse capture.
When user release mouse - you remember position of Shade, destroy it and detach+move original widget.
Feel free to ask, if you want more details.
Here is similar question.
So you suppose to use QDocWidget and enforce stacking of this widgets using tabifyDockWidget.
I have an odd problem here.
I'm working on an application, and within one of my classes I'm monitoring my mouse events.
The weird thing is, my mouse move event will only get called if any mouse button is pressed.
I'm not even filtering for any button presses within the method; the method itself doesn't even get called unless I click on this object itself (the one that's monitoring it).
What generally causes this type of error to happen?
I'm not sure if it's relevant, but I have 2 different things monitoring my mouse inputs: 1) the main program monitoring the global mouse coordinates, and 2) an object within my program monitoring the mouse coordinates within itself.
Edit
So the problem has to be because mouse move event is generally used when people are dragging the cursor along the screen right?
My reason for not needing it like that is because I'm building a custom context menu of sorts, and I need to know when an item is hovered over.
It turns out that I didn't truly set everything within my class to enable mouse tracking.
I somehow thought if the class itself was set to have it enabled, I wouldn't need to set it to all the sub objects, but now I see how that wouldn't make any sense at all.
So just to clarify my solution:
The items that I needed to track my cursor's position needed to have
setMouseTracking(true);
I'm making my first game in Irrlicht (C++), an RTS with mouse control
and when you select a tile (by clicking on it) it lights up and some gui button appear on the screen (not in a gui window mind you, I like it this way):
http://i1139.photobucket.com/albums/n549/Adam_Halley-Prinable/Untitled2.png
However, since i switched to mouse control, the buttons wont register my mouse clicks. The click goes straight through the button and selects the tile behind instead:
http://i1139.photobucket.com/albums/n549/Adam_Halley-Prinable/Untitled3.png
Is there a way I can say "Buttons get top priority for clicks"?
I'm using MyEventReceiver, which i've messed around with to accept mouse clicks and that.
Thanks a bunch :D
If anyone else has the same problem, ill tell you how I solved it :)
Go through the MyEventReceiver.h and get rid of all the "return true;"'s in the mouse section.
Don't ask me why, but it works, and appears to have no side effects. Make sure you leave the "return false;" at the end of the section there.
Your event receiver fires before the GUI gets access to the event, if you want to pass it to the GUI then you can do this by manually posting it to the GUIEnvironment in your event receiver.
if (guienv->postEventFromUser(event))
return true; // abort because the gui wanted it
// .. pick nodes
// possibly post event to scene manager
return true; // remember to return true so gui/smgr don't get the event again