From 17a4630886d4a91362f508f27022ae48e076d9c3 Mon Sep 17 00:00:00 2001 From: Katja Marttila Date: Tue, 5 May 2020 13:31:32 +0300 Subject: CLI: Add new option --file-query to auto answer QFileDialog QFileDialog.getExistingDirectory and QFileDialog.getOpenFileName can be called from scipt. If command line interface is used, user must type the correct directory or file name from command line during install. With --file-query option user can give the values when running the installer or maintenancetool with syntax --file-query filedialogId=C:/temp,filedialogId2=C:/temp/file.txt. Task-number: QTIFW-1631 Change-Id: I5e58be6b509cf00de832646ef31ec4eda90773be Reviewed-by: Arttu Tarkiainen --- doc/scripting-api/packagemanagercore.qdoc | 19 +++++- doc/scripting-api/qfiledialog.qdoc | 7 ++- src/libs/installer/commandlineparser.cpp | 6 ++ src/libs/installer/constants.h | 1 + src/libs/installer/packagemanagercore.cpp | 49 +++++++++++++++ src/libs/installer/packagemanagercore.h | 5 ++ src/libs/installer/scriptengine.cpp | 69 ++++++++++++++++++++- src/libs/installer/scriptengine_p.h | 22 ++++--- src/sdk/sdkapp.h | 12 ++++ .../data/filequeryrepository/A/1.0.2-1meta.7z | Bin 0 -> 907 bytes .../data/filequeryrepository/Updates.xml | 13 ++++ tests/auto/installer/cliinterface/settings.qrc | 2 + .../installer/cliinterface/tst_cliinterface.cpp | 25 ++++++++ 13 files changed, 218 insertions(+), 12 deletions(-) create mode 100644 tests/auto/installer/cliinterface/data/filequeryrepository/A/1.0.2-1meta.7z create mode 100644 tests/auto/installer/cliinterface/data/filequeryrepository/Updates.xml diff --git a/doc/scripting-api/packagemanagercore.qdoc b/doc/scripting-api/packagemanagercore.qdoc index 1cff11c84..cb11cef0d 100644 --- a/doc/scripting-api/packagemanagercore.qdoc +++ b/doc/scripting-api/packagemanagercore.qdoc @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -331,6 +331,23 @@ individual licenses are still printed to a console. */ +/*! + \qmlmethod void installer::setFileDialogAutomaticAnswer(string identifier, string &value) + + Automatically sets the existing directory or filename \a value to QFileDialog with the ID + \a identifier. + + \sa removeFileDialogAutomaticAnswer +*/ + +/*! + \qmlmethod void installer::removeFileDialogAutomaticAnswer(string identifier) + + Removes the automatic answer from QFileDialog with the ID \a identifier. + + \sa setFileDialogAutomaticAnswer +*/ + /*! \qmlmethod float installer::requiredDiskSpace() diff --git a/doc/scripting-api/qfiledialog.qdoc b/doc/scripting-api/qfiledialog.qdoc index 661a702f7..e6cdfdb24 100644 --- a/doc/scripting-api/qfiledialog.qdoc +++ b/doc/scripting-api/qfiledialog.qdoc @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -34,6 +34,11 @@ that displays an existing directory selected by the user. Use the QFileDialog::getOpenFileName() method to create a dialog that displays matching files in the directory selected by the user. + When a command line interface is used, a dialog is not displayed. Instead, + the user can type the directory or the file name in the console. For + automatic installations, \c{--file-query} with \c{identifier=value} pairs + can be given separated with a comma. For example, + \c{--file-query filedialog.id=C:/Temp,filedialog.id2=C:/Temp2}. */ /*! diff --git a/src/libs/installer/commandlineparser.cpp b/src/libs/installer/commandlineparser.cpp index 60b02c3ae..f3939dc36 100644 --- a/src/libs/installer/commandlineparser.cpp +++ b/src/libs/installer/commandlineparser.cpp @@ -163,6 +163,12 @@ CommandLineParser::CommandLineParser() QLatin1String("Automatically answers to message queries with their default values."))); m_parser.addOption(QCommandLineOption(QStringList() << CommandLineOptions::scAcceptLicenses, QLatin1String("Accepts all licenses without user input."))); + m_parser.addOption(QCommandLineOption(QStringList() << CommandLineOptions::scFileDialogAutomaticAnswer, + QLatin1String("Automatically sets the QFileDialog values getExistingDirectory() or getOpenFileName() " + "requested by install script. " + "Several identifier=value pairs can be given separated with comma, " + "for example --file-query filedialog.id=C:\Temp,filedialog.id2=C:\Temp2"), + QLatin1String("identifier=value"))); // Developer options m_parser.addOption(QCommandLineOption(QStringList() diff --git a/src/libs/installer/constants.h b/src/libs/installer/constants.h index fecba0e6c..9e90d9383 100644 --- a/src/libs/installer/constants.h +++ b/src/libs/installer/constants.h @@ -158,6 +158,7 @@ static const QLatin1String scRejectMessageQuery("reject-messages"); static const QLatin1String scMessageAutomaticAnswer("auto-answer"); static const QLatin1String scMessageDefaultAnswer("default-answer"); static const QLatin1String scAcceptLicenses("accept-licenses"); +static const QLatin1String scFileDialogAutomaticAnswer("file-query"); // Misc installation options static const QLatin1String scRootShort("t"); diff --git a/src/libs/installer/packagemanagercore.cpp b/src/libs/installer/packagemanagercore.cpp index 2287e5a73..0bfde19c3 100644 --- a/src/libs/installer/packagemanagercore.cpp +++ b/src/libs/installer/packagemanagercore.cpp @@ -624,6 +624,55 @@ void PackageManagerCore::setAutoAcceptLicenses() d->m_autoAcceptLicenses = true; } +/*! + Automatically sets the existing directory or filename \a value to QFileDialog with the ID + \a identifier. QFileDialog can be called from script. + + This can be used for unattended (automatic) installations. + + \sa {installer::setFileDialogAutomaticAnswer){installer.setFileDialogAutomaticAnswer} + \sa {QFileDialog::getExistingDirectory}{QFileDialog.getExistingDirectory} + \sa {QFileDialog::getOpenFileName}{QFileDialog.getOpenFileName} + */ +void PackageManagerCore::setFileDialogAutomaticAnswer(const QString &identifier, const QString &value) +{ + m_fileDialogAutomaticAnswers.insert(identifier, value); +} + +/*! + Removes the automatic answer from QFileDialog with the ID \a identifier. + QFileDialog can be called from script. + + \sa {installer::removeFileDialogAutomaticAnswer){installer.removeFileDialogAutomaticAnswer} + \sa {QFileDialog::getExistingDirectory}{QFileDialog.getExistingDirectory} + \sa {QFileDialog::getOpenFileName}{QFileDialog.getOpenFileName} + */ +void PackageManagerCore::removeFileDialogAutomaticAnswer(const QString &identifier) +{ + m_fileDialogAutomaticAnswers.remove(identifier); +} + +/*! + Returns \c true if QFileDialog with the ID \a identifier has an automatic answer set. + + \sa {installer.containsFileDialogAutomaticAnswer}{installer::containsFileDialogAutomaticAnswer} + \sa {installer::removeFileDialogAutomaticAnswer){installer.removeFileDialogAutomaticAnswer} + \sa {QFileDialog::getExistingDirectory}{QFileDialog.getExistingDirectory} + \sa {QFileDialog::getOpenFileName}{QFileDialog.getOpenFileName} + */ +bool PackageManagerCore::containsFileDialogAutomaticAnswer(const QString &identifier) const +{ + return m_fileDialogAutomaticAnswers.contains(identifier); +} +/*! + * Returns the hash of file dialog automatic answers + * \sa setFileDialogAutomaticAnswer() + */ +QHash PackageManagerCore::fileDialogAutomaticAnswers() const +{ + return m_fileDialogAutomaticAnswers; +} + /*! Returns the size of the component \a component as \a value. */ diff --git a/src/libs/installer/packagemanagercore.h b/src/libs/installer/packagemanagercore.h index 67e7d6365..e188ed34d 100644 --- a/src/libs/installer/packagemanagercore.h +++ b/src/libs/installer/packagemanagercore.h @@ -187,6 +187,10 @@ public: Q_INVOKABLE void acceptMessageBoxDefaultButton(); Q_INVOKABLE void setAutoAcceptLicenses(); + Q_INVOKABLE void setFileDialogAutomaticAnswer(const QString &identifier, const QString &value); + Q_INVOKABLE void removeFileDialogAutomaticAnswer(const QString &identifier); + Q_INVOKABLE bool containsFileDialogAutomaticAnswer(const QString &identifier) const; + QHash fileDialogAutomaticAnswers() const; quint64 size(QInstaller::Component *component, const QString &value) const; @@ -385,6 +389,7 @@ private: private: PackageManagerCorePrivate *const d; friend class PackageManagerCorePrivate; + QHash m_fileDialogAutomaticAnswers; private: // remove once we deprecate isSelected, setSelected etc... diff --git a/src/libs/installer/scriptengine.cpp b/src/libs/installer/scriptengine.cpp index 994fa1406..accd54957 100644 --- a/src/libs/installer/scriptengine.cpp +++ b/src/libs/installer/scriptengine.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -265,6 +265,71 @@ void GuiProxy::setModified(bool value) m_gui->setModified(value); } +QFileDialogProxy::QFileDialogProxy(PackageManagerCore *core): m_core(core) +{ +} + +QString QFileDialogProxy::getExistingDirectory(const QString &caption, + const QString &dir, const QString &identifier) +{ + if (m_core->isCommandLineInstance()) { + return getExistingFileOrDirectory(caption, identifier, true); + } else { + return QFileDialog::getExistingDirectory(0, caption, dir); + } +} + +QString QFileDialogProxy::getOpenFileName(const QString &caption, const QString &dir, + const QString &filter, const QString &identifier) +{ + if (m_core->isCommandLineInstance()) { + return getExistingFileOrDirectory(caption, identifier, false); + } else { + return QFileDialog::getOpenFileName(0, caption, dir, filter); + } +} + +QString QFileDialogProxy::getExistingFileOrDirectory(const QString &caption, + const QString &identifier, bool isDirectory) +{ + QHash autoAnswers = m_core->fileDialogAutomaticAnswers(); + QString selectedDirectoryOrFile; + QString errorString; + if (autoAnswers.contains(identifier)) { + selectedDirectoryOrFile = autoAnswers.value(identifier); + QFileInfo fileInfo(selectedDirectoryOrFile); + if (isDirectory ? fileInfo.isDir() : fileInfo.isFile()) { + qCDebug(QInstaller::lcInstallerInstallLog).nospace() << "Automatic answer for "<< identifier + << ": " << selectedDirectoryOrFile; + } else { + if (isDirectory) + errorString = QString::fromLatin1("Automatic answer for %1: Directory '%2' not found.") + .arg(identifier, selectedDirectoryOrFile); + else + errorString = QString::fromLatin1("Automatic answer for %1: File '%2' not found.") + .arg(identifier, selectedDirectoryOrFile); + selectedDirectoryOrFile = QString(); + } + } else { + qDebug().nospace().noquote() << identifier << ": " << caption << ": "; + QTextStream stream(stdin); + stream.readLineInto(&selectedDirectoryOrFile); + QFileInfo fileInfo(selectedDirectoryOrFile); + if (isDirectory ? !fileInfo.isDir() : !fileInfo.isFile()) { + if (isDirectory) + errorString = QString::fromLatin1("Directory '%1' not found.") + .arg(selectedDirectoryOrFile); + else + errorString = QString::fromLatin1("File '%1' not found.") + .arg(selectedDirectoryOrFile); + selectedDirectoryOrFile = QString(); + } + } + if (!errorString.isEmpty()) + qCWarning(QInstaller::lcInstallerInstallLog).nospace() << errorString; + return selectedDirectoryOrFile; +} + /*! Constructs a script engine with \a core as parent. @@ -275,7 +340,7 @@ ScriptEngine::ScriptEngine(PackageManagerCore *core) : { QJSValue global = m_engine.globalObject(); global.setProperty(QLatin1String("console"), m_engine.newQObject(new ConsoleProxy)); - global.setProperty(QLatin1String("QFileDialog"), m_engine.newQObject(new QFileDialogProxy)); + global.setProperty(QLatin1String("QFileDialog"), m_engine.newQObject(new QFileDialogProxy(core))); const QJSValue proxy = m_engine.newQObject(new InstallerProxy(this, core)); global.setProperty(QLatin1String("InstallerProxy"), proxy); global.setProperty(QLatin1String("print"), m_engine.newQObject(new ConsoleProxy) diff --git a/src/libs/installer/scriptengine_p.h b/src/libs/installer/scriptengine_p.h index 928f903cb..0fc963bf9 100644 --- a/src/libs/installer/scriptengine_p.h +++ b/src/libs/installer/scriptengine_p.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -77,15 +77,21 @@ class QFileDialogProxy : public QObject Q_DISABLE_COPY(QFileDialogProxy) public: - QFileDialogProxy() {} + QFileDialogProxy(PackageManagerCore *core); public slots : - QString getExistingDirectory(const QString &caption, const QString &dir) const { - return QFileDialog::getExistingDirectory(0, caption, dir); - } - QString getOpenFileName(const QString &caption, const QString &dir, const QString &filter) const { - return QFileDialog::getOpenFileName(0, caption, dir, filter); - } + QString getExistingDirectory(const QString &caption, const QString &dir, + const QString &identifier = QLatin1String("GetExistingDirectory")); + + QString getOpenFileName(const QString &caption, const QString &dir, const QString &filter, + const QString &identifier = QLatin1String("GetExistingFile")); + +private: + QString getExistingFileOrDirectory(const QString &caption, const QString &identifier, + bool isDirectory); + +private: + PackageManagerCore *m_core; }; class QDesktopServicesProxy : public QObject diff --git a/src/sdk/sdkapp.h b/src/sdk/sdkapp.h index ecaee3758..378555633 100644 --- a/src/sdk/sdkapp.h +++ b/src/sdk/sdkapp.h @@ -292,6 +292,18 @@ public: } } + if (m_parser.isSet(CommandLineOptions::scFileDialogAutomaticAnswer)) { + const QString positionalArguments = m_parser.value(CommandLineOptions::scFileDialogAutomaticAnswer); + const QStringList items = positionalArguments.split(QLatin1Char(','), QString::SkipEmptyParts); + + foreach (const QString &item, items) { + if (item.contains(QLatin1Char('='))) { + const QString name = item.section(QLatin1Char('='), 0, 0); + QString value = item.section(QLatin1Char('='), 1, 1); + m_core->setFileDialogAutomaticAnswer(name, value); + } + } + } if (m_parser.isSet(CommandLineOptions::scMessageDefaultAnswer)) { m_core->acceptMessageBoxDefaultButton(); } diff --git a/tests/auto/installer/cliinterface/data/filequeryrepository/A/1.0.2-1meta.7z b/tests/auto/installer/cliinterface/data/filequeryrepository/A/1.0.2-1meta.7z new file mode 100644 index 000000000..a006c5c96 Binary files /dev/null and b/tests/auto/installer/cliinterface/data/filequeryrepository/A/1.0.2-1meta.7z differ diff --git a/tests/auto/installer/cliinterface/data/filequeryrepository/Updates.xml b/tests/auto/installer/cliinterface/data/filequeryrepository/Updates.xml new file mode 100644 index 000000000..72b7938d9 --- /dev/null +++ b/tests/auto/installer/cliinterface/data/filequeryrepository/Updates.xml @@ -0,0 +1,13 @@ + + {AnyApplication} + 1.0.0 + + A + A + A component + 1.0.2-1 + 2015-01-01 + true + + + diff --git a/tests/auto/installer/cliinterface/settings.qrc b/tests/auto/installer/cliinterface/settings.qrc index 85e344d1b..be97adfbe 100644 --- a/tests/auto/installer/cliinterface/settings.qrc +++ b/tests/auto/installer/cliinterface/settings.qrc @@ -19,6 +19,8 @@ data/installPackagesRepository/componentF.subcomponent1.subsubcomponent2/1.0.0content.7z data/installPackagesRepository/componentF.subcomponent2.subsubcomponent1/1.0.0content.7z data/installPackagesRepository/componentF.subcomponent2.subsubcomponent2/1.0.0content.7z + data/filequeryrepository/Updates.xml + data/filequeryrepository/A/1.0.2-1meta.7z data/componentsFromInstallPackagesRepository.xml diff --git a/tests/auto/installer/cliinterface/tst_cliinterface.cpp b/tests/auto/installer/cliinterface/tst_cliinterface.cpp index 6040927ea..ebf63ff9d 100644 --- a/tests/auto/installer/cliinterface/tst_cliinterface.cpp +++ b/tests/auto/installer/cliinterface/tst_cliinterface.cpp @@ -351,6 +351,31 @@ private slots: << "installcontentA.txt" << "installcontentE.txt" << "installcontentG.txt" << "installcontentB.txt" << "installcontentD.txt"); } + void testFileQuery() + { + PackageManagerCore *core = PackageManager::getPackageManagerWithInit(m_installDir, + ":///data/filequeryrepository"); + core->setCommandLineInstance(true); + core->setFileDialogAutomaticAnswer("ValidDirectory", m_installDir); + + QString testFile = qApp->applicationDirPath() + QDir::toNativeSeparators("/test"); + QFile file(testFile); + QVERIFY(file.open(QIODevice::WriteOnly)); + core->setFileDialogAutomaticAnswer("ValidFile", testFile); + + //File dialog launched without ID + core->setFileDialogAutomaticAnswer("GetExistingDirectory", m_installDir); + core->setFileDialogAutomaticAnswer("GetExistingFile", testFile); + + core->installDefaultComponentsSilently(); + + QVERIFY(core->containsFileDialogAutomaticAnswer("ValidFile")); + core->removeFileDialogAutomaticAnswer("ValidFile"); + QVERIFY(!core->containsFileDialogAutomaticAnswer("ValidFile")); + + QVERIFY(file.remove()); + core->deleteLater(); + } void init() { -- cgit v1.2.3