1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29
30#include <QtTest/QtTest>
31#include <QtGui/QtGui>
32#include <QtCore/QTextStream>
33#include <QtCore/QStringList>
34#include <QtCore/QMimeData>
35#include <QtCore/QPoint>
36#include <qeventloop.h>
37#include <qlist.h>
38
39#include <qlistwidget.h>
40#include <qpushbutton.h>
41#include <qboxlayout.h>
42#include <qtabwidget.h>
43#include <qlabel.h>
44#include <qmainwindow.h>
45#include <qtoolbar.h>
46#include <private/qwindow_p.h>
47#include <private/qguiapplication_p.h>
48#include <qpa/qplatformintegration.h>
49#include <qpa/qwindowsysteminterface.h>
50#include <qpa/qplatformdrag.h>
51#include <private/qhighdpiscaling_p.h>
52
53#include <QtTest/private/qtesthelpers_p.h>
54
55using namespace QTestPrivate;
56
57// Compare a window position that may go through scaling in the platform plugin with fuzz.
58static inline bool qFuzzyCompareWindowPosition(const QPoint &p1, const QPoint p2, int fuzz)
59{
60 return (p1 - p2).manhattanLength() <= fuzz;
61}
62
63static QString msgPointMismatch(const QPoint &p1, const QPoint p2)
64{
65 QString result;
66 QDebug(&result) << p1 << "!=" << p2 << ", manhattanLength=" << (p1 - p2).manhattanLength();
67 return result;
68}
69
70class tst_QWidget_window : public QObject
71{
72 Q_OBJECT
73
74public:
75 tst_QWidget_window();
76
77public slots:
78 void initTestCase();
79 void cleanupTestCase();
80 void cleanup();
81
82private slots:
83 void tst_min_max_size();
84 void tst_min_max_size_data();
85 void tst_move_show();
86 void tst_show_move();
87 void tst_show_move_hide_show();
88
89 void tst_resize_show();
90 void tst_show_resize();
91 void tst_show_resize_hide_show();
92
93 void tst_windowFilePathAndwindowTitle_data();
94 void tst_windowFilePathAndwindowTitle();
95 void tst_windowFilePath_data();
96 void tst_windowFilePath();
97
98 void tst_showWithoutActivating();
99 void tst_paintEventOnSecondShow();
100 void tst_exposeObscuredMapped_QTBUG39220();
101 void tst_paintEventOnResize_QTBUG50796();
102
103#if QT_CONFIG(draganddrop)
104 void tst_dnd();
105 void tst_dnd_events();
106 void tst_dnd_propagation();
107#endif
108
109 void tst_qtbug35600();
110 void tst_updateWinId_QTBUG40681();
111 void tst_recreateWindow_QTBUG40817();
112
113 void tst_resize_count();
114 void tst_move_count();
115
116 void tst_showhide_count();
117
118 void tst_eventfilter_on_toplevel();
119
120 void QTBUG_50561_QCocoaBackingStore_paintDevice_crash();
121
122 void setWindowState_data();
123 void setWindowState();
124
125 void nativeShow();
126
127 void QTBUG_56277_resize_on_showEvent();
128
129 void mouseMoveWithPopup_data();
130 void mouseMoveWithPopup();
131
132private:
133 QSize m_testWidgetSize;
134 const int m_fuzz;
135};
136
137tst_QWidget_window::tst_QWidget_window() :
138 m_fuzz(int(QHighDpiScaling::factor(context: QGuiApplication::primaryScreen())))
139{
140 const int screenWidth = QGuiApplication::primaryScreen()->geometry().width();
141 const int width = qMax(a: 200, b: 100 * ((screenWidth + 500) / 1000));
142 m_testWidgetSize = QSize(width, width);
143}
144
145void tst_QWidget_window::initTestCase()
146{
147}
148
149void tst_QWidget_window::cleanupTestCase()
150{
151}
152
153void tst_QWidget_window::cleanup()
154{
155 QVERIFY(QApplication::topLevelWidgets().isEmpty());
156}
157
158/* Test if the maximum/minimum size constraints
159 * are propagated from the widget to the QWidgetWindow
160 * independently of whether they were set before or after
161 * window creation (QTBUG-26745). */
162
163void tst_QWidget_window::tst_min_max_size_data()
164{
165 QTest::addColumn<bool>(name: "setMinMaxSizeBeforeShow");
166 QTest::newRow(dataTag: "Set min/max size after show") << false;
167 QTest::newRow(dataTag: "Set min/max size before show") << true;
168}
169
170void tst_QWidget_window::tst_min_max_size()
171{
172 QFETCH(bool, setMinMaxSizeBeforeShow);
173 const QSize minSize(300, 400);
174 const QSize maxSize(1000, 500);
175 QWidget w1;
176 setFrameless(&w1);
177 (new QVBoxLayout(&w1))->addWidget(new QPushButton("Test"));
178 if (setMinMaxSizeBeforeShow) {
179 w1.setMinimumSize(minSize);
180 w1.setMaximumSize(maxSize);
181 }
182 w1.show();
183 if (!setMinMaxSizeBeforeShow) {
184 w1.setMinimumSize(minSize);
185 w1.setMaximumSize(maxSize);
186 }
187 QVERIFY(QTest::qWaitForWindowExposed(&w1));
188 QCOMPARE(w1.windowHandle()->minimumSize(),minSize);
189 QCOMPARE(w1.windowHandle()->maximumSize(), maxSize);
190}
191
192void tst_QWidget_window::tst_move_show()
193{
194 QWidget w;
195 const QPoint pos(100, 100);
196 w.move(pos);
197 w.show();
198#ifdef Q_OS_WINRT
199 QEXPECT_FAIL("", "Winrt does not support move", Abort);
200#endif
201 QVERIFY2(qFuzzyCompareWindowPosition(w.pos(), pos, m_fuzz),
202 qPrintable(msgPointMismatch(w.pos(), pos)));
203}
204
205void tst_QWidget_window::tst_show_move()
206{
207 QWidget w;
208 w.show();
209 const QPoint pos(100, 100);
210 w.move(pos);
211 QVERIFY2(qFuzzyCompareWindowPosition(w.pos(), pos, m_fuzz),
212 qPrintable(msgPointMismatch(w.pos(), pos)));
213}
214
215void tst_QWidget_window::tst_show_move_hide_show()
216{
217 QWidget w;
218 w.show();
219 const QPoint pos(100, 100);
220 w.move(pos);
221 w.hide();
222 w.show();
223 QVERIFY2(qFuzzyCompareWindowPosition(w.pos(), pos, m_fuzz),
224 qPrintable(msgPointMismatch(w.pos(), pos)));
225}
226
227void tst_QWidget_window::tst_resize_show()
228{
229 QWidget w;
230 w.resize(m_testWidgetSize);
231 w.show();
232#ifdef Q_OS_WINRT
233 QEXPECT_FAIL("", "Winrt does not support resize", Abort);
234#endif
235 QCOMPARE(w.size(), m_testWidgetSize);
236}
237
238void tst_QWidget_window::tst_show_resize()
239{
240 QWidget w;
241 w.show();
242 w.resize(m_testWidgetSize);
243 QCOMPARE(w.size(), m_testWidgetSize);
244}
245
246void tst_QWidget_window::tst_show_resize_hide_show()
247{
248 QWidget w;
249 w.show();
250 w.resize(m_testWidgetSize);
251 w.hide();
252 w.show();
253 QCOMPARE(w.size(), m_testWidgetSize);
254}
255
256class PaintTestWidget : public QWidget
257{
258public:
259 int paintEventCount;
260
261 explicit PaintTestWidget(QWidget *parent = nullptr)
262 : QWidget(parent)
263 , paintEventCount(0)
264 {}
265
266 void paintEvent(QPaintEvent *event) override
267 {
268 ++paintEventCount;
269 QWidget::paintEvent(event);
270 }
271};
272
273void tst_QWidget_window::tst_windowFilePathAndwindowTitle_data()
274{
275 QTest::addColumn<bool>(name: "setWindowTitleBefore");
276 QTest::addColumn<bool>(name: "setWindowTitleAfter");
277 QTest::addColumn<QString>(name: "filePath");
278 QTest::addColumn<QString>(name: "applicationName");
279 QTest::addColumn<QString>(name: "indyWindowTitle");
280 QTest::addColumn<QString>(name: "finalTitleBefore");
281 QTest::addColumn<QString>(name: "finalTitleAfter");
282
283 QString validPath = QApplication::applicationFilePath();
284 QString fileNameOnly = QFileInfo(validPath).fileName() + QLatin1String("[*]");
285 QString windowTitle = QLatin1String("Here is a Window Title");
286 QString defaultPlatString = fileNameOnly;
287
288 QTest::newRow(dataTag: "never Set Title nor AppName") << false << false << validPath << QString() << windowTitle << defaultPlatString << defaultPlatString;
289 QTest::newRow(dataTag: "set title after only, but no AppName") << false << true << validPath << QString() << windowTitle << defaultPlatString << windowTitle;
290 QTest::newRow(dataTag: "set title before only, not AppName") << true << false << validPath << QString() << windowTitle << windowTitle << windowTitle;
291 QTest::newRow(dataTag: "always set title, not appName") << true << true << validPath << QString() << windowTitle << windowTitle << windowTitle;
292
293 QString appName = QLatin1String("Killer App"); // Qt4 used to make it part of windowTitle(), Qt5 doesn't anymore, the QPA plugin takes care of it.
294 QString platString = fileNameOnly;
295
296 QTest::newRow(dataTag: "never Set Title, yes AppName") << false << false << validPath << appName << windowTitle << platString << platString;
297 QTest::newRow(dataTag: "set title after only, yes AppName") << false << true << validPath << appName << windowTitle << platString << windowTitle;
298 QTest::newRow(dataTag: "set title before only, yes AppName") << true << false << validPath << appName << windowTitle << windowTitle << windowTitle;
299 QTest::newRow(dataTag: "always set title, yes appName") << true << true << validPath << appName << windowTitle << windowTitle << windowTitle;
300}
301
302void tst_QWidget_window::tst_windowFilePathAndwindowTitle()
303{
304 QFETCH(bool, setWindowTitleBefore);
305 QFETCH(bool, setWindowTitleAfter);
306 QFETCH(QString, filePath);
307 QFETCH(QString, applicationName);
308 QFETCH(QString, indyWindowTitle);
309 QFETCH(QString, finalTitleBefore);
310 QFETCH(QString, finalTitleAfter);
311
312
313 QWidget widget;
314 QCOMPARE(widget.windowFilePath(), QString());
315
316 if (!applicationName.isEmpty())
317 qApp->setApplicationName(applicationName);
318 else
319 qApp->setApplicationName(QString());
320
321 if (setWindowTitleBefore) {
322 widget.setWindowTitle(indyWindowTitle);
323 }
324 widget.setWindowFilePath(filePath);
325 QCOMPARE(widget.windowTitle(), finalTitleBefore);
326 QCOMPARE(widget.windowFilePath(), filePath);
327
328 if (setWindowTitleAfter) {
329 widget.setWindowTitle(indyWindowTitle);
330 }
331 QCOMPARE(widget.windowTitle(), finalTitleAfter);
332 QCOMPARE(widget.windowFilePath(), filePath);
333}
334
335void tst_QWidget_window::tst_windowFilePath_data()
336{
337 QTest::addColumn<QString>(name: "filePath");
338 QTest::addColumn<QString>(name: "result");
339 QTest::addColumn<bool>(name: "again");
340 QTest::addColumn<QString>(name: "filePath2");
341 QTest::addColumn<QString>(name: "result2");
342
343 QString validPath = QApplication::applicationFilePath();
344 QString invalidPath = QLatin1String("::**Never a Real Path**::");
345
346 QTest::newRow(dataTag: "never Set Path") << QString() << QString() << false << QString() << QString();
347 QTest::newRow(dataTag: "never EVER Set Path") << QString() << QString() << true << QString() << QString();
348 QTest::newRow(dataTag: "Valid Path") << validPath << validPath << false << QString() << QString();
349 QTest::newRow(dataTag: "invalid Path") << invalidPath << invalidPath << false << QString() << QString();
350 QTest::newRow(dataTag: "Valid Path then empty") << validPath << validPath << true << QString() << QString();
351 QTest::newRow(dataTag: "invalid Path then empty") << invalidPath << invalidPath << true << QString() << QString();
352 QTest::newRow(dataTag: "invalid Path then valid") << invalidPath << invalidPath << true << validPath << validPath;
353 QTest::newRow(dataTag: "valid Path then invalid") << validPath << validPath << true << invalidPath << invalidPath;
354}
355
356void tst_QWidget_window::tst_windowFilePath()
357{
358 QFETCH(QString, filePath);
359 QFETCH(QString, result);
360 QFETCH(bool, again);
361 QFETCH(QString, filePath2);
362 QFETCH(QString, result2);
363
364 QWidget widget;
365 QCOMPARE(widget.windowFilePath(), QString());
366 widget.setWindowFilePath(filePath);
367 QCOMPARE(widget.windowFilePath(), result);
368 if (again) {
369 widget.setWindowFilePath(filePath2);
370 QCOMPARE(widget.windowFilePath(), result2);
371 }
372}
373
374void tst_QWidget_window::tst_showWithoutActivating()
375{
376 QString platformName = QGuiApplication::platformName().toLower();
377 if (platformName == "cocoa")
378 QSKIP("Cocoa: This fails. Figure out why.");
379 else if (platformName != QStringLiteral("xcb")
380 && platformName != QStringLiteral("windows")
381 && platformName != QStringLiteral("ios")
382 && platformName != QStringLiteral("tvos")
383 && platformName != QStringLiteral("watchos"))
384 QSKIP("Qt::WA_ShowWithoutActivating is currently supported only on xcb, windows, and ios/tvos/watchos platforms.");
385
386 QWidget w1;
387 w1.setAttribute(Qt::WA_ShowWithoutActivating);
388 w1.show();
389 QVERIFY(!QTest::qWaitForWindowActive(&w1));
390
391 QWidget w2;
392 w2.show();
393 QVERIFY(QTest::qWaitForWindowActive(&w2));
394
395 QWidget w3;
396 w3.setAttribute(Qt::WA_ShowWithoutActivating);
397 w3.show();
398 QVERIFY(!QTest::qWaitForWindowActive(&w3));
399
400 w3.activateWindow();
401 QVERIFY(QTest::qWaitForWindowActive(&w3));
402}
403
404void tst_QWidget_window::tst_paintEventOnSecondShow()
405{
406 PaintTestWidget w;
407 w.show();
408 QVERIFY(QTest::qWaitForWindowExposed(&w));
409 w.hide();
410
411 w.paintEventCount = 0;
412 w.show();
413 QVERIFY(QTest::qWaitForWindowExposed(&w));
414 QApplication::processEvents();
415 QTRY_VERIFY(w.paintEventCount > 0);
416}
417
418void tst_QWidget_window::tst_exposeObscuredMapped_QTBUG39220()
419{
420 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
421 QSKIP("Wayland: This fails. Figure out why.");
422
423 const auto integration = QGuiApplicationPrivate::platformIntegration();
424 if (!integration->hasCapability(cap: QPlatformIntegration::MultipleWindows)
425 || !integration->hasCapability(cap: QPlatformIntegration::NonFullScreenWindows)
426 || QGuiApplication::platformName() == QLatin1String("winrt")) {
427 QSKIP("The platform does not have the required capabilities");
428 }
429 // QTBUG-39220: Fully obscured parent widgets may not receive expose
430 // events (as is the case for frameless, obscured parents on Windows).
431 // Ensure Qt::WA_Mapped is set so updating works.
432 const QRect availableGeometry = QGuiApplication::primaryScreen()->availableGeometry();
433 const QSize size = availableGeometry.size() / 6;
434 QWidget topLevel;
435 setFrameless(&topLevel);
436 topLevel.resize(size);
437 const QPoint sizeP(size.width(), size.height());
438 topLevel.move(availableGeometry.center() - sizeP / 2);
439 QWidget *child = new QWidget(&topLevel);
440 child->resize(size);
441 child->move(ax: 0, ay: 0);
442 QVERIFY(child->winId());
443 topLevel.show();
444 QTRY_VERIFY(child->testAttribute(Qt::WA_Mapped));
445 QVERIFY(topLevel.testAttribute(Qt::WA_Mapped));
446}
447
448void tst_QWidget_window::tst_paintEventOnResize_QTBUG50796()
449{
450 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
451 QSKIP("Wayland: This fails. Figure out why.");
452
453 const QRect availableGeo = QGuiApplication::primaryScreen()->availableGeometry();
454
455 QWidget root;
456 root.setGeometry(ax: availableGeo.width()/2 - 100, ay: availableGeo.height()/2 - 100,
457 aw: 200, ah: 200);
458
459 PaintTestWidget *native = new PaintTestWidget(&root);
460 native->winId(); // We're testing native widgets
461 native->setGeometry(ax: 10, ay: 10, aw: 50, ah: 50);
462
463 root.show();
464 QVERIFY(QTest::qWaitForWindowExposed(&root));
465 QVERIFY(QTest::qWaitForWindowActive(&root));
466 QVERIFY(native->isVisible());
467
468 native->paintEventCount = 0;
469 native->resize(w: native->width() + 10, h: native->height() + 10);
470 QTest::qWait(ms: 50); // Wait for paint events
471 QTRY_COMPARE(native->paintEventCount, 1); // Only one paint event must occur
472}
473
474#if QT_CONFIG(draganddrop)
475
476/* DnD test for QWidgetWindow (handleDrag*Event() functions).
477 * Simulates a drop onto a QWidgetWindow of a top level widget
478 * that has 3 child widgets in a vertical layout with a frame. Only the lower 2
479 * child widgets accepts drops (QTBUG-22987), the bottom child has another child
480 * that does not accept drops.
481 * Sends a series of DnD events to the QWidgetWindow,
482 * entering the top level at the top frame and move
483 * down in steps of 5 pixels, drop onto the bottom widget.
484 * The test compares the sequences of events received by the widgets in readable format.
485 * It also checks whether the address of the mimedata received is the same as the
486 * sending one, that is, no conversion/serialization of text mime data occurs in the
487 * process. */
488
489static const char *expectedLogC[] = {
490 "Event at 11,1 ignored",
491 "Event at 11,21 ignored",
492 "Event at 11,41 ignored",
493 "Event at 11,61 ignored",
494 "Event at 11,81 ignored",
495 "Event at 11,101 ignored",
496 "acceptingDropsWidget1::dragEnterEvent at 1,11 action=1 MIME_DATA_ADDRESS 'testmimetext'",
497 "acceptingDropsWidget1::dragMoveEvent at 1,11 action=1 MIME_DATA_ADDRESS 'testmimetext'",
498 "Event at 11,121 accepted",
499 "acceptingDropsWidget1::dragMoveEvent at 1,31 action=1 MIME_DATA_ADDRESS 'testmimetext'",
500 "Event at 11,141 accepted",
501 "acceptingDropsWidget1::dragMoveEvent at 1,51 action=1 MIME_DATA_ADDRESS 'testmimetext'",
502 "Event at 11,161 accepted",
503 "acceptingDropsWidget1::dragMoveEvent at 1,71 action=1 MIME_DATA_ADDRESS 'testmimetext'",
504 "Event at 11,181 accepted",
505 "acceptingDropsWidget1::dragLeaveEvent QDragLeaveEvent",
506 "Event at 11,201 ignored",
507 "acceptingDropsWidget2::dragEnterEvent at 1,11 action=1 MIME_DATA_ADDRESS 'testmimetext'",
508 "acceptingDropsWidget2::dragMoveEvent at 1,11 action=1 MIME_DATA_ADDRESS 'testmimetext'",
509 "Event at 11,221 accepted",
510 "acceptingDropsWidget2::dragMoveEvent at 1,31 action=1 MIME_DATA_ADDRESS 'testmimetext'",
511 "Event at 11,241 accepted",
512 "acceptingDropsWidget2::dropEvent at 1,51 action=1 MIME_DATA_ADDRESS 'testmimetext'",
513 "Event at 11,261 accepted",
514 "acceptingDropsWidget3::dragEnterEvent at 1,21 action=1 MIME_DATA_ADDRESS 'testmimetext'",
515 "Event at 11,281 accepted",
516 "acceptingDropsWidget3::dragLeaveEvent QDragLeaveEvent",
517 "Event at 11,301 ignored",
518 "acceptingDropsWidget1::dragEnterEvent at 10,10 action=1 MIME_DATA_ADDRESS 'testmimetext'",
519 "Event at 0,0 accepted",
520 "acceptingDropsWidget1::dragMoveEvent at 11,11 action=1 MIME_DATA_ADDRESS 'testmimetext'",
521 "Event at 1,1 accepted",
522 "acceptingDropsWidget1::dropEvent at 12,12 action=1 MIME_DATA_ADDRESS 'testmimetext'",
523 "Event at 2,2 accepted"
524};
525
526// A widget that logs the DnD events it receives into a QStringList.
527class DnDEventLoggerWidget : public QWidget
528{
529public:
530 DnDEventLoggerWidget(QStringList *log, QWidget *w = nullptr, bool ignoreDragMove = false)
531 : QWidget(w), m_log(log), m_ignoreDragMove(ignoreDragMove)
532 {}
533protected:
534 void dragEnterEvent(QDragEnterEvent *);
535 void dragMoveEvent(QDragMoveEvent *);
536 void dragLeaveEvent(QDragLeaveEvent *);
537 void dropEvent(QDropEvent *);
538
539private:
540 void formatDropEvent(const char *function, const QDropEvent *e, QTextStream &str) const;
541 QStringList *m_log;
542 bool m_ignoreDragMove;
543};
544
545void DnDEventLoggerWidget::formatDropEvent(const char *function, const QDropEvent *e, QTextStream &str) const
546{
547 str << objectName() << "::" << function << " at " << e->pos().x() << ',' << e->pos().y()
548 << " action=" << e->dropAction()
549 << ' ' << quintptr(e->mimeData()) << " '" << e->mimeData()->text() << '\'';
550}
551
552void DnDEventLoggerWidget::dragEnterEvent(QDragEnterEvent *e)
553{
554 e->accept();
555 QString message;
556 QTextStream str(&message);
557 formatDropEvent(function: "dragEnterEvent", e, str);
558 m_log->push_back(t: message);
559}
560
561void DnDEventLoggerWidget::dragMoveEvent(QDragMoveEvent *e)
562{
563 if (m_ignoreDragMove)
564 return;
565 e->accept();
566 QString message;
567 QTextStream str(&message);
568 formatDropEvent(function: "dragMoveEvent", e, str);
569 m_log->push_back(t: message);
570}
571
572void DnDEventLoggerWidget::dragLeaveEvent(QDragLeaveEvent *e)
573{
574 e->accept();
575 m_log->push_back(t: objectName() + QLatin1String("::") + QLatin1String("dragLeaveEvent") + QLatin1String(" QDragLeaveEvent"));
576}
577
578void DnDEventLoggerWidget::dropEvent(QDropEvent *e)
579{
580 e->accept();
581 QString message;
582 QTextStream str(&message);
583 formatDropEvent(function: "dropEvent", e, str);
584 m_log->push_back(t: message);
585}
586
587static QString msgEventAccepted(const QDropEvent &e)
588{
589 QString message;
590 QTextStream str(&message);
591 str << "Event at " << e.pos().x() << ',' << e.pos().y() << ' ' << (e.isAccepted() ? "accepted" : "ignored");
592 return message;
593}
594
595void tst_QWidget_window::tst_dnd()
596{
597 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
598 QSKIP("Wayland: This fails. Figure out why.");
599
600 QStringList log;
601 DnDEventLoggerWidget dndTestWidget(&log);
602
603 dndTestWidget.setObjectName(QLatin1String("dndTestWidget"));
604 dndTestWidget.setWindowTitle(dndTestWidget.objectName());
605 dndTestWidget.resize(w: 200, h: 300);
606
607 QWidget *dropsRefusingWidget1 = new DnDEventLoggerWidget(&log, &dndTestWidget);
608 dropsRefusingWidget1->setObjectName(QLatin1String("dropsRefusingWidget1"));
609 dropsRefusingWidget1->resize(w: 180, h: 80);
610 dropsRefusingWidget1->move(ax: 10, ay: 10);
611
612 QWidget *dropsAcceptingWidget1 = new DnDEventLoggerWidget(&log, &dndTestWidget);
613 dropsAcceptingWidget1->setAcceptDrops(true);
614 dropsAcceptingWidget1->setObjectName(QLatin1String("acceptingDropsWidget1"));
615 dropsAcceptingWidget1->resize(w: 180, h: 80);
616 dropsAcceptingWidget1->move(ax: 10, ay: 110);
617
618 // Create a native widget on top of dropsAcceptingWidget1 to check QTBUG-27336
619 QWidget *nativeWidget = new QWidget(dropsAcceptingWidget1);
620 nativeWidget->resize(w: 160, h: 60);
621 nativeWidget->move(ax: 10, ay: 10);
622 nativeWidget->winId();
623
624 QWidget *dropsAcceptingWidget2 = new DnDEventLoggerWidget(&log, &dndTestWidget);
625 dropsAcceptingWidget2->setAcceptDrops(true);
626 dropsAcceptingWidget2->setObjectName(QLatin1String("acceptingDropsWidget2"));
627 dropsAcceptingWidget2->resize(w: 180, h: 80);
628 dropsAcceptingWidget2->move(ax: 10, ay: 210);
629
630 QWidget *dropsRefusingWidget2 = new DnDEventLoggerWidget(&log, dropsAcceptingWidget2);
631 dropsRefusingWidget2->setObjectName(QLatin1String("dropsRefusingDropsWidget2"));
632 dropsRefusingWidget2->resize(w: 160, h: 60);
633 dropsRefusingWidget2->move(ax: 10, ay: 10);
634
635 QWidget *dropsAcceptingWidget3 = new DnDEventLoggerWidget(&log, &dndTestWidget, true);
636 dropsAcceptingWidget3->setAcceptDrops(true);
637 dropsAcceptingWidget3->setObjectName(QLatin1String("acceptingDropsWidget3"));
638 // 260 + 40 = 300 = widget size, must not be more than that.
639 // otherwise it will break WinRT because there the tlw is maximized every time
640 // and this window will receive one more event
641 dropsAcceptingWidget3->resize(w: 180, h: 40);
642 dropsAcceptingWidget3->move(ax: 10, ay: 260);
643
644 dndTestWidget.show();
645 QVERIFY(QTest::qWaitForWindowExposed(&dndTestWidget));
646 qApp->setActiveWindow(&dndTestWidget);
647 QVERIFY(QTest::qWaitForWindowActive(&dndTestWidget));
648
649 QMimeData mimeData;
650 mimeData.setText(QLatin1String("testmimetext"));
651
652 // Simulate DnD events on the QWidgetWindow.
653 QPoint position = QPoint(11, 1);
654 QDragEnterEvent e(position, Qt::CopyAction, &mimeData, Qt::LeftButton, Qt::NoModifier);
655 QWindow *window = dndTestWidget.windowHandle();
656 qApp->sendEvent(receiver: window, event: &e);
657 log.push_back(t: msgEventAccepted(e));
658 while (true) {
659 position.ry() += 20;
660 if (position.y() >= 250 && position.y() < 270) {
661 QDropEvent e(position, Qt::CopyAction, &mimeData, Qt::LeftButton, Qt::NoModifier);
662 qApp->sendEvent(receiver: window, event: &e);
663 log.push_back(t: msgEventAccepted(e));
664 } else {
665 QDragMoveEvent e(position, Qt::CopyAction, &mimeData, Qt::LeftButton, Qt::NoModifier);
666 qApp->sendEvent(receiver: window, event: &e);
667 log.push_back(t: msgEventAccepted(e));
668 }
669 if (position.y() > 290)
670 break;
671 }
672
673 window = nativeWidget->windowHandle();
674 QDragEnterEvent enterEvent(QPoint(0, 0), Qt::CopyAction, &mimeData, Qt::LeftButton, Qt::NoModifier);
675 qApp->sendEvent(receiver: window, event: &enterEvent);
676 log.push_back(t: msgEventAccepted(e: enterEvent));
677
678 QDragMoveEvent moveEvent(QPoint(1, 1), Qt::CopyAction, &mimeData, Qt::LeftButton, Qt::NoModifier);
679 qApp->sendEvent(receiver: window, event: &moveEvent);
680 log.push_back(t: msgEventAccepted(e: moveEvent));
681
682 QDropEvent dropEvent(QPoint(2, 2), Qt::CopyAction, &mimeData, Qt::LeftButton, Qt::NoModifier);
683 qApp->sendEvent(receiver: window, event: &dropEvent);
684 log.push_back(t: msgEventAccepted(e: dropEvent));
685
686 // Compare logs.
687 QStringList expectedLog;
688 const int expectedLogSize = int(sizeof(expectedLogC) / sizeof(expectedLogC[0]));
689 const QString mimeDataAddress = QString::number(quintptr(&mimeData));
690 const QString mimeDataAddressPlaceHolder = QLatin1String("MIME_DATA_ADDRESS");
691 for (int i= 0; i < expectedLogSize; ++i)
692 expectedLog.push_back(t: QString::fromLatin1(str: expectedLogC[i]).replace(before: mimeDataAddressPlaceHolder, after: mimeDataAddress));
693
694 if (log.size() != expectedLog.size()) {
695 for (int i = 0; i < log.size() && i < expectedLog.size(); ++i)
696 QCOMPARE(log.at(i), expectedLog.at(i));
697 const int iMin = std::min(a: log.size(), b: expectedLog.size());
698 for (int i = iMin; i < log.size(); ++i)
699 qDebug() << "log[" << i << "]:" << log.at(i);
700 for (int i = iMin; i < expectedLog.size(); ++i)
701 qDebug() << "exp[" << i << "]:" << log.at(i);
702 }
703 QCOMPARE(log, expectedLog);
704}
705
706class DnDEventRecorder : public QWidget
707{
708 Q_OBJECT
709public:
710 QString _dndEvents;
711 DnDEventRecorder() { setAcceptDrops(true); }
712
713protected:
714 void mousePressEvent(QMouseEvent *)
715 {
716 QMimeData *mimeData = new QMimeData;
717 mimeData->setData(mimetype: "application/x-dnditemdata", data: "some data");
718 QDrag *drag = new QDrag(this);
719 drag->setMimeData(mimeData);
720 drag->exec();
721 }
722
723 void dragEnterEvent(QDragEnterEvent *e)
724 {
725 e->accept();
726 _dndEvents.append(QStringLiteral("DragEnter "));
727 }
728 void dragMoveEvent(QDragMoveEvent *e)
729 {
730 e->accept();
731 _dndEvents.append(QStringLiteral("DragMove "));
732 emit dragMoveReceived();
733 }
734 void dragLeaveEvent(QDragLeaveEvent *e)
735 {
736 e->accept();
737 _dndEvents.append(QStringLiteral("DragLeave "));
738 }
739 void dropEvent(QDropEvent *e)
740 {
741 e->accept();
742 _dndEvents.append(QStringLiteral("DropEvent "));
743 }
744
745signals:
746 void dragMoveReceived();
747};
748
749void tst_QWidget_window::tst_dnd_events()
750{
751 // Note: This test is somewhat a hack as testing DnD with qtestlib is not
752 // supported at the moment. The test verifies that we get an expected event
753 // sequence on dnd operation that does not move a mouse. This logic is implemented
754 // in QGuiApplication, so we have to go via QWindowSystemInterface API (QTest::mouse*).
755 const auto platformName = QGuiApplication::platformName().toLower();
756 // The test is known to work with XCB and platforms that use the default dnd
757 // implementation QSimpleDrag (e.g. qnx). Running on XCB should be sufficient to
758 // catch regressions at cross platform code: QGuiApplication::processDrag/Leave().
759 if (platformName != "xcb")
760 return;
761
762 const QString expectedDndEvents = "DragEnter DragMove DropEvent DragEnter DragMove "
763 "DropEvent DragEnter DragMove DropEvent ";
764 DnDEventRecorder dndWidget;
765 dndWidget.setGeometry(ax: 100, ay: 100, aw: 200, ah: 200);
766 dndWidget.show();
767 QVERIFY(QTest::qWaitForWindowExposed(&dndWidget));
768 QVERIFY(QTest::qWaitForWindowActive(&dndWidget));
769
770 // ### FIXME - QTBUG-35117 ???
771 auto targetCenter = QPoint(dndWidget.width(), dndWidget.height()) / 2;
772 auto targetCenterGlobal = dndWidget.mapToGlobal(targetCenter);
773 QCursor::setPos(targetCenterGlobal);
774 QVERIFY(QTest::qWaitFor([&]() { return QCursor::pos() == targetCenterGlobal; }));
775 QCoreApplication::processEvents(); // clear mouse events generated from cursor
776
777 auto window = dndWidget.window()->windowHandle();
778
779 // Some dnd implementation rely on running internal event loops, so we have to use
780 // the following queued signal hack to simulate mouse clicks in the widget.
781 QObject::connect(sender: &dndWidget, signal: &DnDEventRecorder::dragMoveReceived, context: this, slot: [=]() {
782 QTest::mouseRelease(window, button: Qt::LeftButton);
783 }, type: Qt::QueuedConnection);
784
785 QTest::mousePress(window, button: Qt::LeftButton);
786 QTest::mousePress(window, button: Qt::LeftButton);
787 QTest::mousePress(window, button: Qt::LeftButton);
788
789 QCOMPARE(dndWidget._dndEvents, expectedDndEvents);
790
791 dndWidget._dndEvents.clear();
792 dndWidget.disconnect();
793 int step = 0;
794 QObject::connect(sender: &dndWidget, signal: &DnDEventRecorder::dragMoveReceived, context: this, slot: [window, &step]() {
795 switch (step++) {
796 case 0:
797 QTest::keyPress(window, key: Qt::Key_Shift, modifier: Qt::ShiftModifier);
798 break;
799 case 1:
800 QTest::keyRelease(window, key: Qt::Key_Shift, modifier: Qt::NoModifier);
801 break;
802 default:
803 QTest::mouseRelease(window, button: Qt::LeftButton);
804 break;
805 }
806 }, type: Qt::QueuedConnection);
807
808 QTest::mousePress(window, button: Qt::LeftButton);
809 const QString expectedDndWithModsEvents = "DragEnter DragMove DragMove DragMove DropEvent ";
810 QCOMPARE(dndWidget._dndEvents, expectedDndWithModsEvents);
811}
812
813class DropTarget : public QWidget
814{
815public:
816 explicit DropTarget()
817 {
818 setAcceptDrops(true);
819
820 const QRect availableGeometry = QGuiApplication::primaryScreen()->availableGeometry();
821 auto width = availableGeometry.width() / 6;
822 auto height = availableGeometry.height() / 4;
823
824 setGeometry(ax: availableGeometry.x() + 200, ay: availableGeometry.y() + 200, aw: width, ah: height);
825
826 QLabel *label = new QLabel(QStringLiteral("Test"), this);
827 label->setGeometry(ax: 40, ay: 40, aw: 60, ah: 60);
828 label->setAcceptDrops(true);
829 }
830
831 void dragEnterEvent(QDragEnterEvent *event) override
832 {
833 event->accept();
834 mDndEvents.append(s: "enter ");
835 }
836
837 void dragMoveEvent(QDragMoveEvent *event) override
838 {
839 event->acceptProposedAction();
840 }
841
842 void dragLeaveEvent(QDragLeaveEvent *) override
843 {
844 mDndEvents.append(s: "leave ");
845 }
846
847 void dropEvent(QDropEvent *event) override
848 {
849 event->accept();
850 mDndEvents.append(s: "drop ");
851 }
852
853 QString mDndEvents;
854};
855
856void tst_QWidget_window::tst_dnd_propagation()
857{
858 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
859 QSKIP("Wayland: This fails. Figure out why.");
860
861 QMimeData mimeData;
862 mimeData.setText(QLatin1String("testmimetext"));
863
864 DropTarget target;
865 target.show();
866 QVERIFY(QTest::qWaitForWindowActive(&target));
867
868 Qt::DropActions supportedActions = Qt::DropAction::CopyAction;
869 QWindow *window = target.windowHandle();
870
871 auto posInsideDropTarget = QHighDpi::toNativePixels(value: QPoint(20, 20), context: window->screen());
872 auto posInsideLabel = QHighDpi::toNativePixels(value: QPoint(60, 60), context: window->screen());
873
874 // Enter DropTarget.
875 QWindowSystemInterface::handleDrag(window, dropData: &mimeData, p: posInsideDropTarget, supportedActions, buttons: {}, modifiers: {});
876 // Enter QLabel. This will propagate because default QLabel does
877 // not accept the drop event in dragEnterEvent().
878 QWindowSystemInterface::handleDrag(window, dropData: &mimeData, p: posInsideLabel, supportedActions, buttons: {}, modifiers: {});
879 // Drop on QLabel. DropTarget will get dropEvent(), because it accepted the event.
880 QWindowSystemInterface::handleDrop(window, dropData: &mimeData, p: posInsideLabel, supportedActions, buttons: {}, modifiers: {});
881
882 QGuiApplication::processEvents();
883
884 QCOMPARE(target.mDndEvents, "enter leave enter drop ");
885}
886#endif
887
888void tst_QWidget_window::tst_qtbug35600()
889{
890 QWidget w;
891 w.show();
892
893 QWidget *wA = new QWidget;
894 QHBoxLayout *layoutA = new QHBoxLayout;
895
896 QWidget *wB = new QWidget;
897 layoutA->addWidget(wB);
898
899 QWidget *wC = new QWidget;
900 layoutA->addWidget(wC);
901
902 wA->setLayout(layoutA);
903
904 QWidget *wD = new QWidget;
905 wD->setAttribute(Qt::WA_NativeWindow);
906 wD->setParent(wB);
907
908 QWidget *wE = new QWidget(wC, Qt::Tool | Qt::FramelessWindowHint | Qt::WindowTransparentForInput);
909 wE->show();
910
911 wA->setParent(&w);
912
913 // QTBUG-35600: program may crash here or on exit
914}
915
916void tst_QWidget_window::tst_updateWinId_QTBUG40681()
917{
918 QWidget w;
919 QVBoxLayout *vl = new QVBoxLayout(&w);
920 QLabel *lbl = new QLabel("HELLO1");
921 lbl->setAttribute(Qt::WA_NativeWindow);
922 lbl->setObjectName("label1");
923 vl->addWidget(lbl);
924 w.setMinimumWidth(m_testWidgetSize.width());
925
926 w.show();
927
928 QVERIFY(QTest::qWaitForWindowExposed(&w));
929
930 QCOMPARE(lbl->winId(), lbl->windowHandle()->winId());
931
932 // simulate screen change and notification
933 QWindow *win = w.windowHandle();
934 w.windowHandle()->destroy();
935 lbl->windowHandle()->destroy();
936 w.windowHandle()->create();
937 lbl->windowHandle()->create();
938 QWindowPrivate *p = qt_window_private(window: win);
939 p->emitScreenChangedRecursion(newScreen: win->screen());
940
941 QCOMPARE(lbl->winId(), lbl->windowHandle()->winId());
942}
943
944void tst_QWidget_window::tst_recreateWindow_QTBUG40817()
945{
946 QTabWidget tab;
947 tab.setMinimumWidth(m_testWidgetSize.width());
948
949 QWidget *w = new QWidget;
950 tab.addTab(widget: w, "Tab1");
951 QVBoxLayout *vl = new QVBoxLayout(w);
952 QLabel *lbl = new QLabel("HELLO1");
953 lbl->setObjectName("label1");
954 vl->addWidget(lbl);
955 w = new QWidget;
956 tab.addTab(widget: w, "Tab2");
957 vl = new QVBoxLayout(w);
958 lbl = new QLabel("HELLO2");
959 lbl->setAttribute(Qt::WA_NativeWindow);
960 lbl->setObjectName("label2");
961 vl->addWidget(lbl);
962
963 tab.show();
964
965 QVERIFY(QTest::qWaitForWindowExposed(&tab));
966
967 QWindow *win = tab.windowHandle();
968 win->destroy();
969 QWindowPrivate *p = qt_window_private(window: win);
970 p->create(recursive: true);
971 win->show();
972
973 tab.setCurrentIndex(1);
974}
975
976class ResizeWidget : public QWidget
977{
978Q_OBJECT
979public:
980 ResizeWidget(QWidget *parent = 0)
981 : QWidget(parent)
982 , resizeCount(0)
983 { }
984
985 int resizeCount;
986
987protected:
988 void resizeEvent(QResizeEvent *) override
989 {
990 resizeCount++;
991 }
992};
993
994void tst_QWidget_window::tst_resize_count()
995{
996 {
997 ResizeWidget resize;
998 resize.setWindowFlags(Qt::X11BypassWindowManagerHint);
999 resize.show();
1000 QVERIFY(QTest::qWaitForWindowExposed(&resize));
1001#ifdef Q_OS_WINRT
1002 QEXPECT_FAIL("", "Winrt does not support resize", Abort);
1003#endif
1004 QCOMPARE(resize.resizeCount, 1);
1005 resize.resizeCount = 0;
1006 QSize size = resize.size();
1007 size.rwidth() += 10;
1008 resize.resize(size);
1009 QGuiApplication::sync();
1010 QTRY_COMPARE(resize.resizeCount, 1);
1011
1012 resize.resizeCount = 0;
1013
1014 ResizeWidget child(&resize);
1015 child.resize(m_testWidgetSize);
1016 child.winId();
1017 child.show();
1018 QVERIFY(QTest::qWaitForWindowExposed(&child));
1019 QGuiApplication::sync();
1020 QTRY_COMPARE(child.resizeCount, 1);
1021 child.resizeCount = 0;
1022 size = child.size();
1023 size.rwidth() += 10;
1024 child.resize(size);
1025 QGuiApplication::sync();
1026 QCOMPARE(resize.resizeCount, 0);
1027 QCOMPARE(child.resizeCount, 1);
1028 }
1029 {
1030 ResizeWidget parent;
1031 parent.setWindowFlag(Qt::X11BypassWindowManagerHint);
1032 ResizeWidget child(&parent);
1033 child.resize(m_testWidgetSize);
1034 child.winId();
1035 parent.show();
1036 QVERIFY(QTest::qWaitForWindowExposed(&parent));
1037 parent.resizeCount = 0;
1038 QGuiApplication::sync();
1039 QTRY_COMPARE(child.resizeCount, 1);
1040 child.resizeCount = 0;
1041 QSize size = child.size();
1042 size.rwidth() += 10;
1043 child.resize(size);
1044 QGuiApplication::sync();
1045 QCOMPARE(parent.resizeCount, 0);
1046 QCOMPARE(child.resizeCount, 1);
1047 }
1048
1049}
1050
1051/*!
1052 This test verifies that windows get a balanced number of show
1053 and hide events, no matter how the window was closed.
1054*/
1055void tst_QWidget_window::tst_showhide_count()
1056{
1057 class EventSpy : public QObject
1058 {
1059 public:
1060 EventSpy()
1061 {
1062 QApplication::instance()->installEventFilter(filterObj: this);
1063 }
1064
1065 int takeCount(QWidget *widget, QEvent::Type type) {
1066 const auto entry = Entry(widget, type);
1067 int count = counter[entry];
1068 counter[entry] = 0;
1069 return count;
1070 }
1071 protected:
1072 bool eventFilter(QObject *receiver, QEvent *event)
1073 {
1074 if (QWidget *widget = qobject_cast<QWidget*>(o: receiver)) {
1075 const auto entry = Entry(widget, event->type());
1076 ++counter[entry];
1077 return false;
1078 }
1079 return QObject::eventFilter(watched: receiver, event);
1080 }
1081 private:
1082 using Entry = QPair<QWidget*, QEvent::Type>;
1083 QHash<Entry, int> counter;
1084 };
1085
1086 EventSpy spy;
1087
1088 QWidget w1;
1089 w1.setGeometry(ax: 100, ay: 100, aw: 200, ah: 200);
1090
1091 w1.show();
1092 QCOMPARE(spy.takeCount(&w1, QEvent::Show), 1);
1093 w1.hide();
1094 QCOMPARE(spy.takeCount(&w1, QEvent::Hide), 1);
1095 w1.close();
1096 QCOMPARE(spy.takeCount(&w1, QEvent::Close), 1);
1097 w1.show();
1098 QCOMPARE(spy.takeCount(&w1, QEvent::Show), 1);
1099 w1.close();
1100 QCOMPARE(spy.takeCount(&w1, QEvent::Hide), 1);
1101 QCOMPARE(spy.takeCount(&w1, QEvent::Close), 1);
1102
1103 w1.show();
1104 QWidget *popup = new QWidget(&w1, Qt::Popup);
1105 popup->setGeometry(ax: 120, ay: 120, aw: 30, ah: 30);
1106 popup->show();
1107 popup->close();
1108 QCOMPARE(spy.takeCount(popup, QEvent::Show), 1);
1109 QCOMPARE(spy.takeCount(popup, QEvent::Hide), 1);
1110 QCOMPARE(spy.takeCount(popup, QEvent::Close), 1);
1111
1112 popup->show();
1113
1114 // clicking outside the popup should close the popup
1115 QTest::mousePress(widget: popup->window(), button: Qt::LeftButton, stateKey: {}, pos: QPoint(-10, -10));
1116
1117 QCOMPARE(spy.takeCount(popup, QEvent::Show), 1);
1118 QCOMPARE(spy.takeCount(popup, QEvent::Hide), 1);
1119 QCOMPARE(spy.takeCount(popup, QEvent::Close), 1);
1120}
1121
1122
1123class MoveWidget : public QWidget
1124{
1125Q_OBJECT
1126public:
1127 MoveWidget(QWidget *parent = 0)
1128 : QWidget(parent)
1129 , moveCount(0)
1130 { }
1131
1132 void moveEvent(QMoveEvent *) override
1133 {
1134 moveCount++;
1135 }
1136
1137 int moveCount;
1138};
1139
1140void tst_QWidget_window::tst_move_count()
1141{
1142 MoveWidget move;
1143 move.move(ax: 500,ay: 500);
1144 move.show();
1145 QVERIFY(QTest::qWaitForWindowExposed(&move));
1146 QTRY_VERIFY(move.moveCount >= 1);
1147 move.moveCount = 0;
1148
1149 move.move(ax: 220,ay: 250);
1150 QTRY_VERIFY(move.moveCount >= 1);
1151}
1152
1153class EventFilter : public QObject
1154{
1155public:
1156 int eventCount;
1157
1158 EventFilter()
1159 : QObject(),
1160 eventCount(0)
1161 {
1162 }
1163
1164 static QEvent::Type filterEventType()
1165 {
1166 static int type = QEvent::registerEventType();
1167 return static_cast<QEvent::Type>(type);
1168 }
1169
1170protected:
1171 bool eventFilter(QObject *o, QEvent *e) override
1172 {
1173 if (e->type() == filterEventType())
1174 ++eventCount;
1175
1176 return QObject::eventFilter(watched: o, event: e);
1177 }
1178};
1179
1180void tst_QWidget_window::tst_eventfilter_on_toplevel()
1181{
1182 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1183 QSKIP("Wayland: This fails. Figure out why.");
1184
1185 QWidget w;
1186 EventFilter filter;
1187 w.installEventFilter(filterObj: &filter);
1188 w.show();
1189 QVERIFY(QTest::qWaitForWindowActive(&w));
1190 QVERIFY(w.isWindow());
1191 QCOMPARE(filter.eventCount, 0);
1192
1193 // send an event not handled in a special way by QWidgetWindow::event,
1194 // and check that it's received by the event filter
1195 QCoreApplication::postEvent(receiver: w.windowHandle(), event: new QEvent(EventFilter::filterEventType()));
1196 QTRY_COMPARE(filter.eventCount, 1);
1197}
1198
1199class ApplicationStateSaver
1200{
1201public:
1202 ApplicationStateSaver()
1203 {
1204 QApplication::setAttribute(attribute: Qt::AA_NativeWindows, on: true);
1205 QApplication::setQuitOnLastWindowClosed(false);
1206 }
1207
1208 ~ApplicationStateSaver()
1209 {
1210 QApplication::setAttribute(attribute: Qt::AA_NativeWindows, on: false);
1211 QApplication::setQuitOnLastWindowClosed(true);
1212 }
1213};
1214
1215void tst_QWidget_window::QTBUG_50561_QCocoaBackingStore_paintDevice_crash()
1216{
1217 // Keep application state clean if testcase fails
1218 ApplicationStateSaver as;
1219
1220 QMainWindow w;
1221 w.setMinimumWidth(m_testWidgetSize.width());
1222 w.addToolBar(toolbar: new QToolBar(&w));
1223 w.show();
1224 QVERIFY(QTest::qWaitForWindowExposed(&w));
1225
1226 // Simulate window system close
1227 QCloseEvent *e = new QCloseEvent;
1228 e->accept();
1229 qApp->postEvent(receiver: w.windowHandle(), event: e);
1230 qApp->processEvents();
1231
1232 // Show again
1233 w.show();
1234 qApp->processEvents();
1235
1236 // No crash, all good.
1237 // Wrap up and leave
1238 w.close();
1239}
1240
1241void tst_QWidget_window::setWindowState_data()
1242{
1243 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1244 QSKIP("Wayland: This fails. Figure out why.");
1245
1246 QString platformName = QGuiApplication::platformName().toLower();
1247
1248 QTest::addColumn<Qt::WindowStates>(name: "state");
1249 QTest::newRow(dataTag: "0") << Qt::WindowStates();
1250 QTest::newRow(dataTag: "Qt::WindowMaximized") << Qt::WindowStates(Qt::WindowMaximized);
1251 QTest::newRow(dataTag: "Qt::WindowMinimized") << Qt::WindowStates(Qt::WindowMinimized);
1252 QTest::newRow(dataTag: "Qt::WindowFullScreen") << Qt::WindowStates(Qt::WindowFullScreen);
1253
1254 if (platformName != "xcb" && platformName != "windows" && !platformName.startsWith(s: "wayland")
1255 && platformName != "offscreen")
1256 return; // Combination of states is not preserved on all platforms.
1257 if (platformName == "xcb" && qgetenv(varName: "XDG_CURRENT_DESKTOP") != "KDE"
1258 && qgetenv(varName: "XDG_CURRENT_DESKTOP") != "Unity")
1259 return; // Not all window managers support state combinations.
1260
1261 QTest::newRow(dataTag: "Qt::WindowMaximized|Qt::WindowMinimized")
1262 << (Qt::WindowMaximized | Qt::WindowMinimized);
1263 QTest::newRow(dataTag: "Qt::WindowFullScreen|Qt::WindowMinimized")
1264 << (Qt::WindowFullScreen | Qt::WindowMinimized);
1265 QTest::newRow(dataTag: "Qt::WindowMaximized|Qt::WindowFullScreen")
1266 << (Qt::WindowMaximized | Qt::WindowFullScreen);
1267 QTest::newRow(dataTag: "Qt::WindowMaximized|Qt::WindowFullScreen|Qt::WindowMinimized")
1268 << (Qt::WindowMaximized | Qt::WindowFullScreen | Qt::WindowMinimized);
1269}
1270
1271void tst_QWidget_window::setWindowState()
1272{
1273 QFETCH(Qt::WindowStates, state);
1274
1275 // This tests make sure that the states are preserved when the window is shown.
1276
1277 QWidget w;
1278 w.setWindowState(state);
1279 QCOMPARE(w.windowState(), state);
1280 w.show();
1281#ifdef Q_OS_WINRT
1282 QEXPECT_FAIL("0", "Winrt windows are maximized by default", Abort);
1283 QEXPECT_FAIL("Qt::WindowMinimized", "Winrt windows are maximized by default", Abort);
1284 QEXPECT_FAIL("Qt::WindowFullScreen", "Winrt windows are maximized by default", Abort);
1285#endif
1286 QCOMPARE(w.windowState(), state);
1287 QCOMPARE(w.windowHandle()->windowStates(), state);
1288 if (!(state & Qt::WindowMinimized))
1289 QVERIFY(QTest::qWaitForWindowExposed(&w));
1290 QTRY_COMPARE(w.windowState(), state);
1291 QCOMPARE(w.windowHandle()->windowStates(), state);
1292
1293 // Minimizing keeps other states
1294 w.showMinimized();
1295 QCOMPARE(w.windowState(), state | Qt::WindowMinimized);
1296 QTest::qWait(ms: 200);
1297 QCOMPARE(w.windowState(), state | Qt::WindowMinimized);
1298 QCOMPARE(w.windowHandle()->windowStates(), state | Qt::WindowMinimized);
1299}
1300
1301void tst_QWidget_window::nativeShow()
1302{
1303 // Verify that a native widget can be shown using the QWindow::setVisible() API
1304 QWidget w;
1305 w.winId();
1306 w.windowHandle()->setVisible(true);
1307 QVERIFY(QTest::qWaitForWindowExposed(&w));
1308 QVERIFY(w.isVisible());
1309
1310 // ... and that we can hide it
1311 w.windowHandle()->setVisible(false);
1312 QTRY_VERIFY(!w.isVisible());
1313}
1314
1315class ResizedOnShowEventWidget : public QWidget
1316{
1317public:
1318 void showEvent(QShowEvent *) override
1319 {
1320 const auto *primaryScreen = QApplication::primaryScreen();
1321 auto newSize = primaryScreen->availableGeometry().size() / 4;
1322 if (newSize == geometry().size())
1323 newSize -= QSize(10, 10);
1324 resize(newSize);
1325 }
1326};
1327
1328void tst_QWidget_window::QTBUG_56277_resize_on_showEvent()
1329{
1330 const auto platformName = QGuiApplication::platformName().toLower();
1331 if (platformName != "cocoa" && platformName != "windows")
1332 QSKIP("This can only be consistently tested on desktop platforms with well-known behavior.");
1333
1334 ResizedOnShowEventWidget w;
1335 w.show();
1336 QVERIFY(QTest::qWaitForWindowExposed(&w));
1337 const auto *screen = w.windowHandle()->screen();
1338 const auto geometry = w.geometry();
1339 const int frameHeight = geometry.top() - w.frameGeometry().top();
1340 const int topmostY = screen->availableGeometry().top() + frameHeight;
1341 QVERIFY(geometry.top() > topmostY || geometry.left() > screen->availableGeometry().left());
1342}
1343
1344void tst_QWidget_window::mouseMoveWithPopup_data()
1345{
1346 QTest::addColumn<Qt::WindowType>(name: "windowType");
1347
1348 QTest::addRow(format: "Dialog") << Qt::Dialog;
1349 QTest::addRow(format: "Popup") << Qt::Popup;
1350}
1351
1352void tst_QWidget_window::mouseMoveWithPopup()
1353{
1354 QFETCH(Qt::WindowType, windowType);
1355
1356 class Window : public QWidget
1357 {
1358 public:
1359 Window(QWidget *parent = nullptr, Qt::WindowFlags flags = {})
1360 : QWidget(parent, flags|Qt::CustomizeWindowHint|Qt::FramelessWindowHint)
1361 {}
1362
1363 QSize sizeHint() const
1364 {
1365 if (parent())
1366 return QSize(150, 100);
1367 return QSize(250, 250);
1368 }
1369
1370 Window *popup = nullptr;
1371 Qt::WindowType type = Qt::Popup;
1372 int mousePressCount = 0;
1373 int mouseMoveCount = 0;
1374 int mouseReleaseCount = 0;
1375 void resetCounters()
1376 {
1377 mousePressCount = 0;
1378 mouseMoveCount = 0;
1379 mouseReleaseCount = 0;
1380 }
1381 protected:
1382 void mousePressEvent(QMouseEvent *event)
1383 {
1384 ++mousePressCount;
1385
1386 if (event->button() == Qt::RightButton) {
1387 if (!popup)
1388 popup = new Window(this, type);
1389 popup->move(event->globalPos());
1390 popup->show();
1391 if (!QTest::qWaitForWindowExposed(widget: popup)) {
1392 delete popup;
1393 popup = nullptr;
1394 QSKIP("Failed to expose popup window!");
1395 }
1396 } else {
1397 QWidget::mousePressEvent(event);
1398 }
1399 }
1400 void mouseReleaseEvent(QMouseEvent *event)
1401 {
1402 ++mouseReleaseCount;
1403 QWidget::mouseReleaseEvent(event);
1404 }
1405 void mouseMoveEvent(QMouseEvent *event)
1406 {
1407 ++mouseMoveCount;
1408 QWidget::mouseMoveEvent(event);
1409 }
1410 };
1411 Window topLevel;
1412 topLevel.setObjectName("topLevel");
1413 topLevel.type = windowType;
1414 topLevel.show();
1415 if (!QTest::qWaitForWindowExposed(widget: &topLevel))
1416 QSKIP("Failed to expose window!");
1417
1418 QCOMPARE(QApplication::activePopupWidget(), nullptr);
1419 QCOMPARE(QApplication::activeWindow(), &topLevel);
1420
1421 QPoint mousePos = topLevel.geometry().center();
1422 QWindow *window = nullptr;
1423 Qt::MouseButtons buttons = {};
1424 auto mouseAction = [&](Qt::MouseButton button, QPoint offset = {}) -> QEvent::Type
1425 {
1426 QEvent::Type type;
1427 if (offset != QPoint()) {
1428 type = QEvent::MouseMove;
1429 } else if (buttons & button) {
1430 type = QEvent::MouseButtonRelease;
1431 buttons &= ~button;
1432 } else {
1433 Q_ASSERT(button != Qt::NoButton);
1434 type = QEvent::MouseButtonPress;
1435 buttons |= button;
1436 window = QApplication::activeWindow()->windowHandle();
1437 }
1438
1439 mousePos += offset;
1440
1441 if (!window)
1442 return QEvent::None;
1443
1444 bool result = QWindowSystemInterface::handleMouseEvent(window, local: window->mapFromGlobal(pos: mousePos),
1445 global: mousePos, state: buttons, button, type);
1446 QCoreApplication::processEvents();
1447 if (type == QEvent::MouseButtonRelease && buttons == Qt::NoButton)
1448 window = nullptr;
1449
1450 if (!result)
1451 return QEvent::None;
1452 return type;
1453 };
1454
1455 QCOMPARE(mouseAction(Qt::RightButton), QEvent::MouseButtonPress);
1456 QCOMPARE(topLevel.mousePressCount, 1);
1457 QVERIFY(topLevel.popup);
1458 QCOMPARE(topLevel.popup->mousePressCount, 0);
1459 topLevel.popup->setObjectName(windowType == Qt::Popup ? "Popup" : "Dialog");
1460 QCOMPARE(QApplication::activePopupWidget(), windowType == Qt::Popup ? topLevel.popup : nullptr);
1461 // if popup, then popup gets the mouse move even though it didn't get any press
1462 QCOMPARE(mouseAction(Qt::NoButton, QPoint(10, 10)), QEvent::MouseMove);
1463 QCOMPARE(topLevel.mouseMoveCount, windowType == Qt::Popup ? 0 : 1);
1464 QCOMPARE(topLevel.popup->mouseMoveCount, windowType == Qt::Popup ? 1 : 0);
1465 // if popup, then popup gets the release even though it didn't get any press
1466 QCOMPARE(mouseAction(Qt::RightButton), QEvent::MouseButtonRelease);
1467 QCOMPARE(topLevel.mouseReleaseCount, windowType == Qt::Popup ? 0 : 1);
1468 QCOMPARE(topLevel.popup->mouseReleaseCount, windowType == Qt::Popup ? 1 : 0);
1469
1470 Q_ASSERT(buttons == Qt::NoButton);
1471 topLevel.resetCounters();
1472 topLevel.popup->resetCounters();
1473
1474 // nested popup, same procedure
1475 QCOMPARE(mouseAction(Qt::RightButton), QEvent::MouseButtonPress);
1476 QVERIFY(topLevel.popup);
1477 QCOMPARE(topLevel.popup->mousePressCount, 1);
1478 QVERIFY(topLevel.popup->popup);
1479 topLevel.popup->popup->setObjectName("NestedPopup");
1480 QCOMPARE(QApplication::activePopupWidget(), topLevel.popup->popup);
1481 QCOMPARE(topLevel.popup->popup->mousePressCount, 0);
1482
1483 // nested popup is always a popup and grabs the mouse, so first popup gets nothing
1484 QCOMPARE(mouseAction(Qt::NoButton, QPoint(10, 10)), QEvent::MouseMove);
1485 QCOMPARE(topLevel.popup->mouseMoveCount, 0);
1486 QCOMPARE(topLevel.popup->popup->mouseMoveCount, 1);
1487
1488 // nested popup gets the release, as before
1489 QCOMPARE(mouseAction(Qt::RightButton), QEvent::MouseButtonRelease);
1490 QCOMPARE(topLevel.popup->mouseReleaseCount, 0);
1491 QCOMPARE(topLevel.popup->popup->mouseReleaseCount, 1);
1492
1493 Q_ASSERT(buttons == Qt::NoButton);
1494
1495 // move mouse back into first popup
1496 mouseAction({}, QPoint(-15, -15));
1497 QVERIFY(!topLevel.popup->popup->geometry().contains(mousePos));
1498 QVERIFY(topLevel.popup->geometry().contains(mousePos));
1499
1500 topLevel.popup->resetCounters();
1501 topLevel.popup->popup->resetCounters();
1502
1503 // closing the nested popup by clicking into the first popup/dialog; the nested popup gets the press
1504 QCOMPARE(mouseAction(Qt::LeftButton), QEvent::MouseButtonPress);
1505 QCOMPARE(topLevel.popup->popup->mousePressCount, 1);
1506 QVERIFY(!topLevel.popup->popup->isVisible());
1507 QCOMPARE(QApplication::activePopupWidget(), windowType == Qt::Popup ? topLevel.popup : nullptr);
1508 QCOMPARE(QApplication::activeWindow(), windowType == Qt::Popup ? &topLevel : topLevel.popup);
1509
1510 // the move event following a press that closed the active popup should NOT be delivered to the first popup
1511 QCOMPARE(mouseAction({}, QPoint(-10, -10)), QEvent::MouseMove);
1512 // dialogs might or might not get the event - platform specific behavior in Qt 5
1513 if (topLevel.popup->mouseMoveCount != 0)
1514 QEXPECT_FAIL("Dialog", "Platform specific behavior", Continue);
1515 QCOMPARE(topLevel.popup->mouseMoveCount, 0);
1516 QCOMPARE(topLevel.popup->popup->mouseMoveCount, 0);
1517
1518 // but the release event will still be delivered to the first popup - dialogs might not get it
1519 QCOMPARE(mouseAction(Qt::LeftButton), QEvent::MouseButtonRelease);
1520 if (topLevel.popup->mouseMoveCount != 1
1521 && (QGuiApplication::platformName().startsWith(s: QLatin1String("xcb"), cs: Qt::CaseInsensitive)
1522 || QGuiApplication::platformName().startsWith(s: QLatin1String("offscreen"), cs: Qt::CaseInsensitive)))
1523 QEXPECT_FAIL("Dialog", "Platform specific behavior", Continue);
1524 QCOMPARE(topLevel.popup->mouseReleaseCount, 1);
1525}
1526
1527QTEST_MAIN(tst_QWidget_window)
1528#include "tst_qwidget_window.moc"
1529

source code of qtbase/tests/auto/widgets/kernel/qwidget_window/tst_qwidget_window.cpp