EnterEvents not sent to QLabel, why?
-
Hi to everyone;
the code I posted below is a simplification taken from a solitaire game project;
You can try it, copy and paste it in Qt editor;
I'll describe briefly the situation along with the issue:
You can see three labels: Label1, Label2, Label3 (I'll call them L1, L2, L3); each object (class label ) inherits from QLabel and reimplements some protected methods (QMouseEvent handlers) which enable drag & drop actions; both L1 and L2 can be dragged but they don't accept drop actions; L3, on the contrary, can't be dragged, yet it accepts drops; dropping label X on label Y will cause X to move on top of Y if Y accepts drops (otherwise X will return back to its starting position);
An enter event is sent to the label when the mouse cursor enters it, while a leave event is sent to the label when the mouse cursor leaves it.
Notice that when it's ok to drag, the cursor changes shape to Qt::OpenHandCursor (the standard cursor is Qt::ArrowCursor, set after mouseReleaseEvents and LeaveEvents);
And here's the problem: drag L1 on top of L2 (clearly L1 will move back to its original position) and release without moving the cursor; now the cursor is over L2, but we entered L2 for the first time, hence Qt is supposed to deliver an enterEvent to L2 and a leaveEvent to L1; however, this won't happen (unless you move the mouse), so the cursor remains Qt::ArrowCursor; all the same we are able to drag L2; Why didn't Qt send an enter event to L2, having the cursor left L1 and entered L2? How can I fix this?
A similar problem arises after dropping L1 (or L2) on top of L3. Now the cursor is over L1, but we already entered it, so Qt won't deliver an enter event, hence the cursor remains Qt::ArrowCursor. I want the cursor to change shape to Qt::OpenHandCursor, since we can actually drag L1;
Here's what I tried: you can send an enterEvent using QApplication::sendEvent(...); so it suffices to check which label is under cursor (I wrote the code for labelAtPos() since QApplication::widgetAt() returns the top level widget, that is the label currently dragged, while I need the one beneath it) and send it an enter event. But this is bad because after you move the mouse Qt will automatically send both the enterEvent and the leaveEvent, thus you end up calling enterEvent() two times (I can't understand why Qt doesn't send them instantly instead of waiting until the mouse is moved).
Now you could suggest: try to fake mouse movements using cursor().setPos(...). That seems not to work always (I don't know why but I debugged it a lot and finally discovered it).
Guys I beg your help; Do you suggest better ways to do this? Thanks in advance. I'm using Qt 5.6.1; OS: Ubuntu 16.04 LTS
here's my code:
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> class MainWindow : public QMainWindow {` Q_OBJECT public: MainWindow(QWidget *parent = 0); ~MainWindow(); }; #endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h" #include "label.h" #include <QApplication> #include <QDesktopWidget> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { resize(300, 350); move(QApplication::desktop()->screen()->rect().center() - rect().center()); }
label.h
#ifndef LABEL_H #define LABEL_H #include <QLabel> #include <QEvent> #include <QMouseEvent> class label : public QLabel { Q_OBJECT public: explicit label(QString name, QString color, QPoint pos, bool canDrag, bool canDrop, QWidget *parent = 0); bool isDraggable() const; bool isOkToDrop() const; static label * labelAtPos(QPoint p, QWidget * parent, const label * w); private: QString name; QString color; QPoint startDrag; QPoint originalPosition; bool canDrag; bool canDrop; protected: void mousePressEvent(QMouseEvent*); void mouseMoveEvent(QMouseEvent*); void mouseReleaseEvent(QMouseEvent *); void enterEvent(QEvent*); void leaveEvent(QEvent*); }; #endif // LABEL_H
label.cpp
#include "label.h" #include <QApplication> bool dragging = false; label::label(QString n, QString c, QPoint p, bool drag, bool drop, QWidget *parent) : QLabel(parent) { name = n; color = c; resize(80, 100); setAlignment(Qt::AlignCenter); setText(name); setStyleSheet(QString("background-color: %1").arg(c)); originalPosition = p; canDrag = drag; canDrop = drop; move(p); } bool label::isDraggable() const { return canDrag; } bool label::isOkToDrop() const { return canDrop; } void label::mousePressEvent(QMouseEvent *event) { if(event->button() == Qt::LeftButton && isDraggable()){ raise(); startDrag = event->pos(); setCursor(Qt::ClosedHandCursor); dragging = true; } QLabel::mousePressEvent(event); } void label::mouseMoveEvent(QMouseEvent *event) { if(dragging){ QPoint p = mapToParent(event->pos()); move(p - startDrag); } QLabel::mouseMoveEvent(event); } void label::mouseReleaseEvent(QMouseEvent *event) { if(event->button() == Qt::LeftButton && dragging){ setCursor(Qt::ArrowCursor); QPoint p = mapToParent(event->pos()); label * lab = labelAtPos(p, parentWidget(), this); if(lab && lab->isOkToDrop()){ move(lab->pos()); } else move(originalPosition); dragging = false; } QLabel::mouseReleaseEvent(event); } void label::enterEvent(QEvent *event) { if(isDraggable()) setCursor(Qt::OpenHandCursor); QLabel::enterEvent(event); } void label::leaveEvent(QEvent *event) { setCursor(Qt::ArrowCursor); QLabel::leaveEvent(event); } label * label::labelAtPos(QPoint p, QWidget * parent, const label * w) { // it returns the first label under w, at point p, according to reverse parent's Z-order // if w is null, it returns the first label at point p according to reverse parent's Z-order label * result = 0; bool underw = (w == 0); if (parent) { for (int j = parent->children().count()-1; j >= 0; j--) { label * t = qobject_cast<label *>(parent->children().at(j)); if(t == w) underw = true; if (t && t != w && underw) { QPoint topleft = t->mapToParent(t->rect().topLeft()); QPoint bottomRight = t->mapToParent(t->rect().bottomRight()); QRect rect(topleft, bottomRight); if(rect.contains(p)){ result = t; break; }; }; }; }; return result; }
main.cpp
#include "mainwindow.h" #include "label.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; label lab1("Label 1", "#ff9966", QPoint(20, 20), true, false, &w); label lab2("Label 2", "#ccccff", QPoint(150, 20), true, false, &w); label lab3("Label 3", "#00cc66", QPoint(20, 150), false, true, &w); QLabel lab4("Label1, Label2: can drag, can't drop\nLabel3: can't drag, can drop", &w); lab4.resize(250, 100); lab4.move(20, 250); w.show(); return a.exec(); }
-
Hi and welcome to devnet,
You should also add which version of Qt your are using as well as OS.