7f80c96432
CI metrics show that this test was flaky ~38 time in 3 days. The problem is old: after we get a surface, a window manager can still decide to re-position the window. The fix is to simply send the touch event in a position where it is sure to hit the window (usually the offset is the title bar height). The blacklisting seems to not have worked, I could reproduce the failure on a linux/xcb machine. Change-Id: I5229fe020ba75c984fd3b6c322ad00d769707573 Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
2320 lines
70 KiB
C++
2320 lines
70 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of the test suite of the Qt Toolkit.
|
|
**
|
|
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
|
|
//#define QT_TST_QAPP_DEBUG
|
|
#include <qdebug.h>
|
|
|
|
#include <QtTest/QtTest>
|
|
|
|
#include <QtCore/QAbstractEventDispatcher>
|
|
#include <QtCore/QFileInfo>
|
|
#include <QtCore/QDir>
|
|
#if QT_CONFIG(process)
|
|
# include <QtCore/QProcess>
|
|
#endif
|
|
#include <QtCore/private/qeventloop_p.h>
|
|
|
|
#include <QtGui/QFontDatabase>
|
|
#include <QtGui/QClipboard>
|
|
|
|
#include <QtWidgets/QApplication>
|
|
#include <QtWidgets/QMessageBox>
|
|
#include <QtWidgets/QStyleFactory>
|
|
#include <QtWidgets/QHBoxLayout>
|
|
#include <QtWidgets/QPushButton>
|
|
#include <QtWidgets/QLineEdit>
|
|
#include <QtWidgets/QLabel>
|
|
#include <QtWidgets/QMainWindow>
|
|
#include <QtWidgets/private/qapplication_p.h>
|
|
#include <QtWidgets/QStyle>
|
|
|
|
#include <qpa/qwindowsysteminterface.h>
|
|
#include <private/qhighdpiscaling_p.h>
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
static QWindowSystemInterface::TouchPoint touchPoint(const QTouchEvent::TouchPoint& pt)
|
|
{
|
|
QWindowSystemInterface::TouchPoint p;
|
|
p.id = pt.id();
|
|
p.flags = pt.flags();
|
|
p.normalPosition = pt.normalizedPos();
|
|
p.area = pt.screenRect();
|
|
p.pressure = pt.pressure();
|
|
p.state = pt.state();
|
|
p.velocity = pt.velocity();
|
|
p.rawPositions = pt.rawScreenPositions();
|
|
return p;
|
|
}
|
|
|
|
static QList<struct QWindowSystemInterface::TouchPoint> touchPointList(const QList<QTouchEvent::TouchPoint>& pointList)
|
|
{
|
|
QList<struct QWindowSystemInterface::TouchPoint> newList;
|
|
|
|
Q_FOREACH (QTouchEvent::TouchPoint p, pointList)
|
|
{
|
|
newList.append(touchPoint(p));
|
|
}
|
|
return newList;
|
|
}
|
|
|
|
|
|
|
|
extern bool Q_GUI_EXPORT qt_tab_all_widgets(); // from qapplication.cpp
|
|
QT_END_NAMESPACE
|
|
|
|
class tst_QApplication : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
tst_QApplication();
|
|
|
|
private slots:
|
|
void initTestCase();
|
|
void cleanup();
|
|
void sendEventsOnProcessEvents(); // this must be the first test
|
|
void staticSetup();
|
|
|
|
void alert();
|
|
|
|
void multiple_data();
|
|
void multiple();
|
|
|
|
void nonGui();
|
|
|
|
void setFont_data();
|
|
void setFont();
|
|
|
|
void args_data();
|
|
void args();
|
|
void appName();
|
|
|
|
void lastWindowClosed();
|
|
void quitOnLastWindowClosed();
|
|
void closeAllWindows();
|
|
void testDeleteLater();
|
|
void testDeleteLaterProcessEvents();
|
|
|
|
#if QT_CONFIG(library)
|
|
void libraryPaths();
|
|
void libraryPaths_qt_plugin_path();
|
|
void libraryPaths_qt_plugin_path_2();
|
|
#endif
|
|
|
|
void sendPostedEvents();
|
|
|
|
void thread();
|
|
void desktopSettingsAware();
|
|
|
|
void setActiveWindow();
|
|
|
|
void focusChanged();
|
|
void focusOut();
|
|
void focusMouseClick();
|
|
|
|
void execAfterExit();
|
|
|
|
#ifndef QT_NO_WHEELEVENT
|
|
void wheelScrollLines();
|
|
#endif
|
|
|
|
void task109149();
|
|
|
|
void style();
|
|
|
|
void allWidgets();
|
|
void topLevelWidgets();
|
|
|
|
void setAttribute();
|
|
|
|
void touchEventPropagation();
|
|
|
|
void qtbug_12673();
|
|
void noQuitOnHide();
|
|
|
|
void globalStaticObjectDestruction(); // run this last
|
|
|
|
void abortQuitOnShow();
|
|
|
|
void staticFunctions();
|
|
|
|
void settableStyleHints_data();
|
|
void settableStyleHints(); // Needs to run last as it changes style hints.
|
|
|
|
protected slots:
|
|
void quitApplication();
|
|
|
|
private:
|
|
bool quitApplicationTriggered;
|
|
};
|
|
|
|
class EventSpy : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
QList<int> recordedEvents;
|
|
bool eventFilter(QObject *, QEvent *event)
|
|
{
|
|
recordedEvents.append(event->type());
|
|
return false;
|
|
}
|
|
};
|
|
|
|
void tst_QApplication::initTestCase()
|
|
{
|
|
// chdir to our testdata path and execute helper apps relative to that.
|
|
const QString testdataDir = QFileInfo(QFINDTESTDATA("desktopsettingsaware")).absolutePath();
|
|
QVERIFY2(QDir::setCurrent(testdataDir), qPrintable("Could not chdir to " + testdataDir));
|
|
}
|
|
|
|
void tst_QApplication::sendEventsOnProcessEvents()
|
|
{
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
|
|
EventSpy spy;
|
|
app.installEventFilter(&spy);
|
|
|
|
QCoreApplication::postEvent(&app, new QEvent(QEvent::Type(QEvent::User + 1)));
|
|
QCoreApplication::processEvents();
|
|
QVERIFY(spy.recordedEvents.contains(QEvent::User + 1));
|
|
}
|
|
|
|
|
|
class CloseEventTestWindow : public QWidget
|
|
{
|
|
public:
|
|
CloseEventTestWindow(QWidget *parent = 0)
|
|
: QWidget(parent)
|
|
{
|
|
}
|
|
|
|
void closeEvent(QCloseEvent *event)
|
|
{
|
|
QWidget dialog;
|
|
dialog.show();
|
|
dialog.close();
|
|
|
|
event->ignore();
|
|
}
|
|
};
|
|
|
|
static char *argv0;
|
|
|
|
tst_QApplication::tst_QApplication()
|
|
: quitApplicationTriggered(false)
|
|
{
|
|
}
|
|
|
|
void tst_QApplication::cleanup()
|
|
{
|
|
// TODO: Add cleanup code here.
|
|
// This will be executed immediately after each test is run.
|
|
QVERIFY(QApplication::topLevelWidgets().isEmpty());
|
|
}
|
|
|
|
void tst_QApplication::staticSetup()
|
|
{
|
|
QVERIFY(!qApp);
|
|
|
|
QStyle *style = QStyleFactory::create(QLatin1String("Windows"));
|
|
QVERIFY(style);
|
|
QApplication::setStyle(style);
|
|
|
|
QPalette pal;
|
|
QApplication::setPalette(pal);
|
|
|
|
/*QFont font;
|
|
QApplication::setFont(font);*/
|
|
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
}
|
|
|
|
|
|
// QApp subclass that exits the event loop after 150ms
|
|
class TestApplication : public QApplication
|
|
{
|
|
public:
|
|
TestApplication( int &argc, char **argv )
|
|
: QApplication( argc, argv)
|
|
{
|
|
startTimer( 150 );
|
|
}
|
|
|
|
void timerEvent( QTimerEvent * )
|
|
{
|
|
quit();
|
|
}
|
|
};
|
|
|
|
void tst_QApplication::alert()
|
|
{
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
app.alert(0, 0);
|
|
|
|
QWidget widget;
|
|
QWidget widget2;
|
|
app.alert(&widget, 100);
|
|
widget.show();
|
|
widget2.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&widget));
|
|
QVERIFY(QTest::qWaitForWindowExposed(&widget2));
|
|
QTest::qWait(100);
|
|
app.alert(&widget, -1);
|
|
app.alert(&widget, 250);
|
|
widget2.activateWindow();
|
|
QApplication::setActiveWindow(&widget2);
|
|
app.alert(&widget, 0);
|
|
widget.activateWindow();
|
|
QApplication::setActiveWindow(&widget);
|
|
app.alert(&widget, 200);
|
|
}
|
|
|
|
void tst_QApplication::multiple_data()
|
|
{
|
|
QTest::addColumn<QStringList>("features");
|
|
|
|
// return a list of things to try
|
|
QTest::newRow( "data0" ) << QStringList( "" );
|
|
QTest::newRow( "data1" ) << QStringList( "QFont" );
|
|
QTest::newRow( "data2" ) << QStringList( "QPixmap" );
|
|
QTest::newRow( "data3" ) << QStringList( "QWidget" );
|
|
}
|
|
|
|
void tst_QApplication::multiple()
|
|
{
|
|
QFETCH(QStringList,features);
|
|
|
|
int i = 0;
|
|
int argc = 0;
|
|
while (i++ < 5) {
|
|
TestApplication app(argc, 0);
|
|
|
|
if (features.contains("QFont")) {
|
|
// create font and force loading
|
|
QFont font("Arial", 12);
|
|
QFontInfo finfo(font);
|
|
finfo.exactMatch();
|
|
}
|
|
if (features.contains("QPixmap")) {
|
|
QPixmap pix(100, 100);
|
|
pix.fill(Qt::black);
|
|
}
|
|
if (features.contains("QWidget")) {
|
|
QWidget widget;
|
|
}
|
|
|
|
QVERIFY(!app.exec());
|
|
}
|
|
}
|
|
|
|
void tst_QApplication::nonGui()
|
|
{
|
|
#ifdef Q_OS_HPUX
|
|
// ### This is only to allow us to generate a test report for now.
|
|
QSKIP("This test shuts down the window manager on HP-UX.");
|
|
#endif
|
|
|
|
int argc = 0;
|
|
QApplication app(argc, 0, false);
|
|
QCOMPARE(qApp, &app);
|
|
}
|
|
|
|
void tst_QApplication::setFont_data()
|
|
{
|
|
QTest::addColumn<QString>("family");
|
|
QTest::addColumn<int>("pointsize");
|
|
QTest::addColumn<bool>("beforeAppConstructor");
|
|
|
|
int argc = 0;
|
|
QApplication app(argc, 0); // Needed for QFontDatabase
|
|
|
|
int cnt = 0;
|
|
QFontDatabase fdb;
|
|
QStringList families = fdb.families();
|
|
for (QStringList::const_iterator itr = families.begin();
|
|
itr != families.end();
|
|
++itr) {
|
|
if (cnt < 3) {
|
|
QString family = *itr;
|
|
QStringList styles = fdb.styles(family);
|
|
if (styles.size() > 0) {
|
|
QString style = styles.first();
|
|
QList<int> sizes = fdb.pointSizes(family, style);
|
|
if (!sizes.size())
|
|
sizes = fdb.standardSizes();
|
|
if (sizes.size() > 0) {
|
|
const QByteArray cntB = QByteArray::number(cnt);
|
|
QTest::newRow(("data" + cntB + "a").constData())
|
|
<< family
|
|
<< sizes.first()
|
|
<< false;
|
|
QTest::newRow(("data" + cntB + "b").constData())
|
|
<< family
|
|
<< sizes.first()
|
|
<< true;
|
|
}
|
|
}
|
|
}
|
|
++cnt;
|
|
}
|
|
|
|
QTest::newRow("nonexistingfont after") << "nosuchfont_probably_quiteunlikely"
|
|
<< 0 << false;
|
|
QTest::newRow("nonexistingfont before") << "nosuchfont_probably_quiteunlikely"
|
|
<< 0 << true;
|
|
|
|
QTest::newRow("largescaleable after") << "smoothtimes" << 100 << false;
|
|
QTest::newRow("largescaleable before") << "smoothtimes" << 100 << true;
|
|
|
|
QTest::newRow("largeunscaleale after") << "helvetica" << 100 << false;
|
|
QTest::newRow("largeunscaleale before") << "helvetica" << 100 << true;
|
|
}
|
|
|
|
void tst_QApplication::setFont()
|
|
{
|
|
QFETCH( QString, family );
|
|
QFETCH( int, pointsize );
|
|
QFETCH( bool, beforeAppConstructor );
|
|
|
|
QFont font( family, pointsize );
|
|
if (beforeAppConstructor) {
|
|
QApplication::setFont( font );
|
|
QCOMPARE(QApplication::font(), font);
|
|
}
|
|
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
if (!beforeAppConstructor)
|
|
QApplication::setFont( font );
|
|
|
|
QCOMPARE( app.font(), font );
|
|
}
|
|
|
|
void tst_QApplication::args_data()
|
|
{
|
|
QTest::addColumn<int>("argc_in");
|
|
QTest::addColumn<QString>("args_in");
|
|
QTest::addColumn<int>("argc_out");
|
|
QTest::addColumn<QString>("args_out");
|
|
|
|
QTest::newRow( "App name" ) << 1 << "/usr/bin/appname" << 1 << "/usr/bin/appname";
|
|
QTest::newRow( "No arguments" ) << 0 << QString() << 0 << QString();
|
|
QTest::newRow( "App name, style" ) << 3 << "/usr/bin/appname -style windows" << 1 << "/usr/bin/appname";
|
|
QTest::newRow( "App name, style, arbitrary, reverse" ) << 5 << "/usr/bin/appname -style windows -arbitrary -reverse"
|
|
<< 2 << "/usr/bin/appname -arbitrary";
|
|
}
|
|
|
|
void tst_QApplication::task109149()
|
|
{
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
QApplication::setFont(QFont("helvetica", 100));
|
|
|
|
QWidget w;
|
|
w.setWindowTitle("hello");
|
|
w.show();
|
|
|
|
app.processEvents();
|
|
}
|
|
|
|
static char ** QString2cstrings( const QString &args )
|
|
{
|
|
static QList<QByteArray> cache;
|
|
|
|
int i;
|
|
char **argarray = 0;
|
|
QStringList list = args.split(' ');;
|
|
argarray = new char*[list.count()+1];
|
|
|
|
for (i = 0; i < (int)list.count(); ++i ) {
|
|
QByteArray l1 = list[i].toLatin1();
|
|
argarray[i] = l1.data();
|
|
cache.append(l1);
|
|
}
|
|
argarray[i] = 0;
|
|
|
|
return argarray;
|
|
}
|
|
|
|
static QString cstrings2QString( char **args )
|
|
{
|
|
QString string;
|
|
if ( !args )
|
|
return string;
|
|
|
|
int i = 0;
|
|
while ( args[i] ) {
|
|
string += args[i];
|
|
if ( args[i+1] )
|
|
string += QLatin1Char(' ');
|
|
++i;
|
|
}
|
|
return string;
|
|
}
|
|
|
|
void tst_QApplication::args()
|
|
{
|
|
QFETCH( int, argc_in );
|
|
QFETCH( QString, args_in );
|
|
QFETCH( int, argc_out );
|
|
QFETCH( QString, args_out );
|
|
|
|
char **argv = QString2cstrings( args_in );
|
|
|
|
QApplication app( argc_in, argv);
|
|
QString argv_out = cstrings2QString(argv);
|
|
|
|
QCOMPARE( argc_in, argc_out );
|
|
QCOMPARE( argv_out, args_out );
|
|
|
|
delete [] argv;
|
|
// Make sure we switch back to native style.
|
|
QApplicationPrivate::styleOverride = QString();
|
|
}
|
|
|
|
void tst_QApplication::appName()
|
|
{
|
|
char argv0[] = "tst_qapplication";
|
|
char *argv[] = { argv0, 0 };
|
|
int argc = 1;
|
|
QApplication app(argc, argv);
|
|
QCOMPARE(::qAppName(), QString::fromLatin1("tst_qapplication"));
|
|
QCOMPARE(QCoreApplication::applicationName(), QString::fromLatin1("tst_qapplication"));
|
|
}
|
|
|
|
class CloseWidget : public QWidget
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
CloseWidget()
|
|
{
|
|
startTimer(500);
|
|
}
|
|
|
|
protected:
|
|
void timerEvent(QTimerEvent *)
|
|
{
|
|
close();
|
|
}
|
|
|
|
};
|
|
|
|
void tst_QApplication::lastWindowClosed()
|
|
{
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
|
|
QSignalSpy spy(&app, SIGNAL(lastWindowClosed()));
|
|
|
|
QPointer<QDialog> dialog = new QDialog;
|
|
QVERIFY(dialog->testAttribute(Qt::WA_QuitOnClose));
|
|
QTimer::singleShot(1000, dialog, SLOT(accept()));
|
|
dialog->exec();
|
|
QVERIFY(dialog);
|
|
QCOMPARE(spy.count(), 0);
|
|
|
|
QPointer<CloseWidget>widget = new CloseWidget;
|
|
QVERIFY(widget->testAttribute(Qt::WA_QuitOnClose));
|
|
widget->show();
|
|
QObject::connect(&app, SIGNAL(lastWindowClosed()), widget, SLOT(deleteLater()));
|
|
app.exec();
|
|
QVERIFY(!widget);
|
|
QCOMPARE(spy.count(), 1);
|
|
spy.clear();
|
|
|
|
delete dialog;
|
|
|
|
// show 3 windows, close them, should only get lastWindowClosed once
|
|
QWidget w1;
|
|
QWidget w2;
|
|
QWidget w3;
|
|
w1.show();
|
|
w2.show();
|
|
w3.show();
|
|
|
|
QTimer::singleShot(1000, &app, SLOT(closeAllWindows()));
|
|
app.exec();
|
|
QCOMPARE(spy.count(), 1);
|
|
}
|
|
|
|
class QuitOnLastWindowClosedDialog : public QDialog
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
QPushButton *okButton;
|
|
|
|
QuitOnLastWindowClosedDialog()
|
|
{
|
|
QHBoxLayout *hbox = new QHBoxLayout(this);
|
|
okButton = new QPushButton("&ok", this);
|
|
|
|
hbox->addWidget(okButton);
|
|
connect(okButton, SIGNAL(clicked()), this, SLOT(accept()));
|
|
connect(okButton, SIGNAL(clicked()), this, SLOT(ok_clicked()));
|
|
}
|
|
|
|
public slots:
|
|
void ok_clicked()
|
|
{
|
|
QDialog other;
|
|
|
|
QTimer timer;
|
|
connect(&timer, SIGNAL(timeout()), &other, SLOT(accept()));
|
|
QSignalSpy spy(&timer, SIGNAL(timeout()));
|
|
QSignalSpy appSpy(qApp, SIGNAL(lastWindowClosed()));
|
|
|
|
timer.start(1000);
|
|
other.exec();
|
|
|
|
// verify that the eventloop ran and let the timer fire
|
|
QCOMPARE(spy.count(), 1);
|
|
QCOMPARE(appSpy.count(), 1);
|
|
}
|
|
};
|
|
|
|
class QuitOnLastWindowClosedWindow : public QWidget
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
QuitOnLastWindowClosedWindow()
|
|
{ }
|
|
|
|
public slots:
|
|
void execDialogThenShow()
|
|
{
|
|
QDialog dialog;
|
|
QTimer timer1;
|
|
connect(&timer1, SIGNAL(timeout()), &dialog, SLOT(accept()));
|
|
QSignalSpy spy1(&timer1, SIGNAL(timeout()));
|
|
timer1.setSingleShot(true);
|
|
timer1.start(1000);
|
|
dialog.exec();
|
|
QCOMPARE(spy1.count(), 1);
|
|
|
|
show();
|
|
}
|
|
};
|
|
|
|
void tst_QApplication::quitOnLastWindowClosed()
|
|
{
|
|
{
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
|
|
QuitOnLastWindowClosedDialog d;
|
|
d.show();
|
|
QTimer::singleShot(1000, d.okButton, SLOT(animateClick()));
|
|
|
|
QSignalSpy appSpy(&app, SIGNAL(lastWindowClosed()));
|
|
app.exec();
|
|
|
|
// lastWindowClosed() signal should only be sent after the last dialog is closed
|
|
QCOMPARE(appSpy.count(), 2);
|
|
}
|
|
{
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
QSignalSpy appSpy(&app, SIGNAL(lastWindowClosed()));
|
|
|
|
QDialog dialog;
|
|
QTimer timer1;
|
|
connect(&timer1, SIGNAL(timeout()), &dialog, SLOT(accept()));
|
|
QSignalSpy spy1(&timer1, SIGNAL(timeout()));
|
|
timer1.setSingleShot(true);
|
|
timer1.start(1000);
|
|
dialog.exec();
|
|
QCOMPARE(spy1.count(), 1);
|
|
QCOMPARE(appSpy.count(), 0);
|
|
|
|
QTimer timer2;
|
|
connect(&timer2, SIGNAL(timeout()), &app, SLOT(quit()));
|
|
QSignalSpy spy2(&timer2, SIGNAL(timeout()));
|
|
timer2.setSingleShot(true);
|
|
timer2.start(1000);
|
|
int returnValue = app.exec();
|
|
QCOMPARE(returnValue, 0);
|
|
QCOMPARE(spy2.count(), 1);
|
|
QCOMPARE(appSpy.count(), 0);
|
|
}
|
|
{
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
QTimer timer;
|
|
timer.setInterval(100);
|
|
|
|
QSignalSpy spy(&app, SIGNAL(aboutToQuit()));
|
|
QSignalSpy spy2(&timer, SIGNAL(timeout()));
|
|
|
|
QMainWindow mainWindow;
|
|
QDialog *dialog = new QDialog(&mainWindow);
|
|
|
|
QVERIFY(app.quitOnLastWindowClosed());
|
|
QVERIFY(mainWindow.testAttribute(Qt::WA_QuitOnClose));
|
|
QVERIFY(dialog->testAttribute(Qt::WA_QuitOnClose));
|
|
|
|
mainWindow.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&mainWindow));
|
|
dialog->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(dialog));
|
|
|
|
timer.start();
|
|
QTimer::singleShot(1000, &mainWindow, SLOT(close())); // This should quit the application
|
|
QTimer::singleShot(2000, &app, SLOT(quit())); // This makes sure we quit even if it didn't
|
|
|
|
app.exec();
|
|
|
|
QCOMPARE(spy.count(), 1);
|
|
QVERIFY(spy2.count() < 15); // Should be around 10 if closing caused the quit
|
|
}
|
|
{
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
|
|
QSignalSpy spy(&app, SIGNAL(aboutToQuit()));
|
|
|
|
CloseEventTestWindow mainWindow;
|
|
|
|
QVERIFY(app.quitOnLastWindowClosed());
|
|
QVERIFY(mainWindow.testAttribute(Qt::WA_QuitOnClose));
|
|
|
|
mainWindow.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&mainWindow));
|
|
|
|
QTimer::singleShot(1000, &mainWindow, SLOT(close())); // This should NOT quit the application (see CloseEventTestWindow)
|
|
quitApplicationTriggered = false;
|
|
QTimer::singleShot(2000, this, SLOT(quitApplication())); // This actually quits the application.
|
|
|
|
app.exec();
|
|
|
|
QCOMPARE(spy.count(), 1);
|
|
QVERIFY(quitApplicationTriggered);
|
|
}
|
|
{
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
QSignalSpy appSpy(&app, SIGNAL(lastWindowClosed()));
|
|
|
|
// exec a dialog for 1 second, then show the window
|
|
QuitOnLastWindowClosedWindow window;
|
|
QTimer::singleShot(0, &window, SLOT(execDialogThenShow()));
|
|
|
|
QTimer timer;
|
|
QSignalSpy timerSpy(&timer, SIGNAL(timeout()));
|
|
connect(&timer, SIGNAL(timeout()), &window, SLOT(close()));
|
|
timer.setSingleShot(true);
|
|
timer.start(2000);
|
|
int returnValue = app.exec();
|
|
QCOMPARE(returnValue, 0);
|
|
// failure here means the timer above didn't fire, and the
|
|
// quit was caused the dialog being closed (not the window)
|
|
QCOMPARE(timerSpy.count(), 1);
|
|
QCOMPARE(appSpy.count(), 2);
|
|
}
|
|
{
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
QVERIFY(app.quitOnLastWindowClosed());
|
|
|
|
QWindow w;
|
|
w.show();
|
|
|
|
QWidget wid;
|
|
wid.show();
|
|
|
|
QTimer::singleShot(1000, &wid, SLOT(close())); // This should NOT quit the application because the
|
|
// QWindow is still there.
|
|
quitApplicationTriggered = false;
|
|
QTimer::singleShot(2000, this, SLOT(quitApplication())); // This causes the quit.
|
|
|
|
app.exec();
|
|
|
|
QVERIFY(quitApplicationTriggered); // Should be around 20 if closing did not caused the quit
|
|
}
|
|
{ // QTBUG-31569: If the last widget with Qt::WA_QuitOnClose set is closed, other
|
|
// widgets that don't have the attribute set should be closed automatically.
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
QVERIFY(app.quitOnLastWindowClosed());
|
|
|
|
QWidget w1;
|
|
w1.show();
|
|
|
|
QWidget w2;
|
|
w2.setAttribute(Qt::WA_QuitOnClose, false);
|
|
w2.show();
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&w2));
|
|
|
|
QTimer timer;
|
|
timer.setInterval(100);
|
|
timer.start();
|
|
QSignalSpy timerSpy(&timer, SIGNAL(timeout()));
|
|
|
|
QTimer::singleShot(100, &w1, SLOT(close()));
|
|
app.exec();
|
|
|
|
QVERIFY(timerSpy.count() < 10);
|
|
}
|
|
}
|
|
|
|
class PromptOnCloseWidget : public QWidget
|
|
{
|
|
public:
|
|
void closeEvent(QCloseEvent *event)
|
|
{
|
|
QMessageBox *messageBox = new QMessageBox(this);
|
|
messageBox->setWindowTitle("Unsaved data");
|
|
messageBox->setText("Would you like to save or discard your current data?");
|
|
messageBox->setStandardButtons(QMessageBox::Save|QMessageBox::Discard|QMessageBox::Cancel);
|
|
messageBox->setDefaultButton(QMessageBox::Save);
|
|
|
|
messageBox->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(messageBox));
|
|
|
|
// verify that all windows are visible
|
|
foreach (QWidget *w, qApp->topLevelWidgets())
|
|
QVERIFY(w->isVisible());
|
|
// flush event queue
|
|
qApp->processEvents();
|
|
// close all windows
|
|
qApp->closeAllWindows();
|
|
|
|
if (messageBox->standardButton(messageBox->clickedButton()) == QMessageBox::Cancel)
|
|
event->ignore();
|
|
else
|
|
event->accept();
|
|
|
|
delete messageBox;
|
|
}
|
|
};
|
|
|
|
void tst_QApplication::closeAllWindows()
|
|
{
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
|
|
// create some windows
|
|
new QWidget;
|
|
new QWidget;
|
|
new QWidget;
|
|
|
|
// show all windows
|
|
foreach (QWidget *w, app.topLevelWidgets()) {
|
|
w->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(w));
|
|
}
|
|
// verify that they are visible
|
|
foreach (QWidget *w, app.topLevelWidgets())
|
|
QVERIFY(w->isVisible());
|
|
// empty event queue
|
|
app.processEvents();
|
|
// close all windows
|
|
app.closeAllWindows();
|
|
// all windows should no longer be visible
|
|
foreach (QWidget *w, app.topLevelWidgets())
|
|
QVERIFY(!w->isVisible());
|
|
|
|
// add a window that prompts the user when closed
|
|
PromptOnCloseWidget *promptOnCloseWidget = new PromptOnCloseWidget;
|
|
// show all windows
|
|
foreach (QWidget *w, app.topLevelWidgets()) {
|
|
w->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(w));
|
|
}
|
|
// close the last window to open the prompt (eventloop recurses)
|
|
promptOnCloseWidget->close();
|
|
// all windows should not be visible, except the one that opened the prompt
|
|
foreach (QWidget *w, app.topLevelWidgets()) {
|
|
if (w == promptOnCloseWidget)
|
|
QVERIFY(w->isVisible());
|
|
else
|
|
QVERIFY(!w->isVisible());
|
|
}
|
|
|
|
qDeleteAll(app.topLevelWidgets());
|
|
}
|
|
|
|
bool isPathListIncluded(const QStringList &l, const QStringList &r)
|
|
{
|
|
int size = r.count();
|
|
if (size > l.count())
|
|
return false;
|
|
#if defined (Q_OS_WIN)
|
|
Qt::CaseSensitivity cs = Qt::CaseInsensitive;
|
|
#else
|
|
Qt::CaseSensitivity cs = Qt::CaseSensitive;
|
|
#endif
|
|
int i = 0, j = 0;
|
|
for ( ; i < l.count() && j < r.count(); ++i) {
|
|
if (QDir::toNativeSeparators(l[i]).compare(QDir::toNativeSeparators(r[j]), cs) == 0) {
|
|
++j;
|
|
i = -1;
|
|
}
|
|
}
|
|
return j == r.count();
|
|
}
|
|
|
|
#if QT_CONFIG(library)
|
|
#define QT_TST_QAPP_DEBUG
|
|
void tst_QApplication::libraryPaths()
|
|
{
|
|
const QString testDir = QFileInfo(QFINDTESTDATA("test/test.pro")).absolutePath();
|
|
QVERIFY(!testDir.isEmpty());
|
|
{
|
|
QApplication::setLibraryPaths(QStringList() << testDir);
|
|
QCOMPARE(QApplication::libraryPaths(), (QStringList() << testDir));
|
|
|
|
// creating QApplication adds the applicationDirPath to the libraryPath
|
|
int argc = 1;
|
|
QApplication app(argc, &argv0);
|
|
QString appDirPath = QDir(app.applicationDirPath()).canonicalPath();
|
|
|
|
QStringList actual = QApplication::libraryPaths();
|
|
actual.sort();
|
|
QStringList expected = QSet<QString>::fromList((QStringList() << testDir << appDirPath)).toList();
|
|
expected.sort();
|
|
|
|
QVERIFY2(isPathListIncluded(actual, expected),
|
|
qPrintable("actual:\n - " + actual.join("\n - ") +
|
|
"\nexpected:\n - " + expected.join("\n - ")));
|
|
}
|
|
{
|
|
// creating QApplication adds the applicationDirPath and plugin install path to the libraryPath
|
|
int argc = 1;
|
|
QApplication app(argc, &argv0);
|
|
QString appDirPath = app.applicationDirPath();
|
|
QString installPathPlugins = QLibraryInfo::location(QLibraryInfo::PluginsPath);
|
|
|
|
QStringList actual = QApplication::libraryPaths();
|
|
actual.sort();
|
|
|
|
QStringList expected = QSet<QString>::fromList((QStringList() << installPathPlugins << appDirPath)).toList();
|
|
expected.sort();
|
|
|
|
QVERIFY2(isPathListIncluded(actual, expected),
|
|
qPrintable("actual:\n - " + actual.join("\n - ") +
|
|
"\nexpected:\n - " + expected.join("\n - ")));
|
|
|
|
// setting the library paths overrides everything
|
|
QApplication::setLibraryPaths(QStringList() << testDir);
|
|
QVERIFY2(isPathListIncluded(QApplication::libraryPaths(), (QStringList() << testDir)),
|
|
qPrintable("actual:\n - " + QApplication::libraryPaths().join("\n - ") +
|
|
"\nexpected:\n - " + testDir));
|
|
}
|
|
{
|
|
#ifdef QT_TST_QAPP_DEBUG
|
|
qDebug() << "Initial library path:" << QApplication::libraryPaths();
|
|
#endif
|
|
|
|
int count = QApplication::libraryPaths().count();
|
|
#if 0
|
|
// this test doesn't work if KDE 4 is installed
|
|
QCOMPARE(count, 1); // before creating QApplication, only the PluginsPath is in the libraryPaths()
|
|
#endif
|
|
QString installPathPlugins = QLibraryInfo::location(QLibraryInfo::PluginsPath);
|
|
QApplication::addLibraryPath(installPathPlugins);
|
|
#ifdef QT_TST_QAPP_DEBUG
|
|
qDebug() << "installPathPlugins" << installPathPlugins;
|
|
qDebug() << "After adding plugins path:" << QApplication::libraryPaths();
|
|
#endif
|
|
QCOMPARE(QApplication::libraryPaths().count(), count);
|
|
QApplication::addLibraryPath(testDir);
|
|
QCOMPARE(QApplication::libraryPaths().count(), count + 1);
|
|
|
|
// creating QApplication adds the applicationDirPath to the libraryPath
|
|
int argc = 1;
|
|
QApplication app(argc, &argv0);
|
|
QString appDirPath = app.applicationDirPath();
|
|
qDebug() << QApplication::libraryPaths();
|
|
// On Windows CE these are identical and might also be the case for other
|
|
// systems too
|
|
if (appDirPath != installPathPlugins)
|
|
QCOMPARE(QApplication::libraryPaths().count(), count + 2);
|
|
}
|
|
{
|
|
int argc = 1;
|
|
QApplication app(argc, &argv0);
|
|
|
|
#ifdef QT_TST_QAPP_DEBUG
|
|
qDebug() << "Initial library path:" << app.libraryPaths();
|
|
#endif
|
|
int count = app.libraryPaths().count();
|
|
QString installPathPlugins = QLibraryInfo::location(QLibraryInfo::PluginsPath);
|
|
app.addLibraryPath(installPathPlugins);
|
|
#ifdef QT_TST_QAPP_DEBUG
|
|
qDebug() << "installPathPlugins" << installPathPlugins;
|
|
qDebug() << "After adding plugins path:" << app.libraryPaths();
|
|
#endif
|
|
QCOMPARE(app.libraryPaths().count(), count);
|
|
|
|
QString appDirPath = app.applicationDirPath();
|
|
|
|
app.addLibraryPath(appDirPath);
|
|
app.addLibraryPath(appDirPath + "/..");
|
|
#ifdef QT_TST_QAPP_DEBUG
|
|
qDebug() << "appDirPath" << appDirPath;
|
|
qDebug() << "After adding appDirPath && appDirPath + /..:" << app.libraryPaths();
|
|
#endif
|
|
QCOMPARE(app.libraryPaths().count(), count + 1);
|
|
#ifdef Q_OS_MAC
|
|
app.addLibraryPath(appDirPath + "/../MacOS");
|
|
#else
|
|
app.addLibraryPath(appDirPath + "/tmp/..");
|
|
#endif
|
|
#ifdef QT_TST_QAPP_DEBUG
|
|
qDebug() << "After adding appDirPath + /tmp/..:" << app.libraryPaths();
|
|
#endif
|
|
QCOMPARE(app.libraryPaths().count(), count + 1);
|
|
}
|
|
}
|
|
|
|
void tst_QApplication::libraryPaths_qt_plugin_path()
|
|
{
|
|
int argc = 1;
|
|
|
|
QApplication app(argc, &argv0);
|
|
QString appDirPath = app.applicationDirPath();
|
|
|
|
// Our hook into libraryPaths() initialization: Set the QT_PLUGIN_PATH environment variable
|
|
QString installPathPluginsDeCanon = appDirPath + QString::fromLatin1("/tmp/..");
|
|
QByteArray ascii = QFile::encodeName(installPathPluginsDeCanon);
|
|
qputenv("QT_PLUGIN_PATH", ascii);
|
|
|
|
QVERIFY(!app.libraryPaths().contains(appDirPath + QString::fromLatin1("/tmp/..")));
|
|
}
|
|
|
|
void tst_QApplication::libraryPaths_qt_plugin_path_2()
|
|
{
|
|
#ifdef Q_OS_UNIX
|
|
QByteArray validPath = QDir("/tmp").canonicalPath().toLatin1();
|
|
QByteArray nonExistentPath = "/nonexistent";
|
|
QByteArray pluginPath = validPath + ':' + nonExistentPath;
|
|
#elif defined(Q_OS_WIN)
|
|
QByteArray validPath = "C:\\windows";
|
|
QByteArray nonExistentPath = "Z:\\nonexistent";
|
|
QByteArray pluginPath = validPath + ';' + nonExistentPath;
|
|
#endif
|
|
|
|
{
|
|
// Our hook into libraryPaths() initialization: Set the QT_PLUGIN_PATH environment variable
|
|
qputenv("QT_PLUGIN_PATH", pluginPath);
|
|
|
|
int argc = 1;
|
|
|
|
QApplication app(argc, &argv0);
|
|
|
|
// library path list should contain the default plus the one valid path
|
|
QStringList expected =
|
|
QStringList()
|
|
<< QLibraryInfo::location(QLibraryInfo::PluginsPath)
|
|
<< QDir(app.applicationDirPath()).canonicalPath()
|
|
<< QDir(QDir::fromNativeSeparators(QString::fromLatin1(validPath))).canonicalPath();
|
|
|
|
QVERIFY2(isPathListIncluded(app.libraryPaths(), expected),
|
|
qPrintable("actual:\n - " + app.libraryPaths().join("\n - ") +
|
|
"\nexpected:\n - " + expected.join("\n - ")));
|
|
}
|
|
|
|
{
|
|
int argc = 1;
|
|
|
|
QApplication app(argc, &argv0);
|
|
|
|
// library paths are initialized by the QApplication, setting
|
|
// the environment variable here doesn't work
|
|
qputenv("QT_PLUGIN_PATH", pluginPath);
|
|
|
|
// library path list should contain the default
|
|
QStringList expected =
|
|
QStringList()
|
|
<< QLibraryInfo::location(QLibraryInfo::PluginsPath)
|
|
<< app.applicationDirPath();
|
|
QVERIFY(isPathListIncluded(app.libraryPaths(), expected));
|
|
|
|
qputenv("QT_PLUGIN_PATH", QByteArray());
|
|
}
|
|
}
|
|
#endif
|
|
|
|
class SendPostedEventsTester : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
QList<int> eventSpy;
|
|
bool event(QEvent *e);
|
|
private slots:
|
|
void doTest();
|
|
};
|
|
|
|
bool SendPostedEventsTester::event(QEvent *e)
|
|
{
|
|
eventSpy.append(e->type());
|
|
return QObject::event(e);
|
|
}
|
|
|
|
void SendPostedEventsTester::doTest()
|
|
{
|
|
QPointer<SendPostedEventsTester> p = this;
|
|
QApplication::postEvent(this, new QEvent(QEvent::User));
|
|
// DeferredDelete should not be delivered until returning from this function
|
|
QApplication::postEvent(this, new QDeferredDeleteEvent());
|
|
|
|
QEventLoop eventLoop;
|
|
QMetaObject::invokeMethod(&eventLoop, "quit", Qt::QueuedConnection);
|
|
eventLoop.exec();
|
|
QVERIFY(p != 0);
|
|
|
|
QCOMPARE(eventSpy.count(), 2);
|
|
QCOMPARE(eventSpy.at(0), int(QEvent::MetaCall));
|
|
QCOMPARE(eventSpy.at(1), int(QEvent::User));
|
|
eventSpy.clear();
|
|
}
|
|
|
|
void tst_QApplication::sendPostedEvents()
|
|
{
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
SendPostedEventsTester *tester = new SendPostedEventsTester;
|
|
QMetaObject::invokeMethod(tester, "doTest", Qt::QueuedConnection);
|
|
QMetaObject::invokeMethod(&app, "quit", Qt::QueuedConnection);
|
|
QPointer<SendPostedEventsTester> p = tester;
|
|
(void) app.exec();
|
|
QVERIFY(p.isNull());
|
|
}
|
|
|
|
void tst_QApplication::thread()
|
|
{
|
|
QThread *currentThread = QThread::currentThread();
|
|
// no app, but still have a valid thread
|
|
QVERIFY(currentThread != 0);
|
|
|
|
// the thread should be running and not finished
|
|
QVERIFY(currentThread->isRunning());
|
|
QVERIFY(!currentThread->isFinished());
|
|
|
|
// this should probably be in the tst_QObject::thread() test, but
|
|
// we put it here since we want to make sure that objects created
|
|
// *before* the QApplication has a thread
|
|
QObject object;
|
|
QObject child(&object);
|
|
QCOMPARE(object.thread(), currentThread);
|
|
QCOMPARE(child.thread(), currentThread);
|
|
|
|
{
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
|
|
// current thread still valid
|
|
QVERIFY(QThread::currentThread() != 0);
|
|
// thread should be the same as before
|
|
QCOMPARE(QThread::currentThread(), currentThread);
|
|
|
|
// app's thread should be the current thread
|
|
QCOMPARE(app.thread(), currentThread);
|
|
|
|
// the thread should still be running and not finished
|
|
QVERIFY(currentThread->isRunning());
|
|
QVERIFY(!currentThread->isFinished());
|
|
|
|
QTestEventLoop::instance().enterLoop(1);
|
|
}
|
|
|
|
// app dead, current thread still valid
|
|
QVERIFY(QThread::currentThread() != 0);
|
|
QCOMPARE(QThread::currentThread(), currentThread);
|
|
|
|
// the thread should still be running and not finished
|
|
QVERIFY(currentThread->isRunning());
|
|
QVERIFY(!currentThread->isFinished());
|
|
|
|
// should still have a thread
|
|
QCOMPARE(object.thread(), currentThread);
|
|
QCOMPARE(child.thread(), currentThread);
|
|
|
|
// do the test again, making sure that the thread is the same as
|
|
// before
|
|
{
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
|
|
// current thread still valid
|
|
QVERIFY(QThread::currentThread() != 0);
|
|
// thread should be the same as before
|
|
QCOMPARE(QThread::currentThread(), currentThread);
|
|
|
|
// app's thread should be the current thread
|
|
QCOMPARE(app.thread(), currentThread);
|
|
|
|
// the thread should be running and not finished
|
|
QVERIFY(currentThread->isRunning());
|
|
QVERIFY(!currentThread->isFinished());
|
|
|
|
// should still have a thread
|
|
QCOMPARE(object.thread(), currentThread);
|
|
QCOMPARE(child.thread(), currentThread);
|
|
|
|
QTestEventLoop::instance().enterLoop(1);
|
|
}
|
|
|
|
// app dead, current thread still valid
|
|
QVERIFY(QThread::currentThread() != 0);
|
|
QCOMPARE(QThread::currentThread(), currentThread);
|
|
|
|
// the thread should still be running and not finished
|
|
QVERIFY(currentThread->isRunning());
|
|
QVERIFY(!currentThread->isFinished());
|
|
|
|
// should still have a thread
|
|
QCOMPARE(object.thread(), currentThread);
|
|
QCOMPARE(child.thread(), currentThread);
|
|
}
|
|
|
|
class DeleteLaterWidget : public QWidget
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
DeleteLaterWidget(QApplication *_app, QWidget *parent = 0)
|
|
: QWidget(parent) { app = _app; child_deleted = false; }
|
|
|
|
bool child_deleted;
|
|
QApplication *app;
|
|
|
|
public slots:
|
|
void runTest();
|
|
void checkDeleteLater();
|
|
void childDeleted() { child_deleted = true; }
|
|
};
|
|
|
|
|
|
void DeleteLaterWidget::runTest()
|
|
{
|
|
QObject *stillAlive = this->findChild<QObject*>("deleteLater");
|
|
|
|
QWidget *w = new QWidget(this);
|
|
connect(w, SIGNAL(destroyed()), this, SLOT(childDeleted()));
|
|
|
|
w->deleteLater();
|
|
QVERIFY(!child_deleted);
|
|
|
|
QDialog dlg;
|
|
QTimer::singleShot(500, &dlg, SLOT(reject()));
|
|
dlg.exec();
|
|
|
|
QVERIFY(!child_deleted);
|
|
app->processEvents();
|
|
QVERIFY(!child_deleted);
|
|
|
|
QTimer::singleShot(500, this, SLOT(checkDeleteLater()));
|
|
|
|
app->processEvents();
|
|
|
|
QVERIFY(!stillAlive); // verify at the end to make test terminate
|
|
}
|
|
|
|
void DeleteLaterWidget::checkDeleteLater()
|
|
{
|
|
QVERIFY(child_deleted);
|
|
|
|
close();
|
|
}
|
|
|
|
void tst_QApplication::testDeleteLater()
|
|
{
|
|
#ifdef Q_OS_MAC
|
|
QSKIP("This test fails and then hangs on OS X, see QTBUG-24318");
|
|
#endif
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit()));
|
|
|
|
DeleteLaterWidget *wgt = new DeleteLaterWidget(&app);
|
|
QTimer::singleShot(500, wgt, SLOT(runTest()));
|
|
|
|
QObject *object = new QObject(wgt);
|
|
object->setObjectName("deleteLater");
|
|
object->deleteLater();
|
|
|
|
QObject *stillAlive = wgt->findChild<QObject*>("deleteLater");
|
|
QVERIFY(stillAlive);
|
|
|
|
app.exec();
|
|
|
|
delete wgt;
|
|
|
|
}
|
|
|
|
class EventLoopNester : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public slots:
|
|
void deleteLaterAndEnterLoop()
|
|
{
|
|
QEventLoop eventLoop;
|
|
QPointer<QObject> p(this);
|
|
deleteLater();
|
|
/*
|
|
DeferredDelete events are compressed, meaning this second
|
|
deleteLater() will *not* delete the object in the nested
|
|
event loop
|
|
*/
|
|
QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection);
|
|
QTimer::singleShot(1000, &eventLoop, SLOT(quit()));
|
|
eventLoop.exec();
|
|
QVERIFY(p);
|
|
}
|
|
void deleteLaterAndExitLoop()
|
|
{
|
|
// Check that 'p' is not deleted before exec returns, since the call
|
|
// to QEventLoop::quit() should stop 'eventLoop' from processing
|
|
// any more events (that is, delete later) until we return to the
|
|
// _current_ event loop:
|
|
QEventLoop eventLoop;
|
|
QPointer<QObject> p(this);
|
|
QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection);
|
|
QMetaObject::invokeMethod(&eventLoop, "quit", Qt::QueuedConnection);
|
|
eventLoop.exec();
|
|
QVERIFY(p); // not dead yet
|
|
}
|
|
|
|
void processEventsOnly()
|
|
{
|
|
QApplication::processEvents();
|
|
}
|
|
void sendPostedEventsWithDeferredDelete()
|
|
{
|
|
QApplication::sendPostedEvents(0, QEvent::DeferredDelete);
|
|
}
|
|
|
|
void deleteLaterAndProcessEvents()
|
|
{
|
|
QEventLoop eventLoop;
|
|
|
|
QPointer<QObject> p = this;
|
|
deleteLater();
|
|
|
|
// trying to delete this object in a deeper eventloop just won't work
|
|
QMetaObject::invokeMethod(this,
|
|
"processEventsOnly",
|
|
Qt::QueuedConnection);
|
|
QMetaObject::invokeMethod(&eventLoop, "quit", Qt::QueuedConnection);
|
|
eventLoop.exec();
|
|
QVERIFY(p);
|
|
QMetaObject::invokeMethod(this,
|
|
"sendPostedEventsWithDeferredDelete",
|
|
Qt::QueuedConnection);
|
|
QMetaObject::invokeMethod(&eventLoop, "quit", Qt::QueuedConnection);
|
|
eventLoop.exec();
|
|
QVERIFY(p);
|
|
|
|
// trying to delete it from this eventloop still doesn't work
|
|
QApplication::processEvents();
|
|
QVERIFY(p);
|
|
|
|
// however, it *will* work with this magic incantation
|
|
QApplication::sendPostedEvents(0, QEvent::DeferredDelete);
|
|
QVERIFY(!p);
|
|
}
|
|
};
|
|
|
|
void tst_QApplication::testDeleteLaterProcessEvents()
|
|
{
|
|
int argc = 0;
|
|
|
|
// Calling processEvents() with no event dispatcher does nothing.
|
|
QObject *object = new QObject;
|
|
QPointer<QObject> p(object);
|
|
object->deleteLater();
|
|
QApplication::processEvents();
|
|
QVERIFY(p);
|
|
delete object;
|
|
|
|
{
|
|
QApplication app(argc, 0);
|
|
// If you call processEvents() with an event dispatcher present, but
|
|
// outside any event loops, deferred deletes are not processed unless
|
|
// sendPostedEvents(0, DeferredDelete) is called.
|
|
object = new QObject;
|
|
p = object;
|
|
object->deleteLater();
|
|
app.processEvents();
|
|
QVERIFY(p);
|
|
QApplication::sendPostedEvents(0, QEvent::DeferredDelete);
|
|
QVERIFY(!p);
|
|
|
|
// If you call deleteLater() on an object when there is no parent
|
|
// event loop, and then enter an event loop, the object will get
|
|
// deleted.
|
|
object = new QObject;
|
|
p = object;
|
|
object->deleteLater();
|
|
QEventLoop loop;
|
|
QTimer::singleShot(1000, &loop, SLOT(quit()));
|
|
loop.exec();
|
|
QVERIFY(!p);
|
|
}
|
|
{
|
|
// When an object is in an event loop, then calls deleteLater() and enters
|
|
// an event loop recursively, it should not die until the parent event
|
|
// loop continues.
|
|
QApplication app(argc, 0);
|
|
QEventLoop loop;
|
|
EventLoopNester *nester = new EventLoopNester;
|
|
p = nester;
|
|
QTimer::singleShot(3000, &loop, SLOT(quit()));
|
|
QTimer::singleShot(0, nester, SLOT(deleteLaterAndEnterLoop()));
|
|
|
|
loop.exec();
|
|
QVERIFY(!p);
|
|
}
|
|
|
|
{
|
|
// When the event loop that calls deleteLater() is exited
|
|
// immediately, the object should die when returning to the
|
|
// parent event loop
|
|
QApplication app(argc, 0);
|
|
QEventLoop loop;
|
|
EventLoopNester *nester = new EventLoopNester;
|
|
p = nester;
|
|
QTimer::singleShot(3000, &loop, SLOT(quit()));
|
|
QTimer::singleShot(0, nester, SLOT(deleteLaterAndExitLoop()));
|
|
|
|
loop.exec();
|
|
QVERIFY(!p);
|
|
}
|
|
|
|
{
|
|
// when the event loop that calls deleteLater() also calls
|
|
// processEvents() immediately afterwards, the object should
|
|
// not die until the parent loop continues
|
|
QApplication app(argc, 0);
|
|
QEventLoop loop;
|
|
EventLoopNester *nester = new EventLoopNester();
|
|
p = nester;
|
|
QTimer::singleShot(3000, &loop, SLOT(quit()));
|
|
QTimer::singleShot(0, nester, SLOT(deleteLaterAndProcessEvents()));
|
|
|
|
loop.exec();
|
|
QVERIFY(!p);
|
|
}
|
|
}
|
|
|
|
/*
|
|
Test for crash with QApplication::setDesktopSettingsAware(false).
|
|
*/
|
|
void tst_QApplication::desktopSettingsAware()
|
|
{
|
|
#if QT_CONFIG(process)
|
|
QString path;
|
|
{
|
|
// We need an application object for QFINDTESTDATA to work
|
|
// properly in all cases.
|
|
int argc = 0;
|
|
QCoreApplication app(argc, 0);
|
|
path = QFINDTESTDATA("desktopsettingsaware/");
|
|
}
|
|
QVERIFY2(!path.isEmpty(), "Cannot locate desktopsettingsaware helper application");
|
|
path += "desktopsettingsaware";
|
|
QProcess testProcess;
|
|
testProcess.start(path);
|
|
QVERIFY2(testProcess.waitForStarted(),
|
|
qPrintable(QString::fromLatin1("Cannot start '%1': %2").arg(path, testProcess.errorString())));
|
|
QVERIFY(testProcess.waitForFinished(10000));
|
|
QCOMPARE(int(testProcess.state()), int(QProcess::NotRunning));
|
|
QVERIFY(int(testProcess.error()) != int(QProcess::Crashed));
|
|
#endif
|
|
}
|
|
|
|
void tst_QApplication::setActiveWindow()
|
|
{
|
|
int argc = 0;
|
|
QApplication MyApp(argc, 0);
|
|
|
|
QWidget* w = new QWidget;
|
|
QVBoxLayout* layout = new QVBoxLayout(w);
|
|
|
|
QLineEdit* pb1 = new QLineEdit("Testbutton1", w);
|
|
QLineEdit* pb2 = new QLineEdit("Test Line Edit", w);
|
|
|
|
layout->addWidget(pb1);
|
|
layout->addWidget(pb2);
|
|
|
|
pb2->setFocus();
|
|
pb2->setParent(0);
|
|
delete pb2;
|
|
|
|
w->show();
|
|
QApplication::setActiveWindow(w); // needs this on twm (focus follows mouse)
|
|
QVERIFY(pb1->hasFocus());
|
|
delete w;
|
|
}
|
|
|
|
|
|
/* This might fail on some X11 window managers? */
|
|
void tst_QApplication::focusChanged()
|
|
{
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
|
|
QSignalSpy spy(&app, SIGNAL(focusChanged(QWidget*,QWidget*)));
|
|
QWidget *now = 0;
|
|
QWidget *old = 0;
|
|
|
|
QWidget parent1;
|
|
QHBoxLayout hbox1(&parent1);
|
|
QLabel lb1(&parent1);
|
|
QLineEdit le1(&parent1);
|
|
QPushButton pb1(&parent1);
|
|
hbox1.addWidget(&lb1);
|
|
hbox1.addWidget(&le1);
|
|
hbox1.addWidget(&pb1);
|
|
|
|
QCOMPARE(spy.count(), 0);
|
|
|
|
parent1.show();
|
|
QApplication::setActiveWindow(&parent1); // needs this on twm (focus follows mouse)
|
|
QCOMPARE(spy.count(), 1);
|
|
QCOMPARE(spy.at(0).count(), 2);
|
|
old = qvariant_cast<QWidget*>(spy.at(0).at(0));
|
|
now = qvariant_cast<QWidget*>(spy.at(0).at(1));
|
|
QCOMPARE(now, &le1);
|
|
QCOMPARE(now, QApplication::focusWidget());
|
|
QVERIFY(!old);
|
|
spy.clear();
|
|
QCOMPARE(spy.count(), 0);
|
|
|
|
pb1.setFocus();
|
|
QCOMPARE(spy.count(), 1);
|
|
old = qvariant_cast<QWidget*>(spy.at(0).at(0));
|
|
now = qvariant_cast<QWidget*>(spy.at(0).at(1));
|
|
QCOMPARE(now, &pb1);
|
|
QCOMPARE(now, QApplication::focusWidget());
|
|
QCOMPARE(old, &le1);
|
|
spy.clear();
|
|
|
|
lb1.setFocus();
|
|
QCOMPARE(spy.count(), 1);
|
|
old = qvariant_cast<QWidget*>(spy.at(0).at(0));
|
|
now = qvariant_cast<QWidget*>(spy.at(0).at(1));
|
|
QCOMPARE(now, &lb1);
|
|
QCOMPARE(now, QApplication::focusWidget());
|
|
QCOMPARE(old, &pb1);
|
|
spy.clear();
|
|
|
|
lb1.clearFocus();
|
|
QCOMPARE(spy.count(), 1);
|
|
old = qvariant_cast<QWidget*>(spy.at(0).at(0));
|
|
now = qvariant_cast<QWidget*>(spy.at(0).at(1));
|
|
QVERIFY(!now);
|
|
QCOMPARE(now, QApplication::focusWidget());
|
|
QCOMPARE(old, &lb1);
|
|
spy.clear();
|
|
|
|
QWidget parent2;
|
|
QHBoxLayout hbox2(&parent2);
|
|
QLabel lb2(&parent2);
|
|
QLineEdit le2(&parent2);
|
|
QPushButton pb2(&parent2);
|
|
hbox2.addWidget(&lb2);
|
|
hbox2.addWidget(&le2);
|
|
hbox2.addWidget(&pb2);
|
|
|
|
parent2.show();
|
|
QApplication::setActiveWindow(&parent2); // needs this on twm (focus follows mouse)
|
|
QVERIFY(spy.count() > 0); // one for deactivation, one for activation on Windows
|
|
old = qvariant_cast<QWidget*>(spy.at(spy.count()-1).at(0));
|
|
now = qvariant_cast<QWidget*>(spy.at(spy.count()-1).at(1));
|
|
QCOMPARE(now, &le2);
|
|
QCOMPARE(now, QApplication::focusWidget());
|
|
QVERIFY(!old);
|
|
spy.clear();
|
|
|
|
QTestKeyEvent tab(QTest::Press, Qt::Key_Tab, 0, 0);
|
|
QTestKeyEvent backtab(QTest::Press, Qt::Key_Backtab, 0, 0);
|
|
QTestMouseEvent click(QTest::MouseClick, Qt::LeftButton, 0, QPoint(5, 5), 0);
|
|
|
|
bool tabAllControls = true;
|
|
#ifdef Q_OS_MAC
|
|
// Mac has two modes, one where you tab to everything, one where you can
|
|
// only tab to input controls, here's what we get. Determine which ones we
|
|
// should get.
|
|
QSettings appleSettings(QLatin1String("apple.com"));
|
|
QVariant appleValue = appleSettings.value(QLatin1String("AppleKeyboardUIMode"), 0);
|
|
tabAllControls = (appleValue.toInt() & 0x2);
|
|
#endif
|
|
|
|
// make sure Qt's idea of tabbing between widgets matches what we think it should
|
|
QCOMPARE(qt_tab_all_widgets(), tabAllControls);
|
|
|
|
tab.simulate(now);
|
|
if (!tabAllControls) {
|
|
QCOMPARE(spy.count(), 0);
|
|
QCOMPARE(now, QApplication::focusWidget());
|
|
} else {
|
|
QVERIFY(spy.count() > 0);
|
|
old = qvariant_cast<QWidget*>(spy.at(0).at(0));
|
|
now = qvariant_cast<QWidget*>(spy.at(0).at(1));
|
|
QCOMPARE(now, &pb2);
|
|
QCOMPARE(now, QApplication::focusWidget());
|
|
QCOMPARE(old, &le2);
|
|
spy.clear();
|
|
}
|
|
|
|
if (!tabAllControls) {
|
|
QCOMPARE(spy.count(), 0);
|
|
QCOMPARE(now, QApplication::focusWidget());
|
|
} else {
|
|
tab.simulate(now);
|
|
QVERIFY(spy.count() > 0);
|
|
old = qvariant_cast<QWidget*>(spy.at(0).at(0));
|
|
now = qvariant_cast<QWidget*>(spy.at(0).at(1));
|
|
QCOMPARE(now, &le2);
|
|
QCOMPARE(now, QApplication::focusWidget());
|
|
QCOMPARE(old, &pb2);
|
|
spy.clear();
|
|
}
|
|
|
|
if (!tabAllControls) {
|
|
QCOMPARE(spy.count(), 0);
|
|
QCOMPARE(now, QApplication::focusWidget());
|
|
} else {
|
|
backtab.simulate(now);
|
|
QVERIFY(spy.count() > 0);
|
|
old = qvariant_cast<QWidget*>(spy.at(0).at(0));
|
|
now = qvariant_cast<QWidget*>(spy.at(0).at(1));
|
|
QCOMPARE(now, &pb2);
|
|
QCOMPARE(now, QApplication::focusWidget());
|
|
QCOMPARE(old, &le2);
|
|
spy.clear();
|
|
}
|
|
|
|
|
|
if (!tabAllControls) {
|
|
QCOMPARE(spy.count(), 0);
|
|
QCOMPARE(now, QApplication::focusWidget());
|
|
old = &pb2;
|
|
} else {
|
|
backtab.simulate(now);
|
|
QVERIFY(spy.count() > 0);
|
|
old = qvariant_cast<QWidget*>(spy.at(0).at(0));
|
|
now = qvariant_cast<QWidget*>(spy.at(0).at(1));
|
|
QCOMPARE(now, &le2);
|
|
QCOMPARE(now, QApplication::focusWidget());
|
|
QCOMPARE(old, &pb2);
|
|
spy.clear();
|
|
}
|
|
|
|
click.simulate(old);
|
|
if (!(pb2.focusPolicy() & Qt::ClickFocus)) {
|
|
QCOMPARE(spy.count(), 0);
|
|
QCOMPARE(now, QApplication::focusWidget());
|
|
} else {
|
|
QVERIFY(spy.count() > 0);
|
|
old = qvariant_cast<QWidget*>(spy.at(0).at(0));
|
|
now = qvariant_cast<QWidget*>(spy.at(0).at(1));
|
|
QCOMPARE(now, &pb2);
|
|
QCOMPARE(now, QApplication::focusWidget());
|
|
QCOMPARE(old, &le2);
|
|
spy.clear();
|
|
|
|
click.simulate(old);
|
|
QVERIFY(spy.count() > 0);
|
|
old = qvariant_cast<QWidget*>(spy.at(0).at(0));
|
|
now = qvariant_cast<QWidget*>(spy.at(0).at(1));
|
|
QCOMPARE(now, &le2);
|
|
QCOMPARE(now, QApplication::focusWidget());
|
|
QCOMPARE(old, &pb2);
|
|
spy.clear();
|
|
}
|
|
|
|
parent1.activateWindow();
|
|
QApplication::setActiveWindow(&parent1); // needs this on twm (focus follows mouse)
|
|
QVERIFY(spy.count() == 1 || spy.count() == 2); // one for deactivation, one for activation on Windows
|
|
|
|
//on windows, the change of focus is made in 2 steps
|
|
//(the focusChanged SIGNAL is emitted twice)
|
|
if (spy.count()==1)
|
|
old = qvariant_cast<QWidget*>(spy.at(spy.count()-1).at(0));
|
|
else
|
|
old = qvariant_cast<QWidget*>(spy.at(spy.count()-2).at(0));
|
|
now = qvariant_cast<QWidget*>(spy.at(spy.count()-1).at(1));
|
|
QCOMPARE(now, &le1);
|
|
QCOMPARE(now, QApplication::focusWidget());
|
|
QCOMPARE(old, &le2);
|
|
spy.clear();
|
|
}
|
|
|
|
class LineEdit : public QLineEdit
|
|
{
|
|
public:
|
|
LineEdit(QWidget *parent = 0) : QLineEdit(parent) { }
|
|
|
|
protected:
|
|
void focusOutEvent(QFocusEvent *e) {
|
|
QLineEdit::focusOutEvent(e);
|
|
if (objectName() == "le1")
|
|
setStyleSheet("");
|
|
}
|
|
|
|
void focusInEvent(QFocusEvent *e) {
|
|
QLineEdit::focusInEvent(e);
|
|
if (objectName() == "le2")
|
|
setStyleSheet("");
|
|
}
|
|
};
|
|
|
|
void tst_QApplication::focusOut()
|
|
{
|
|
int argc = 1;
|
|
QApplication app(argc, &argv0);
|
|
|
|
// Tests the case where the style pointer changes when on focus in/out
|
|
// (the above is the case when the stylesheet changes)
|
|
QWidget w;
|
|
QLineEdit *le1 = new LineEdit(&w);
|
|
le1->setObjectName("le1");
|
|
le1->setStyleSheet("background: #fee");
|
|
le1->setFocus();
|
|
|
|
QLineEdit *le2 = new LineEdit(&w);
|
|
le2->setObjectName("le2");
|
|
le2->setStyleSheet("background: #fee");
|
|
le2->move(100, 100);
|
|
w.show();
|
|
|
|
QTest::qWait(2000);
|
|
le2->setFocus();
|
|
QTest::qWait(2000);
|
|
}
|
|
|
|
void tst_QApplication::focusMouseClick()
|
|
{
|
|
int argc = 1;
|
|
QApplication app(argc, &argv0);
|
|
|
|
QWidget w;
|
|
w.setFocusPolicy(Qt::StrongFocus);
|
|
QWidget w2(&w);
|
|
w2.setFocusPolicy(Qt::TabFocus);
|
|
w.show();
|
|
w.setFocus();
|
|
QTRY_COMPARE(QApplication::focusWidget(), &w);
|
|
|
|
// front most widget has Qt::TabFocus, parent widget accepts clicks as well
|
|
// now send a mouse button press event and check what happens with the focus
|
|
// it should be given to the parent widget
|
|
QMouseEvent ev(QEvent::MouseButtonPress, QPointF(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
|
|
QSpontaneKeyEvent::setSpontaneous(&ev);
|
|
QVERIFY(ev.spontaneous());
|
|
qApp->notify(&w2, &ev);
|
|
QCOMPARE(QApplication::focusWidget(), &w);
|
|
|
|
// then we give the inner widget strong focus -> it should get focus
|
|
w2.setFocusPolicy(Qt::StrongFocus);
|
|
QSpontaneKeyEvent::setSpontaneous(&ev);
|
|
QVERIFY(ev.spontaneous());
|
|
qApp->notify(&w2, &ev);
|
|
QTRY_COMPARE(QApplication::focusWidget(), &w2);
|
|
|
|
// now back to tab focus and click again (it already had focus) -> focus should stay
|
|
// (focus was revoked as of QTBUG-34042)
|
|
w2.setFocusPolicy(Qt::TabFocus);
|
|
QSpontaneKeyEvent::setSpontaneous(&ev);
|
|
QVERIFY(ev.spontaneous());
|
|
qApp->notify(&w2, &ev);
|
|
QCOMPARE(QApplication::focusWidget(), &w2);
|
|
}
|
|
|
|
void tst_QApplication::execAfterExit()
|
|
{
|
|
int argc = 1;
|
|
QApplication app(argc, &argv0);
|
|
QMetaObject::invokeMethod(&app, "quit", Qt::QueuedConnection);
|
|
// this should be ignored, as exec() will reset the exitCode
|
|
QApplication::exit(1);
|
|
int exitCode = app.exec();
|
|
QCOMPARE(exitCode, 0);
|
|
|
|
// the quitNow flag should have been reset, so we can spin an
|
|
// eventloop after QApplication::exec() returns
|
|
QEventLoop eventLoop;
|
|
QMetaObject::invokeMethod(&eventLoop, "quit", Qt::QueuedConnection);
|
|
exitCode = eventLoop.exec();
|
|
QCOMPARE(exitCode, 0);
|
|
}
|
|
|
|
#ifndef QT_NO_WHEELEVENT
|
|
void tst_QApplication::wheelScrollLines()
|
|
{
|
|
int argc = 1;
|
|
QApplication app(argc, &argv0);
|
|
// If wheelScrollLines returns 0, the mose wheel will be disabled.
|
|
QVERIFY(app.wheelScrollLines() > 0);
|
|
}
|
|
#endif // !QT_NO_WHEELEVENT
|
|
|
|
void tst_QApplication::style()
|
|
{
|
|
int argc = 1;
|
|
|
|
{
|
|
QApplication app(argc, &argv0);
|
|
QPointer<QStyle> style = app.style();
|
|
app.setStyle(QStyleFactory::create(QLatin1String("Windows")));
|
|
QVERIFY(style.isNull());
|
|
}
|
|
|
|
QApplication app(argc, &argv0);
|
|
|
|
// qApp style can never be 0
|
|
QVERIFY(QApplication::style() != 0);
|
|
}
|
|
|
|
void tst_QApplication::allWidgets()
|
|
{
|
|
int argc = 1;
|
|
QApplication app(argc, &argv0);
|
|
QWidget *w = new QWidget;
|
|
QVERIFY(app.allWidgets().contains(w)); // uncreate widget test
|
|
QVERIFY(app.allWidgets().contains(w)); // created widget test
|
|
delete w;
|
|
w = 0;
|
|
QVERIFY(!app.allWidgets().contains(w)); // removal test
|
|
}
|
|
|
|
void tst_QApplication::topLevelWidgets()
|
|
{
|
|
int argc = 1;
|
|
QApplication app(argc, &argv0);
|
|
QWidget *w = new QWidget;
|
|
w->show();
|
|
#ifndef QT_NO_CLIPBOARD
|
|
QClipboard *clipboard = QApplication::clipboard();
|
|
QString originalText = clipboard->text();
|
|
clipboard->setText(QString("newText"));
|
|
#endif
|
|
app.processEvents();
|
|
QVERIFY(QApplication::topLevelWidgets().contains(w));
|
|
QCOMPARE(QApplication::topLevelWidgets().count(), 1);
|
|
delete w;
|
|
w = 0;
|
|
app.processEvents();
|
|
QCOMPARE(QApplication::topLevelWidgets().count(), 0);
|
|
}
|
|
|
|
|
|
|
|
void tst_QApplication::setAttribute()
|
|
{
|
|
int argc = 1;
|
|
QApplication app(argc, &argv0);
|
|
QVERIFY(!QApplication::testAttribute(Qt::AA_ImmediateWidgetCreation));
|
|
QWidget *w = new QWidget;
|
|
QVERIFY(!w->testAttribute(Qt::WA_WState_Created));
|
|
delete w;
|
|
|
|
QApplication::setAttribute(Qt::AA_ImmediateWidgetCreation);
|
|
QVERIFY(QApplication::testAttribute(Qt::AA_ImmediateWidgetCreation));
|
|
w = new QWidget;
|
|
QVERIFY(w->testAttribute(Qt::WA_WState_Created));
|
|
QWidget *w2 = new QWidget(w);
|
|
w2->setParent(0);
|
|
QVERIFY(w2->testAttribute(Qt::WA_WState_Created));
|
|
delete w;
|
|
delete w2;
|
|
|
|
QApplication::setAttribute(Qt::AA_ImmediateWidgetCreation, false);
|
|
QVERIFY(!QApplication::testAttribute(Qt::AA_ImmediateWidgetCreation));
|
|
w = new QWidget;
|
|
QVERIFY(!w->testAttribute(Qt::WA_WState_Created));
|
|
delete w;
|
|
}
|
|
|
|
class TouchEventPropagationTestWidget : public QWidget
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
bool seenTouchEvent, acceptTouchEvent, seenMouseEvent, acceptMouseEvent;
|
|
|
|
TouchEventPropagationTestWidget(QWidget *parent = 0)
|
|
: QWidget(parent), seenTouchEvent(false), acceptTouchEvent(false), seenMouseEvent(false), acceptMouseEvent(false)
|
|
{
|
|
setAttribute(Qt::WA_TouchPadAcceptSingleTouchEvents);
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
seenTouchEvent = acceptTouchEvent = seenMouseEvent = acceptMouseEvent = false;
|
|
}
|
|
|
|
bool event(QEvent *event)
|
|
{
|
|
switch (event->type()) {
|
|
case QEvent::MouseButtonPress:
|
|
case QEvent::MouseMove:
|
|
case QEvent::MouseButtonRelease:
|
|
// qDebug() << objectName() << "seenMouseEvent = true";
|
|
seenMouseEvent = true;
|
|
event->setAccepted(acceptMouseEvent);
|
|
break;
|
|
case QEvent::TouchBegin:
|
|
case QEvent::TouchUpdate:
|
|
case QEvent::TouchEnd:
|
|
// qDebug() << objectName() << "seenTouchEvent = true";
|
|
seenTouchEvent = true;
|
|
event->setAccepted(acceptTouchEvent);
|
|
break;
|
|
default:
|
|
return QWidget::event(event);
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
void tst_QApplication::touchEventPropagation()
|
|
{
|
|
int argc = 1;
|
|
QApplication app(argc, &argv0);
|
|
|
|
QList<QTouchEvent::TouchPoint> pressedTouchPoints;
|
|
QTouchEvent::TouchPoint press(0);
|
|
press.setState(Qt::TouchPointPressed);
|
|
pressedTouchPoints << press;
|
|
|
|
QList<QTouchEvent::TouchPoint> releasedTouchPoints;
|
|
QTouchEvent::TouchPoint release(0);
|
|
release.setState(Qt::TouchPointReleased);
|
|
releasedTouchPoints << release;
|
|
|
|
QTouchDevice *device = QTest::createTouchDevice();
|
|
|
|
{
|
|
// touch event behavior on a window
|
|
TouchEventPropagationTestWidget window;
|
|
window.resize(200, 200);
|
|
window.setObjectName("1. window");
|
|
window.show(); // Must have an explicitly specified QWindow for handleTouchEvent,
|
|
// passing 0 would result in using topLevelAt() which is not ok in this case
|
|
// as the screen position in the point is bogus.
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
|
// QPA always takes screen positions and since we map the TouchPoint back to QPA's structure first,
|
|
// we must ensure there is a screen position in the TouchPoint that maps to a local 0, 0.
|
|
const QPoint deviceGlobalPos =
|
|
QHighDpi::toNativePixels(window.mapToGlobal(QPoint(0, 0)), window.windowHandle()->screen());
|
|
pressedTouchPoints[0].setScreenPos(deviceGlobalPos);
|
|
releasedTouchPoints[0].setScreenPos(deviceGlobalPos);
|
|
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(pressedTouchPoints));
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(releasedTouchPoints));
|
|
QCoreApplication::processEvents();
|
|
QVERIFY(!window.seenTouchEvent);
|
|
QVERIFY(window.seenMouseEvent); // QApplication may transform ignored touch events in mouse events
|
|
|
|
window.reset();
|
|
window.setAttribute(Qt::WA_AcceptTouchEvents);
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(pressedTouchPoints));
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(releasedTouchPoints));
|
|
QCoreApplication::processEvents();
|
|
QVERIFY(window.seenTouchEvent);
|
|
QVERIFY(window.seenMouseEvent);
|
|
|
|
window.reset();
|
|
window.acceptTouchEvent = true;
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(pressedTouchPoints));
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(releasedTouchPoints));
|
|
QCoreApplication::processEvents();
|
|
QVERIFY(window.seenTouchEvent);
|
|
QVERIFY(!window.seenMouseEvent);
|
|
}
|
|
|
|
{
|
|
// touch event behavior on a window with a child widget
|
|
TouchEventPropagationTestWidget window;
|
|
window.resize(200, 200);
|
|
window.setObjectName("2. window");
|
|
TouchEventPropagationTestWidget widget(&window);
|
|
widget.resize(200, 200);
|
|
widget.setObjectName("2. widget");
|
|
window.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
|
const QPoint deviceGlobalPos =
|
|
QHighDpi::toNativePixels(window.mapToGlobal(QPoint(50, 150)), window.windowHandle()->screen());
|
|
pressedTouchPoints[0].setScreenPos(deviceGlobalPos);
|
|
releasedTouchPoints[0].setScreenPos(deviceGlobalPos);
|
|
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(pressedTouchPoints));
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(releasedTouchPoints));
|
|
QTRY_VERIFY(widget.seenMouseEvent);
|
|
QVERIFY(!widget.seenTouchEvent);
|
|
QVERIFY(!window.seenTouchEvent);
|
|
QVERIFY(window.seenMouseEvent);
|
|
|
|
window.reset();
|
|
widget.reset();
|
|
widget.setAttribute(Qt::WA_AcceptTouchEvents);
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(pressedTouchPoints));
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(releasedTouchPoints));
|
|
QCoreApplication::processEvents();
|
|
QVERIFY(widget.seenTouchEvent);
|
|
QVERIFY(widget.seenMouseEvent);
|
|
QVERIFY(!window.seenTouchEvent);
|
|
QVERIFY(window.seenMouseEvent);
|
|
|
|
window.reset();
|
|
widget.reset();
|
|
widget.acceptMouseEvent = true;
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(pressedTouchPoints));
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(releasedTouchPoints));
|
|
QCoreApplication::processEvents();
|
|
QVERIFY(widget.seenTouchEvent);
|
|
QVERIFY(widget.seenMouseEvent);
|
|
QVERIFY(!window.seenTouchEvent);
|
|
QVERIFY(!window.seenMouseEvent);
|
|
|
|
window.reset();
|
|
widget.reset();
|
|
widget.acceptTouchEvent = true;
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(pressedTouchPoints));
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(releasedTouchPoints));
|
|
QCoreApplication::processEvents();
|
|
QVERIFY(widget.seenTouchEvent);
|
|
QVERIFY(!widget.seenMouseEvent);
|
|
QVERIFY(!window.seenTouchEvent);
|
|
QVERIFY(!window.seenMouseEvent);
|
|
|
|
window.reset();
|
|
widget.reset();
|
|
widget.setAttribute(Qt::WA_AcceptTouchEvents, false);
|
|
window.setAttribute(Qt::WA_AcceptTouchEvents);
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(pressedTouchPoints));
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(releasedTouchPoints));
|
|
QCoreApplication::processEvents();
|
|
QVERIFY(!widget.seenTouchEvent);
|
|
QVERIFY(widget.seenMouseEvent);
|
|
QVERIFY(window.seenTouchEvent);
|
|
QVERIFY(window.seenMouseEvent);
|
|
|
|
window.reset();
|
|
widget.reset();
|
|
window.acceptTouchEvent = true;
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(pressedTouchPoints));
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(releasedTouchPoints));
|
|
QCoreApplication::processEvents();
|
|
QVERIFY(!widget.seenTouchEvent);
|
|
QVERIFY(!widget.seenMouseEvent);
|
|
QVERIFY(window.seenTouchEvent);
|
|
QVERIFY(!window.seenMouseEvent);
|
|
|
|
window.reset();
|
|
widget.reset();
|
|
widget.acceptMouseEvent = true; // doesn't matter, touch events are propagated first
|
|
window.acceptTouchEvent = true;
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(pressedTouchPoints));
|
|
QWindowSystemInterface::handleTouchEvent(window.windowHandle(),
|
|
0,
|
|
device,
|
|
touchPointList(releasedTouchPoints));
|
|
QCoreApplication::processEvents();
|
|
QVERIFY(!widget.seenTouchEvent);
|
|
QVERIFY(!widget.seenMouseEvent);
|
|
QVERIFY(window.seenTouchEvent);
|
|
QVERIFY(!window.seenMouseEvent);
|
|
}
|
|
}
|
|
|
|
void tst_QApplication::qtbug_12673()
|
|
{
|
|
QString path;
|
|
{
|
|
// We need an application object for QFINDTESTDATA to work
|
|
// properly in all cases.
|
|
int argc = 0;
|
|
QCoreApplication app(argc, 0);
|
|
path = QFINDTESTDATA("modal/");
|
|
}
|
|
QVERIFY2(!path.isEmpty(), "Cannot locate modal helper application");
|
|
path += "modal";
|
|
|
|
#if QT_CONFIG(process)
|
|
QProcess testProcess;
|
|
QStringList arguments;
|
|
testProcess.start(path, arguments);
|
|
QVERIFY2(testProcess.waitForStarted(),
|
|
qPrintable(QString::fromLatin1("Cannot start '%1': %2").arg(path, testProcess.errorString())));
|
|
QVERIFY(testProcess.waitForFinished(20000));
|
|
QCOMPARE(testProcess.exitStatus(), QProcess::NormalExit);
|
|
#else
|
|
QSKIP( "No QProcess support", SkipAll);
|
|
#endif
|
|
}
|
|
|
|
class NoQuitOnHideWidget : public QWidget
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
explicit NoQuitOnHideWidget(QWidget *parent = 0)
|
|
: QWidget(parent)
|
|
{
|
|
QTimer::singleShot(0, this, SLOT(hide()));
|
|
QTimer::singleShot(500, this, SLOT(exitApp()));
|
|
}
|
|
|
|
private slots:
|
|
void exitApp() {
|
|
qApp->exit(1);
|
|
}
|
|
};
|
|
|
|
void tst_QApplication::noQuitOnHide()
|
|
{
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
NoQuitOnHideWidget window1;
|
|
window1.show();
|
|
QCOMPARE(app.exec(), 1);
|
|
}
|
|
|
|
class ShowCloseShowWidget : public QWidget
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
ShowCloseShowWidget(bool showAgain, QWidget *parent = 0)
|
|
: QWidget(parent), showAgain(showAgain)
|
|
{
|
|
QTimer::singleShot(0, this, SLOT(doClose()));
|
|
QTimer::singleShot(500, this, SLOT(exitApp()));
|
|
}
|
|
|
|
private slots:
|
|
void doClose() {
|
|
close();
|
|
if (showAgain)
|
|
show();
|
|
}
|
|
|
|
void exitApp() {
|
|
qApp->exit(1);
|
|
}
|
|
|
|
private:
|
|
bool showAgain;
|
|
};
|
|
|
|
void tst_QApplication::abortQuitOnShow()
|
|
{
|
|
int argc = 0;
|
|
QApplication app(argc, 0);
|
|
ShowCloseShowWidget window1(false);
|
|
window1.show();
|
|
QCOMPARE(app.exec(), 0);
|
|
|
|
ShowCloseShowWidget window2(true);
|
|
window2.show();
|
|
QCOMPARE(app.exec(), 1);
|
|
}
|
|
|
|
// Test that static functions do not crash if there is no application instance.
|
|
void tst_QApplication::staticFunctions()
|
|
{
|
|
QApplication::setStyle(QStringLiteral("blub"));
|
|
QApplication::colorSpec();
|
|
QApplication::setColorSpec(42);
|
|
QApplication::allWidgets();
|
|
QApplication::topLevelWidgets();
|
|
QApplication::desktop();
|
|
QApplication::activePopupWidget();
|
|
QApplication::activeModalWidget();
|
|
QApplication::focusWidget();
|
|
QApplication::activeWindow();
|
|
QApplication::setActiveWindow(Q_NULLPTR);
|
|
QApplication::widgetAt(QPoint(0, 0));
|
|
QApplication::topLevelAt(QPoint(0, 0));
|
|
QApplication::setGlobalStrut(QSize(0, 0));
|
|
QApplication::globalStrut();
|
|
QApplication::isEffectEnabled(Qt::UI_General);
|
|
QApplication::setEffectEnabled(Qt::UI_General, false);
|
|
}
|
|
|
|
void tst_QApplication::settableStyleHints_data()
|
|
{
|
|
QTest::addColumn<bool>("appInstance");
|
|
QTest::newRow("app") << true;
|
|
QTest::newRow("no-app") << false;
|
|
}
|
|
|
|
void tst_QApplication::settableStyleHints()
|
|
{
|
|
QFETCH(bool, appInstance);
|
|
int argc = 0;
|
|
QScopedPointer<QApplication> app;
|
|
if (appInstance)
|
|
app.reset(new QApplication(argc, 0));
|
|
|
|
QApplication::setCursorFlashTime(437);
|
|
QCOMPARE(QApplication::cursorFlashTime(), 437);
|
|
QApplication::setDoubleClickInterval(128);
|
|
QCOMPARE(QApplication::doubleClickInterval(), 128);
|
|
QApplication::setStartDragDistance(122000);
|
|
QCOMPARE(QApplication::startDragDistance(), 122000);
|
|
QApplication::setStartDragTime(834);
|
|
QCOMPARE(QApplication::startDragTime(), 834);
|
|
QApplication::setKeyboardInputInterval(309);
|
|
QCOMPARE(QApplication::keyboardInputInterval(), 309);
|
|
}
|
|
|
|
/*
|
|
This test is meant to ensure that certain objects (public & commonly used)
|
|
can safely be used in a Q_GLOBAL_STATIC such that their destructors are
|
|
executed *after* the destruction of QApplication.
|
|
*/
|
|
Q_GLOBAL_STATIC(QLocale, tst_qapp_locale);
|
|
#if QT_CONFIG(process)
|
|
Q_GLOBAL_STATIC(QProcess, tst_qapp_process);
|
|
#endif
|
|
#ifndef QT_NO_FILESYSTEMWATCHER
|
|
Q_GLOBAL_STATIC(QFileSystemWatcher, tst_qapp_fileSystemWatcher);
|
|
#endif
|
|
#ifndef QT_NO_SHAREDMEMORY
|
|
Q_GLOBAL_STATIC(QSharedMemory, tst_qapp_sharedMemory);
|
|
#endif
|
|
Q_GLOBAL_STATIC(QElapsedTimer, tst_qapp_elapsedTimer);
|
|
Q_GLOBAL_STATIC(QMutex, tst_qapp_mutex);
|
|
Q_GLOBAL_STATIC(QWidget, tst_qapp_widget);
|
|
Q_GLOBAL_STATIC(QPixmap, tst_qapp_pixmap);
|
|
Q_GLOBAL_STATIC(QFont, tst_qapp_font);
|
|
Q_GLOBAL_STATIC(QRegion, tst_qapp_region);
|
|
Q_GLOBAL_STATIC(QFontDatabase, tst_qapp_fontDatabase);
|
|
#ifndef QT_NO_CURSOR
|
|
Q_GLOBAL_STATIC(QCursor, tst_qapp_cursor);
|
|
#endif
|
|
|
|
void tst_QApplication::globalStaticObjectDestruction()
|
|
{
|
|
int argc = 1;
|
|
QApplication app(argc, &argv0);
|
|
QVERIFY(tst_qapp_locale());
|
|
#if QT_CONFIG(process)
|
|
QVERIFY(tst_qapp_process());
|
|
#endif
|
|
#ifndef QT_NO_FILESYSTEMWATCHER
|
|
QVERIFY(tst_qapp_fileSystemWatcher());
|
|
#endif
|
|
#ifndef QT_NO_SHAREDMEMORY
|
|
QVERIFY(tst_qapp_sharedMemory());
|
|
#endif
|
|
QVERIFY(tst_qapp_elapsedTimer());
|
|
QVERIFY(tst_qapp_mutex());
|
|
QVERIFY(tst_qapp_widget());
|
|
QVERIFY(tst_qapp_pixmap());
|
|
QVERIFY(tst_qapp_font());
|
|
QVERIFY(tst_qapp_region());
|
|
QVERIFY(tst_qapp_fontDatabase());
|
|
#ifndef QT_NO_CURSOR
|
|
QVERIFY(tst_qapp_cursor());
|
|
#endif
|
|
}
|
|
|
|
void tst_QApplication::quitApplication()
|
|
{
|
|
quitApplicationTriggered = true;
|
|
qApp->quit();
|
|
}
|
|
|
|
//QTEST_APPLESS_MAIN(tst_QApplication)
|
|
int main(int argc, char *argv[])
|
|
{
|
|
tst_QApplication tc;
|
|
argv0 = argv[0];
|
|
return QTest::qExec(&tc, argc, argv);
|
|
}
|
|
|
|
#include "tst_qapplication.moc"
|