diff options
author | Ville Voutilainen <ville.voutilainen@qt.io> | 2020-05-07 09:07:39 +0300 |
---|---|---|
committer | Ville Voutilainen <ville.voutilainen@qt.io> | 2020-05-25 11:18:09 +0000 |
commit | 52188918c0d4c90c38be99eb2c4586dd3a11b67f (patch) | |
tree | 3bdab8da448f5fa9c1d770d39b7c614844100285 | |
parent | 14666c801aeaa4a7b12ed43e33f50e71f4345269 (diff) | |
download | qt-creator-52188918c0d4c90c38be99eb2c4586dd3a11b67f.tar.gz |
Android: add service editor to manifest editor
Task-number: QTCREATORBUG-23937
Change-Id: Iec0435721504df744ec985bd3e5cefcc0700e852
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
-rw-r--r-- | src/plugins/android/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/plugins/android/android.pro | 3 | ||||
-rw-r--r-- | src/plugins/android/android.qbs | 3 | ||||
-rw-r--r-- | src/plugins/android/androidmanifesteditorwidget.cpp | 238 | ||||
-rw-r--r-- | src/plugins/android/androidmanifesteditorwidget.h | 10 | ||||
-rw-r--r-- | src/plugins/android/androidservicewidget.cpp | 410 | ||||
-rw-r--r-- | src/plugins/android/androidservicewidget.h | 89 | ||||
-rw-r--r-- | src/plugins/android/androidservicewidget_p.h | 59 |
8 files changed, 805 insertions, 8 deletions
diff --git a/src/plugins/android/CMakeLists.txt b/src/plugins/android/CMakeLists.txt index e672527ea6..c19da7c15a 100644 --- a/src/plugins/android/CMakeLists.txt +++ b/src/plugins/android/CMakeLists.txt @@ -40,6 +40,7 @@ add_qtc_plugin(Android androidsdkmanagerwidget.cpp androidsdkmanagerwidget.h androidsdkmanagerwidget.ui androidsdkmodel.cpp androidsdkmodel.h androidsdkpackage.cpp androidsdkpackage.h + androidservicewidget.cpp androidservicewidget.h androidservicewidget_p.h androidsettingswidget.cpp androidsettingswidget.h androidsettingswidget.ui androidsignaloperation.cpp androidsignaloperation.h androidtoolchain.cpp androidtoolchain.h diff --git a/src/plugins/android/android.pro b/src/plugins/android/android.pro index dd62a79bcd..529e4d090c 100644 --- a/src/plugins/android/android.pro +++ b/src/plugins/android/android.pro @@ -13,6 +13,8 @@ HEADERS += \ androidmanifesteditoriconwidget.h \ androidrunconfiguration.h \ androidruncontrol.h \ + androidservicewidget.h \ + androidservicewidget_p.h \ androidsettingswidget.h \ androidtoolchain.h \ androiderrormessage.h \ @@ -60,6 +62,7 @@ SOURCES += \ androidmanifesteditoriconwidget.cpp \ androidrunconfiguration.cpp \ androidruncontrol.cpp \ + androidservicewidget.cpp \ androidsettingswidget.cpp \ androidtoolchain.cpp \ androiderrormessage.cpp \ diff --git a/src/plugins/android/android.qbs b/src/plugins/android/android.qbs index 0710d851fe..0edfce86a5 100644 --- a/src/plugins/android/android.qbs +++ b/src/plugins/android/android.qbs @@ -93,6 +93,9 @@ Project { "androidsdkmodel.h", "androidsdkpackage.cpp", "androidsdkpackage.h", + "androidservicewidget.cpp", + "androidservicewidget.h", + "androidservicewidget_p.h", "androidsettingswidget.cpp", "androidsettingswidget.h", "androidsettingswidget.ui", diff --git a/src/plugins/android/androidmanifesteditorwidget.cpp b/src/plugins/android/androidmanifesteditorwidget.cpp index 7e1dbf540e..3a7296b508 100644 --- a/src/plugins/android/androidmanifesteditorwidget.cpp +++ b/src/plugins/android/androidmanifesteditorwidget.cpp @@ -30,6 +30,7 @@ #include "androidconstants.h" #include "androidmanifestdocument.h" #include "androidmanager.h" +#include "androidservicewidget.h" #include <coreplugin/icore.h> #include <coreplugin/infobar.h> @@ -68,6 +69,7 @@ #include <QLabel> #include <QLineEdit> #include <QListView> +#include <QMessageBox> #include <QPushButton> #include <QScrollArea> #include <QSpinBox> @@ -253,6 +255,9 @@ void AndroidManifestEditorWidget::initializePage() formLayout->addRow(QString(), m_iconButtons); + m_services = new AndroidServiceWidget(this); + formLayout->addRow(tr("Android services:"), m_services); + applicationGroupBox->setLayout(formLayout); connect(m_appNameLineEdit, &QLineEdit::textEdited, @@ -264,6 +269,12 @@ void AndroidManifestEditorWidget::initializePage() connect(m_styleExtractMethod, QOverload<int>::of(&QComboBox::currentIndexChanged), this, setDirtyFunc); + connect(m_services, &AndroidServiceWidget::servicesModified, + this, setDirtyFunc); + connect(m_services, &AndroidServiceWidget::servicesModified, + this, &AndroidManifestEditorWidget::clearInvalidServiceInfo); + connect(m_services, &AndroidServiceWidget::servicesInvalid, + this, &AndroidManifestEditorWidget::setInvalidServiceInfo); } @@ -539,6 +550,14 @@ AndroidManifestEditorWidget::EditorPage AndroidManifestEditorWidget::activePage( return AndroidManifestEditorWidget::EditorPage(currentIndex()); } +bool servicesValid(const QList<AndroidServiceData> &services) +{ + for (auto &&x : services) + if (!x.isValid()) + return false; + return true; +} + bool AndroidManifestEditorWidget::setActivePage(EditorPage page) { EditorPage prevPage = activePage(); @@ -547,6 +566,11 @@ bool AndroidManifestEditorWidget::setActivePage(EditorPage page) return true; if (page == Source) { + if (!servicesValid(m_services->services())) { + QMessageBox::critical(nullptr, tr("Service Definition Invalid"), + tr("Cannot switch to source when there are invalid services.")); + return false; + } syncToEditor(); } else { if (!syncToWidgets()) @@ -567,8 +591,14 @@ bool AndroidManifestEditorWidget::setActivePage(EditorPage page) void AndroidManifestEditorWidget::preSave() { - if (activePage() != Source) + if (activePage() != Source) { + if (!servicesValid(m_services->services())) { + QMessageBox::critical(nullptr, tr("Service Definition Invalid"), + tr("Cannot save when there are invalid services.")); + return; + } syncToEditor(); + } // no need to emit changed() since this is called as part of saving updateInfoBar(); @@ -707,7 +737,26 @@ void AndroidManifestEditorWidget::hideInfoBar() { Core::InfoBar *infoBar = m_textEditorWidget->textDocument()->infoBar(); infoBar->removeInfo(infoBarId); - m_timerParseCheck.stop(); + m_timerParseCheck.stop(); +} + +static const char kServicesInvalid[] = "AndroidServiceDefinitionInvalid"; + +void AndroidManifestEditorWidget::setInvalidServiceInfo() +{ + Core::Id id(kServicesInvalid); + if (m_textEditorWidget->textDocument()->infoBar()->containsInfo(id)) + return; + Core::InfoBarEntry info(id, + tr("Services invalid. " + "Manifest cannot be saved. Correct the service definitions before saving.")); + m_textEditorWidget->textDocument()->infoBar()->addInfo(info); + +} + +void AndroidManifestEditorWidget::clearInvalidServiceInfo() +{ + m_textEditorWidget->textDocument()->infoBar()->removeInfo(Core::Id(kServicesInvalid)); } void setApiLevel(QComboBox *box, const QDomElement &element, const QString &attribute) @@ -810,6 +859,33 @@ void AndroidManifestEditorWidget::syncToWidgets(const QDomDocument &doc) m_permissionsModel->setPermissions(permissions); updateAddRemovePermissionButtons(); + QList<AndroidServiceData> services; + QDomElement serviceElem = applicationElement.firstChildElement(QLatin1String("service")); + while (!serviceElem.isNull()) { + AndroidServiceData service; + service.setClassName(serviceElem.attribute(QLatin1String("android:name"))); + QString process = serviceElem.attribute(QLatin1String("android:process")); + service.setRunInExternalProcess(!process.isEmpty()); + service.setExternalProcessName(process); + QDomElement serviceMetadataElem = serviceElem.firstChildElement(QLatin1String("meta-data")); + while (!serviceMetadataElem.isNull()) { + QString metadataName = serviceMetadataElem.attribute(QLatin1String("android:name")); + if (metadataName == QLatin1String("android.app.lib_name")) { + QString metadataValue = serviceMetadataElem.attribute(QLatin1String("android:value")); + service.setRunInExternalLibrary(metadataValue != QLatin1String("-- %%INSERT_APP_LIB_NAME%% --")); + service.setExternalLibraryName(metadataValue); + } + else if (metadataName == QLatin1String("android.app.arguments")) { + QString metadataValue = serviceMetadataElem.attribute(QLatin1String("android:value")); + service.setServiceArguments(metadataValue); + } + serviceMetadataElem = serviceMetadataElem.nextSiblingElement(QLatin1String("meta-data")); + } + services << service; + serviceElem = serviceElem.nextSiblingElement(QLatin1String("service")); + } + m_services->setServices(services); + m_iconButtons->loadIcons(); m_stayClean = false; @@ -988,13 +1064,19 @@ void AndroidManifestEditorWidget::parseApplication(QXmlStreamReader &reader, QXm while (!reader.atEnd()) { if (reader.isEndElement()) { + parseNewServices(writer); writer.writeCurrentToken(reader); + m_services->servicesSaved(); return; } else if (reader.isStartElement()) { if (reader.name() == QLatin1String("activity")) parseActivity(reader, writer); + else if (reader.name() == QLatin1String("service")) + parseService(reader, writer); else parseUnknownElement(reader, writer); + } else if (reader.isWhitespace()) { + /* no copying of whitespace */ } else { writer.writeCurrentToken(reader); } @@ -1003,6 +1085,144 @@ void AndroidManifestEditorWidget::parseApplication(QXmlStreamReader &reader, QXm } } +static int findService(const QString &name, const QList<AndroidServiceData> &data) +{ + for (int i = 0; i < data.size(); ++i) { + if (data[i].className() == name) + return i; + } + return -1; +} + +static void writeServiceMetadataElement(const char *name, + const char *attributeName, + const char *value, + QXmlStreamWriter &writer) +{ + writer.writeStartElement(QLatin1String("meta-data")); + writer.writeAttribute(QLatin1String("android:name"), QLatin1String(name)); + writer.writeAttribute(QLatin1String(attributeName), QLatin1String(value)); + writer.writeEndElement(); + +} + +static void writeServiceMetadataElement(const char *name, + const char *attributeName, + const QString &value, + QXmlStreamWriter &writer) +{ + writer.writeStartElement(QLatin1String("meta-data")); + writer.writeAttribute(QLatin1String("android:name"), QLatin1String(name)); + writer.writeAttribute(QLatin1String(attributeName), value); + writer.writeEndElement(); + +} + +static void addServiceArgumentsAndLibName(const AndroidServiceData &service, QXmlStreamWriter &writer) +{ + if (!service.isRunInExternalLibrary() && !service.serviceArguments().isEmpty()) + writeServiceMetadataElement("android.app.arguments", "android:value", service.serviceArguments(), writer); + if (service.isRunInExternalLibrary() && !service.externalLibraryName().isEmpty()) + writeServiceMetadataElement("android.app.lib_name", "android:value", service.externalLibraryName(), writer); + else + writeServiceMetadataElement("android.app.lib_name", "android:value", "-- %%INSERT_APP_LIB_NAME%% --", writer); +} + +static void addServiceMetadata(QXmlStreamWriter &writer) +{ + writeServiceMetadataElement("android.app.qt_sources_resource_id", "android:resource", "@array/qt_sources", writer); + writeServiceMetadataElement("android.app.repository", "android:value", "default", writer); + writeServiceMetadataElement("android.app.qt_libs_resource_id", "android:resource", "@array/qt_libs", writer); + writeServiceMetadataElement("android.app.bundled_libs_resource_id", "android:resource", "@array/bundled_libs", writer); + writeServiceMetadataElement("android.app.bundle_local_qt_libs", "android:value", "-- %%BUNDLE_LOCAL_QT_LIBS%% --", writer); + writeServiceMetadataElement("android.app.use_local_qt_libs", "android:value", "-- %%USE_LOCAL_QT_LIBS%% --", writer); + writeServiceMetadataElement("android.app.libs_prefix", "android:value", "/data/local/tmp/qt/", writer); + writeServiceMetadataElement("android.app.load_local_libs_resource_id", "android:resource", "@array/load_local_libs", writer); + writeServiceMetadataElement("android.app.load_local_jars", "android:value", "-- %%INSERT_LOCAL_JARS%% --", writer); + writeServiceMetadataElement("android.app.static_init_classes", "android:value", "-- %%INSERT_INIT_CLASSES%% --", writer); +} + +void AndroidManifestEditorWidget::parseService(QXmlStreamReader &reader, QXmlStreamWriter &writer) +{ + Q_ASSERT(reader.isStartElement()); + const auto &services = m_services->services(); + QString serviceName = reader.attributes().value(QLatin1String("android:name")).toString(); + int serviceIndex = findService(serviceName, services); + const AndroidServiceData* serviceFound = (serviceIndex >= 0) ? &services[serviceIndex] : nullptr; + if (serviceFound && serviceFound->isValid()) { + writer.writeStartElement(reader.name().toString()); + writer.writeAttribute(QLatin1String("android:name"), serviceFound->className()); + if (serviceFound->isRunInExternalProcess()) + writer.writeAttribute(QLatin1String("android:process"), serviceFound->externalProcessName()); + } + + reader.readNext(); + + bool bundleTagFound = false; + + while (!reader.atEnd()) { + if (reader.isEndElement()) { + if (serviceFound && serviceFound->isValid()) { + addServiceArgumentsAndLibName(*serviceFound, writer); + if (serviceFound->isRunInExternalProcess() && !bundleTagFound) + addServiceMetadata(writer); + writer.writeCurrentToken(reader); + } + return; + } else if (reader.isStartElement()) { + if (serviceFound && !serviceFound->isValid()) + parseUnknownElement(reader, writer, true); + else if (reader.name() == QLatin1String("meta-data")) { + QString metaTagName = reader.attributes().value(QLatin1String("android:name")).toString(); + if (serviceFound) { + if (metaTagName == QLatin1String("android.app.bundle_local_qt_libs")) + bundleTagFound = true; + if (metaTagName == QLatin1String("android.app.arguments")) + parseUnknownElement(reader, writer, true); + else if (metaTagName == QLatin1String("android.app.lib_name")) + parseUnknownElement(reader, writer, true); + else if (serviceFound->isRunInExternalProcess() + || metaTagName == QLatin1String("android.app.background_running")) + parseUnknownElement(reader, writer); + else + parseUnknownElement(reader, writer, true); + } else + parseUnknownElement(reader, writer, true); + } else + parseUnknownElement(reader, writer, true); + } else if (reader.isWhitespace()) { + /* no copying of whitespace */ + } else { + if (serviceFound) + writer.writeCurrentToken(reader); + } + reader.readNext(); + } +} + +void AndroidManifestEditorWidget::parseNewServices(QXmlStreamWriter &writer) +{ + const auto &services = m_services->services(); + for (const auto &x : services) { + if (x.isNewService() && x.isValid()) { + writer.writeStartElement(QLatin1String("service")); + writer.writeAttribute(QLatin1String("android:name"), x.className()); + if (x.isRunInExternalProcess()) { + writer.writeAttribute(QLatin1String("android:process"), + x.externalProcessName()); + } + addServiceArgumentsAndLibName(x, writer); + if (x.isRunInExternalProcess()) + addServiceMetadata(writer); + writer.writeStartElement(QLatin1String("meta-data")); + writer.writeAttribute(QLatin1String("android:name"), QLatin1String("android.app.background_running")); + writer.writeAttribute(QLatin1String("android:value"), QLatin1String("true")); + writer.writeEndElement(); + writer.writeEndElement(); + } + } +} + void AndroidManifestEditorWidget::parseActivity(QXmlStreamReader &reader, QXmlStreamWriter &writer) { Q_ASSERT(reader.isStartElement()); @@ -1180,20 +1400,24 @@ QString AndroidManifestEditorWidget::parseComment(QXmlStreamReader &reader, QXml return commentText; } -void AndroidManifestEditorWidget::parseUnknownElement(QXmlStreamReader &reader, QXmlStreamWriter &writer) +void AndroidManifestEditorWidget::parseUnknownElement(QXmlStreamReader &reader, QXmlStreamWriter &writer, + bool ignore) { Q_ASSERT(reader.isStartElement()); - writer.writeCurrentToken(reader); + if (!ignore) + writer.writeCurrentToken(reader); reader.readNext(); while (!reader.atEnd()) { if (reader.isEndElement()) { - writer.writeCurrentToken(reader); + if (!ignore) + writer.writeCurrentToken(reader); return; } else if (reader.isStartElement()) { - parseUnknownElement(reader, writer); + parseUnknownElement(reader, writer, ignore); } else { - writer.writeCurrentToken(reader); + if (!ignore) + writer.writeCurrentToken(reader); } reader.readNext(); } diff --git a/src/plugins/android/androidmanifesteditorwidget.h b/src/plugins/android/androidmanifesteditorwidget.h index 2384264257..6d49cf7f42 100644 --- a/src/plugins/android/androidmanifesteditorwidget.h +++ b/src/plugins/android/androidmanifesteditorwidget.h @@ -54,6 +54,7 @@ namespace Internal { class AndroidManifestEditor; class AndroidManifestEditorIconContainerWidget; class AndroidManifestEditorWidget; +class AndroidServiceWidget; class PermissionsModel: public QAbstractListModel { @@ -136,10 +137,15 @@ private: void updateInfoBar(const QString &errorMessage, int line, int column); void hideInfoBar(); + void setInvalidServiceInfo(); + void clearInvalidServiceInfo(); + void updateTargetComboBox(); void parseManifest(QXmlStreamReader &reader, QXmlStreamWriter &writer); void parseApplication(QXmlStreamReader &reader, QXmlStreamWriter &writer); + void parseService(QXmlStreamReader &reader, QXmlStreamWriter &writer); + void parseNewServices(QXmlStreamWriter &writer); void parseActivity(QXmlStreamReader &reader, QXmlStreamWriter &writer); bool parseMetaData(QXmlStreamReader &reader, QXmlStreamWriter &writer); void parseUsesSdk(QXmlStreamReader &reader, QXmlStreamWriter &writer); @@ -147,7 +153,7 @@ private: QXmlStreamWriter &writer, const QSet<QString> &permissions); QString parseComment(QXmlStreamReader &reader, QXmlStreamWriter &writer); - void parseUnknownElement(QXmlStreamReader &reader, QXmlStreamWriter &writer); + void parseUnknownElement(QXmlStreamReader &reader, QXmlStreamWriter &writer, bool ignore=false); bool m_dirty; // indicates that we need to call syncToEditor() bool m_stayClean; @@ -178,6 +184,8 @@ private: QPushButton *m_removePermissionButton; QComboBox *m_permissionsComboBox; + // Services + AndroidServiceWidget *m_services; QTimer m_timerParseCheck; TextEditor::TextEditorWidget *m_textEditorWidget; AndroidManifestEditor *m_editor; diff --git a/src/plugins/android/androidservicewidget.cpp b/src/plugins/android/androidservicewidget.cpp new file mode 100644 index 0000000000..465d2e2600 --- /dev/null +++ b/src/plugins/android/androidservicewidget.cpp @@ -0,0 +1,410 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ + +#include "androidservicewidget.h" +#include "androidservicewidget_p.h" + +#include <utils/utilsicons.h> + +#include <QAbstractTableModel> +#include <QGridLayout> +#include <QHBoxLayout> +#include <QHeaderView> +#include <QPushButton> +#include <QTableView> + +namespace Android { +namespace Internal { + +bool AndroidServiceData::isValid() const +{ + return !m_className.isEmpty() + && (!m_isRunInExternalProcess || !m_externalProcessName.isEmpty()) + && (!m_isRunInExternalLibrary || !m_externalLibName.isEmpty()) + && (m_isRunInExternalLibrary || !m_serviceArguments.isEmpty()); +} + +void AndroidServiceData::setClassName(const QString &className) +{ + m_className = className; +} + +QString AndroidServiceData::className() const +{ + return m_className; +} + +void AndroidServiceData::setRunInExternalProcess(bool isRunInExternalProcess) +{ + m_isRunInExternalProcess = isRunInExternalProcess; + if (!m_isRunInExternalProcess) { + m_isRunInExternalLibrary = false; + m_externalProcessName.clear(); + m_externalLibName.clear(); + } +} + +bool AndroidServiceData::isRunInExternalProcess() const +{ + return m_isRunInExternalProcess; +} + +void AndroidServiceData::setExternalProcessName(const QString &externalProcessName) +{ + if (m_isRunInExternalProcess) + m_externalProcessName = externalProcessName; +} + +QString AndroidServiceData::externalProcessName() const +{ + return m_externalProcessName; +} + +void AndroidServiceData::setRunInExternalLibrary(bool isRunInExternalLibrary) +{ + if (m_isRunInExternalProcess) + m_isRunInExternalLibrary = isRunInExternalLibrary; + if (!m_isRunInExternalLibrary) + m_externalLibName.clear(); + else + m_serviceArguments.clear(); +} + +bool AndroidServiceData::isRunInExternalLibrary() const +{ + return m_isRunInExternalLibrary; +} + +void AndroidServiceData::setExternalLibraryName(const QString &externalLibraryName) +{ + if (m_isRunInExternalLibrary) + m_externalLibName = externalLibraryName; +} + +QString AndroidServiceData::externalLibraryName() const +{ + return m_externalLibName; +} + +void AndroidServiceData::setServiceArguments(const QString &serviceArguments) +{ + if (!m_isRunInExternalLibrary) + m_serviceArguments = serviceArguments; +} + +QString AndroidServiceData::serviceArguments() const +{ + return m_serviceArguments; +} + +void AndroidServiceData::setNewService(bool isNewService) +{ + m_isNewService = isNewService; +} + +bool AndroidServiceData::isNewService() const +{ + return m_isNewService; +} + +void AndroidServiceWidget::AndroidServiceModel::setServices(const QList<AndroidServiceData> &androidServices) +{ + beginResetModel(); + m_services = androidServices; + endResetModel(); +} + +const QList<AndroidServiceData> &AndroidServiceWidget::AndroidServiceModel::services() +{ + return m_services; +} + +void AndroidServiceWidget::AndroidServiceModel::addService() +{ + int rowIndex = m_services.size(); + beginInsertRows(QModelIndex(), rowIndex, rowIndex); + AndroidServiceData service; + service.setNewService(true); + m_services.push_back(service); + endInsertRows(); + invalidDataChanged(); +} + +void AndroidServiceWidget::AndroidServiceModel::removeService(int row) +{ + beginRemoveRows(QModelIndex(), row, row); + m_services.removeAt(row); + endRemoveRows(); +} + +void AndroidServiceWidget::AndroidServiceModel::servicesSaved() +{ + for (auto && x : m_services) + x.setNewService(false); +} + +int AndroidServiceWidget::AndroidServiceModel::rowCount(const QModelIndex &/*parent*/) const +{ + return m_services.count(); +} + +int AndroidServiceWidget::AndroidServiceModel::columnCount(const QModelIndex &/*parent*/) const +{ + return 6; +} + +Qt::ItemFlags AndroidServiceWidget::AndroidServiceModel::flags(const QModelIndex &index) const +{ + if (index.column() == 0) + return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable; + else if (index.column() == 1) + return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable; + else if (index.column() == 2 && index.row() <= m_services.count()) { + if (m_services[index.row()].isRunInExternalProcess()) + return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable; + return Qt::ItemIsSelectable; + } else if (index.column() == 3 && index.row() <= m_services.count()) { + if (m_services[index.row()].isRunInExternalProcess()) + return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable; + return Qt::ItemIsUserCheckable | Qt::ItemIsSelectable; + } else if (index.column() == 4 && index.row() <= m_services.count()) { + if (m_services[index.row()].isRunInExternalLibrary()) + return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable; + return Qt::ItemIsSelectable; + } else if (index.column() == 5 && index.row() <= m_services.count()) { + if (!m_services[index.row()].isRunInExternalLibrary()) + return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable; + return Qt::ItemIsSelectable; + } + return Qt::ItemIsSelectable; +} + +QVariant AndroidServiceWidget::AndroidServiceModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::ToolTipRole && orientation == Qt::Horizontal) { + if (section == 0) + return tr("The name of the class implementing the service"); + else if (section == 1) + return tr("Checked if the service is run in an external process"); + else if (section == 2) + return tr("The name of the external process.\n" + "Prefix with : if the process is private, use a lowercase name if the process is global."); + else if (section == 3) + return tr("Checked if the service is in a separate dynamic library"); + else if (section == 4) + return tr("The name of the separate dynamic library"); + else if (section == 5) + return tr("The arguments for telling the app to run the service instead of the main activity"); + } else if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { + if (section == 0) + return tr("Service class name"); + else if (section == 1) + return tr("Run in external process"); + else if (section == 2) + return tr("Process name"); + else if (section == 3) + return tr("Run in external library"); + else if (section == 4) + return tr("Library name"); + else if (section == 5) + return tr("Service arguments"); + } + return {}; +} + +QVariant AndroidServiceWidget::AndroidServiceModel::data(const QModelIndex &index, int role) const +{ + if (!(index.row() >= 0 && index.row() < m_services.count())) + return {}; + if (role == Qt::CheckStateRole) { + if (index.column() == 3) + return m_services[index.row()].isRunInExternalLibrary() ? Qt::Checked : Qt::Unchecked; + else if (index.column() == 1 && index.row() <= m_services.count()) + return m_services[index.row()].isRunInExternalProcess() ? Qt::Checked : Qt::Unchecked; + return QVariant(); + } else if (role == Qt::DisplayRole) { + if (index.column() == 0) + return m_services[index.row()].className(); + else if (index.column() == 1) + return tr("Run in external process"); + else if (index.column() == 2) + return m_services[index.row()].externalProcessName(); + else if (index.column() == 3) + return tr("Run in external library"); + else if (index.column() == 4) + return m_services[index.row()].externalLibraryName(); + else if (index.column() == 5) + return m_services[index.row()].serviceArguments(); + } else if (role == Qt::ToolTipRole) { + if (index.column() == 0 && m_services[index.row()].className().isEmpty()) + return tr("The class name must be set"); + else if (index.column() == 2 && m_services[index.row()].isRunInExternalProcess()) + return tr("The process name must be set for a service run in an external process"); + else if (index.column() == 4 && m_services[index.row()].isRunInExternalLibrary()) + return tr("The library name must be set for a service run in an external library"); + else if (index.column() == 5 && !m_services[index.row()].isRunInExternalLibrary()) + return tr("The service arguments must be set for a service not run in an external library"); + } else if (role == Qt::EditRole) { + if (index.column() == 0) + return m_services[index.row()].className(); + else if (index.column() == 2) + return m_services[index.row()].externalProcessName(); + else if (index.column() == 4) + return m_services[index.row()].externalLibraryName(); + else if (index.column() == 5) + return m_services[index.row()].serviceArguments(); + } else if (role == Qt::DecorationRole) { + if (index.column() == 0) { + if (m_services[index.row()].className().isEmpty()) + return Utils::Icons::WARNING.icon(); + } else if (index.column() == 2) { + if (m_services[index.row()].isRunInExternalProcess() + && m_services[index.row()].externalProcessName().isEmpty()) + return Utils::Icons::WARNING.icon(); + } else if (index.column() == 4) { + if (m_services[index.row()].isRunInExternalLibrary() + && m_services[index.row()].externalLibraryName().isEmpty()) + return Utils::Icons::WARNING.icon(); + } else if (index.column() == 5) { + if (!m_services[index.row()].isRunInExternalLibrary() + && m_services[index.row()].serviceArguments().isEmpty()) + return Utils::Icons::WARNING.icon(); + } + } + return {}; +} + +bool AndroidServiceWidget::AndroidServiceModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!(index.row() >= 0 && index.row() < m_services.count())) + return {}; + if (role == Qt::CheckStateRole) { + if (index.column() == 1) + m_services[index.row()].setRunInExternalProcess((value == Qt::Checked) ? true : false); + else if (index.column() == 3) + m_services[index.row()].setRunInExternalLibrary((value == Qt::Checked) ? true : false); + dataChanged(createIndex(index.row(), 0), createIndex(index.row(), 5)); + if (m_services[index.row()].isValid()) + validDataChanged(); + else + invalidDataChanged(); + } else if (role == Qt::EditRole) { + if (index.column() == 0) { + QString className = value.toString(); + if (!className.isEmpty() && className[0] != QChar('.')) + className.push_front(QChar('.')); + m_services[index.row()].setClassName(className); + m_services[index.row()].setNewService(true); + } else if (index.column() == 2) { + m_services[index.row()].setExternalProcessName(value.toString()); + } else if (index.column() == 4) { + m_services[index.row()].setExternalLibraryName(value.toString()); + } else if (index.column() == 5) { + m_services[index.row()].setServiceArguments(value.toString()); + } + dataChanged(index, index); + if (m_services[index.row()].isValid()) + validDataChanged(); + else + invalidDataChanged(); + } + return true; +} + +AndroidServiceWidget::AndroidServiceWidget(QWidget *parent) : QWidget(parent), + m_model(new AndroidServiceModel), m_tableView(new QTableView(this)) +{ + m_tableView->setModel(m_model.data()); + m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows); + QSizePolicy sizePolicy; + sizePolicy.setHorizontalPolicy(QSizePolicy::Expanding); + m_tableView->setSizePolicy(sizePolicy); + m_tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + auto layout = new QHBoxLayout(this); + layout->addWidget(m_tableView, 1); + auto buttonLayout = new QGridLayout(); + auto addButton = new QPushButton(this); + addButton->setText(tr("Add")); + buttonLayout->addWidget(addButton, 0, 0); + m_removeButton = new QPushButton(this); + m_removeButton->setText(tr("Remove")); + m_removeButton->setEnabled(false); + buttonLayout->addWidget(m_removeButton, 1, 0); + layout->addLayout(buttonLayout); + layout->setAlignment(buttonLayout, Qt::AlignTop); + connect(addButton, &QAbstractButton::clicked, + this, &AndroidServiceWidget::addService); + connect(m_removeButton, &QAbstractButton::clicked, + this, &AndroidServiceWidget::removeService); + connect(m_tableView->selectionModel(), &QItemSelectionModel::selectionChanged, + [this](const QItemSelection &selected, const QItemSelection &/*deselected*/) { + if (!selected.isEmpty()) + m_removeButton->setEnabled(true); + }); + connect(m_model.data(), &AndroidServiceWidget::AndroidServiceModel::validDataChanged, + [this] {servicesModified();}); + connect(m_model.data(), &AndroidServiceWidget::AndroidServiceModel::invalidDataChanged, + [this] {servicesInvalid();}); +} + +AndroidServiceWidget::~AndroidServiceWidget() +{ + +} + +void AndroidServiceWidget::setServices(const QList<AndroidServiceData> &androidServices) +{ + m_removeButton->setEnabled(false); + m_model->setServices(androidServices); +} + +const QList<AndroidServiceData> &AndroidServiceWidget::services() +{ + return m_model->services(); +} + +void AndroidServiceWidget::servicesSaved() +{ + m_model->servicesSaved(); +} + +void AndroidServiceWidget::addService() +{ + m_model->addService(); +} + +void AndroidServiceWidget::removeService() +{ + auto selections = m_tableView->selectionModel()->selectedRows(); + for (const auto &x : selections) { + m_model->removeService(x.row()); + m_removeButton->setEnabled(false); + servicesModified(); + break; + } +} + +} // namespace Internal +} // namespace Android diff --git a/src/plugins/android/androidservicewidget.h b/src/plugins/android/androidservicewidget.h new file mode 100644 index 0000000000..cc30003b65 --- /dev/null +++ b/src/plugins/android/androidservicewidget.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include <QList> +#include <QString> +#include <QWidget> + +class QPushButton; +class QTableView; + +namespace Android { +namespace Internal { + +struct AndroidServiceData +{ +public: + bool isValid() const; + void setClassName(const QString &className); + QString className() const; + void setRunInExternalProcess(bool isRunInExternalProcess); + bool isRunInExternalProcess() const; + void setExternalProcessName(const QString &externalProcessName); + QString externalProcessName() const; + void setRunInExternalLibrary(bool isRunInExternalLibrary); + bool isRunInExternalLibrary() const ; + void setExternalLibraryName(const QString &externalLibraryName); + QString externalLibraryName() const; + void setServiceArguments(const QString &serviceArguments); + QString serviceArguments() const; + void setNewService(bool isNewService); + bool isNewService() const; +private: + QString m_className; + bool m_isRunInExternalProcess = false; + QString m_externalProcessName; + bool m_isRunInExternalLibrary = false; + QString m_externalLibName; + QString m_serviceArguments; + bool m_isNewService = false; +}; + +class AndroidServiceWidget : public QWidget +{ + Q_OBJECT +public: + explicit AndroidServiceWidget(QWidget *parent = nullptr); + ~AndroidServiceWidget(); + void setServices(const QList<AndroidServiceData> &androidServices); + const QList<AndroidServiceData> &services(); + void servicesSaved(); +private: + void addService(); + void removeService(); +signals: + void servicesModified(); + void servicesInvalid(); +private: + class AndroidServiceModel; + QScopedPointer<AndroidServiceModel> m_model; + QTableView *m_tableView; + QPushButton *m_removeButton; +}; + +} // namespace Internal +} // namespace Android diff --git a/src/plugins/android/androidservicewidget_p.h b/src/plugins/android/androidservicewidget_p.h new file mode 100644 index 0000000000..89b03639a1 --- /dev/null +++ b/src/plugins/android/androidservicewidget_p.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include "androidservicewidget.h" + +#include <QAbstractTableModel> + +namespace Android { +namespace Internal { + +class AndroidServiceWidget::AndroidServiceModel : public QAbstractTableModel +{ + Q_OBJECT +public: + void setServices(const QList<AndroidServiceData> &androidServices); + const QList<AndroidServiceData> &services(); + void addService(); + void removeService(int row); + void servicesSaved(); +signals: + void validDataChanged(); + void invalidDataChanged(); +private: + int rowCount(const QModelIndex &/*parent*/ = QModelIndex()) const override; + int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; +private: + QList<AndroidServiceData> m_services; +}; + +} // namespace Internal +} // namespace Android |