Details
-
Bug
-
Resolution: Unresolved
-
P3: Somewhat important
-
None
-
5.8.0
-
None
Description
The following widget hierarchy has two widgets which indicate whether they are currently focused (blue frame), as well as whether the left mouse button is currently pressed down (red frame):
#include <QtWidgets> class HighlightFocusWidget : public QWidget { public: HighlightFocusWidget(QWidget* parent) : QWidget(parent) , m_mouseDown(false) { setAutoFillBackground(false); setFocusPolicy(Qt::WheelFocus); connect((QApplication*)QApplication::instance(), &QApplication::focusChanged, this, [this]() { update(); }); } protected: virtual void paintEvent(QPaintEvent* event) override { QPainter painter(this); painter.setPen(Qt::NoPen); QColor color = (this == QApplication::focusWidget()) ? QColor(Qt::blue) : palette().color(QPalette::Background); painter.setBrush(color); painter.drawRect(rect()); if (m_mouseDown) { painter.setBrush(Qt::red); painter.drawRect(rect().adjusted(4, 4, -4, -4)); } } virtual void mousePressEvent(QMouseEvent* event) override { if (event->button() == Qt::LeftButton) { m_mouseDown = true; update(); } } virtual void mouseReleaseEvent(QMouseEvent* event) override { if (event->button() == Qt::LeftButton) { m_mouseDown = false; update(); } } virtual void contextMenuEvent(QContextMenuEvent *event) override { QMenu menu(this); menu.addAction("Menu Entry"); menu.exec(event->globalPos()); } private: bool m_mouseDown; }; class PaintOnScreenWidget : public QWidget { public: PaintOnScreenWidget() { setAttribute(Qt::WA_PaintOnScreen); } protected: virtual QPaintEngine* paintEngine() const override { return 0; } }; int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget* w = new QWidget(); QHBoxLayout* layout = new QHBoxLayout(w); for (int i = 0; i < 2; ++i) { HighlightFocusWidget* hightlightWidget = new HighlightFocusWidget(w); layout->addWidget(hightlightWidget); QHBoxLayout* innerLayout = new QHBoxLayout(hightlightWidget); innerLayout->setContentsMargins(8, 8, 8, 8); QWidget* paintOnScreenWidget = new PaintOnScreenWidget(); innerLayout->addWidget(paintOnScreenWidget); QHBoxLayout* paintOnScreenLayout = new QHBoxLayout(paintOnScreenWidget); paintOnScreenLayout->setContentsMargins(0, 0, 0, 0); QWidget* overlayWidget = new QWidget(/*paintOnScreenWidget*/); overlayWidget->setAttribute(Qt::WA_DontShowOnScreen); paintOnScreenLayout->addWidget(overlayWidget); } w->setFixedSize(QSize(600, 400)); w->show(); return app.exec(); }
A context menu can be invoked for both of the widgets and this can lead to incorrect focus handling when the context menu is closed: Right-click in the left half of the window to invoke the menu for the left HightlightFocusWidget and then left-click on the other HighlightFocusWidget on the right. The widget on the right hand side will not get focused but will receive a mouse press event. It will however not receive the corresponding mouse release event afterwards (as the red frame is still being displayed).
This behavior has to do with the Qt::WA_PaintOnScreen flag (+paintEngine() returning null) and the Qt::WA_DontShowOnScreen flag. I couldn't figure out exactly what is going wrong but found that QWidgetWindow::event() has special case handling for the Qt::WA_DontShowOnScreen flag, which when disabled correctly handles focus in the case described above.
Oddly, a workaround for this is to directly set the parent of overlayWidget when it is constructed (commented out in the example above). This at least gets rid of the spurious mouse down event.