Tracking mouse cursor in QWidget - c++

I am using the mouseMoveEvent to track the position of the mouse cursor in a simple QT app. My problem is that I want the mouseMoveEvent to fire only when the cursor is in a 400x400 QWidget. Right now it is firing no matter where the mouse is. Here is my code...
void IPA2::mouseMoveEvent(QMouseEvent * event) {
cout << event->x() << endl;
cout << event->y() << endl;
}
IPA2 is the name of my class. The ui was created in designer mode.

If I understand you correctly, you may just perform a check here like if (x,y in range) do_something.
Another way would be to create a fake widget with 400x400 dimensions and reimplement it's mouse event.
The third (probably an overkill) is to use event filters (see here).
Update:
You can't just "easily" handle the mouse events using the Qt Designer. Each .ui scheme is almost always paired up with the corresponding implementation for that scheme. This is where your handling should be done.
Qt Designer is great for automatic signal-slot handling, but mouseMoveEvent is an event and has nothing to do with the slot system.
I would say how would I implement this and you can choose (see the three possible ways before).
I would create some DummyWidget, which would have a 400x400 dimensions and custom virtual mouseMoveEvent method, which would actually handle the mouse movement.
In the constructor of my main window (which also does the .ui-based construction) I would say something like
dummy_widget_ = new DummyWidget(...);
// `dummy_widget_` is a private `DummyWidget*` member of the main window
and then probably would reposition it somewhere.
That's it - now when my main window is created, a dummy widget is added to it and every mouse movement at that widget is handled (because we've provided custom implementation).
Another moment, which is related to mouse events only: http://doc.qt.nokia.com/4.7/qwidget.html#mouseTracking-prop

Related

mouse tracking does not work In the widget in the main widget

I implemented showing button when mouse is under QTextEdit in the QWidget.
when I test code, I found it sometimes didn't work showing button
bug cause is that MouseMoveEvent doesn't work in Qwidget when I move in the QTextEdit.
I don't understand why MouseMoveEvent doesn't work...
here is my code
this->setMouseTracking(true); // this is QWidget
ui.textEdit_log->setMouseTracking(true); // this is textEdit
void QWidget::mouseMoveEvent(QMouseEvent* event) // reImplemented mouseMoveEvent
{
if (ui.textEdit_log->underMouse())
{
m_pButton->show();
}
else
{
m_pButton->hide();
}
}
Mouse tracking for QTextEdit will only work by subclassing QTextEdit and reimplementing it's mouseMoveEvent() from where you can pass the event up to the parent's mouseMoveEvent().
OR
Instead of using ui.textEdit_log->setMouseTracking(true) use ui.textEdit_log->setAttribute(Qt::WA_TransparentForMouseEvents) this will make your textedit_log invisible for mouse events. In this case you can use the childrenRect() function of your QWidget to determine if your mouse is over the textedit_log.
As far as software design goes, subclassing is a conceptually very heavy operation - it indicates a very deep coupling. It should be used with due concern for the maintainability impact it has. An almost no case should subclassing be used to merely observe the state of an object! - any API that forces you to do that is essentially broken.
Qt provides convenience "event listener" methods, but these methods are not meant to be used as external observers, i.e. you should never override them if all you want is to observe the objectfrom outside. The event filters should be used instead, since they introduce the weakest of possible couplings, and are very generic.
The mouse tracking events will be sent to the widget that tracks the mouse cursor - the editor widget. So no other widget will hear them. You have to implement QObject::filterEvent in a class of your choice (whatever class is convenient), then install that event filter on the editor widget. Your filter will intercept all events sent to the editor, including mouse move events. You should not subclass QTextEdit - use event filters instead!
In any case, the idea to do this via mouse movement tracking is overkill and has some overhead. Instead, look for QEnterEvent and QLeaveEvent - those should be sent to the editor widget without mouse tracking enabled. But still - do not intercept those via any subclassing!
Do note that QWidget is-a QObject, so the event filter can be a widget class, or a non-widget class.
I solved this problem like this
void Log::enterEvent(QEvent* event)
{
m_pButton->show();
}
void Log::leaveEvent(QEvent* event)
{
m_mousePos = this->mapFromGlobal(QCursor::pos());
QPoint logPos = mapToGlobal(this->normalGeometry().bottomLeft());
QPoint buttonPos = m_pButton->pos();
QPoint buttonRelatviePos = buttonPos - logPos;
// hide button when button is not in the button pos
if ((m_mousePos.x() < buttonRelatviePos.x()) or (m_mousePos.x() > buttonRelatviePos.x() + m_pButton->width()) or
(m_mousePos.y() < buttonRelatviePos.y()) or (m_mousePos.y() > buttonRelatviePos.y() + m_pButton->height()))
{
m_pButton->hide();
}
}

How to enable mouse tracking on a QWindow

I'm using a QWindow (Not a QMainWindow) with OpenGL. I need to use a QWindow to correctly control the OGL context.
I'm trying to follow the Scribble example to implement something similar to panning, but I can't find a paradigmatic way to trigger the mouseMoveEvent().
How can I get a "tooltip" effect where mouseMoveEvent() is constantly triggered, similar to setMouseTracking()?
It works fine for me. I created a test program with a MainWindow that inherits QWindow instead of QMainWindow, and handles the mouse move event to print the cursor position:
void MainWindow::mouseMoveEvent(QMouseEvent *e)
{
qDebug("%d, %d", e->pos().x(), e->pos().y());
}
It works, as I move the mouse I get events even without pressing any mouse buttons.
If mouse tracking is disabled (the default), the widget only receives mouse move events when at least one mouse button is pressed while the mouse is being moved.
you can call hasMouseTracking() or setMouseTracking() to control mouse's tracing state.
When mouse is traced, mouseMoveEvent() will be called, and you can reimpletment mouseMoveEven() to acquire mouse position, just as #sashoalm did.
BTW, mouse event is always transfered to your app, but filtered by it's parent or itself. You can reimpletment eventFilter() to code your own filter.

Change QWidget Parent During Mouse Event

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.

Qt mouse leave event while button is pressed

I need to detect if the mouse pointer leaves my custom widget even if a mouse button is pressed.
According to this post, Qt does not cause a leaveEvent in case a button is pressed, at least not in version 4.4. I am working with 4.7.3, but still do not get a leaveEvent in the described case. I also tried with the various drag-related events, but no luck. Does anyone have an idea how to deal with this?
Actually there is an even better option than using the mouse events of the parent widget: You can implement the mouseMoveEvent function of the child widget and get the QMouseEvent::pos() position which is relative to the top left corner of the widget. That means, if you know the size of the widget (use for example QWidget::rect()), you can compute within the widget if the mouse pointer is still on the widget or not without having to change the parent widget.
Well, I did something similar to #gregseth.
Write your own MouseLeaveEvent that is invoked from MouseMoveEvent
when the mouse's position is outside of the widget's boundary.
As Qt's documentation says "Mouse move events will occur only when a mouse button is pressed down"
I got the same problem. The only workaround I found is to manage the mouseMove event of the parent widget, and to check if the position of the cursor is inside or outside the boundaries of the widget you want the event on.

Qt -- pass events to multiple objects?

I basically have 3 layers (Window > Scene > View) that each need to handle a mouseMove event without blocking the others. It seems only the youngest child is getting the event though. I was hoping I could process the event and then call event->ignore() to pass the event back up the stack, but it doesn't seem to be working.
Some relevant code if you need it:
void EditorWindow::createScene() {
m_scene = new EditorScene(this);
m_view = new EditorView(m_scene);
// ...
}
void EditorScene::mouseMoveEvent(QGraphicsSceneMouseEvent* mouseEvent) {
printf("B\n");
// ...
}
void EditorView::mouseMoveEvent(QMouseEvent* event) {
printf("C\n");
event->ignore();
}
Only "C" is being printed. Note that EditorScene and EditorView receive different types of mouse events so it's not completely trivial to pass them around.
The EditorWindow also needs the mouse coordinates; currently I'm sending a signal from one of the children which is caught by the window... but it shouldn't really be necessary to relay it that way, should it?
Found this nice article. Calling ignore() tells Qt to find another receiver. Sounds like it should work, but perhaps it means an unrelated receiver. The proper way to propagate it is actually to call BaseClass::Event like so:
void EditorView::mouseMoveEvent(QMouseEvent* event) {
QGraphicsView::mouseMoveEvent(event); // propogate to parent widget
printf("C\n");
}
Now it's printing BCBCBC... which is great, but I can't seem to nudge it up one more level...
Another edit: It was being propogated up properly, I just didn't have setMouseTracking enabled.
QGraphicsView::mouseMoveEvent(event);
Doesn't propagate up to the parent -- it actually propagates down to the scene.
Here is what's happens -- QGraphicsView receives QMouseEvent, translates it into QGraphicsSceneMouseEvent and passes it to the scene. Scene then passes it to appropriate item or, in your case, prints "B". Event handler then returns back to EditorView and prints "C".
Then, if you explicitly ignore event (mouse move is accepted by default), Qt event handler will pass the event to parent of EditorView. So try ignoring after you print "C".
Another thing about mouse move is this:
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.
So make sure you have tracking enabled on parent of EditorView (or that you press buttons :)).
EDIT:
BTW, EditorScene is not a parent of EditorView. Well, it is in your code, but only in QObject meaning of parentship (memory management only).
QGraphicsScene and View don't have normal family relationship -- scene can have multiple views and those views are children of unrelated parents.
For window event propagation purposes you must have QWidget based parent. In fact, I'm pretty sure you reparent EditorView to EditorWindow, or one of its children (when you add it into layout).
INSTAEDIT:
For coordinates you want View itself to emit a signal. Both for decoupling reasons and because you probably want to show local coordinates of the view, and not of the parent window and not screen coordinates (right?). If you actually want scene coordinates, View is right choice too, because it knows transformation matrix.
Coordinates go like this:
Screen -> EditorWindow local -> EditorView local -> Scene transformed -> whatever item local transformed.
QGraphicsView::mousePressEvent( e ) in my mousePressEvent did the trick!