diff --git a/src/plugins/help/images/home.png b/src/libs/utils/images/home.png similarity index 100% rename from src/plugins/help/images/home.png rename to src/libs/utils/images/home.png diff --git a/src/plugins/help/images/home@2x.png b/src/libs/utils/images/home@2x.png similarity index 100% rename from src/plugins/help/images/home@2x.png rename to src/libs/utils/images/home@2x.png diff --git a/src/libs/utils/images/project.png b/src/libs/utils/images/project.png new file mode 100644 index 00000000000..f43b78dee09 Binary files /dev/null and b/src/libs/utils/images/project.png differ diff --git a/src/libs/utils/images/project@2x.png b/src/libs/utils/images/project@2x.png new file mode 100644 index 00000000000..432016e10f9 Binary files /dev/null and b/src/libs/utils/images/project@2x.png differ diff --git a/src/libs/utils/json.cpp b/src/libs/utils/json.cpp index d0cc0bbd136..ae97e1fd528 100644 --- a/src/libs/utils/json.cpp +++ b/src/libs/utils/json.cpp @@ -671,7 +671,7 @@ JsonSchemaManager::JsonSchemaManager(const QStringList &searchPaths) { foreach (const QString &path, m_searchPaths) { QDir dir(path); - if (!dir.exists() && !dir.mkpath(path)) + if (!dir.exists()) continue; dir.setNameFilters(QStringList(QLatin1String("*.json"))); foreach (const QFileInfo &fi, dir.entryInfoList()) diff --git a/src/libs/utils/utils.qrc b/src/libs/utils/utils.qrc index 63ca09e10f5..95a473da2ab 100644 --- a/src/libs/utils/utils.qrc +++ b/src/libs/utils/utils.qrc @@ -176,5 +176,9 @@ images/pan@2x.png images/wizardicon-file.png images/wizardicon-file@2x.png + images/project.png + images/project@2x.png + images/home.png + images/home@2x.png diff --git a/src/libs/utils/utilsicons.cpp b/src/libs/utils/utilsicons.cpp index 271d3122f66..016d3d5fb46 100644 --- a/src/libs/utils/utilsicons.cpp +++ b/src/libs/utils/utilsicons.cpp @@ -28,6 +28,11 @@ namespace Utils { namespace Icons { + +const Icon HOME({ + {QLatin1String(":/utils/images/home.png"), Utils::Theme::PanelTextColorDark}}, Icon::Tint); +const Icon HOME_TOOLBAR({ + {QLatin1String(":/utils/images/home.png"), Utils::Theme::IconsBaseColor}}); const Icon EDIT_CLEAR({ {QLatin1String(":/utils/images/editclear.png"), Theme::PanelTextColorMid}}, Icon::Tint); const Icon EDIT_CLEAR_TOOLBAR({ @@ -46,6 +51,8 @@ const Icon PREV({ {QLatin1String(":/utils/images/prev.png"), Theme::IconsWarningColor}}, Icon::MenuTintedStyle); const Icon PREV_TOOLBAR({ {QLatin1String(":/utils/images/prev.png"), Theme::IconsNavigationArrowsColor}}); +const Icon PROJECT({ + {QLatin1String(":/utils/images/project.png"), Theme::PanelTextColorDark}}, Icon::Tint); const Icon ZOOM({ {QLatin1String(":/utils/images/zoom.png"), Theme::PanelTextColorMid}}, Icon::Tint); const Icon ZOOM_TOOLBAR({ diff --git a/src/libs/utils/utilsicons.h b/src/libs/utils/utilsicons.h index 5725e9a5382..9a3a586e147 100644 --- a/src/libs/utils/utilsicons.h +++ b/src/libs/utils/utilsicons.h @@ -31,6 +31,8 @@ namespace Utils { namespace Icons { +QTCREATOR_UTILS_EXPORT extern const Icon HOME; +QTCREATOR_UTILS_EXPORT extern const Icon HOME_TOOLBAR; QTCREATOR_UTILS_EXPORT extern const Icon EDIT_CLEAR; QTCREATOR_UTILS_EXPORT extern const Icon EDIT_CLEAR_TOOLBAR; QTCREATOR_UTILS_EXPORT extern const Icon LOCKED_TOOLBAR; @@ -40,6 +42,7 @@ QTCREATOR_UTILS_EXPORT extern const Icon NEXT; QTCREATOR_UTILS_EXPORT extern const Icon NEXT_TOOLBAR; QTCREATOR_UTILS_EXPORT extern const Icon PREV; QTCREATOR_UTILS_EXPORT extern const Icon PREV_TOOLBAR; +QTCREATOR_UTILS_EXPORT extern const Icon PROJECT; QTCREATOR_UTILS_EXPORT extern const Icon ZOOM; QTCREATOR_UTILS_EXPORT extern const Icon ZOOM_TOOLBAR; QTCREATOR_UTILS_EXPORT extern const Icon ZOOMIN_TOOLBAR; diff --git a/src/plugins/android/android.pro b/src/plugins/android/android.pro index 67cc03f6ca4..3c2a5669b5c 100644 --- a/src/plugins/android/android.pro +++ b/src/plugins/android/android.pro @@ -50,7 +50,10 @@ HEADERS += \ androidsdkmanager.h \ androidavdmanager.h \ androidrunconfigurationwidget.h \ - adbcommandswidget.h + adbcommandswidget.h \ + androidsdkpackage.h \ + androidsdkmodel.h \ + androidsdkmanagerwidget.h SOURCES += \ androidconfigurations.cpp \ @@ -94,7 +97,10 @@ SOURCES += \ androidsdkmanager.cpp \ androidavdmanager.cpp \ androidrunconfigurationwidget.cpp \ - adbcommandswidget.cpp + adbcommandswidget.cpp \ + androidsdkpackage.cpp \ + androidsdkmodel.cpp \ + androidsdkmanagerwidget.cpp FORMS += \ androidsettingswidget.ui \ @@ -104,7 +110,8 @@ FORMS += \ androiddeployqtwidget.ui \ androidbuildapkwidget.ui \ androidrunconfigurationwidget.ui \ - adbcommandswidget.ui + adbcommandswidget.ui \ + androidsdkmanagerwidget.ui RESOURCES = android.qrc diff --git a/src/plugins/android/androidavdmanager.cpp b/src/plugins/android/androidavdmanager.cpp index 028fe2c7974..5af95e7a260 100644 --- a/src/plugins/android/androidavdmanager.cpp +++ b/src/plugins/android/androidavdmanager.cpp @@ -54,8 +54,6 @@ const char avdInfoAbiKey[] = "abi.type"; const char avdInfoTargetKey[] = "target"; const char avdInfoErrorKey[] = "Error:"; -const QVersionNumber avdManagerIntroVersion(25, 3 ,0); - const int avdCreateTimeoutMs = 30000; /*! @@ -101,37 +99,37 @@ static bool checkForTimeout(const chrono::steady_clock::time_point &start, return timedOut; } -static AndroidConfig::CreateAvdInfo createAvdCommand(const AndroidConfig config, - const AndroidConfig::CreateAvdInfo &info) +static CreateAvdInfo createAvdCommand(const AndroidConfig config, const CreateAvdInfo &info) { - AndroidConfig::CreateAvdInfo result = info; + CreateAvdInfo result = info; if (!result.isValid()) { qCDebug(avdManagerLog) << "AVD Create failed. Invalid CreateAvdInfo" << result.name - << result.target.name << result.target.apiLevel; + << result.sdkPlatform->displayText() << result.sdkPlatform->apiLevel(); result.error = QApplication::translate("AndroidAvdManager", "Cannot create AVD. Invalid input."); return result; } - QStringList arguments({"create", "avd", "-k", result.target.package, "-n", result.name}); + QStringList arguments({"create", "avd", "-k", result.sdkPlatform->sdkStylePath(), "-n", result.name}); if (!result.abi.isEmpty()) { - SystemImage image = Utils::findOrDefault(result.target.systemImages, + SystemImage *image = Utils::findOrDefault(result.sdkPlatform->systemImages(), Utils::equal(&SystemImage::abiName, result.abi)); - if (image.isValid()) { - arguments << "-k" << image.package; + if (image && image->isValid()) { + arguments << "-k" << image->sdkStylePath(); } else { + QString name = result.sdkPlatform->displayText(); qCDebug(avdManagerLog) << "AVD Create failed. Cannot find system image for the platform" - << result.abi << result.target.name; + << result.abi << name; result.error = QApplication::translate("AndroidAvdManager", "Cannot create AVD. Cannot find system image for " - "the ABI %1(%2).").arg(result.abi).arg(result.target.name); + "the ABI %1(%2).").arg(result.abi).arg(name); return result; } } else { - arguments << "-k" << result.target.package; + arguments << "-k" << result.sdkPlatform->sdkStylePath(); } if (result.sdcardSize > 0) @@ -219,14 +217,9 @@ AndroidAvdManager::~AndroidAvdManager() } -bool AndroidAvdManager::avdManagerUiToolAvailable() const -{ - return m_config.sdkToolsVersion() < avdManagerIntroVersion; -} - void AndroidAvdManager::launchAvdManagerUiTool() const { - if (avdManagerUiToolAvailable()) { + if (m_config.useNativeUiTools()) { m_androidTool->launchAvdManager(); } else { qCDebug(avdManagerLog) << "AVD Ui tool launch failed. UI tool not available" @@ -234,10 +227,9 @@ void AndroidAvdManager::launchAvdManagerUiTool() const } } -QFuture -AndroidAvdManager::createAvd(AndroidConfig::CreateAvdInfo info) const +QFuture AndroidAvdManager::createAvd(CreateAvdInfo info) const { - if (m_config.sdkToolsVersion() < avdManagerIntroVersion) + if (m_config.useNativeUiTools()) return m_androidTool->createAvd(info); return Utils::runAsync(&createAvdCommand, m_config, info); @@ -245,7 +237,7 @@ AndroidAvdManager::createAvd(AndroidConfig::CreateAvdInfo info) const bool AndroidAvdManager::removeAvd(const QString &name) const { - if (m_config.sdkToolsVersion() < avdManagerIntroVersion) + if (m_config.useNativeUiTools()) return m_androidTool->removeAvd(name); Utils::SynchronousProcess proc; @@ -258,7 +250,7 @@ bool AndroidAvdManager::removeAvd(const QString &name) const QFuture AndroidAvdManager::avdList() const { - if (m_config.sdkToolsVersion() < avdManagerIntroVersion) + if (m_config.useNativeUiTools()) return m_androidTool->androidVirtualDevicesFuture(); return Utils::runAsync(&AvdManagerOutputParser::listVirtualDevices, m_parser.get(), m_config); diff --git a/src/plugins/android/androidavdmanager.h b/src/plugins/android/androidavdmanager.h index 4e8633efda9..8bb487ca663 100644 --- a/src/plugins/android/androidavdmanager.h +++ b/src/plugins/android/androidavdmanager.h @@ -40,9 +40,8 @@ public: AndroidAvdManager(const AndroidConfig& config = AndroidConfigurations::currentConfig()); ~AndroidAvdManager(); - bool avdManagerUiToolAvailable() const; void launchAvdManagerUiTool() const; - QFuture createAvd(AndroidConfig::CreateAvdInfo info) const; + QFuture createAvd(CreateAvdInfo info) const; bool removeAvd(const QString &name) const; QFuture avdList() const; diff --git a/src/plugins/android/androidbuildapkstep.cpp b/src/plugins/android/androidbuildapkstep.cpp index e3a648c8b44..e3dab7ad7bd 100644 --- a/src/plugins/android/androidbuildapkstep.cpp +++ b/src/plugins/android/androidbuildapkstep.cpp @@ -29,6 +29,7 @@ #include "androidconfigurations.h" #include "androidconstants.h" #include "androidmanager.h" +#include "androidsdkmanager.h" #include "androidqtsupport.h" #include "certificatesmodel.h" @@ -92,7 +93,8 @@ private: AndroidBuildApkStep::AndroidBuildApkStep(ProjectExplorer::BuildStepList *parent, const Core::Id id) : ProjectExplorer::AbstractProcessStep(parent, id), - m_buildTargetSdk(AndroidConfig::apiLevelNameFor(AndroidConfigurations::currentConfig().highestAndroidSdk())) + m_buildTargetSdk(AndroidConfig::apiLevelNameFor(AndroidConfigurations:: + sdkManager()->latestAndroidSdkPlatform())) { //: AndroidBuildApkStep default display name setDefaultDisplayName(tr("Build Android APK")); @@ -233,8 +235,10 @@ bool AndroidBuildApkStep::fromMap(const QVariantMap &map) m_keystorePath = Utils::FileName::fromString(map.value(KeystoreLocationKey).toString()); m_signPackage = false; // don't restore this m_buildTargetSdk = map.value(BuildTargetSdkKey).toString(); - if (m_buildTargetSdk.isEmpty()) - m_buildTargetSdk = AndroidConfig::apiLevelNameFor(AndroidConfigurations::currentConfig().highestAndroidSdk()); + if (m_buildTargetSdk.isEmpty()) { + m_buildTargetSdk = AndroidConfig::apiLevelNameFor(AndroidConfigurations:: + sdkManager()->latestAndroidSdkPlatform()); + } m_verbose = map.value(VerboseOutputKey).toBool(); m_useMinistro = map.value(UseMinistroKey).toBool(); return ProjectExplorer::BuildStep::fromMap(map); diff --git a/src/plugins/android/androidbuildapkwidget.cpp b/src/plugins/android/androidbuildapkwidget.cpp index 18b1fff3279..dceb496f5ec 100644 --- a/src/plugins/android/androidbuildapkwidget.cpp +++ b/src/plugins/android/androidbuildapkwidget.cpp @@ -29,6 +29,7 @@ #include "androidconfigurations.h" #include "androidcreatekeystorecertificate.h" #include "androidmanager.h" +#include "androidsdkmanager.h" #include "ui_androidbuildapkwidget.h" #include @@ -47,6 +48,8 @@ using namespace Android; using namespace Internal; +const int minApiSupported = 9; + AndroidBuildApkWidget::AndroidBuildApkWidget(AndroidBuildApkStep *step) : ProjectExplorer::BuildStepConfigWidget(), m_ui(new Ui::AndroidBuildApkWidget), @@ -55,9 +58,8 @@ AndroidBuildApkWidget::AndroidBuildApkWidget(AndroidBuildApkStep *step) m_ui->setupUi(this); // Target sdk combobox - int minApiLevel = 9; - const AndroidConfig &config = AndroidConfigurations::currentConfig(); - QStringList targets = AndroidConfig::apiLevelNamesFor(config.sdkTargets(minApiLevel)); + QStringList targets = AndroidConfig::apiLevelNamesFor(AndroidConfigurations::sdkManager()-> + filteredSdkPlatforms(minApiSupported)); targets.removeDuplicates(); m_ui->targetSDKComboBox->addItems(targets); m_ui->targetSDKComboBox->setCurrentIndex(targets.indexOf(AndroidManager::buildTargetSDK(step->target()))); diff --git a/src/plugins/android/androidconfigurations.cpp b/src/plugins/android/androidconfigurations.cpp index b52810e93f0..a44f605780e 100644 --- a/src/plugins/android/androidconfigurations.cpp +++ b/src/plugins/android/androidconfigurations.cpp @@ -87,6 +87,7 @@ namespace { const QLatin1String SettingsGroup("AndroidConfigurations"); const QLatin1String SDKLocationKey("SDKLocation"); + const QLatin1String SDKManagerToolArgsKey("SDKManagerToolArgs"); const QLatin1String NDKLocationKey("NDKLocation"); const QLatin1String OpenJDKLocationKey("OpenJDKLocation"); const QLatin1String KeystoreLocationKey("KeystoreLocation"); @@ -246,6 +247,7 @@ void AndroidConfig::load(const QSettings &settings) // user settings m_partitionSize = settings.value(PartitionSizeKey, 1024).toInt(); m_sdkLocation = FileName::fromString(settings.value(SDKLocationKey).toString()); + m_sdkManagerToolArgs = settings.value(SDKManagerToolArgsKey).toStringList(); m_ndkLocation = FileName::fromString(settings.value(NDKLocationKey).toString()); m_openJDKLocation = FileName::fromString(settings.value(OpenJDKLocationKey).toString()); m_keystoreLocation = FileName::fromString(settings.value(KeystoreLocationKey).toString()); @@ -261,6 +263,7 @@ void AndroidConfig::load(const QSettings &settings) && settings.value(changeTimeStamp).toInt() != QFileInfo(sdkSettingsFileName()).lastModified().toMSecsSinceEpoch() / 1000) { // persisten settings m_sdkLocation = FileName::fromString(reader.restoreValue(SDKLocationKey, m_sdkLocation.toString()).toString()); + m_sdkManagerToolArgs = reader.restoreValue(SDKManagerToolArgsKey, m_sdkManagerToolArgs).toStringList(); m_ndkLocation = FileName::fromString(reader.restoreValue(NDKLocationKey, m_ndkLocation.toString()).toString()); m_openJDKLocation = FileName::fromString(reader.restoreValue(OpenJDKLocationKey, m_openJDKLocation.toString()).toString()); m_keystoreLocation = FileName::fromString(reader.restoreValue(KeystoreLocationKey, m_keystoreLocation.toString()).toString()); @@ -272,7 +275,6 @@ void AndroidConfig::load(const QSettings &settings) m_makeExtraSearchDirectories << extraDirectory; // persistent settings } - m_availableSdkPlatformsUpToDate = false; m_NdkInformationUpToDate = false; } @@ -284,6 +286,7 @@ void AndroidConfig::save(QSettings &settings) const // user settings settings.setValue(SDKLocationKey, m_sdkLocation.toString()); + settings.setValue(SDKManagerToolArgsKey, m_sdkManagerToolArgs); settings.setValue(NDKLocationKey, m_ndkLocation.toString()); settings.setValue(OpenJDKLocationKey, m_openJDKLocation.toString()); settings.setValue(KeystoreLocationKey, m_keystoreLocation.toString()); @@ -333,40 +336,15 @@ void AndroidConfig::updateNdkInformation() const m_NdkInformationUpToDate = true; } -void AndroidConfig::updateAvailableSdkPlatforms() const -{ - if (m_availableSdkPlatformsUpToDate) - return; - - m_availableSdkPlatforms.clear(); - AndroidSdkManager sdkManager(*this); - bool success = false; - m_availableSdkPlatforms = sdkManager.availableSdkPlatforms(&success); - if (success) - m_availableSdkPlatformsUpToDate = true; -} - -QStringList AndroidConfig::apiLevelNamesFor(const QList &platforms) +QStringList AndroidConfig::apiLevelNamesFor(const SdkPlatformList &platforms) { return Utils::transform(platforms, AndroidConfig::apiLevelNameFor); } -QString AndroidConfig::apiLevelNameFor(const SdkPlatform &platform) +QString AndroidConfig::apiLevelNameFor(const SdkPlatform *platform) { - return platform.apiLevel > 0 ? QString("android-%1").arg(platform.apiLevel) : ""; -} - -QList AndroidConfig::sdkTargets(int minApiLevel) const -{ - updateAvailableSdkPlatforms(); - QList result; - for (int i = 0; i < m_availableSdkPlatforms.size(); ++i) { - if (m_availableSdkPlatforms.at(i).apiLevel >= minApiLevel) - result << m_availableSdkPlatforms.at(i); - else - break; - } - return result; + return platform && platform->apiLevel() > 0 ? + QString("android-%1").arg(platform->apiLevel()) : ""; } FileName AndroidConfig::adbToolPath() const @@ -523,20 +501,6 @@ QVector AndroidConfig::connectedDevices(const QString &adbToo return devices; } -AndroidConfig::CreateAvdInfo AndroidConfig::gatherCreateAVDInfo(QWidget *parent, int minApiLevel, QString targetArch) const -{ - CreateAvdInfo result; - AvdDialog d(minApiLevel, targetArch, this, parent); - if (d.exec() != QDialog::Accepted || !d.isValid()) - return result; - - result.target = d.target(); - result.name = d.name(); - result.abi = d.abi(); - result.sdcardSize = d.sdcardSize(); - return result; -} - bool AndroidConfig::isConnected(const QString &serialNumber) const { QVector devices = connectedDevices(); @@ -716,12 +680,10 @@ QStringList AndroidConfig::getAbis(const QString &adbToolPath, const QString &de return result; } -SdkPlatform AndroidConfig::highestAndroidSdk() const +bool AndroidConfig::useNativeUiTools() const { - updateAvailableSdkPlatforms(); - if (m_availableSdkPlatforms.isEmpty()) - return SdkPlatform(); - return m_availableSdkPlatforms.first(); + const QVersionNumber version = sdkToolsVersion(); + return !version.isNull() && version <= QVersionNumber(25, 3 ,0); } QString AndroidConfig::bestNdkPlatformMatch(int target) const @@ -743,7 +705,6 @@ FileName AndroidConfig::sdkLocation() const void AndroidConfig::setSdkLocation(const FileName &sdkLocation) { m_sdkLocation = sdkLocation; - m_availableSdkPlatformsUpToDate = false; } QVersionNumber AndroidConfig::sdkToolsVersion() const @@ -770,6 +731,17 @@ QVersionNumber AndroidConfig::buildToolsVersion() const return maxVersion; } + +QStringList AndroidConfig::sdkManagerToolArgs() const +{ + return m_sdkManagerToolArgs; +} + +void AndroidConfig::setSdkManagerToolArgs(const QStringList &args) +{ + m_sdkManagerToolArgs = args; +} + FileName AndroidConfig::ndkLocation() const { return m_ndkLocation; @@ -836,7 +808,6 @@ FileName AndroidConfig::openJDKLocation() const void AndroidConfig::setOpenJDKLocation(const FileName &openJDKLocation) { m_openJDKLocation = openJDKLocation; - m_availableSdkPlatformsUpToDate = false; } FileName AndroidConfig::keystoreLocation() const @@ -1129,6 +1100,11 @@ const AndroidConfig &AndroidConfigurations::currentConfig() return m_instance->m_config; // ensure that m_instance is initialized } +AndroidSdkManager *AndroidConfigurations::sdkManager() +{ + return m_instance->m_sdkManager.get(); +} + AndroidConfigurations *AndroidConfigurations::instance() { return m_instance; @@ -1143,7 +1119,8 @@ void AndroidConfigurations::save() } AndroidConfigurations::AndroidConfigurations(QObject *parent) - : QObject(parent) + : QObject(parent), + m_sdkManager(new AndroidSdkManager(m_config)) { load(); @@ -1155,6 +1132,11 @@ AndroidConfigurations::AndroidConfigurations(QObject *parent) m_instance = this; } +AndroidConfigurations::~AndroidConfigurations() +{ + +} + static FileName javaHomeForJavac(const FileName &location) { QFileInfo fileInfo = location.toFileInfo(); @@ -1265,13 +1247,4 @@ void AndroidConfigurations::updateAndroidDevice() AndroidConfigurations *AndroidConfigurations::m_instance = 0; -bool SdkPlatform::operator <(const SdkPlatform &other) const -{ - if (apiLevel != other.apiLevel) - return apiLevel > other.apiLevel; - if (name != other.name) - return name < other.name; - return false; -} - } // namespace Android diff --git a/src/plugins/android/androidconfigurations.h b/src/plugins/android/androidconfigurations.h index 75b6dd16abc..4565d5e3635 100644 --- a/src/plugins/android/androidconfigurations.h +++ b/src/plugins/android/androidconfigurations.h @@ -26,7 +26,7 @@ #pragma once #include "android_global.h" - +#include "androidsdkpackage.h" #include #include @@ -52,7 +52,9 @@ class Project; namespace Utils { class Environment; } namespace Android { + class AndroidPlugin; +namespace Internal { class AndroidSdkManager; } class AndroidDeviceInfo { @@ -74,31 +76,16 @@ public: }; using AndroidDeviceInfoList = QList; -//! Defines an Android system image. -class SystemImage +class CreateAvdInfo { public: - bool isValid() const { return (apiLevel != -1) && !abiName.isEmpty(); } - int apiLevel = -1; - QString abiName; - QString package; - Utils::FileName installedLocation; -}; -using SystemImageList = QList; - - -class SdkPlatform -{ -public: - bool isValid() const { return !name.isEmpty() && apiLevel != -1; } - bool operator <(const SdkPlatform &other) const; - int apiLevel = -1; + bool isValid() const { return sdkPlatform && sdkPlatform->isValid() && !name.isEmpty(); } + const SdkPlatform *sdkPlatform = nullptr; QString name; - QString package; - Utils::FileName installedLocation; - SystemImageList systemImages; + QString abi; + int sdcardSize = 0; + QString error; // only used in the return value of createAVD }; -using SdkPlatformList = QList; class ANDROID_EXPORT AndroidConfig { @@ -106,14 +93,15 @@ public: void load(const QSettings &settings); void save(QSettings &settings) const; - static QStringList apiLevelNamesFor(const QList &platforms); - static QString apiLevelNameFor(const SdkPlatform &platform); - QList sdkTargets(int minApiLevel = 0) const; + static QStringList apiLevelNamesFor(const SdkPlatformList &platforms); + static QString apiLevelNameFor(const SdkPlatform *platform); Utils::FileName sdkLocation() const; void setSdkLocation(const Utils::FileName &sdkLocation); QVersionNumber sdkToolsVersion() const; QVersionNumber buildToolsVersion() const; + QStringList sdkManagerToolArgs() const; + void setSdkManagerToolArgs(const QStringList &args); Utils::FileName ndkLocation() const; QVersionNumber ndkVersion() const; @@ -147,19 +135,6 @@ public: Utils::FileName keytoolPath() const; - class CreateAvdInfo - { - public: - bool isValid() const { return target.isValid() && !name.isEmpty(); } - SdkPlatform target; - QString name; - QString abi; - int sdcardSize = 0; - QString error; // only used in the return value of createAVD - }; - - CreateAvdInfo gatherCreateAVDInfo(QWidget *parent, int minApiLevel = 0, QString targetArch = QString()) const; - QVector connectedDevices(QString *error = 0) const; static QVector connectedDevices(const QString &adbToolPath, QString *error = 0); @@ -175,7 +150,8 @@ public: OpenGl getOpenGLEnabled(const QString &emulator) const; bool isConnected(const QString &serialNumber) const; - SdkPlatform highestAndroidSdk() const; + bool useNativeUiTools() const; + private: static QString getDeviceProperty(const QString &adbToolPath, const QString &device, const QString &property); @@ -189,10 +165,10 @@ private: bool isBootToQt(const QString &device) const; static QString getAvdName(const QString &serialnumber); - void updateAvailableSdkPlatforms() const; void updateNdkInformation() const; Utils::FileName m_sdkLocation; + QStringList m_sdkManagerToolArgs; Utils::FileName m_ndkLocation; Utils::FileName m_openJDKLocation; Utils::FileName m_keystoreLocation; @@ -201,9 +177,6 @@ private: bool m_automaticKitCreation = true; //caches - mutable bool m_availableSdkPlatformsUpToDate = false; - mutable SdkPlatformList m_availableSdkPlatforms; - mutable bool m_NdkInformationUpToDate = false; mutable QString m_toolchainHost; mutable QVector m_availableNdkPlatforms; @@ -218,6 +191,7 @@ class ANDROID_EXPORT AndroidConfigurations : public QObject public: static const AndroidConfig ¤tConfig(); + static Internal::AndroidSdkManager *sdkManager(); static void setConfig(const AndroidConfig &config); static AndroidConfigurations *instance(); @@ -236,16 +210,17 @@ signals: private: AndroidConfigurations(QObject *parent); + ~AndroidConfigurations(); void load(); void save(); static AndroidConfigurations *m_instance; AndroidConfig m_config; + std::unique_ptr m_sdkManager; QMap > m_defaultDeviceForAbi; bool m_force32bit; }; } // namespace Android -Q_DECLARE_METATYPE(Android::SdkPlatform) diff --git a/src/plugins/android/androiddevicedialog.cpp b/src/plugins/android/androiddevicedialog.cpp index 67cd02e9f84..9c07238d2dc 100644 --- a/src/plugins/android/androiddevicedialog.cpp +++ b/src/plugins/android/androiddevicedialog.cpp @@ -26,6 +26,7 @@ #include "androiddevicedialog.h" #include "androidmanager.h" #include "androidavdmanager.h" +#include "avddialog.h" #include "ui_androiddevicedialog.h" #include @@ -580,9 +581,10 @@ void AndroidDeviceDialog::devicesRefreshed() void AndroidDeviceDialog::createAvd() { m_ui->createAVDButton->setEnabled(false); - AndroidConfig::CreateAvdInfo info = AndroidConfigurations::currentConfig().gatherCreateAVDInfo(this, m_apiLevel, m_abi); + CreateAvdInfo info = AvdDialog::gatherCreateAVDInfo(this, AndroidConfigurations::sdkManager(), + m_apiLevel, m_abi); - if (!info.target.isValid()) { + if (!info.isValid()) { m_ui->createAVDButton->setEnabled(true); return; } @@ -593,7 +595,7 @@ void AndroidDeviceDialog::createAvd() void AndroidDeviceDialog::avdAdded() { m_ui->createAVDButton->setEnabled(true); - AndroidConfig::CreateAvdInfo info = m_futureWatcherAddDevice.result(); + CreateAvdInfo info = m_futureWatcherAddDevice.result(); if (!info.error.isEmpty()) { QMessageBox::critical(this, QApplication::translate("AndroidConfig", "Error Creating AVD"), info.error); return; diff --git a/src/plugins/android/androiddevicedialog.h b/src/plugins/android/androiddevicedialog.h index f882b0bfcd2..db379c068e9 100644 --- a/src/plugins/android/androiddevicedialog.h +++ b/src/plugins/android/androiddevicedialog.h @@ -79,7 +79,7 @@ private: QString m_defaultDevice; std::unique_ptr m_avdManager; QVector m_connectedDevices; - QFutureWatcher m_futureWatcherAddDevice; + QFutureWatcher m_futureWatcherAddDevice; QFutureWatcher m_futureWatcherRefreshDevices; }; diff --git a/src/plugins/android/androidmanager.cpp b/src/plugins/android/androidmanager.cpp index bec6c84ddb9..460c749ebad 100644 --- a/src/plugins/android/androidmanager.cpp +++ b/src/plugins/android/androidmanager.cpp @@ -35,6 +35,7 @@ #include "androidqtversion.h" #include "androidbuildapkstep.h" #include "androidavdmanager.h" +#include "androidsdkmanager.h" #include #include @@ -174,7 +175,8 @@ QString AndroidManager::buildTargetSDK(ProjectExplorer::Target *target) if (androidBuildApkStep) return androidBuildApkStep->buildTargetSdk(); - QString fallback = AndroidConfig::apiLevelNameFor(AndroidConfigurations::currentConfig().highestAndroidSdk()); + QString fallback = AndroidConfig::apiLevelNameFor( + AndroidConfigurations::sdkManager()->latestAndroidSdkPlatform()); return fallback; } diff --git a/src/plugins/android/androidsdkmanager.cpp b/src/plugins/android/androidsdkmanager.cpp index ac518bdd21c..4adf95401ea 100644 --- a/src/plugins/android/androidsdkmanager.cpp +++ b/src/plugins/android/androidsdkmanager.cpp @@ -24,15 +24,19 @@ ****************************************************************************/ #include "androidsdkmanager.h" +#include "androidconstants.h" #include "androidmanager.h" #include "androidtoolmanager.h" #include "utils/algorithm.h" #include "utils/qtcassert.h" +#include "utils/runextensions.h" #include "utils/synchronousprocess.h" -#include "utils/environment.h" +#include "utils/qtcprocess.h" +#include #include +#include #include #include @@ -48,12 +52,32 @@ namespace Internal { const QVersionNumber sdkManagerIntroVersion(25, 3 ,0); const char installLocationKey[] = "Installed Location:"; -const char apiLevelPropertyKey[] = "AndroidVersion.ApiLevel"; -const char abiPropertyKey[] = "SystemImage.Abi"; +const char revisionKey[] = "Version:"; +const char descriptionKey[] = "Description:"; +const char commonArgsKey[] = "Common Arguments:"; const int sdkManagerCmdTimeoutS = 60; +const int sdkManagerOperationTimeoutS = 600; + +const QRegularExpression assertionReg("(\\(\\s*y\\s*[\\/\\\\]\\s*n\\s*\\)\\s*)(?[\\:\\?])", + QRegularExpression::CaseInsensitiveOption | + QRegularExpression::MultilineOption); using namespace Utils; +using SdkCmdFutureInterface = QFutureInterface; + +int platformNameToApiLevel(const QString &platformName) +{ + int apiLevel = -1; + QRegularExpression re("(android-)(?[0-9]{1,})", + QRegularExpression::CaseInsensitiveOption); + QRegularExpressionMatch match = re.match(platformName); + if (match.hasMatch()) { + QString apiLevelStr = match.captured("apiLevel"); + apiLevel = apiLevelStr.toInt(); + } + return apiLevel; +} /*! Parses the \a line for a [spaces]key[spaces]value[spaces] pattern and returns @@ -70,24 +94,139 @@ static bool valueForKey(QString key, const QString &line, QString *value = nullp return false; } -/*! - Runs the \c sdkmanger tool specific to configuration \a config with arguments \a args. Returns - \c true if the command is successfully executed. Output is copied into \a output. The function - blocks the calling thread. - */ -static bool sdkManagerCommand(const AndroidConfig config, const QStringList &args, QString *output, - int timeout = sdkManagerCmdTimeoutS) +int parseProgress(const QString &out, bool &foundAssertion) +{ + int progress = -1; + if (out.isEmpty()) + return progress; + QRegularExpression reg("(?\\d*)%"); + QStringList lines = out.split(QRegularExpression("[\\n\\r]"), QString::SkipEmptyParts); + for (const QString &line : lines) { + QRegularExpressionMatch match = reg.match(line); + if (match.hasMatch()) { + progress = match.captured("progress").toInt(); + if (progress < 0 || progress > 100) + progress = -1; + } + if (!foundAssertion) + foundAssertion = assertionReg.match(line).hasMatch(); + } + return progress; +} + +void watcherDeleter(QFutureWatcher *watcher) +{ + if (!watcher->isFinished() && !watcher->isCanceled()) + watcher->cancel(); + + if (!watcher->isFinished()) + watcher->waitForFinished(); + + delete watcher; +} + +/*! + Runs the \c sdkmanger tool with arguments \a args. Returns \c true if the command is + successfully executed. Output is copied into \a output. The function blocks the calling thread. + */ +static bool sdkManagerCommand(const Utils::FileName &toolPath, const QStringList &args, + QString *output, int timeout = sdkManagerCmdTimeoutS) { - QString sdkManagerToolPath = config.sdkManagerToolPath().toString(); SynchronousProcess proc; proc.setTimeoutS(timeout); proc.setTimeOutMessageBoxEnabled(true); - SynchronousProcessResponse response = proc.run(sdkManagerToolPath, args); + SynchronousProcessResponse response = proc.run(toolPath.toString(), args); if (output) *output = response.allOutput(); return response.result == SynchronousProcessResponse::Finished; } +/*! + Runs the \c sdkmanger tool with arguments \a args. The operation command progress is updated in + to the future interface \a fi and \a output is populated with command output. The command listens + to cancel signal emmitted by \a sdkManager and kill the commands. The command is also killed + after the lapse of \a timeout seconds. The function blocks the calling thread. + */ +static void sdkManagerCommand(const Utils::FileName &toolPath, const QStringList &args, + AndroidSdkManager &sdkManager, SdkCmdFutureInterface &fi, + AndroidSdkManager::OperationOutput &output, double progressQuota, + bool interruptible = true, int timeout = sdkManagerOperationTimeoutS) +{ + int offset = fi.progressValue(); + SynchronousProcess proc; + bool assertionFound = false; + proc.setStdErrBufferedSignalsEnabled(true); + proc.setStdOutBufferedSignalsEnabled(true); + proc.setTimeoutS(timeout); + QObject::connect(&proc, &SynchronousProcess::stdOutBuffered, + [offset, progressQuota, &proc, &assertionFound, &fi](const QString &out) { + int progressPercent = parseProgress(out, assertionFound); + if (assertionFound) + proc.terminate(); + if (progressPercent != -1) + fi.setProgressValue(offset + qRound((progressPercent / 100.0) * progressQuota)); + }); + QObject::connect(&proc, &SynchronousProcess::stdErrBuffered, [&output](const QString &err) { + output.stdError = err; + }); + if (interruptible) { + QObject::connect(&sdkManager, &AndroidSdkManager::cancelActiveOperations, + &proc, &SynchronousProcess::terminate); + } + SynchronousProcessResponse response = proc.run(toolPath.toString(), args); + if (assertionFound) { + output.success = false; + output.stdOutput = response.stdOut(); + output.stdError = QCoreApplication::translate("Android::Internal::AndroidSdkManager", + "Operation requires user interaction." + "Please use \"sdkmanager\" commandline tool"); + } else { + output.success = response.result == SynchronousProcessResponse::Finished; + } +} + + +class AndroidSdkManagerPrivate +{ +public: + AndroidSdkManagerPrivate(AndroidSdkManager &sdkManager, const AndroidConfig &config); + ~AndroidSdkManagerPrivate(); + + AndroidSdkPackageList filteredPackages(AndroidSdkPackage::PackageState state, + AndroidSdkPackage::PackageType type, + bool forceUpdate = false); + const AndroidSdkPackageList &allPackages(bool forceUpdate = false); + void refreshSdkPackages(bool forceReload = false); + + void parseCommonArguments(QFutureInterface &fi); + void updateInstalled(SdkCmdFutureInterface &fi); + void update(SdkCmdFutureInterface &fi, const QStringList &install, + const QStringList &uninstall); + void checkPendingLicense(SdkCmdFutureInterface &fi); + void getPendingLicense(SdkCmdFutureInterface &fi); + + void addWatcher(const QFuture &future); + void setLicenseInput(bool acceptLicense); + + std::unique_ptr, decltype(&watcherDeleter)> m_activeOperation; + +private: + QByteArray getUserInput() const; + void clearUserInput(); + void reloadSdkPackages(); + void clearPackages(); + bool onLicenseStdOut(const QString &output, bool notify, + AndroidSdkManager::OperationOutput &result, SdkCmdFutureInterface &fi); + + AndroidSdkManager &m_sdkManager; + const AndroidConfig &m_config; + AndroidSdkPackageList m_allPackages; + FileName lastSdkManagerPath; + QString m_licenseTextCache; + QByteArray m_licenseUserInput; + mutable QReadWriteLock m_licenseInputLock; +}; + /*! \class SdkManagerOutputParser \brief The SdkManagerOutputParser class is a helper class to parse the output of the \c sdkmanager @@ -95,68 +234,190 @@ static bool sdkManagerCommand(const AndroidConfig config, const QStringList &arg */ class SdkManagerOutputParser { + class GenericPackageData + { + public: + bool isValid() const { return !revision.isNull() && !description.isNull(); } + QStringList headerParts; + QVersionNumber revision; + QString description; + Utils::FileName installedLocation; + QMap extraData; + }; + public: enum MarkerTag { - None = 0x01, - InstalledPackagesMarker = 0x02, - AvailablePackagesMarkers = 0x04, - AvailableUpdatesMarker = 0x08, - EmptyMarker = 0x10, - PlatformMarker = 0x20, - SystemImageMarker = 0x40, + None = 0x001, + InstalledPackagesMarker = 0x002, + AvailablePackagesMarkers = 0x004, + AvailableUpdatesMarker = 0x008, + EmptyMarker = 0x010, + PlatformMarker = 0x020, + SystemImageMarker = 0x040, + BuildToolsMarker = 0x080, + SdkToolsMarker = 0x100, + PlatformToolsMarker = 0x200, SectionMarkers = InstalledPackagesMarker | AvailablePackagesMarkers | AvailableUpdatesMarker }; + SdkManagerOutputParser(AndroidSdkPackageList &container) : m_packages(container) {} void parsePackageListing(const QString &output); - SdkPlatformList m_installedPlatforms; + AndroidSdkPackageList &m_packages; private: - void compileData(); + void compilePackageAssociations(); void parsePackageData(MarkerTag packageMarker, const QStringList &data); - bool parsePlatform(const QStringList &data, SdkPlatform *platform) const; - bool parseSystemImage(const QStringList &data, SystemImage *image); + bool parseAbstractData(GenericPackageData &output, const QStringList &input, int minParts, + const QString &logStrTag, QStringList extraKeys = QStringList()) const; + AndroidSdkPackage *parsePlatform(const QStringList &data) const; + QPair parseSystemImage(const QStringList &data) const; + BuildTools *parseBuildToolsPackage(const QStringList &data) const; + SdkTools *parseSdkToolsPackage(const QStringList &data) const; + PlatformTools *parsePlatformToolsPackage(const QStringList &data) const; MarkerTag parseMarkers(const QString &line); MarkerTag m_currentSection = MarkerTag::None; - SystemImageList m_installedSystemImages; + QHash m_systemImages; }; const std::map markerTags { - {SdkManagerOutputParser::MarkerTag::InstalledPackagesMarker, "Installed packages:"}, - {SdkManagerOutputParser::MarkerTag::AvailablePackagesMarkers, "Available Packages:"}, - {SdkManagerOutputParser::MarkerTag::AvailablePackagesMarkers, "Available Updates:"}, - {SdkManagerOutputParser::MarkerTag::PlatformMarker, "platforms"}, - {SdkManagerOutputParser::MarkerTag::SystemImageMarker, "system-images"} + {SdkManagerOutputParser::MarkerTag::InstalledPackagesMarker, "Installed packages:"}, + {SdkManagerOutputParser::MarkerTag::AvailablePackagesMarkers, "Available Packages:"}, + {SdkManagerOutputParser::MarkerTag::AvailablePackagesMarkers, "Available Updates:"}, + {SdkManagerOutputParser::MarkerTag::PlatformMarker, "platforms"}, + {SdkManagerOutputParser::MarkerTag::SystemImageMarker, "system-images"}, + {SdkManagerOutputParser::MarkerTag::BuildToolsMarker, "build-tools"}, + {SdkManagerOutputParser::MarkerTag::SdkToolsMarker, "tools"}, + {SdkManagerOutputParser::MarkerTag::PlatformToolsMarker, "platform-tools"} }; -AndroidSdkManager::AndroidSdkManager(const AndroidConfig &config): - m_config(config), - m_parser(new SdkManagerOutputParser) +AndroidSdkManager::AndroidSdkManager(const AndroidConfig &config, QObject *parent): + QObject(parent), + m_d(new AndroidSdkManagerPrivate(*this, config)) { } AndroidSdkManager::~AndroidSdkManager() { - + cancelOperatons(); } -SdkPlatformList AndroidSdkManager::availableSdkPlatforms(bool *ok) +SdkPlatformList AndroidSdkManager::installedSdkPlatforms() { - bool success = false; - if (m_config.sdkToolsVersion() < sdkManagerIntroVersion) { - AndroidToolManager toolManager(m_config); - return toolManager.availableSdkPlatforms(ok); + AndroidSdkPackageList list = m_d->filteredPackages(AndroidSdkPackage::Installed, + AndroidSdkPackage::SdkPlatformPackage); + return Utils::transform(list, [](AndroidSdkPackage *p) { + return static_cast(p); + }); +} + +const AndroidSdkPackageList &AndroidSdkManager::allSdkPackages() +{ + return m_d->allPackages(); +} + +AndroidSdkPackageList AndroidSdkManager::availableSdkPackages() +{ + return m_d->filteredPackages(AndroidSdkPackage::Available, AndroidSdkPackage::AnyValidType); +} + +AndroidSdkPackageList AndroidSdkManager::installedSdkPackages() +{ + return m_d->filteredPackages(AndroidSdkPackage::Installed, AndroidSdkPackage::AnyValidType); +} + +SdkPlatform *AndroidSdkManager::latestAndroidSdkPlatform(AndroidSdkPackage::PackageState state) +{ + SdkPlatform *result = nullptr; + const AndroidSdkPackageList list = m_d->filteredPackages(state, + AndroidSdkPackage::SdkPlatformPackage); + for (AndroidSdkPackage *p : list) { + auto platform = static_cast(p); + if (!result || result->apiLevel() < platform->apiLevel()) + result = platform; } + return result; +} - QString packageListing; - if (sdkManagerCommand(m_config, QStringList({"--list", "--verbose"}), &packageListing)) - m_parser->parsePackageListing(packageListing); +SdkPlatformList AndroidSdkManager::filteredSdkPlatforms(int minApiLevel, + AndroidSdkPackage::PackageState state) +{ + const AndroidSdkPackageList list = m_d->filteredPackages(state, + AndroidSdkPackage::SdkPlatformPackage); - if (ok) - *ok = success; - return m_parser->m_installedPlatforms; + SdkPlatformList result; + for (AndroidSdkPackage *p : list) { + auto platform = static_cast(p); + if (platform && platform->apiLevel() >= minApiLevel) + result << platform; + } + return result; +} + +void AndroidSdkManager::reloadPackages(bool forceReload) +{ + m_d->refreshSdkPackages(forceReload); +} + +bool AndroidSdkManager::isBusy() const +{ + return m_d->m_activeOperation && !m_d->m_activeOperation->isFinished(); +} + +QFuture AndroidSdkManager::availableArguments() const +{ + return Utils::runAsync(&AndroidSdkManagerPrivate::parseCommonArguments, m_d.get()); +} + +QFuture AndroidSdkManager::updateAll() +{ + if (isBusy()) { + return QFuture(); + } + auto future = Utils::runAsync(&AndroidSdkManagerPrivate::updateInstalled, m_d.get()); + m_d->addWatcher(future); + return future; +} + +QFuture +AndroidSdkManager::update(const QStringList &install, const QStringList &uninstall) +{ + if (isBusy()) + return QFuture(); + auto future = Utils::runAsync(&AndroidSdkManagerPrivate::update, m_d.get(), install, uninstall); + m_d->addWatcher(future); + return future; +} + +QFuture AndroidSdkManager::checkPendingLicenses() +{ + if (isBusy()) + return QFuture(); + auto future = Utils::runAsync(&AndroidSdkManagerPrivate::checkPendingLicense, m_d.get()); + m_d->addWatcher(future); + return future; +} + +QFuture AndroidSdkManager::runLicenseCommand() +{ + if (isBusy()) + return QFuture(); + auto future = Utils::runAsync(&AndroidSdkManagerPrivate::getPendingLicense, m_d.get()); + m_d->addWatcher(future); + return future; +} + +void AndroidSdkManager::cancelOperatons() +{ + emit cancelActiveOperations(); + m_d->m_activeOperation.reset(); +} + +void AndroidSdkManager::acceptSdkLicense(bool accept) +{ + m_d->setLicenseInput(accept); } void SdkManagerOutputParser::parsePackageListing(const QString &output) @@ -173,10 +434,9 @@ void SdkManagerOutputParser::parsePackageListing(const QString &output) } }; - QRegularExpression delimiters("[\n\r]"); + QRegularExpression delimiters("[\\n\\r]"); foreach (QString outputLine, output.split(delimiters)) { MarkerTag marker = parseMarkers(outputLine.trimmed()); - if (marker & SectionMarkers) { // Section marker found. Update the current section being parsed. m_currentSection = marker; @@ -184,12 +444,8 @@ void SdkManagerOutputParser::parsePackageListing(const QString &output) continue; } - if (m_currentSection == None - || m_currentSection == AvailablePackagesMarkers // At this point. Not interested in - || m_currentSection == AvailableUpdatesMarker) { // available or update packages. - // Let go of verbose output utill a valid section starts. - continue; - } + if (m_currentSection == None) + continue; // Continue with the verbose output until a valid section starts. if (marker == EmptyMarker) { // Empty marker. Occurs at the end of a package details. @@ -211,20 +467,53 @@ void SdkManagerOutputParser::parsePackageListing(const QString &output) packageData << outputLine; } } - compileData(); - Utils::sort(m_installedPlatforms); + compilePackageAssociations(); } -void SdkManagerOutputParser::compileData() +void SdkManagerOutputParser::compilePackageAssociations() { + // Return true if package p is already installed i.e. there exists a installed package having + // same sdk style path and same revision as of p. + auto isInstalled = [](const AndroidSdkPackageList &container, AndroidSdkPackage *p) { + return Utils::anyOf(container, [p](AndroidSdkPackage *other) { + return other->state() == AndroidSdkPackage::Installed && + other->sdkStylePath() == p->sdkStylePath() && + other->revision() == p->revision(); + }); + }; + + auto deleteAlreadyInstalled = [isInstalled](AndroidSdkPackageList &packages) { + for (auto p = packages.begin(); p != packages.end();) { + if ((*p)->state() == AndroidSdkPackage::Available && isInstalled(packages, *p)) { + delete *p; + p = packages.erase(p); + } else { + ++p; + } + } + }; + + // Remove already installed packages. + deleteAlreadyInstalled(m_packages); + + // Filter out available images that are already installed. + AndroidSdkPackageList images = m_systemImages.keys(); + deleteAlreadyInstalled(images); + // Associate the system images with sdk platforms. - for (auto &image : m_installedSystemImages) { - auto findPlatfom = [image](const SdkPlatform &platform) { - return platform.apiLevel == image.apiLevel; - }; - auto itr = std::find_if(m_installedPlatforms.begin(), m_installedPlatforms.end(), findPlatfom); - if (itr != m_installedPlatforms.end()) - itr->systemImages.append(image); + for (AndroidSdkPackage *image : images) { + int imageApi = m_systemImages[image]; + auto itr = std::find_if(m_packages.begin(), m_packages.end(), + [imageApi](const AndroidSdkPackage *p) { + const SdkPlatform *platform = nullptr; + if (p->type() == AndroidSdkPackage::SdkPlatformPackage) + platform = static_cast(p); + return platform && platform->apiLevel() == imageApi; + }); + if (itr != m_packages.end()) { + SdkPlatform *platform = static_cast(*itr); + platform->addSystemImage(static_cast(image)); + } } } @@ -232,27 +521,37 @@ void SdkManagerOutputParser::parsePackageData(MarkerTag packageMarker, const QSt { QTC_ASSERT(!data.isEmpty() && packageMarker != None, return); - if (m_currentSection != MarkerTag::InstalledPackagesMarker) - return; // For now, only interested in installed packages. + AndroidSdkPackage *package = nullptr; + auto createPackage = [&](std::function creator) { + if ((package = creator(this, data))) + m_packages.append(package); + }; switch (packageMarker) { + case MarkerTag::BuildToolsMarker: + createPackage(&SdkManagerOutputParser::parseBuildToolsPackage); + break; + + case MarkerTag::SdkToolsMarker: + createPackage(&SdkManagerOutputParser::parseSdkToolsPackage); + break; + + case MarkerTag::PlatformToolsMarker: + createPackage(&SdkManagerOutputParser::parsePlatformToolsPackage); + break; + case MarkerTag::PlatformMarker: - { - SdkPlatform platform; - if (parsePlatform(data, &platform)) - m_installedPlatforms.append(platform); - else - qCDebug(sdkManagerLog) << "Platform: Parsing failed: " << data; - } + createPackage(&SdkManagerOutputParser::parsePlatform); break; case MarkerTag::SystemImageMarker: { - SystemImage image; - if (parseSystemImage(data, &image)) - m_installedSystemImages.append(image); - else - qCDebug(sdkManagerLog) << "System Image: Parsing failed: " << data; + QPair result = parseSystemImage(data); + if (result.first) { + m_systemImages[result.first] = result.second; + package = result.first; + } } break; @@ -260,71 +559,148 @@ void SdkManagerOutputParser::parsePackageData(MarkerTag packageMarker, const QSt qCDebug(sdkManagerLog) << "Unhandled package: " << markerTags.at(packageMarker); break; } -} -bool SdkManagerOutputParser::parsePlatform(const QStringList &data, SdkPlatform *platform) const -{ - QTC_ASSERT(platform && !data.isEmpty(), return false); - - QStringList parts = data.at(0).split(';'); - if (parts.count() < 2) { - qCDebug(sdkManagerLog) << "Platform: Unexpected header: "<< data; - return false; - } - platform->name = parts[1]; - platform->package = data.at(0); - - foreach (QString line, data) { - QString value; - if (valueForKey(installLocationKey, line, &value)) - platform->installedLocation = Utils::FileName::fromString(value); - } - - int apiLevel = AndroidManager::findApiLevel(platform->installedLocation); - if (apiLevel != -1) - platform->apiLevel = apiLevel; - else - qCDebug(sdkManagerLog) << "Platform: Can not parse api level: "<< data; - - return apiLevel != -1; -} - -bool SdkManagerOutputParser::parseSystemImage(const QStringList &data, SystemImage *image) -{ - QTC_ASSERT(image && !data.isEmpty(), return false); - - QStringList parts = data.at(0).split(';'); - QTC_ASSERT(!data.isEmpty() && parts.count() >= 4, - qCDebug(sdkManagerLog) << "System Image: Unexpected header: " << data); - - image->package = data.at(0); - foreach (QString line, data) { - QString value; - if (valueForKey(installLocationKey, line, &value)) - image->installedLocation = Utils::FileName::fromString(value); - } - - Utils::FileName propertiesPath = image->installedLocation; - propertiesPath.appendPath("/source.properties"); - if (propertiesPath.exists()) { - // Installed System Image. - QSettings imageProperties(propertiesPath.toString(), QSettings::IniFormat); - bool validApiLevel = false; - image->apiLevel = imageProperties.value(apiLevelPropertyKey).toInt(&validApiLevel); - if (!validApiLevel) { - qCDebug(sdkManagerLog) << "System Image: Can not parse api level: "<< data; - return false; + if (package) { + switch (m_currentSection) { + case MarkerTag::InstalledPackagesMarker: + package->setState(AndroidSdkPackage::Installed); + break; + case MarkerTag::AvailablePackagesMarkers: + case MarkerTag::AvailableUpdatesMarker: + package->setState(AndroidSdkPackage::Available); + break; + default: + qCDebug(sdkManagerLog) << "Invalid section marker: " << markerTags.at(m_currentSection); + break; } - image->abiName = imageProperties.value(abiPropertyKey).toString(); - } else if (parts.count() >= 4){ - image->apiLevel = parts[1].section('-', 1, 1).toInt(); - image->abiName = parts[3]; - } else { - qCDebug(sdkManagerLog) << "System Image: Can not parse: "<< data; + } +} + +bool SdkManagerOutputParser::parseAbstractData(SdkManagerOutputParser::GenericPackageData &output, + const QStringList &input, int minParts, + const QString &logStrTag, + QStringList extraKeys) const +{ + if (input.isEmpty()) { + qCDebug(sdkManagerLog) << logStrTag + ": Empty input"; return false; } - return true; + output.headerParts = input.at(0).split(';'); + if (output.headerParts.count() < minParts) { + qCDebug(sdkManagerLog) << logStrTag + "%1: Unexpected header:" << input; + return false; + } + + extraKeys << installLocationKey << revisionKey << descriptionKey; + foreach (QString line, input) { + QString value; + for (auto key: extraKeys) { + if (valueForKey(key, line, &value)) { + if (key == installLocationKey) + output.installedLocation = Utils::FileName::fromString(value); + else if (key == revisionKey) + output.revision = QVersionNumber::fromString(value); + else if (key == descriptionKey) + output.description = value; + else + output.extraData[key] = value; + break; + } + } + } + + return output.isValid(); +} + +AndroidSdkPackage *SdkManagerOutputParser::parsePlatform(const QStringList &data) const +{ + SdkPlatform *platform = nullptr; + GenericPackageData packageData; + if (parseAbstractData(packageData, data, 2, "Platform")) { + int apiLevel = platformNameToApiLevel(packageData.headerParts.at(1)); + if (apiLevel == -1) { + qCDebug(sdkManagerLog) << "Platform: Can not parse api level:"<< data; + return nullptr; + } + platform = new SdkPlatform(packageData.revision, data.at(0), apiLevel); + platform->setDescriptionText(packageData.description); + platform->setInstalledLocation(packageData.installedLocation); + } else { + qCDebug(sdkManagerLog) << "Platform: Parsing failed. Minimum required data unavailable:" + << data; + } + return platform; +} + +QPair SdkManagerOutputParser::parseSystemImage(const QStringList &data) const +{ + QPair result(nullptr, -1); + GenericPackageData packageData; + if (parseAbstractData(packageData, data, 4, "System-image")) { + int apiLevel = platformNameToApiLevel(packageData.headerParts.at(1)); + if (apiLevel == -1) { + qCDebug(sdkManagerLog) << "System-image: Can not parse api level:"<< data; + return result; + } + auto image = new SystemImage(packageData.revision, data.at(0), + packageData.headerParts.at(3)); + image->setInstalledLocation(packageData.installedLocation); + image->setDisplayText(packageData.description); + image->setDescriptionText(packageData.description); + result = qMakePair(image, apiLevel); + } else { + qCDebug(sdkManagerLog) << "System-image: Minimum required data unavailable: "<< data; + } + return result; +} + +BuildTools *SdkManagerOutputParser::parseBuildToolsPackage(const QStringList &data) const +{ + BuildTools *buildTools = nullptr; + GenericPackageData packageData; + if (parseAbstractData(packageData, data, 2, "Build-tools")) { + buildTools = new BuildTools(packageData.revision, data.at(0)); + buildTools->setDescriptionText(packageData.description); + buildTools->setDisplayText(packageData.description); + buildTools->setInstalledLocation(packageData.installedLocation); + } else { + qCDebug(sdkManagerLog) << "Build-tools: Parsing failed. Minimum required data unavailable:" + << data; + } + return buildTools; +} + +SdkTools *SdkManagerOutputParser::parseSdkToolsPackage(const QStringList &data) const +{ + SdkTools *sdkTools = nullptr; + GenericPackageData packageData; + if (parseAbstractData(packageData, data, 1, "SDK-tools")) { + sdkTools = new SdkTools(packageData.revision, data.at(0)); + sdkTools->setDescriptionText(packageData.description); + sdkTools->setDisplayText(packageData.description); + sdkTools->setInstalledLocation(packageData.installedLocation); + } else { + qCDebug(sdkManagerLog) << "SDK-tools: Parsing failed. Minimum required data unavailable:" + << data; + } + return sdkTools; +} + +PlatformTools *SdkManagerOutputParser::parsePlatformToolsPackage(const QStringList &data) const +{ + PlatformTools *platformTools = nullptr; + GenericPackageData packageData; + if (parseAbstractData(packageData, data, 1, "Platform-tools")) { + platformTools = new PlatformTools(packageData.revision, data.at(0)); + platformTools->setDescriptionText(packageData.description); + platformTools->setDisplayText(packageData.description); + platformTools->setInstalledLocation(packageData.installedLocation); + } else { + qCDebug(sdkManagerLog) << "Platform-tools: Parsing failed. Minimum required data " + "unavailable:" << data; + } + return platformTools; } SdkManagerOutputParser::MarkerTag SdkManagerOutputParser::parseMarkers(const QString &line) @@ -340,5 +716,298 @@ SdkManagerOutputParser::MarkerTag SdkManagerOutputParser::parseMarkers(const QSt return None; } +AndroidSdkManagerPrivate::AndroidSdkManagerPrivate(AndroidSdkManager &sdkManager, + const AndroidConfig &config): + m_activeOperation(nullptr, watcherDeleter), + m_sdkManager(sdkManager), + m_config(config) +{ +} + +AndroidSdkManagerPrivate::~AndroidSdkManagerPrivate() +{ + clearPackages(); +} + +AndroidSdkPackageList +AndroidSdkManagerPrivate::filteredPackages(AndroidSdkPackage::PackageState state, + AndroidSdkPackage::PackageType type, bool forceUpdate) +{ + refreshSdkPackages(forceUpdate); + return Utils::filtered(m_allPackages, [state, type](const AndroidSdkPackage *p) { + return p->state() & state && p->type() & type; + }); +} + +const AndroidSdkPackageList &AndroidSdkManagerPrivate::allPackages(bool forceUpdate) +{ + refreshSdkPackages(forceUpdate); + return m_allPackages; +} + +void AndroidSdkManagerPrivate::reloadSdkPackages() +{ + m_sdkManager.packageReloadBegin(); + clearPackages(); + + lastSdkManagerPath = m_config.sdkManagerToolPath(); + + if (m_config.sdkToolsVersion().isNull()) { + // Configuration has invalid sdk path or corrupt installation. + m_sdkManager.packageReloadFinished(); + return; + } + + if (m_config.sdkToolsVersion() < sdkManagerIntroVersion) { + // Old Sdk tools. + AndroidToolManager toolManager(m_config); + auto toAndroidSdkPackages = [](SdkPlatform *p) -> AndroidSdkPackage *{ + return p; + }; + m_allPackages = Utils::transform(toolManager.availableSdkPlatforms(), toAndroidSdkPackages); + } else { + QString packageListing; + QStringList args({"--list", "--verbose"}); + args << m_config.sdkManagerToolArgs(); + if (sdkManagerCommand(m_config.sdkManagerToolPath(), args, &packageListing)) { + SdkManagerOutputParser parser(m_allPackages); + parser.parsePackageListing(packageListing); + } + } + m_sdkManager.packageReloadFinished(); +} + +void AndroidSdkManagerPrivate::refreshSdkPackages(bool forceReload) +{ + // Sdk path changed. Updated packages. + // QTC updates the package listing only + if (m_config.sdkManagerToolPath() != lastSdkManagerPath || forceReload) + reloadSdkPackages(); +} + +void AndroidSdkManagerPrivate::updateInstalled(SdkCmdFutureInterface &fi) +{ + fi.setProgressRange(0, 100); + fi.setProgressValue(0); + AndroidSdkManager::OperationOutput result; + result.type = AndroidSdkManager::UpdateAll; + result.stdOutput = QCoreApplication::translate("AndroidSdkManager", + "Updating installed packages."); + fi.reportResult(result); + QStringList args("--update"); + args << m_config.sdkManagerToolArgs(); + if (!fi.isCanceled()) + sdkManagerCommand(m_config.sdkManagerToolPath(), args, m_sdkManager, fi, result, 100); + else + qCDebug(sdkManagerLog) << "Update: Operation cancelled before start"; + + if (result.stdError.isEmpty() && !result.success) + result.stdError = QCoreApplication::translate("AndroidSdkManager", "Failed."); + result.stdOutput = QCoreApplication::translate("AndroidSdkManager", "Done\n\n"); + fi.reportResult(result); + fi.setProgressValue(100); +} + +void AndroidSdkManagerPrivate::update(SdkCmdFutureInterface &fi, const QStringList &install, + const QStringList &uninstall) +{ + fi.setProgressRange(0, 100); + fi.setProgressValue(0); + double progressQuota = 100.0 / (install.count() + uninstall.count()); + int currentProgress = 0; + + QString installTag = QCoreApplication::translate("AndroidSdkManager", "Installing"); + QString uninstallTag = QCoreApplication::translate("AndroidSdkManager", "Uninstalling"); + + auto doOperation = [&](const QString& packagePath, const QStringList& args, + bool isInstall) { + AndroidSdkManager::OperationOutput result; + result.type = AndroidSdkManager::UpdatePackage; + result.stdOutput = QString("%1 %2").arg(isInstall ? installTag : uninstallTag) + .arg(packagePath); + fi.reportResult(result); + if (fi.isCanceled()) { + qCDebug(sdkManagerLog) << args << "Update: Operation cancelled before start"; + } else { + sdkManagerCommand(m_config.sdkManagerToolPath(), args, m_sdkManager, fi, result, + progressQuota, isInstall); + } + currentProgress += progressQuota; + fi.setProgressValue(currentProgress); + if (result.stdError.isEmpty() && !result.success) + result.stdError = QCoreApplication::translate("AndroidSdkManager", "Failed"); + result.stdOutput = QCoreApplication::translate("AndroidSdkManager", "Done\n\n"); + fi.reportResult(result); + return fi.isCanceled(); + }; + + + // Uninstall packages + for (const QString &sdkStylePath : uninstall) { + // Uninstall operations are not interptible. We don't want to leave half uninstalled. + QStringList args; + args << "--uninstall" << sdkStylePath << m_config.sdkManagerToolArgs(); + if (doOperation(sdkStylePath, args, false)) + break; + } + + // Install packages + for (const QString &sdkStylePath : install) { + QStringList args(sdkStylePath); + args << m_config.sdkManagerToolArgs(); + if (doOperation(sdkStylePath, args, true)) + break; + } + fi.setProgressValue(100); +} + +void AndroidSdkManagerPrivate::checkPendingLicense(SdkCmdFutureInterface &fi) +{ + fi.setProgressRange(0, 100); + fi.setProgressValue(0); + AndroidSdkManager::OperationOutput result; + result.type = AndroidSdkManager::LicenseCheck; + QStringList args("--licenses"); + if (!fi.isCanceled()) + sdkManagerCommand(m_config.sdkManagerToolPath(), args, m_sdkManager, fi, result, 100.0); + else + qCDebug(sdkManagerLog) << "Update: Operation cancelled before start"; + + fi.reportResult(result); + fi.setProgressValue(100); +} + +void AndroidSdkManagerPrivate::getPendingLicense(SdkCmdFutureInterface &fi) +{ + fi.setProgressRange(0, 100); + fi.setProgressValue(0); + AndroidSdkManager::OperationOutput result; + result.type = AndroidSdkManager::LicenseWorkflow; + QtcProcess licenseCommand; + bool reviewingLicenses = false; + licenseCommand.setCommand(m_config.sdkManagerToolPath().toString(), {"--licenses"}); + if (Utils::HostOsInfo::isWindowsHost()) + licenseCommand.setUseCtrlCStub(true); + licenseCommand.start(); + QTextCodec *codec = QTextCodec::codecForLocale(); + int inputCounter = 0, steps = -1; + while (!licenseCommand.waitForFinished(200)) { + QString stdOut = codec->toUnicode(licenseCommand.readAllStandardOutput()); + bool assertionFound = false; + if (!stdOut.isEmpty()) + assertionFound = onLicenseStdOut(stdOut, reviewingLicenses, result, fi); + + if (reviewingLicenses) { + // Check user input + QByteArray userInput = getUserInput(); + if (!userInput.isEmpty()) { + clearUserInput(); + licenseCommand.write(userInput); + ++inputCounter; + if (steps != -1) + fi.setProgressValue(qRound((inputCounter / (double)steps) * 100)); + } + } else if (assertionFound) { + // The first assertion is to start reviewing licenses. Always accept. + reviewingLicenses = true; + QRegularExpression reg("(\\d+\\sof\\s)(?\\d+)"); + QRegularExpressionMatch match = reg.match(stdOut); + if (match.hasMatch()) + steps = match.captured("steps").toInt(); + licenseCommand.write("Y\n"); + } + + if (fi.isCanceled()) { + licenseCommand.terminate(); + if (!licenseCommand.waitForFinished(300)) { + licenseCommand.kill(); + licenseCommand.waitForFinished(200); + } + } + if (licenseCommand.state() == QProcess::NotRunning) + break; + } + + m_licenseTextCache.clear(); + result.success = licenseCommand.exitStatus() == QtcProcess::NormalExit; + if (!result.success) { + result.stdError = QCoreApplication::translate("Android::Internal::AndroidSdkManager", + "License command failed.\n\n"); + } + fi.reportResult(result); + fi.setProgressValue(100); +} + +void AndroidSdkManagerPrivate::setLicenseInput(bool acceptLicense) +{ + QWriteLocker locker(&m_licenseInputLock); + m_licenseUserInput = acceptLicense ? "Y\n" : "n\n"; +} + +QByteArray AndroidSdkManagerPrivate::getUserInput() const +{ + QReadLocker locker(&m_licenseInputLock); + return m_licenseUserInput; +} + +void AndroidSdkManagerPrivate::clearUserInput() +{ + QWriteLocker locker(&m_licenseInputLock); + m_licenseUserInput.clear(); +} + +bool AndroidSdkManagerPrivate::onLicenseStdOut(const QString &output, bool notify, + AndroidSdkManager::OperationOutput &result, + SdkCmdFutureInterface &fi) +{ + m_licenseTextCache.append(output); + QRegularExpressionMatch assertionMatch = assertionReg.match(m_licenseTextCache); + if (assertionMatch.hasMatch()) { + if (notify) { + result.stdOutput = m_licenseTextCache; + fi.reportResult(result); + } + // Clear the current contents. The found license text is dispatched. Continue collecting the + // next license text. + m_licenseTextCache.clear(); + return true; + } + return false; +} + +void AndroidSdkManagerPrivate::addWatcher(const QFuture &future) +{ + if (future.isFinished()) + return; + m_activeOperation.reset(new QFutureWatcher()); + m_activeOperation->setFuture(future); +} + +void AndroidSdkManagerPrivate::parseCommonArguments(QFutureInterface &fi) +{ + QString argumentDetails; + QString output; + sdkManagerCommand(m_config.sdkManagerToolPath(), QStringList("--help"), &output); + bool foundTag = false; + for (const QString& line : output.split('\n')) { + if (fi.isCanceled()) + break; + if (foundTag) + argumentDetails.append(line + "\n"); + else if (line.startsWith(commonArgsKey)) + foundTag = true; + } + + if (!fi.isCanceled()) + fi.reportResult(argumentDetails); +} + +void AndroidSdkManagerPrivate::clearPackages() +{ + for (AndroidSdkPackage *p : m_allPackages) + delete p; + m_allPackages.clear(); +} + } // namespace Internal } // namespace Android diff --git a/src/plugins/android/androidsdkmanager.h b/src/plugins/android/androidsdkmanager.h index b1da078b6ef..fa15002cd2d 100644 --- a/src/plugins/android/androidsdkmanager.h +++ b/src/plugins/android/androidsdkmanager.h @@ -25,26 +25,75 @@ #pragma once #include "utils/fileutils.h" -#include "androidconfigurations.h" +#include "androidsdkpackage.h" + +#include +#include #include namespace Android { + +class AndroidConfig; + namespace Internal { -class SdkManagerOutputParser; +class AndroidSdkManagerPrivate; -class AndroidSdkManager +class AndroidSdkManager : public QObject { + Q_OBJECT public: - AndroidSdkManager(const AndroidConfig &config); + enum CommandType + { + None, + UpdateAll, + UpdatePackage, + LicenseCheck, + LicenseWorkflow + }; + + struct OperationOutput + { + bool success = false; + CommandType type = None; + QString stdOutput; + QString stdError; + }; + + AndroidSdkManager(const AndroidConfig &config, QObject *parent = nullptr); ~AndroidSdkManager(); - SdkPlatformList availableSdkPlatforms(bool *ok = nullptr); + SdkPlatformList installedSdkPlatforms(); + const AndroidSdkPackageList &allSdkPackages(); + AndroidSdkPackageList availableSdkPackages(); + AndroidSdkPackageList installedSdkPackages(); + + SdkPlatform *latestAndroidSdkPlatform(AndroidSdkPackage::PackageState state + = AndroidSdkPackage::Installed); + SdkPlatformList filteredSdkPlatforms(int minApiLevel, + AndroidSdkPackage::PackageState state + = AndroidSdkPackage::Installed); + void reloadPackages(bool forceReload = false); + bool isBusy() const; + + QFuture availableArguments() const; + QFuture updateAll(); + QFuture update(const QStringList &install, const QStringList &uninstall); + QFuture checkPendingLicenses(); + QFuture runLicenseCommand(); + + void cancelOperatons(); + void acceptSdkLicense(bool accept); + +signals: + void packageReloadBegin(); + void packageReloadFinished(); + void cancelActiveOperations(); private: - const AndroidConfig &m_config; - std::unique_ptr m_parser; + std::unique_ptr m_d; + friend class AndroidSdkManagerPrivate; }; } // namespace Internal diff --git a/src/plugins/android/androidsdkmanagerwidget.cpp b/src/plugins/android/androidsdkmanagerwidget.cpp new file mode 100644 index 00000000000..7ffa294e318 --- /dev/null +++ b/src/plugins/android/androidsdkmanagerwidget.cpp @@ -0,0 +1,579 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 "androidsdkmanagerwidget.h" + +#include "ui_androidsdkmanagerwidget.h" +#include "androidconfigurations.h" +#include "androidsdkmanager.h" +#include "androidsdkmodel.h" + +#include "utils/runextensions.h" +#include "utils/outputformatter.h" +#include "utils/runextensions.h" +#include "utils/qtcassert.h" +#include "utils/utilsicons.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace { +Q_LOGGING_CATEGORY(androidSdkMgrUiLog, "qtc.android.sdkManagerUi") +} + +namespace Android { +namespace Internal { + +using namespace std::placeholders; + +class OptionsDialog : public QDialog +{ +public: + OptionsDialog(AndroidSdkManager *sdkManager, const QStringList &args, + QWidget *parent = nullptr); + ~OptionsDialog(); + + QStringList sdkManagerArguments() const; + +private: + QPlainTextEdit *argumentDetailsEdit; + QLineEdit *argumentsEdit; + QFuture m_optionsFuture; +}; + +class PackageFilterModel : public QSortFilterProxyModel +{ +public: + PackageFilterModel(AndroidSdkModel* sdkModel); + + void setAcceptedPackageState(AndroidSdkPackage::PackageState state); + bool filterAcceptsRow(int source_row, const QModelIndex &sourceParent) const override; + +private: + AndroidSdkPackage::PackageState m_packageState = AndroidSdkPackage::AnyValidState; +}; + +AndroidSdkManagerWidget::AndroidSdkManagerWidget(AndroidConfig &config, + AndroidSdkManager *sdkManager, QWidget *parent) : + QWidget(parent), + m_androidConfig(config), + m_sdkManager(sdkManager), + m_sdkModel(new AndroidSdkModel(m_androidConfig, m_sdkManager, this)), + m_ui(new Ui::AndroidSdkManagerWidget) +{ + QTC_CHECK(sdkManager); + m_ui->setupUi(this); + m_ui->sdkLicensebuttonBox->hide(); + m_ui->sdkLicenseLabel->hide(); + m_ui->warningLabel->setElideMode(Qt::ElideRight); + m_ui->warningIconLabel->setPixmap(Utils::Icons::WARNING.pixmap()); + m_ui->viewStack->setCurrentWidget(m_ui->packagesStack); + + m_formatter = new Utils::OutputFormatter; + m_formatter->setPlainTextEdit(m_ui->outputEdit); + + connect(m_sdkModel, &AndroidSdkModel::dataChanged, [this]() { + if (m_ui->viewStack->currentWidget() == m_ui->packagesStack) + m_ui->applySelectionButton->setEnabled(!m_sdkModel->userSelection().isEmpty()); + }); + + connect(m_sdkModel, &AndroidSdkModel::modelAboutToBeReset, [this]() { + m_ui->applySelectionButton->setEnabled(false); + m_ui->expandCheck->setChecked(false); + cancelPendingOperations(); + switchView(PackageListing); + }); + + auto proxyModel = new PackageFilterModel(m_sdkModel); + m_ui->packagesView->setModel(proxyModel); + m_ui->packagesView->header()->setSectionResizeMode(AndroidSdkModel::packageNameColumn, + QHeaderView::ResizeToContents); + m_ui->packagesView->header()->setSectionResizeMode(AndroidSdkModel::apiLevelColumn, + QHeaderView::ResizeToContents); + m_ui->packagesView->header()->setSectionResizeMode(AndroidSdkModel::packageRevisionColumn, + QHeaderView::ResizeToContents); + connect(m_ui->expandCheck, &QCheckBox::stateChanged, [this](int state) { + if (state == Qt::Checked) + m_ui->packagesView->expandAll(); + else + m_ui->packagesView->collapseAll(); + }); + connect(m_ui->updateInstalledButton, &QPushButton::clicked, + this, &AndroidSdkManagerWidget::onUpdatePackages); + connect(m_ui->showAllRadio, &QRadioButton::toggled, [this, proxyModel](bool checked) { + if (checked) { + proxyModel->setAcceptedPackageState(AndroidSdkPackage::AnyValidState); + m_sdkModel->resetSelection(); + } + }); + connect(m_ui->showInstalledRadio, &QRadioButton::toggled, [this, proxyModel](bool checked) { + if (checked) { + proxyModel->setAcceptedPackageState(AndroidSdkPackage::Installed); + m_sdkModel->resetSelection(); + } + }); + connect(m_ui->showAvailableRadio, &QRadioButton::toggled, [this, proxyModel](bool checked) { + if (checked) { + proxyModel->setAcceptedPackageState(AndroidSdkPackage::Available); + m_sdkModel->resetSelection(); + } + }); + + connect(m_ui->applySelectionButton, &QPushButton::clicked, + this, &AndroidSdkManagerWidget::onApplyButton); + connect(m_ui->cancelButton, &QPushButton::clicked, this, + &AndroidSdkManagerWidget::onCancel); + connect(m_ui->nativeSdkManagerButton, &QPushButton::clicked, + this, &AndroidSdkManagerWidget::onNativeSdkManager); + connect(m_ui->optionsButton, &QPushButton::clicked, + this, &AndroidSdkManagerWidget::onSdkManagerOptions); + connect(m_ui->sdkLicensebuttonBox, &QDialogButtonBox::accepted, [this]() { + m_sdkManager->acceptSdkLicense(true); + m_ui->sdkLicensebuttonBox->setEnabled(false); // Wait for next license to enable controls + }); + connect(m_ui->sdkLicensebuttonBox, &QDialogButtonBox::rejected, [this]() { + m_sdkManager->acceptSdkLicense(false); + m_ui->sdkLicensebuttonBox->setEnabled(false); // Wait for next license to enable controls + }); +} + +AndroidSdkManagerWidget::~AndroidSdkManagerWidget() +{ + if (m_currentOperation) + delete m_currentOperation; + cancelPendingOperations(); + delete m_formatter; + delete m_ui; +} + +void AndroidSdkManagerWidget::setSdkManagerControlsEnabled(bool enable) +{ + m_ui->packagesTypeGroup->setEnabled(enable); + m_ui->expandCheck->setVisible(enable); + m_ui->warningIconLabel->setVisible(!enable); + m_ui->warningLabel->setVisible(!enable); + m_ui->packagesView->setEnabled(enable); + m_ui->updateInstalledButton->setEnabled(enable); + m_ui->optionsButton->setEnabled(enable); +} + +void AndroidSdkManagerWidget::installEssentials() +{ + m_sdkModel->selectMissingEssentials(); + m_ui->applySelectionButton->click(); +} + +void AndroidSdkManagerWidget::beginLicenseCheck() +{ + m_formatter->appendMessage(tr("Checking pending licenses...\n"), Utils::NormalMessageFormat); + addPackageFuture(m_sdkManager->checkPendingLicenses()); +} + +void AndroidSdkManagerWidget::onApplyButton() +{ + QTC_ASSERT(m_currentView == PackageListing, return); + + if (m_sdkManager->isBusy()) { + m_formatter->appendMessage(tr("\nSDK Manager is busy."), Utils::StdErrFormat); + return; + } + + const QList packagesToUpdate = m_sdkModel->userSelection(); + if (packagesToUpdate.isEmpty()) + return; + + QStringList installPackages, uninstallPackages; + for (auto package : packagesToUpdate) { + QString str = QString(" %1").arg(package->descriptionText()); + if (package->state() == AndroidSdkPackage::Installed) + uninstallPackages << str; + else + installPackages << str; + } + + QMessageBox messageDlg(QMessageBox::Information, tr("Android SDK Changes"), + tr("%n Android SDK packages shall be updated.", + "", packagesToUpdate.count()), + QMessageBox::Ok | QMessageBox::Cancel, this); + + QString details; + if (!uninstallPackages.isEmpty()) + details = tr("[Packages to be uninstalled:]\n").append(uninstallPackages.join("\n")); + + if (!installPackages.isEmpty()) { + if (!uninstallPackages.isEmpty()) + details.append("\n\n"); + details.append("[Packages to be installed:]\n").append(installPackages.join("\n")); + } + messageDlg.setDetailedText(details); + if (messageDlg.exec() == QMessageBox::Cancel) + return; + + switchView(Operations); + m_pendingCommand = AndroidSdkManager::UpdatePackage; + // User agreed with the selection. Check for licenses. + if (!installPackages.isEmpty()) { + // Pending license affects installtion only. + beginLicenseCheck(); + } else { + // Uninstall only. Go Ahead. + beginExecution(); + } +} + +void AndroidSdkManagerWidget::onUpdatePackages() +{ + if (m_sdkManager->isBusy()) { + m_formatter->appendMessage(tr("\nSDK Manager is busy."), Utils::StdErrFormat); + return; + } + switchView(Operations); + m_pendingCommand = AndroidSdkManager::UpdateAll; + beginLicenseCheck(); +} + +void AndroidSdkManagerWidget::onCancel() +{ + cancelPendingOperations(); +} + +void AndroidSdkManagerWidget::onNativeSdkManager() +{ + if (m_androidConfig.useNativeUiTools()) { + QProcess::startDetached(m_androidConfig.androidToolPath().toString()); + } else { + QMessageBox::warning(this, tr("Native SDK Manager Not Available"), + tr("SDK manager UI tool is not available in the installed SDK tools" + "(version %1). Use the command line tool \"sdkmanager\" for " + "advanced SDK management.") + .arg(m_androidConfig.sdkToolsVersion().toString())); + } +} + +void AndroidSdkManagerWidget::onOperationResult(int index) +{ + QTC_ASSERT(m_currentOperation, return); + AndroidSdkManager::OperationOutput result = m_currentOperation->resultAt(index); + if (result.type == AndroidSdkManager::LicenseWorkflow) { + // Show license controls and enable to user input. + m_ui->sdkLicenseLabel->setVisible(true); + m_ui->sdkLicensebuttonBox->setVisible(true); + m_ui->sdkLicensebuttonBox->setEnabled(true); + m_ui->sdkLicensebuttonBox->button(QDialogButtonBox::No)->setDefault(true); + } + auto breakLine = [](const QString &line) { return line.endsWith("\n") ? line : line + "\n";}; + if (!result.stdError.isEmpty() && result.type != AndroidSdkManager::LicenseCheck) + m_formatter->appendMessage(breakLine(result.stdError), Utils::StdErrFormat); + if (!result.stdOutput.isEmpty() && result.type != AndroidSdkManager::LicenseCheck) + m_formatter->appendMessage(breakLine(result.stdOutput), Utils::StdOutFormat); + m_ui->outputEdit->ensureCursorVisible(); +} + +void AndroidSdkManagerWidget::onLicenseCheckResult(const AndroidSdkManager::OperationOutput& output) +{ + if (output.success) { + // No assertion was found. Looks like all license are accepted. Go Ahead. + runPendingCommand(); + } else { + // Assertion was found. Provide user workflow to accept licenses. + QString warningMessage = tr("\nPlease note that the installation and use of Android SDK " + "packages may fail if respective licenses are not accepted."); + int userSelection = QMessageBox::question(this, tr("Android SDK Licenses"), + output.stdOutput + warningMessage, + QMessageBox::Yes | QMessageBox::No); + if (userSelection == QMessageBox::Yes) { + // Run license workflow. + beginLicenseWorkflow(); + } else { + // User decided to go ahead anyways. + runPendingCommand(); + } + } +} + +void AndroidSdkManagerWidget::addPackageFuture(const QFuture + &future) +{ + QTC_ASSERT(!m_currentOperation, return); + if (!future.isFinished() || !future.isCanceled()) { + m_currentOperation = new QFutureWatcher; + m_currentOperation->setFuture(future); + connect(m_currentOperation, + &QFutureWatcher::resultReadyAt, + this, &AndroidSdkManagerWidget::onOperationResult); + connect(m_currentOperation, &QFutureWatcher::finished, + this, &AndroidSdkManagerWidget::packageFutureFinished); + connect(m_currentOperation, + &QFutureWatcher::progressValueChanged, + [this](int value) { + m_ui->operationProgress->setValue(value); + }); + } else { + qCDebug(androidSdkMgrUiLog) << "Operation canceled/finished before adding to the queue"; + if (m_sdkManager->isBusy()) { + m_formatter->appendMessage(tr("SDK Manager is busy. Operation cancelled."), + Utils::StdErrFormat); + } + notifyOperationFinished(); + switchView(PackageListing); + } +} + +void AndroidSdkManagerWidget::beginExecution() +{ + const QList packagesToUpdate = m_sdkModel->userSelection(); + if (packagesToUpdate.isEmpty()) { + switchView(PackageListing); + return; + } + + QStringList installSdkPaths, uninstallSdkPaths; + for (auto package : packagesToUpdate) { + if (package->state() == AndroidSdkPackage::Installed) + uninstallSdkPaths << package->sdkStylePath(); + else + installSdkPaths << package->sdkStylePath(); + } + m_formatter->appendMessage(tr("Installing/Uninstalling selected packages...\n"), + Utils::NormalMessageFormat); + m_formatter->appendMessage(tr("Closing the %1 dialog will cancel the running and scheduled SDK " + "operations.\n").arg(Utils::HostOsInfo::isMacHost() ? + tr("preferences") : tr("options")), + Utils::LogMessageFormat); + + addPackageFuture(m_sdkManager->update(installSdkPaths, uninstallSdkPaths)); +} + +void AndroidSdkManagerWidget::beginUpdate() +{ + m_formatter->appendMessage(tr("Updating installed packages...\n"), Utils::NormalMessageFormat); + m_formatter->appendMessage(tr("Closing the %1 dialog will cancel the running and scheduled SDK " + "operations.\n").arg(Utils::HostOsInfo::isMacHost() ? + tr("preferences") : tr("options")), + Utils::LogMessageFormat); + addPackageFuture(m_sdkManager->updateAll()); +} + +void AndroidSdkManagerWidget::beginLicenseWorkflow() +{ + switchView(LicenseWorkflow); + addPackageFuture(m_sdkManager->runLicenseCommand()); +} + +void AndroidSdkManagerWidget::notifyOperationFinished() +{ + if (!m_currentOperation || m_currentOperation->isFinished()) { + QMessageBox::information(this, tr("Android SDK Changes"), + tr("Android SDK operations finished."), QMessageBox::Ok); + m_ui->operationProgress->setValue(0); + } +} + +void AndroidSdkManagerWidget::packageFutureFinished() +{ + QTC_ASSERT (m_currentOperation, return); + + bool continueWorkflow = true; + if (m_currentOperation->isCanceled()) { + m_formatter->appendMessage(tr("Operation cancelled.\n"), Utils::StdErrFormat); + continueWorkflow = false; + } + m_ui->operationProgress->setValue(100); + int resultCount = m_currentOperation->future().resultCount(); + if (continueWorkflow && resultCount > 0) { + AndroidSdkManager::OperationOutput output = m_currentOperation->resultAt(resultCount -1); + AndroidSdkManager::CommandType type = output.type; + m_currentOperation->deleteLater(); + m_currentOperation = nullptr; + switch (type) { + case AndroidSdkManager::LicenseCheck: + onLicenseCheckResult(output); + break; + case AndroidSdkManager::LicenseWorkflow: + m_ui->sdkLicensebuttonBox->hide(); + m_ui->sdkLicenseLabel->hide(); + runPendingCommand(); + break; + case AndroidSdkManager::UpdateAll: + case AndroidSdkManager::UpdatePackage: + notifyOperationFinished(); + switchView(PackageListing); + m_sdkManager->reloadPackages(true); + break; + default: + break; + } + } else { + m_currentOperation->deleteLater(); + m_currentOperation = nullptr; + switchView(PackageListing); + m_sdkManager->reloadPackages(true); + } +} + +void AndroidSdkManagerWidget::cancelPendingOperations() +{ + if (!m_sdkManager->isBusy()) { + m_formatter->appendMessage(tr("\nNo pending operations to cancel...\n"), + Utils::NormalMessageFormat); + switchView(PackageListing); + return; + } + m_formatter->appendMessage(tr("\nCancelling pending operations...\n"), + Utils::NormalMessageFormat); + m_sdkManager->cancelOperatons(); +} + +void AndroidSdkManagerWidget::switchView(AndroidSdkManagerWidget::View view) +{ + if (m_currentView == PackageListing) { + m_formatter->clear(); + m_ui->outputEdit->clear(); + } + m_currentView = view; + if (m_currentView == PackageListing) + emit updatingSdkFinished(); + else + emit updatingSdk(); + + m_ui->operationProgress->setValue(0); + m_ui->viewStack->setCurrentWidget(m_currentView == PackageListing ? + m_ui->packagesStack : m_ui->outputStack); +} + +void AndroidSdkManagerWidget::runPendingCommand() +{ + if (m_pendingCommand == AndroidSdkManager::UpdatePackage) + beginExecution(); // License workflow can only start when updating packages. + else if (m_pendingCommand == AndroidSdkManager::UpdateAll) + beginUpdate(); + else + QTC_ASSERT(false, qCDebug(androidSdkMgrUiLog) << "Unexpected state: No pending command."); +} + +void AndroidSdkManagerWidget::onSdkManagerOptions() +{ + OptionsDialog dlg(m_sdkManager, m_androidConfig.sdkManagerToolArgs(), this); + if (dlg.exec() == QDialog::Accepted) { + QStringList arguments = dlg.sdkManagerArguments(); + if (arguments != m_androidConfig.sdkManagerToolArgs()) { + m_androidConfig.setSdkManagerToolArgs(arguments); + m_sdkManager->reloadPackages(true); + } + } +} + +PackageFilterModel::PackageFilterModel(AndroidSdkModel *sdkModel) : + QSortFilterProxyModel(sdkModel) +{ + setSourceModel(sdkModel); +} + +void PackageFilterModel::setAcceptedPackageState(AndroidSdkPackage::PackageState state) +{ + m_packageState = state; + invalidateFilter(); +} + +bool PackageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + QModelIndex srcIndex = sourceModel()->index(sourceRow, 0, sourceParent); + if (!srcIndex.isValid()) + return false; + + auto packageState = [](const QModelIndex& i) { + return (AndroidSdkPackage::PackageState)i.data(AndroidSdkModel::PackageStateRole).toInt(); + }; + + bool showTopLevel = false; + if (!sourceParent.isValid()) { + // Top Level items + for (int row = 0; row < sourceModel()->rowCount(srcIndex); ++row) { + QModelIndex childIndex = sourceModel()->index(row, 0, srcIndex); + if (m_packageState & packageState(childIndex)) { + showTopLevel = true; + break; + } + } + } + + return showTopLevel || (packageState(srcIndex) & m_packageState); +} + +OptionsDialog::OptionsDialog(AndroidSdkManager *sdkManager, const QStringList &args, + QWidget *parent) : QDialog(parent) +{ + QTC_CHECK(sdkManager); + resize(800, 480); + setWindowTitle(tr("SDK Manager Arguments")); + + argumentDetailsEdit = new QPlainTextEdit(this); + argumentDetailsEdit->setReadOnly(true); + + auto populateOptions = [this](const QString& options) { + if (options.isEmpty()) { + argumentDetailsEdit->setPlainText(tr("Cannot load available arguments for " + "\"sdkmanager\" command.")); + } else { + argumentDetailsEdit->setPlainText(options); + } + }; + m_optionsFuture = sdkManager->availableArguments(); + Utils::onResultReady(m_optionsFuture, populateOptions); + + auto dialogButtons = new QDialogButtonBox(this); + dialogButtons->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); + connect(dialogButtons, &QDialogButtonBox::accepted, this, &OptionsDialog::accept); + connect(dialogButtons, &QDialogButtonBox::rejected, this, &OptionsDialog::reject); + + argumentsEdit = new QLineEdit(this); + argumentsEdit->setText(args.join(" ")); + + auto gridLayout = new QGridLayout(this); + gridLayout->addWidget(new QLabel(tr("SDK manager arguments:"), this), 0, 0, 1, 1); + gridLayout->addWidget(argumentsEdit, 0, 1, 1, 1); + gridLayout->addWidget(new QLabel(tr("Available arguments:"), this), 1, 0, 1, 2); + gridLayout->addWidget(argumentDetailsEdit, 2, 0, 1, 2); + gridLayout->addWidget(dialogButtons, 3, 0, 1, 2); +} + +OptionsDialog::~OptionsDialog() +{ + m_optionsFuture.cancel(); + m_optionsFuture.waitForFinished(); +} + +QStringList OptionsDialog::sdkManagerArguments() const +{ + QString userInput = argumentsEdit->text().simplified(); + return userInput.isEmpty() ? QStringList() : userInput.split(' '); +} + +} // namespace Internal +} // namespace Android diff --git a/src/plugins/android/androidsdkmanagerwidget.h b/src/plugins/android/androidsdkmanagerwidget.h new file mode 100644 index 00000000000..29b2ec064e1 --- /dev/null +++ b/src/plugins/android/androidsdkmanagerwidget.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 "androidconfigurations.h" +#include "androidsdkmanager.h" + +#include +#include + +namespace Utils { class OutputFormatter; } + +namespace Android { +namespace Internal { + +class AndroidSdkManager; +namespace Ui { + class AndroidSdkManagerWidget; +} + +class AndroidSdkModel; + +class AndroidSdkManagerWidget : public QWidget +{ + Q_OBJECT + + enum View { + PackageListing, + Operations, + LicenseWorkflow + }; + +public: + AndroidSdkManagerWidget(AndroidConfig &config, AndroidSdkManager *sdkManager, + QWidget *parent = nullptr); + ~AndroidSdkManagerWidget(); + + void setSdkManagerControlsEnabled(bool enable); + void installEssentials(); + +signals: + void updatingSdk(); + void updatingSdkFinished(); + +private: + void onApplyButton(); + void onUpdatePackages(); + void onCancel(); + void onNativeSdkManager(); + void onOperationResult(int index); + void onLicenseCheckResult(const AndroidSdkManager::OperationOutput &output); + void onSdkManagerOptions(); + void addPackageFuture(const QFuture &future); + void beginLicenseCheck(); + void beginExecution(); + void beginUpdate(); + void beginLicenseWorkflow(); + void notifyOperationFinished(); + void packageFutureFinished(); + void cancelPendingOperations(); + void switchView(View view); + void runPendingCommand(); + + AndroidConfig &m_androidConfig; + AndroidSdkManager::CommandType m_pendingCommand = AndroidSdkManager::None; + View m_currentView = PackageListing; + AndroidSdkManager *m_sdkManager = nullptr; + AndroidSdkModel *m_sdkModel = nullptr; + Ui::AndroidSdkManagerWidget *m_ui = nullptr; + Utils::OutputFormatter *m_formatter = nullptr; + QFutureWatcher *m_currentOperation = nullptr; +}; + +} // namespace Internal +} // namespace Android diff --git a/src/plugins/android/androidsdkmanagerwidget.ui b/src/plugins/android/androidsdkmanagerwidget.ui new file mode 100644 index 00000000000..335ed19a5d9 --- /dev/null +++ b/src/plugins/android/androidsdkmanagerwidget.ui @@ -0,0 +1,311 @@ + + + Android::Internal::AndroidSdkManagerWidget + + + + 0 + 0 + 664 + 396 + + + + Android SDK Manager + + + + -1 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 4 + + + + + + + + 0 + 0 + + + + Expand All + + + true + + + + + + + + + + + + + + + 0 + 0 + + + + SDK manger is not available with the current version of SDK tools. Use native SDK manager. + + + + + + + + + 20 + + + false + + + + + + + + + Update Installed + + + + + + + false + + + Apply + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + Show Packages + + + + 12 + + + + + Available + + + + + + + Installed + + + + + + + All + + + true + + + + + + + + + + Advanced Options... + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 0 + 5 + + + + + + + + Native SDK Manager... + + + + + + + + + + + + + Cancel + + + + + + + 100 + + + 0 + + + true + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + + + + + + 0 + 0 + + + + Do you want to accept the Android SDK license? + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + + 0 + 0 + + + + QDialogButtonBox::No|QDialogButtonBox::Yes + + + + + + + + + + + + Utils::ElidingLabel + QLabel +
utils/elidinglabel.h
+
+
+ + packagesView + showAllRadio + showInstalledRadio + showAvailableRadio + outputEdit + + + +
diff --git a/src/plugins/android/androidsdkmodel.cpp b/src/plugins/android/androidsdkmodel.cpp new file mode 100644 index 00000000000..1b3b1a5af38 --- /dev/null +++ b/src/plugins/android/androidsdkmodel.cpp @@ -0,0 +1,352 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 "androidsdkmodel.h" +#include "androidmanager.h" +#include "androidsdkmanager.h" + +#include "utils/algorithm.h" +#include "utils/qtcassert.h" +#include "utils/utilsicons.h" + +#include + +namespace Android { +namespace Internal { + +const int packageColCount = 4; + +AndroidSdkModel::AndroidSdkModel(const AndroidConfig &config, AndroidSdkManager *sdkManager, + QObject *parent) + : QAbstractItemModel(parent), + m_config(config), + m_sdkManager(sdkManager) +{ + QTC_CHECK(m_sdkManager); + connect(m_sdkManager, &AndroidSdkManager::packageReloadBegin, [this]() { + clearContainers(); + beginResetModel(); + }); + connect(m_sdkManager, &AndroidSdkManager::packageReloadFinished, [this]() { + refreshData(); + endResetModel(); + }); +} + +QVariant AndroidSdkModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_UNUSED(orientation) + QVariant data; + if (role == Qt::DisplayRole) { + switch (section) { + case packageNameColumn: + data = tr("Package"); + break; + case packageRevisionColumn: + data = tr("Revision"); + break; + case apiLevelColumn: + data = tr("API"); + break; + case operationColumn: + data = tr("Operation"); + break; + default: + break; + } + } + return data; +} + +QModelIndex AndroidSdkModel::index(int row, int column, const QModelIndex &parent) const +{ + if (parent.isValid()) { + // Packages under top items. + if (parent.row() == 0) { + // Tools packages + if (row < m_tools.count()) + return createIndex(row, column, const_cast(m_tools.at(row))); + } else if (parent.row() < m_sdkPlatforms.count() + 1) { + // Platform packages + const SdkPlatform *sdkPlatform = m_sdkPlatforms.at(parent.row() - 1); + SystemImageList images = sdkPlatform->systemImages(AndroidSdkPackage::AnyValidState); + if (row < images.count() + 1) { + if (row == 0) + return createIndex(row, column, const_cast(sdkPlatform)); + else + return createIndex(row, column, images.at(row - 1)); + } + } + } else if (row < m_sdkPlatforms.count() + 1) { + return createIndex(row, column); // Top level items (Tools & platform) + } + + return QModelIndex(); +} + +QModelIndex AndroidSdkModel::parent(const QModelIndex &index) const +{ + void *ip = index.internalPointer(); + if (!ip) + return QModelIndex(); + + auto package = static_cast(ip); + if (package->type() == AndroidSdkPackage::SystemImagePackage) { + auto image = static_cast(package); + int row = m_sdkPlatforms.indexOf(const_cast(image->platform())); + if (row > -1) + return createIndex(row + 1, 0); + } else if (package->type() == AndroidSdkPackage::SdkPlatformPackage) { + int row = m_sdkPlatforms.indexOf(static_cast(package)); + if (row > -1) + return createIndex(row + 1, 0); + } else { + return createIndex(0, 0); // Tools + } + + return QModelIndex(); +} + +int AndroidSdkModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) + return m_sdkPlatforms.count() + 1; + + if (!parent.internalPointer()) { + if (parent.row() == 0) // Tools + return m_tools.count(); + + if (parent.row() <= m_sdkPlatforms.count()) { + const SdkPlatform * platform = m_sdkPlatforms.at(parent.row() - 1); + return platform->systemImages(AndroidSdkPackage::AnyValidState).count() + 1; + } + } + + return 0; +} + +int AndroidSdkModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return packageColCount; +} + +QVariant AndroidSdkModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + + if (!index.parent().isValid()) { + // Top level tools + if (index.row() == 0) { + return role == Qt::DisplayRole && index.column() == packageNameColumn ? + QVariant(tr("Tools")) : QVariant(); + } + // Top level platforms + const SdkPlatform *platform = m_sdkPlatforms.at(index.row() - 1); + if (role == Qt::DisplayRole) { + if (index.column() == packageNameColumn) { + QString androidName = AndroidManager::androidNameForApiLevel(platform->apiLevel()); + if (androidName.startsWith("Android")) + return androidName; + else + return platform->displayText(); + } else if (index.column() == apiLevelColumn) { + return platform->apiLevel(); + } + } + return QVariant(); + } + + auto p = static_cast(index.internalPointer()); + QString apiLevelStr; + if (p->type() == AndroidSdkPackage::SdkPlatformPackage) + apiLevelStr = QString::number(static_cast(p)->apiLevel()); + + if (p->type() == AndroidSdkPackage::SystemImagePackage) + apiLevelStr = QString::number(static_cast(p)->platform()->apiLevel()); + + if (role == Qt::DisplayRole) { + switch (index.column()) { + case packageNameColumn: + return p->type() == AndroidSdkPackage::SdkPlatformPackage ? + tr("SDK Platform") : p->displayText(); + case packageRevisionColumn: + return p->revision().toString(); + case apiLevelColumn: + return apiLevelStr; + case operationColumn: + if (p->type() == AndroidSdkPackage::SdkToolsPackage && + p->state() == AndroidSdkPackage::Installed) { + return tr("Update Only"); + } else { + return p->state() == AndroidSdkPackage::Installed ? tr("Uninstall") : tr("Install"); + } + default: + break; + } + } + + if (role == Qt::DecorationRole && index.column() == packageNameColumn) { + return p->state() == AndroidSdkPackage::Installed ? Utils::Icons::OK.icon() : + Utils::Icons::EMPTY16.icon(); + } + + if (role == Qt::CheckStateRole && index.column() == operationColumn ) + return m_changeState.contains(p) ? Qt::Checked : Qt::Unchecked; + + if (role == Qt::ToolTipRole) + return QString("%1 - (%2)").arg(p->descriptionText()).arg(p->sdkStylePath()); + + if (role == PackageTypeRole) + return p->type(); + + if (role == PackageStateRole) + return p->state(); + + return QVariant(); +} + +QHash AndroidSdkModel::roleNames() const +{ + QHash roles; + roles[PackageTypeRole] = "PackageRole"; + roles[PackageStateRole] = "PackageState"; + return roles; +} + +Qt::ItemFlags AndroidSdkModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags f = QAbstractItemModel::flags(index); + if (index.column() == operationColumn) + f |= Qt::ItemIsUserCheckable; + + void *ip = index.internalPointer(); + if (ip && index.column() == operationColumn) { + auto package = static_cast(ip); + if (package->state() == AndroidSdkPackage::Installed && + package->type() == AndroidSdkPackage::SdkToolsPackage) { + f &= ~Qt::ItemIsEnabled; + } + } + return f; +} + +bool AndroidSdkModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + void *ip = index.internalPointer(); + if (ip && role == Qt::CheckStateRole) { + auto package = static_cast(ip); + if (value.toInt() == Qt::Checked) { + m_changeState << package; + emit dataChanged(index, index, {Qt::CheckStateRole}); + } else if (m_changeState.remove(package)) { + emit dataChanged(index, index, {Qt::CheckStateRole}); + } + return true; + } + return false; +} + +void AndroidSdkModel::selectMissingEssentials() +{ + resetSelection(); + bool selectPlatformTool = !m_config.adbToolPath().exists(); + bool selectBuildTools = m_config.buildToolsVersion().isNull(); + auto addTool = [this](QList::const_iterator itr) { + m_changeState << *itr; + auto i = index(std::distance(m_tools.cbegin(), itr), 0, index(0, 0)); + emit dataChanged(i, i, {Qt::CheckStateRole}); + }; + for (auto tool = m_tools.cbegin(); tool != m_tools.cend(); ++tool) { + if (selectPlatformTool && (*tool)->type() == AndroidSdkPackage::PlatformToolsPackage) { + // Select Platform tools + addTool(tool); + selectPlatformTool = false; + } + if (selectBuildTools && (*tool)->type() == AndroidSdkPackage::BuildToolsPackage) { + // Select build tools + addTool(tool); + selectBuildTools = false; + } + if (!selectPlatformTool && !selectBuildTools) + break; + } + + // Select SDK platform + if (m_sdkManager->installedSdkPlatforms().isEmpty() && !m_sdkPlatforms.isEmpty()) { + auto i = index(0, 0, index(1,0)); + m_changeState << m_sdkPlatforms.at(0); + emit dataChanged(i , i, {Qt::CheckStateRole}); + } +} + + +QList AndroidSdkModel::userSelection() const +{ + return m_changeState.toList(); +} + +void AndroidSdkModel::resetSelection() +{ + beginResetModel(); + m_changeState.clear(); + endResetModel(); +} + +void AndroidSdkModel::clearContainers() +{ + m_sdkPlatforms.clear(); + m_tools.clear(); + m_changeState.clear(); +} + +void AndroidSdkModel::refreshData() +{ + clearContainers(); + for (AndroidSdkPackage *p : m_sdkManager->allSdkPackages()) { + if (p->type() == AndroidSdkPackage::SdkPlatformPackage) + m_sdkPlatforms << static_cast(p); + else + m_tools << p; + } + Utils::sort(m_sdkPlatforms, [](const SdkPlatform *p1, const SdkPlatform *p2) { + return p1->apiLevel() > p2->apiLevel(); + }); + + Utils::sort(m_tools, [](const AndroidSdkPackage *p1, const AndroidSdkPackage *p2) { + if (p1->state() == p2->state()) { + if (p1->type() == p2->type()) + return p1->revision() > p2->revision(); + else + return p1->type() > p2->type(); + } else { + return p1->state() < p2->state(); + } + }); +} + +} // namespace Internal +} // namespace Android diff --git a/src/plugins/android/androidsdkmodel.h b/src/plugins/android/androidsdkmodel.h new file mode 100644 index 00000000000..8c36b442efe --- /dev/null +++ b/src/plugins/android/androidsdkmodel.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 "androidconfigurations.h" + +#include + +#include + +namespace Android { +namespace Internal { + +class AndroidSdkManager; + +class AndroidSdkModel : public QAbstractItemModel +{ + Q_OBJECT +public: + enum PackageColumn { + packageNameColumn = 0, + apiLevelColumn, + packageRevisionColumn, + operationColumn + }; + + enum ExtraRoles { + PackageTypeRole = Qt::UserRole + 1, + PackageStateRole + }; + + explicit AndroidSdkModel(const AndroidConfig &config, AndroidSdkManager *sdkManager, + QObject *parent = 0); + + // QAbstractItemModel overrides. + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + + void selectMissingEssentials(); + QList userSelection() const; + void resetSelection(); + +private: + void clearContainers(); + void refreshData(); + +private: + const AndroidConfig &m_config; + AndroidSdkManager *m_sdkManager; + QList m_sdkPlatforms; + QList m_tools; + QSet m_changeState; +}; + +} // namespace Internal +} // namespace Android diff --git a/src/plugins/android/androidsdkpackage.cpp b/src/plugins/android/androidsdkpackage.cpp new file mode 100644 index 00000000000..b7d15105854 --- /dev/null +++ b/src/plugins/android/androidsdkpackage.cpp @@ -0,0 +1,256 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 "androidsdkpackage.h" + +#include "utils/algorithm.h" + +namespace Android { + +AndroidSdkPackage::AndroidSdkPackage(QVersionNumber version, QString sdkStylePathStr, + QObject *parent) : + QObject(parent), + m_revision(version), + m_sdkStylePath(sdkStylePathStr) +{ + +} + +bool AndroidSdkPackage::operator <(const AndroidSdkPackage &other) const +{ + if (typeid(*this) != typeid(other)) + return type() < other.type(); + return displayText() < other.displayText(); +} + +QString AndroidSdkPackage::displayText() const +{ + return m_displayText; +} + +QString AndroidSdkPackage::descriptionText() const +{ + return m_descriptionText; +} + +const QVersionNumber &AndroidSdkPackage::revision() const +{ + return m_revision; +} + +AndroidSdkPackage::PackageState AndroidSdkPackage::state() const +{ + return m_state; +} + +const QString &AndroidSdkPackage::sdkStylePath() const +{ + return m_sdkStylePath; +} + +const Utils::FileName &AndroidSdkPackage::installedLocation() const +{ + return m_installedLocation; +} + +void AndroidSdkPackage::setDisplayText(const QString &str) +{ + m_displayText = str; +} + +void AndroidSdkPackage::setDescriptionText(const QString &str) +{ + m_descriptionText = str; +} + +void AndroidSdkPackage::setState(AndroidSdkPackage::PackageState state) +{ + m_state = state; +} + +void AndroidSdkPackage::setInstalledLocation(const Utils::FileName &path) +{ + m_installedLocation = path; + if (m_installedLocation.exists()) + updatePackageDetails(); +} + +void AndroidSdkPackage::updatePackageDetails() +{ + +} + +SystemImage::SystemImage(QVersionNumber version, QString sdkStylePathStr, QString abi, + SdkPlatform *platform): + AndroidSdkPackage(version, sdkStylePathStr, platform), + m_platform(platform), + m_abiName(abi) +{ +} + +bool SystemImage::isValid() const +{ + return m_platform && m_platform->isValid(); +} + +AndroidSdkPackage::PackageType SystemImage::type() const +{ + return SystemImagePackage; +} + +const QString &SystemImage::abiName() const +{ + return m_abiName; +} + +const SdkPlatform *SystemImage::platform() const +{ + return m_platform.data(); +} + +void SystemImage::setPlatform(SdkPlatform *platform) +{ + m_platform = platform; +} + +SdkPlatform::SdkPlatform(QVersionNumber version, QString sdkStylePathStr, int api, QObject *parent) : + AndroidSdkPackage(version, sdkStylePathStr, parent), + m_apiLevel(api) +{ + setDisplayText(QString("android-%1") + .arg(m_apiLevel != -1 ? QString::number(m_apiLevel) : "Unknown")); +} + +SdkPlatform::~SdkPlatform() +{ + for (SystemImage *image : m_systemImages) + delete image; + m_systemImages.clear(); +} + +bool SdkPlatform::isValid() const +{ + return m_apiLevel != -1; +} + +AndroidSdkPackage::PackageType SdkPlatform::type() const +{ + return SdkPlatformPackage; +} + +bool SdkPlatform::operator <(const AndroidSdkPackage &other) const +{ + if (typeid(*this) != typeid(other)) + return AndroidSdkPackage::operator <(other); + + const SdkPlatform &platform = static_cast(other); + if (platform.m_apiLevel == m_apiLevel) + return AndroidSdkPackage::operator <(other); + + return platform.m_apiLevel < m_apiLevel; +} + +int SdkPlatform::apiLevel() const +{ + return m_apiLevel; +} + +QVersionNumber SdkPlatform::version() const +{ + return m_version; +} + +void SdkPlatform::addSystemImage(SystemImage *image) +{ + // Ordered insert. Installed images on top with lexical comparison of the display name. + auto itr = m_systemImages.begin(); + while (itr != m_systemImages.end()) { + SystemImage *currentImage = *itr; + if (currentImage->state() == image->state()) { + if (currentImage->displayText() > image->displayText()) + break; + } else if (currentImage->state() > image->state()) { + break; + } + ++itr; + } + m_systemImages.insert(itr, image); + image->setPlatform(this); +} + +SystemImageList SdkPlatform::systemImages(PackageState state) const +{ + return Utils::filtered(m_systemImages, [state](const SystemImage *image) { + return image->state() & state; + }); +} + +BuildTools::BuildTools(QVersionNumber revision, QString sdkStylePathStr, QObject *parent): + AndroidSdkPackage(revision, sdkStylePathStr, parent) +{ +} + +bool BuildTools::isValid() const +{ + return true; +} + +AndroidSdkPackage::PackageType BuildTools::type() const +{ + return AndroidSdkPackage::BuildToolsPackage; +} + +SdkTools::SdkTools(QVersionNumber revision, QString sdkStylePathStr, QObject *parent): + AndroidSdkPackage(revision, sdkStylePathStr, parent) +{ + +} + +bool SdkTools::isValid() const +{ + return true; +} + +AndroidSdkPackage::PackageType SdkTools::type() const +{ + return AndroidSdkPackage::SdkToolsPackage; +} + +PlatformTools::PlatformTools(QVersionNumber revision, QString sdkStylePathStr, QObject *parent): + AndroidSdkPackage(revision, sdkStylePathStr, parent) +{ + +} + +bool PlatformTools::isValid() const +{ + return true; +} + +AndroidSdkPackage::PackageType PlatformTools::type() const +{ + return AndroidSdkPackage::PlatformToolsPackage; +} + +} // namespace Android diff --git a/src/plugins/android/androidsdkpackage.h b/src/plugins/android/androidsdkpackage.h new file mode 100644 index 00000000000..486c604b604 --- /dev/null +++ b/src/plugins/android/androidsdkpackage.h @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 "utils/fileutils.h" + +#include +#include +#include +#include + +#pragma once + +namespace Android { + +namespace Internal { + class SdkManagerOutputParser; + class AndroidToolOutputParser; +} +class SdkPlatform; +class SystemImage; + +class AndroidSdkPackage : public QObject +{ + Q_OBJECT + +public: + enum PackageType { + UnknownPackage = 1 << 0, + SdkToolsPackage = 1 << 1, + BuildToolsPackage = 1 << 2, + PlatformToolsPackage = 1 << 3, + SdkPlatformPackage = 1 << 4, + SystemImagePackage = 1 << 5, + AnyValidType = SdkToolsPackage | BuildToolsPackage | PlatformToolsPackage | + SdkPlatformPackage | SystemImagePackage + }; + + enum PackageState { + Unknown = 1 << 0, + Installed = 1 << 1, + Available = 1 << 2, + AnyValidState = Installed | Available + }; + + AndroidSdkPackage(QVersionNumber revision, QString sdkStylePathStr, QObject *parent = nullptr); + virtual ~AndroidSdkPackage() { } + + virtual bool isValid() const = 0; + virtual PackageType type() const = 0; + virtual bool operator <(const AndroidSdkPackage &other) const; + + QString displayText() const; + QString descriptionText() const; + const QVersionNumber &revision() const; + PackageState state() const; + const QString &sdkStylePath() const; + const Utils::FileName &installedLocation() const; + +protected: + void setDisplayText(const QString &str); + void setDescriptionText(const QString &str); + void setState(PackageState state); + void setInstalledLocation(const Utils::FileName &path); + + virtual void updatePackageDetails(); + +private: + QString m_displayText; + QString m_descriptionText; + QVersionNumber m_revision; + PackageState m_state = PackageState::Unknown; + QString m_sdkStylePath; + Utils::FileName m_installedLocation; + + friend class Internal::SdkManagerOutputParser; + friend class Internal::AndroidToolOutputParser; +}; +using AndroidSdkPackageList = QList; + +class SystemImage : public AndroidSdkPackage +{ + Q_OBJECT +public: + SystemImage(QVersionNumber revision, QString sdkStylePathStr, QString abi, + SdkPlatform *platform = nullptr); + +// AndroidSdkPackage Overrides + bool isValid() const override; + PackageType type() const override; + + const QString &abiName() const; + const SdkPlatform *platform() const; + void setPlatform(SdkPlatform *platform); + +private: + QPointer m_platform; + QString m_abiName; +}; +using SystemImageList = QList; + + +class SdkPlatform : public AndroidSdkPackage +{ + Q_OBJECT +public: + SdkPlatform(QVersionNumber revision, QString sdkStylePathStr, int api, + QObject *parent = nullptr); + + ~SdkPlatform(); + +// AndroidSdkPackage Overrides + bool isValid() const override; + PackageType type() const override; + bool operator <(const AndroidSdkPackage &other) const override; + + int apiLevel() const; + QVersionNumber version() const; + void addSystemImage(SystemImage *image); + SystemImageList systemImages(AndroidSdkPackage::PackageState state + = AndroidSdkPackage::Installed) const; + +private: + SystemImageList m_systemImages; + int m_apiLevel = -1; + QVersionNumber m_version; +}; +using SdkPlatformList = QList; + +class BuildTools : public AndroidSdkPackage +{ +public: + BuildTools(QVersionNumber revision, QString sdkStylePathStr, QObject *parent = nullptr); + +// AndroidSdkPackage Overrides +public: + bool isValid() const override; + PackageType type() const override; +}; + +class PlatformTools : public AndroidSdkPackage +{ +public: + PlatformTools(QVersionNumber revision, QString sdkStylePathStr, QObject *parent = nullptr); + +// AndroidSdkPackage Overrides +public: + bool isValid() const override; + PackageType type() const override; +}; + +class SdkTools : public AndroidSdkPackage +{ +public: + SdkTools(QVersionNumber revision, QString sdkStylePathStr, QObject *parent = nullptr); + +// AndroidSdkPackage Overrides +public: + bool isValid() const override; + PackageType type() const override; +}; +} // namespace Android + + diff --git a/src/plugins/android/androidsettingswidget.cpp b/src/plugins/android/androidsettingswidget.cpp index d5c49623b1b..a198cc3cebd 100644 --- a/src/plugins/android/androidsettingswidget.cpp +++ b/src/plugins/android/androidsettingswidget.cpp @@ -31,8 +31,13 @@ #include "androidconstants.h" #include "androidtoolchain.h" #include "androidavdmanager.h" +#include "androidsdkmanager.h" +#include "avddialog.h" +#include "androidsdkmanagerwidget.h" +#include #include +#include #include #include #include @@ -59,6 +64,100 @@ namespace Android { namespace Internal { +namespace { +enum JavaValidation { + JavaPathExistsRow, + JavaJdkValidRow +}; + +enum AndroidValidation { + SdkPathExistsRow, + SdkToolsInstalledRow, + PlatformToolsInstalledRow, + BuildToolsInstalledRow, + PlatformSdkInstalledRow, + NdkPathExistsRow, + NdkDirStructureRow, + NdkinstallDirOkRow +}; +} + +class SummaryWidget : public QWidget +{ + class RowData { + public: + QLabel *m_iconLabel = nullptr; + Utils::ElidingLabel *m_textLabel = nullptr; + bool m_valid = false; + }; + +public: + SummaryWidget(const QMap &validationPoints, const QString &validText, + const QString &invalidText, Utils::DetailsWidget *detailsWidget) : + QWidget(detailsWidget), + m_validText(validText), + m_invalidText(invalidText), + m_detailsWidget(detailsWidget) + { + QTC_CHECK(m_detailsWidget); + auto layout = new QGridLayout(this); + layout->setMargin(12); + int row = 0; + for (auto itr = validationPoints.cbegin(); itr != validationPoints.cend(); ++itr) { + RowData data; + data.m_iconLabel = new QLabel(this); + layout->addWidget(data.m_iconLabel, row, 0, 1, 1); + data.m_textLabel = new Utils::ElidingLabel(itr.value(), this); + data.m_textLabel->setElideMode(Qt::ElideRight); + data.m_textLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + layout->addWidget(data.m_textLabel, row, 1, 1, 1); + m_validationData[itr.key()] = data; + setPointValid(itr.key(), true); + ++row; + } + } + + void setPointValid(int key, bool valid) + { + if (!m_validationData.contains(key)) + return; + RowData& data = m_validationData[key]; + data.m_valid = valid; + data.m_iconLabel->setPixmap(data.m_valid ? Utils::Icons::OK.pixmap() : + Utils::Icons::BROKEN.pixmap()); + updateUi(); + } + + bool rowsOk(QList keys) const + { + for (auto key : keys) { + if (!m_validationData[key].m_valid) + return false; + } + return true; + } + + bool allRowsOk() const { return rowsOk(m_validationData.keys()); } + void setInfoText(const QString &text) { + m_infoText = text; + updateUi(); + } + +private: + void updateUi() { + bool ok = allRowsOk(); + m_detailsWidget->setIcon(ok ? Utils::Icons::OK.icon() : + Utils::Icons::CRITICAL.icon()); + m_detailsWidget->setSummaryText(ok ? QString("%1 %2").arg(m_validText).arg(m_infoText) + : m_invalidText); + } + QString m_validText; + QString m_invalidText; + QString m_infoText; + Utils::DetailsWidget *m_detailsWidget = nullptr; + QMap m_validationData; +}; + void AvdModel::setAvdList(const AndroidDeviceInfoList &list) { beginResetModel(); @@ -125,14 +224,54 @@ int AvdModel::columnCount(const QModelIndex &/*parent*/) const AndroidSettingsWidget::AndroidSettingsWidget(QWidget *parent) : QWidget(parent), - m_sdkState(NotSet), - m_ndkState(NotSet), - m_javaState(NotSet), m_ui(new Ui_AndroidSettingsWidget), m_androidConfig(AndroidConfigurations::currentConfig()), - m_avdManager(new AndroidAvdManager(m_androidConfig)) + m_avdManager(new AndroidAvdManager(m_androidConfig)), + m_sdkManager(new AndroidSdkManager(m_androidConfig)) { m_ui->setupUi(this); + m_sdkManagerWidget = new AndroidSdkManagerWidget(m_androidConfig, m_sdkManager.get(), + m_ui->sdkManagerTab); + auto sdkMangerLayout = new QVBoxLayout(m_ui->sdkManagerTab); + sdkMangerLayout->setMargin(0); + sdkMangerLayout->addWidget(m_sdkManagerWidget); + connect(m_sdkManagerWidget, &AndroidSdkManagerWidget::updatingSdk, [this]() { + m_ui->SDKLocationPathChooser->setEnabled(false); + // Disable the tab bar to restrict the user moving away from sdk manager tab untill + // operations finish. + m_ui->managerTabWidget->tabBar()->setEnabled(false); + }); + connect(m_sdkManagerWidget, &AndroidSdkManagerWidget::updatingSdkFinished, [this]() { + m_ui->SDKLocationPathChooser->setEnabled(true); + m_ui->managerTabWidget->tabBar()->setEnabled(true); + }); + + QMap javaValidationPoints; + javaValidationPoints[JavaPathExistsRow] = tr("JDK path exists."); + javaValidationPoints[JavaJdkValidRow] = tr("JDK path is a valid JDK root folder."); + auto javaSummary = new SummaryWidget(javaValidationPoints, tr("Java Settings are OK."), + tr("Java settings have errors."), m_ui->javaDetailsWidget); + m_ui->javaDetailsWidget->setWidget(javaSummary); + + QMap androidValidationPoints; + androidValidationPoints[SdkPathExistsRow] = tr("Android SDK path exists."); + androidValidationPoints[SdkToolsInstalledRow] = tr("SDK tools installed."); + androidValidationPoints[PlatformToolsInstalledRow] = tr("Platform tools installed."); + androidValidationPoints[BuildToolsInstalledRow] = tr("Build tools installed."); + androidValidationPoints[PlatformSdkInstalledRow] = tr("Platform SDK installed."); + androidValidationPoints[NdkPathExistsRow] = tr("Android NDK path exists."); + androidValidationPoints[NdkDirStructureRow] = tr("Android NDK directory structure is correct."); + androidValidationPoints[NdkinstallDirOkRow] = tr("Android NDK installed into a path without " + "spaces."); + auto androidSummary = new SummaryWidget(androidValidationPoints, tr("Android settings are OK."), + tr("Android settings have errors."), + m_ui->androidDetailsWidget); + m_ui->androidDetailsWidget->setWidget(androidSummary); + + auto kitsDetailsLabel = new QLabel(m_ui->kitWarningDetails); + kitsDetailsLabel->setWordWrap(true); + m_ui->kitWarningDetails->setWidget(kitsDetailsLabel); + m_ui->kitWarningDetails->setIcon(Utils::Icons::WARNING.icon()); m_ui->SDKLocationPathChooser->setFileName(m_androidConfig.sdkLocation()); m_ui->SDKLocationPathChooser->setPromptDialogTitle(tr("Select Android SDK folder")); @@ -149,29 +288,16 @@ AndroidSettingsWidget::AndroidSettingsWidget(QWidget *parent) m_ui->downloadOpenJDKToolButton->setVisible(!Utils::HostOsInfo::isLinuxHost()); - const QPixmap warningPixmap = Utils::Icons::WARNING.pixmap(); - m_ui->jdkWarningIconLabel->setPixmap(warningPixmap); - m_ui->kitWarningIconLabel->setPixmap(warningPixmap); - - const QPixmap errorPixmap = Utils::Icons::CRITICAL.pixmap(); - m_ui->sdkWarningIconLabel->setPixmap(errorPixmap); - m_ui->ndkWarningIconLabel->setPixmap(errorPixmap); - connect(&m_virtualDevicesWatcher, &QFutureWatcherBase::finished, this, &AndroidSettingsWidget::updateAvds); - - check(All); - applyToUi(All); - connect(&m_futureWatcher, &QFutureWatcherBase::finished, this, &AndroidSettingsWidget::avdAdded); - connect(m_ui->NDKLocationPathChooser, &Utils::PathChooser::rawPathChanged, - this, &AndroidSettingsWidget::ndkLocationEditingFinished); + this, &AndroidSettingsWidget::validateNdk); connect(m_ui->SDKLocationPathChooser, &Utils::PathChooser::rawPathChanged, - this, &AndroidSettingsWidget::sdkLocationEditingFinished); + this, &AndroidSettingsWidget::onSdkPathChanged); connect(m_ui->OpenJDKLocationPathChooser, &Utils::PathChooser::rawPathChanged, - this, &AndroidSettingsWidget::openJDKLocationEditingFinished); + this, &AndroidSettingsWidget::validateJdk); connect(m_ui->AVDAddPushButton, &QAbstractButton::clicked, this, &AndroidSettingsWidget::addAVD); connect(m_ui->AVDRemovePushButton, &QAbstractButton::clicked, @@ -184,7 +310,7 @@ AndroidSettingsWidget::AndroidSettingsWidget(QWidget *parent) this, &AndroidSettingsWidget::avdActivated); connect(m_ui->DataPartitionSizeSpinBox, &QAbstractSpinBox::editingFinished, this, &AndroidSettingsWidget::dataPartitionSizeEditingFinished); - connect(m_ui->manageAVDPushButton, &QAbstractButton::clicked, + connect(m_ui->nativeAvdManagerButton, &QAbstractButton::clicked, this, &AndroidSettingsWidget::manageAVD); connect(m_ui->CreateKitCheckBox, &QAbstractButton::toggled, this, &AndroidSettingsWidget::createKitToggled); @@ -194,152 +320,24 @@ AndroidSettingsWidget::AndroidSettingsWidget(QWidget *parent) this, &AndroidSettingsWidget::openNDKDownloadUrl); connect(m_ui->downloadOpenJDKToolButton, &QAbstractButton::clicked, this, &AndroidSettingsWidget::openOpenJDKDownloadUrl); + // Validate SDK again after any change in SDK packages. + connect(m_sdkManager.get(), &AndroidSdkManager::packageReloadFinished, + this, &AndroidSettingsWidget::validateSdk); + validateJdk(); + validateNdk(); + // Reloading SDK packages is still synchronous. Use zero timer to let settings dialog open + // first. + QTimer::singleShot(0, std::bind(&AndroidSdkManager::reloadPackages, m_sdkManager.get(), false)); } AndroidSettingsWidget::~AndroidSettingsWidget() { + // Deleting m_sdkManagerWidget will cancel all ongoing and pending sdkmanager operations. + delete m_sdkManagerWidget; delete m_ui; m_futureWatcher.waitForFinished(); } -void AndroidSettingsWidget::check(AndroidSettingsWidget::Mode mode) -{ - if (mode & Sdk) { - m_sdkState = verifySdkInstallation(&m_sdkInstallationError) ? Okay : Error; - } - - if (mode & Ndk) { - m_ndkState = Okay; - Utils::FileName platformPath = m_androidConfig.ndkLocation(); - Utils::FileName toolChainPath = m_androidConfig.ndkLocation(); - Utils::FileName sourcesPath = m_androidConfig.ndkLocation(); - if (m_androidConfig.ndkLocation().isEmpty()) { - m_ndkState = NotSet; - } else if (!platformPath.appendPath(QLatin1String("platforms")).exists() - || !toolChainPath.appendPath(QLatin1String("toolchains")).exists() - || !sourcesPath.appendPath(QLatin1String("sources/cxx-stl")).exists()) { - m_ndkState = Error; - m_ndkErrorMessage = tr("\"%1\" does not seem to be an Android NDK top folder.") - .arg(m_androidConfig.ndkLocation().toUserOutput()); - } else if (platformPath.toString().contains(QLatin1Char(' '))) { - m_ndkState = Error; - m_ndkErrorMessage = tr("The Android NDK cannot be installed into a path with spaces."); - } else { - QList compilerPaths - = AndroidToolChainFactory::toolchainPathsForNdk(m_androidConfig.ndkLocation()); - m_ndkCompilerCount = compilerPaths.count(); - - // See if we have qt versions for those toolchains - QSet toolchainsForAbi; - foreach (const AndroidToolChainFactory::AndroidToolChainInformation &ati, compilerPaths) { - if (ati.language == Core::Id(ProjectExplorer::Constants::CXX_LANGUAGE_ID)) - toolchainsForAbi.insert(ati.abi); - } - - const QList androidQts - = QtSupport::QtVersionManager::versions([](const QtSupport::BaseQtVersion *v) { - return v->type() == QLatin1String(Constants::ANDROIDQT) && !v->qtAbis().isEmpty(); - }); - QSet qtVersionsForAbi; - foreach (QtSupport::BaseQtVersion *qtVersion, androidQts) - qtVersionsForAbi.insert(qtVersion->qtAbis().first()); - - QSet missingQtArchs = toolchainsForAbi.subtract(qtVersionsForAbi); - if (missingQtArchs.isEmpty()) { - m_ndkMissingQtArchs.clear(); - } else { - if (missingQtArchs.count() == 1) { - m_ndkMissingQtArchs = tr("Qt version for architecture %1 is missing.\n" - "To add the Qt version, select Options > Build & Run > Qt Versions.") - .arg((*missingQtArchs.constBegin()).toString()); - } else { - m_ndkMissingQtArchs = tr("Qt versions for %n architectures are missing.\n" - "To add the Qt versions, select Options > Build & Run > Qt Versions.", - nullptr, missingQtArchs.size()); - } - } - } - } - - if (mode & Java) { - m_javaState = Okay; - if (m_androidConfig.openJDKLocation().isEmpty()) { - m_javaState = NotSet; - } else { - Utils::FileName bin = m_androidConfig.openJDKLocation(); - bin.appendPath(QLatin1String("bin/javac" QTC_HOST_EXE_SUFFIX)); - if (!m_androidConfig.openJDKLocation().exists() || !bin.exists()) - m_javaState = Error; - } - } -} - -void AndroidSettingsWidget::applyToUi(AndroidSettingsWidget::Mode mode) -{ - if (mode & Sdk) { - if (m_sdkState == Error) { - m_ui->sdkWarningIconLabel->setVisible(true); - m_ui->sdkWarningLabel->setVisible(true); - m_ui->sdkWarningLabel->setText(m_sdkInstallationError); - } else { - m_ui->sdkWarningIconLabel->setVisible(false); - m_ui->sdkWarningLabel->setVisible(false); - } - } - - if (mode & Ndk) { - if (m_ndkState == NotSet) { - m_ui->ndkWarningIconLabel->setVisible(false); - m_ui->toolchainFoundLabel->setVisible(false); - m_ui->kitWarningIconLabel->setVisible(false); - m_ui->kitWarningLabel->setVisible(false); - } else if (m_ndkState == Error) { - m_ui->toolchainFoundLabel->setText(m_ndkErrorMessage); - m_ui->toolchainFoundLabel->setVisible(true); - m_ui->ndkWarningIconLabel->setVisible(true); - m_ui->kitWarningIconLabel->setVisible(false); - m_ui->kitWarningLabel->setVisible(false); - } else { - if (m_ndkCompilerCount > 0) { - m_ui->ndkWarningIconLabel->setVisible(false); - m_ui->toolchainFoundLabel->setText(tr("Found %n toolchains for this NDK.", 0, m_ndkCompilerCount)); - m_ui->toolchainFoundLabel->setVisible(true); - } else { - m_ui->ndkWarningIconLabel->setVisible(false); - m_ui->toolchainFoundLabel->setVisible(false); - } - - if (m_ndkMissingQtArchs.isEmpty()) { - m_ui->kitWarningIconLabel->setVisible(false); - m_ui->kitWarningLabel->setVisible(false); - } else { - m_ui->kitWarningIconLabel->setVisible(true); - m_ui->kitWarningLabel->setVisible(true); - m_ui->kitWarningLabel->setText(m_ndkMissingQtArchs); - } - } - } - - if (mode & Java) { - Utils::FileName location = m_androidConfig.openJDKLocation(); - bool error = m_javaState == Error; - m_ui->jdkWarningIconLabel->setVisible(error); - m_ui->jdkWarningLabel->setVisible(error); - if (error) - m_ui->jdkWarningLabel->setText(tr("\"%1\" does not seem to be a JDK folder.").arg(location.toUserOutput())); - } - - if (mode & Sdk || mode & Java) { - if (m_sdkState == Okay && m_javaState == Okay) { - m_ui->AVDManagerFrame->setEnabled(true); - } else { - m_ui->AVDManagerFrame->setEnabled(false); - } - - startUpdateAvd(); - } -} - void AndroidSettingsWidget::disableAvdControls() { m_ui->AVDAddPushButton->setEnabled(false); @@ -371,75 +369,89 @@ void AndroidSettingsWidget::updateAvds() enableAvdControls(); } -bool AndroidSettingsWidget::verifySdkInstallation(QString *errorDetails) const -{ - if (m_androidConfig.sdkLocation().isEmpty()) { - if (errorDetails) - *errorDetails = tr("Android SDK path not set."); - return false; - } - - if (!m_androidConfig.sdkLocation().exists()) { - if (errorDetails) - *errorDetails = tr("Android SDK path does not exist."); - return false; - } - - if (m_androidConfig.sdkToolsVersion().isNull()) { - if (errorDetails) - *errorDetails = tr("The SDK path does not seem to be a valid Android SDK top folder."); - return false; - } - - QStringList missingComponents; - if (!m_androidConfig.adbToolPath().exists()) - missingComponents << "Platform Tools"; - - if (m_androidConfig.buildToolsVersion().isNull()) - missingComponents << "Build Tools"; - - if (m_androidConfig.sdkTargets().isEmpty()) - missingComponents << "Platform SDK"; - - if (!missingComponents.isEmpty() && errorDetails) { - *errorDetails = tr("Android SDK components missing (%1).\nUse Android SDK Manager to " - "manage SDK components.").arg(missingComponents.join(", ")); - } - - return missingComponents.isEmpty(); -} - void AndroidSettingsWidget::saveSettings() { - sdkLocationEditingFinished(); - ndkLocationEditingFinished(); - openJDKLocationEditingFinished(); - dataPartitionSizeEditingFinished(); AndroidConfigurations::setConfig(m_androidConfig); } -void AndroidSettingsWidget::sdkLocationEditingFinished() +void AndroidSettingsWidget::validateJdk() { - m_androidConfig.setSdkLocation(Utils::FileName::fromUserInput(m_ui->SDKLocationPathChooser->rawPath())); + auto javaPath = Utils::FileName::fromUserInput(m_ui->OpenJDKLocationPathChooser->rawPath()); + m_androidConfig.setOpenJDKLocation(javaPath); + bool jdkPathExists = m_androidConfig.openJDKLocation().exists(); + auto summaryWidget = static_cast(m_ui->javaDetailsWidget->widget()); + summaryWidget->setPointValid(JavaPathExistsRow, jdkPathExists); - check(Sdk); - applyToUi(Sdk); + Utils::FileName bin = m_androidConfig.openJDKLocation(); + bin.appendPath(QLatin1String("bin/javac" QTC_HOST_EXE_SUFFIX)); + summaryWidget->setPointValid(JavaJdkValidRow, jdkPathExists && bin.exists()); + updateUI(); } -void AndroidSettingsWidget::ndkLocationEditingFinished() +void AndroidSettingsWidget::validateNdk() { - m_androidConfig.setNdkLocation(Utils::FileName::fromUserInput(m_ui->NDKLocationPathChooser->rawPath())); + auto ndkPath = Utils::FileName::fromUserInput(m_ui->NDKLocationPathChooser->rawPath()); + m_androidConfig.setNdkLocation(ndkPath); - check(Ndk); - applyToUi(Ndk); + auto summaryWidget = static_cast(m_ui->androidDetailsWidget->widget()); + summaryWidget->setPointValid(NdkPathExistsRow, m_androidConfig.ndkLocation().exists()); + + Utils::FileName ndkPlatformsDir(ndkPath); + ndkPlatformsDir.appendPath("platforms"); + Utils::FileName ndkToolChainsDir(ndkPath); + ndkToolChainsDir.appendPath("toolchains"); + Utils::FileName ndkSourcesDir(ndkPath); + ndkSourcesDir.appendPath("sources/cxx-stl"); + summaryWidget->setPointValid(NdkDirStructureRow, + ndkPlatformsDir.exists() + && ndkToolChainsDir.exists() + && ndkSourcesDir.exists()); + summaryWidget->setPointValid(NdkinstallDirOkRow, + ndkPlatformsDir.exists() && + !ndkPlatformsDir.toString().contains(' ')); + updateUI(); } -void AndroidSettingsWidget::openJDKLocationEditingFinished() +void AndroidSettingsWidget::onSdkPathChanged() { - m_androidConfig.setOpenJDKLocation(Utils::FileName::fromUserInput(m_ui->OpenJDKLocationPathChooser->rawPath())); + auto sdkPath = Utils::FileName::fromUserInput(m_ui->SDKLocationPathChooser->rawPath()); + m_androidConfig.setSdkLocation(sdkPath); + // Package reload will trigger validateSdk. + m_sdkManager->reloadPackages(); +} - check(Java); - applyToUi(Java); +void AndroidSettingsWidget::validateSdk() +{ + auto summaryWidget = static_cast(m_ui->androidDetailsWidget->widget()); + summaryWidget->setPointValid(SdkPathExistsRow, m_androidConfig.sdkLocation().exists()); + summaryWidget->setPointValid(SdkToolsInstalledRow, + !m_androidConfig.sdkToolsVersion().isNull()); + summaryWidget->setPointValid(PlatformToolsInstalledRow, + m_androidConfig.adbToolPath().exists()); + summaryWidget->setPointValid(BuildToolsInstalledRow, + !m_androidConfig.buildToolsVersion().isNull()); + + // installedSdkPlatforms should not trigger a package reload as validate SDK is only called + // after AndroidSdkManager::packageReloadFinished. + summaryWidget->setPointValid(PlatformSdkInstalledRow, + !m_sdkManager->installedSdkPlatforms().isEmpty()); + updateUI(); + bool sdkToolsOk = summaryWidget->rowsOk({SdkPathExistsRow, SdkToolsInstalledRow}); + bool componentsOk = summaryWidget->rowsOk({PlatformToolsInstalledRow, + BuildToolsInstalledRow, + PlatformSdkInstalledRow}); + + if (sdkToolsOk && !componentsOk && !m_androidConfig.useNativeUiTools()) { + // Ask user to install essential SDK components. Works only for sdk tools version >= 26.0.0 + QString message = tr("Android SDK installation is missing necessary packages. Do you " + "want to install the missing packages?"); + auto userInput = QMessageBox::information(this, tr("Missing Android SDK packages"), + message, QMessageBox::Yes | QMessageBox::No); + if (userInput == QMessageBox::Yes) { + m_ui->managerTabWidget->setCurrentWidget(m_ui->sdkManagerTab); + m_sdkManagerWidget->installEssentials(); + } + } } void AndroidSettingsWidget::openSDKDownloadUrl() @@ -460,9 +472,9 @@ void AndroidSettingsWidget::openOpenJDKDownloadUrl() void AndroidSettingsWidget::addAVD() { disableAvdControls(); - AndroidConfig::CreateAvdInfo info = m_androidConfig.gatherCreateAVDInfo(this); + CreateAvdInfo info = AvdDialog::gatherCreateAVDInfo(this, m_sdkManager.get()); - if (!info.target.isValid()) { + if (!info.isValid()) { enableAvdControls(); return; } @@ -472,7 +484,7 @@ void AndroidSettingsWidget::addAVD() void AndroidSettingsWidget::avdAdded() { - AndroidConfig::CreateAvdInfo info = m_futureWatcher.result(); + CreateAvdInfo info = m_futureWatcher.result(); if (!info.error.isEmpty()) { enableAvdControls(); QMessageBox::critical(this, QApplication::translate("AndroidConfig", "Error Creating AVD"), info.error); @@ -520,9 +532,76 @@ void AndroidSettingsWidget::createKitToggled() m_androidConfig.setAutomaticKitCreation(m_ui->CreateKitCheckBox->isChecked()); } +void AndroidSettingsWidget::checkMissingQtVersion() +{ + auto summaryWidget = static_cast(m_ui->androidDetailsWidget->widget()); + if (!summaryWidget->allRowsOk()) { + m_ui->kitWarningDetails->setVisible(false); + m_ui->kitWarningDetails->setState(Utils::DetailsWidget::Collapsed); + return; + } + + QList compilerPaths + = AndroidToolChainFactory::toolchainPathsForNdk(m_androidConfig.ndkLocation()); + + // See if we have qt versions for those toolchains + QSet toolchainsForAbi; + foreach (const AndroidToolChainFactory::AndroidToolChainInformation &ati, compilerPaths) { + if (ati.language == Core::Id(ProjectExplorer::Constants::CXX_LANGUAGE_ID)) + toolchainsForAbi.insert(ati.abi); + } + + const QList androidQts + = QtSupport::QtVersionManager::versions([](const QtSupport::BaseQtVersion *v) { + return v->type() == QLatin1String(Constants::ANDROIDQT) && !v->qtAbis().isEmpty(); + }); + QSet qtVersionsForAbi; + foreach (QtSupport::BaseQtVersion *qtVersion, androidQts) + qtVersionsForAbi.insert(qtVersion->qtAbis().first()); + + QSet missingQtArchs = toolchainsForAbi.subtract(qtVersionsForAbi); + bool isArchMissing = !missingQtArchs.isEmpty(); + m_ui->kitWarningDetails->setVisible(isArchMissing); + if (isArchMissing) { + m_ui->kitWarningDetails->setSummaryText(tr("Cannot create kits for all architectures.")); + QLabel *detailsLabel = static_cast(m_ui->kitWarningDetails->widget()); + QStringList archNames; + for (auto abi : missingQtArchs) + archNames << abi.toString(); + detailsLabel->setText(tr("Qt versions are missing for the following architectures:\n%1" + "\n\nTo add the Qt version, select Options > Build & Run > Qt" + " Versions.").arg(archNames.join(", "))); + } +} + +void AndroidSettingsWidget::updateUI() +{ + auto javaSummaryWidget = static_cast(m_ui->javaDetailsWidget->widget()); + auto androidSummaryWidget = static_cast(m_ui->androidDetailsWidget->widget()); + bool javaSetupOk = javaSummaryWidget->allRowsOk(); + bool sdkToolsOk = androidSummaryWidget->rowsOk({SdkPathExistsRow, SdkToolsInstalledRow}); + bool androidSetupOk = androidSummaryWidget->allRowsOk(); + + m_ui->avdManagerTab->setEnabled(javaSetupOk && androidSetupOk); + m_ui->sdkManagerTab->setEnabled(sdkToolsOk); + m_sdkManagerWidget->setSdkManagerControlsEnabled(!m_androidConfig.useNativeUiTools()); + + auto infoText = tr("(SDK Version: %1, NDK Version: %2)") + .arg(m_androidConfig.sdkToolsVersion().toString()) + .arg(m_androidConfig.ndkVersion().toString()); + androidSummaryWidget->setInfoText(androidSetupOk ? infoText : ""); + + m_ui->javaDetailsWidget->setState(javaSetupOk ? Utils::DetailsWidget::Collapsed : + Utils::DetailsWidget::Expanded); + m_ui->androidDetailsWidget->setState(androidSetupOk ? Utils::DetailsWidget::Collapsed : + Utils::DetailsWidget::Expanded); + startUpdateAvd(); + checkMissingQtVersion(); +} + void AndroidSettingsWidget::manageAVD() { - if (m_avdManager->avdManagerUiToolAvailable()) { + if (m_androidConfig.useNativeUiTools()) { m_avdManager->launchAvdManagerUiTool(); } else { QMessageBox::warning(this, tr("AVD Manager Not Available"), diff --git a/src/plugins/android/androidsettingswidget.h b/src/plugins/android/androidsettingswidget.h index 32127543657..8e22f511fa2 100644 --- a/src/plugins/android/androidsettingswidget.h +++ b/src/plugins/android/androidsettingswidget.h @@ -42,6 +42,8 @@ QT_END_NAMESPACE namespace Android { namespace Internal { +class AndroidSdkManagerWidget; + class AndroidAvdManager; class AvdModel: public QAbstractTableModel @@ -73,9 +75,10 @@ public: void saveSettings(); private: - void sdkLocationEditingFinished(); - void ndkLocationEditingFinished(); - void openJDKLocationEditingFinished(); + void validateJdk(); + void validateNdk(); + void onSdkPathChanged(); + void validateSdk(); void openSDKDownloadUrl(); void openNDKDownloadUrl(); void openOpenJDKDownloadUrl(); @@ -88,34 +91,25 @@ private: void manageAVD(); void createKitToggled(); + void checkMissingQtVersion(); + void updateUI(); void updateAvds(); private: - enum Mode { Sdk = 1, Ndk = 2, Java = 4, All = Sdk | Ndk | Java }; - enum State { NotSet = 0, Okay = 1, Error = 2 }; - bool verifySdkInstallation(QString *errorDetails = nullptr) const; - void check(Mode mode); - void applyToUi(Mode mode); void startUpdateAvd(); void enableAvdControls(); void disableAvdControls(); - State m_sdkState; - QString m_sdkInstallationError; - State m_ndkState; - QString m_ndkErrorMessage; - int m_ndkCompilerCount; - QString m_ndkMissingQtArchs; - State m_javaState; - Ui_AndroidSettingsWidget *m_ui; + AndroidSdkManagerWidget *m_sdkManagerWidget = nullptr; AndroidConfig m_androidConfig; AvdModel m_AVDModel; - QFutureWatcher m_futureWatcher; + QFutureWatcher m_futureWatcher; QFutureWatcher m_virtualDevicesWatcher; QString m_lastAddedAvd; std::unique_ptr m_avdManager; + std::unique_ptr m_sdkManager; }; } // namespace Internal diff --git a/src/plugins/android/androidsettingswidget.ui b/src/plugins/android/androidsettingswidget.ui index 0d8bcb774d5..4f164010153 100644 --- a/src/plugins/android/androidsettingswidget.ui +++ b/src/plugins/android/androidsettingswidget.ui @@ -6,382 +6,348 @@ 0 0 - 843 - 625 + 1131 + 826 Android Configuration - - - - - QFrame::StyledPanel + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + 0 + 0 + - - QFrame::Raised + + Java Settings - - - - - false + + + + + + 0 + 0 + + + + + + + + Download JDK + + + + :/android/images/download.png:/android/images/download.png + + + + + + + + 0 + 0 + - Start... + JDK location: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - AVD Manager - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - System/data partition size: - - - - - - - Mb - - - 99999 - - - 1024 - - - - - - - Start AVD Manager... - - - - - - - - - false - - - Remove - - - - - - - Add... - - - - - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - Qt::ElideMiddle - - - false - - - - - - - Qt::Vertical - - + + + - 20 - 129 + 0 + 0 - + - - - - - - - - - - - - - - - - 0 - 0 - - - - - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - - - Download Android NDK - - - - :/android/images/download.png:/android/images/download.png + + + + Android Settings + + + + + + + + + 0 + 0 + + + + + + + + Download Android NDK + + + + :/android/images/download.png:/android/images/download.png + + + + + + + + 0 + 0 + + + + Android NDK location: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + Android SDK location: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Download Android SDK + + + + :/android/images/download.png:/android/images/download.png + + + + + + + - - + + - + 0 0 - Android SDK location: + Automatically create kits for Android tool chains - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + true - - - - Download JDK - - - - :/android/images/download.png:/android/images/download.png - - + + - - - - Download Android SDK - - - - :/android/images/download.png:/android/images/download.png - - - - - - - 2 - - - - - - 0 - 0 - - - - - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - - - 2 - - + + + 0 - - - - - 0 - 0 - + + + AVD Manager + + + + 0 - - Automatically create kits for Android tool chains + + 0 - - true + + 0 - - - - - - - - - 0 - 0 - - - - Android NDK location: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - 2 - - - - - - 0 - 0 - + + 0 - - - - - - - - - - 0 - 0 - - - - - - - - - - - - - - - - 0 - 0 - - - - - - - - - - - - 0 - 0 - - - - - - - true - - - - - - - - - - 0 - 0 - - - - JDK location: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + + + + + 0 + 0 + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + Qt::ElideMiddle + + + false + + + + + + + + + System/data partition size: + + + + + + + + 0 + 0 + + + + Mb + + + 99999 + + + 1024 + + + + + + + + 0 + 0 + + + + Add... + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 0 + 8 + + + + + + + + false + + + + 0 + 0 + + + + Remove + + + + + + + false + + + + 0 + 0 + + + + Start... + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Native AVD Manager... + + + + + + + + + + SDK Manager + + @@ -393,6 +359,12 @@
utils/pathchooser.h
1 + + Utils::DetailsWidget + QWidget +
utils/detailswidget.h
+ 1 +
diff --git a/src/plugins/android/androidtoolmanager.cpp b/src/plugins/android/androidtoolmanager.cpp index 39fa2a2c426..fc214ab1139 100644 --- a/src/plugins/android/androidtoolmanager.cpp +++ b/src/plugins/android/androidtoolmanager.cpp @@ -47,7 +47,7 @@ class AndroidToolOutputParser { public: void parseTargetListing(const QString &output, const FileName &sdkLocation, - SdkPlatformList *platformList); + SdkPlatformList &platformList); QList m_installedPlatforms; }; @@ -104,7 +104,7 @@ SdkPlatformList AndroidToolManager::availableSdkPlatforms(bool *ok) const QString targetListing; if (androidToolCommand(m_config.androidToolPath(), QStringList({"list", "target"}), androidToolEnvironment(), &targetListing)) { - m_parser->parseTargetListing(targetListing, m_config.sdkLocation(), &list); + m_parser->parseTargetListing(targetListing, m_config.sdkLocation(), list); success = true; } else { qCDebug(androidToolLog) << "Android tool target listing failed"; @@ -121,8 +121,7 @@ void AndroidToolManager::launchAvdManager() const QProcess::startDetached(m_config.androidToolPath().toString(), QStringList("avd")); } -QFuture -AndroidToolManager::createAvd(AndroidConfig::CreateAvdInfo info) const +QFuture AndroidToolManager::createAvd(CreateAvdInfo info) const { return Utils::runAsync(&AndroidToolManager::createAvdImpl, info, m_config.androidToolPath(), androidToolEnvironment()); @@ -159,15 +158,14 @@ Environment AndroidToolManager::androidToolEnvironment() const return env; } -AndroidConfig::CreateAvdInfo AndroidToolManager::createAvdImpl(AndroidConfig::CreateAvdInfo info, - FileName androidToolPath, - Environment env) +CreateAvdInfo AndroidToolManager::createAvdImpl(CreateAvdInfo info, FileName androidToolPath, + Environment env) { QProcess proc; proc.setProcessEnvironment(env.toProcessEnvironment()); QStringList arguments; arguments << QLatin1String("create") << QLatin1String("avd") - << QLatin1String("-t") << AndroidConfig::apiLevelNameFor(info.target) + << QLatin1String("-t") << AndroidConfig::apiLevelNameFor(info.sdkPlatform) << QLatin1String("-n") << info.name << QLatin1String("-b") << info.abi; if (info.sdcardSize > 0) @@ -293,24 +291,36 @@ AndroidDeviceInfoList AndroidToolManager::androidVirtualDevices(const Utils::Fil void AndroidToolOutputParser::parseTargetListing(const QString &output, const Utils::FileName &sdkLocation, - SdkPlatformList *platformList) + SdkPlatformList &platformList) { - if (!platformList) - return; - - auto addSystemImage = [](const QStringList& abiList, SdkPlatform &platform) { + auto addSystemImage = [](const QStringList& abiList, SdkPlatform *platform) { + QTC_ASSERT(platform, return); foreach (auto imageAbi, abiList) { - SystemImage image; - image.abiName = imageAbi; - image.apiLevel = platform.apiLevel; - platform.systemImages.append(image); + auto image = new SystemImage(QVersionNumber(), "", imageAbi, platform); + platform->addSystemImage(image); } }; - SdkPlatform platform; - QStringList abiList; - foreach (const QString &l, output.split('\n')) { - const QString line = l.trimmed(); + class { + public: + QStringList abiList; + QVersionNumber revision; + int apiLevel = -1; + QString description; + Utils::FileName installedLocation; + + void clear() { + abiList.clear(); + revision = QVersionNumber(); + apiLevel = -1; + description.clear(); + installedLocation.clear(); + } + } platformParams; + + QStringList outputLines = output.split('\n'); + for (int index = 0; index < outputLines.count(); ++index) { + const QString line = outputLines.at(index).trimmed(); if (line.startsWith(QLatin1String("id:")) && line.contains(QLatin1String("android-"))) { int index = line.indexOf(QLatin1String("\"android-")); if (index == -1) @@ -319,33 +329,33 @@ void AndroidToolOutputParser::parseTargetListing(const QString &output, const QString tmp = androidTarget.mid(androidTarget.lastIndexOf(QLatin1Char('-')) + 1); Utils::FileName platformPath = sdkLocation; platformPath.appendPath(QString("/platforms/android-%1").arg(tmp)); - platform.installedLocation = platformPath; - platform.apiLevel = AndroidManager::findApiLevel(platformPath); + platformParams.installedLocation = platformPath; + platformParams.apiLevel = AndroidManager::findApiLevel(platformPath); } else if (line.startsWith(QLatin1String("Name:"))) { - platform.name = line.mid(6); + platformParams.description = line.mid(6); + } else if (line.startsWith(QLatin1String("Revision:"))) { + platformParams.revision = QVersionNumber::fromString(line.mid(10)); } else if (line.startsWith(QLatin1String("Tag/ABIs :"))) { - abiList = cleanAndroidABIs(line.mid(10).trimmed().split(QLatin1String(", "))); + platformParams.abiList = cleanAndroidABIs(line.mid(10).trimmed().split(QLatin1String(", "))); } else if (line.startsWith(QLatin1String("ABIs"))) { - abiList = cleanAndroidABIs(line.mid(6).trimmed().split(QLatin1String(", "))); - } else if (line.startsWith(QLatin1String("---")) || line.startsWith(QLatin1String("==="))) { - if (platform.apiLevel == -1) + platformParams.abiList = cleanAndroidABIs(line.mid(6).trimmed().split(QLatin1String(", "))); + } else if (line.startsWith(QLatin1String("---")) + || line.startsWith(QLatin1String("===")) + || index == outputLines.count() - 1) { + if (platformParams.apiLevel == -1) continue; - - addSystemImage(abiList, platform); - *platformList << platform; - - platform = SdkPlatform(); - abiList.clear(); + auto platform = new SdkPlatform(platformParams.revision, + QString("platforms;android-%1").arg(platformParams.apiLevel), + platformParams.apiLevel); + platform->setState(AndroidSdkPackage::Installed); + platform->setDescriptionText(platformParams.description); + platform->setInstalledLocation(platformParams.installedLocation); + addSystemImage(platformParams.abiList, platform); + platformList << platform; + platformParams.clear(); } } - - // The last parsed Platform. - if (platform.apiLevel != -1) { - addSystemImage(abiList, platform); - *platformList << platform; - } - - Utils::sort(*platformList); + Utils::sort(platformList); } } // namespace Internal diff --git a/src/plugins/android/androidtoolmanager.h b/src/plugins/android/androidtoolmanager.h index faaa9428ab6..61f7018bf92 100644 --- a/src/plugins/android/androidtoolmanager.h +++ b/src/plugins/android/androidtoolmanager.h @@ -51,15 +51,15 @@ public: SdkPlatformList availableSdkPlatforms(bool *ok = nullptr) const; void launchAvdManager() const; - QFuture createAvd(AndroidConfig::CreateAvdInfo info) const; + QFuture createAvd(CreateAvdInfo info) const; bool removeAvd(const QString &name) const; QFuture androidVirtualDevicesFuture() const; // Helper methods private: Utils::Environment androidToolEnvironment() const; - static AndroidConfig::CreateAvdInfo createAvdImpl(AndroidConfig::CreateAvdInfo info, - Utils::FileName androidToolPath, Utils::Environment env); + static CreateAvdInfo createAvdImpl(CreateAvdInfo info, Utils::FileName androidToolPath, + Utils::Environment env); static AndroidDeviceInfoList androidVirtualDevices(const Utils::FileName &androidTool, const Utils::FileName &sdkLlocationPath, const Utils::Environment &environment); diff --git a/src/plugins/android/avddialog.cpp b/src/plugins/android/avddialog.cpp index 03d29d4e232..702b2120d79 100644 --- a/src/plugins/android/avddialog.cpp +++ b/src/plugins/android/avddialog.cpp @@ -24,11 +24,12 @@ ****************************************************************************/ #include "avddialog.h" -#include "androidconfigurations.h" +#include "androidsdkmanager.h" #include #include #include +#include #include #include @@ -37,10 +38,14 @@ using namespace Android; using namespace Android::Internal; -AvdDialog::AvdDialog(int minApiLevel, const QString &targetArch, const AndroidConfig *config, QWidget *parent) : - QDialog(parent), m_config(config), m_minApiLevel(minApiLevel), +AvdDialog::AvdDialog(int minApiLevel, AndroidSdkManager *sdkManager, const QString &targetArch, + QWidget *parent) : + QDialog(parent), + m_sdkManager(sdkManager), + m_minApiLevel(minApiLevel), m_allowedNameChars(QLatin1String("[a-z|A-Z|0-9|._-]*")) { + QTC_CHECK(m_sdkManager); m_avdDialog.setupUi(this); m_hideTipTimer.setInterval(2000); m_hideTipTimer.setSingleShot(true); @@ -70,12 +75,27 @@ AvdDialog::AvdDialog(int minApiLevel, const QString &targetArch, const AndroidCo bool AvdDialog::isValid() const { - return !name().isEmpty() && target().isValid() && !abi().isEmpty(); + return !name().isEmpty() && sdkPlatform() && sdkPlatform()->isValid() && !abi().isEmpty(); } -SdkPlatform AvdDialog::target() const +CreateAvdInfo AvdDialog::gatherCreateAVDInfo(QWidget *parent, AndroidSdkManager *sdkManager, + int minApiLevel, QString targetArch) { - return m_avdDialog.targetComboBox->currentData().value(); + CreateAvdInfo result; + AvdDialog d(minApiLevel, sdkManager, targetArch, parent); + if (d.exec() != QDialog::Accepted || !d.isValid()) + return result; + + result.sdkPlatform = d.sdkPlatform(); + result.name = d.name(); + result.abi = d.abi(); + result.sdcardSize = d.sdcardSize(); + return result; +} + +const SdkPlatform* AvdDialog::sdkPlatform() const +{ + return m_avdDialog.targetComboBox->currentData().value(); } QString AvdDialog::name() const @@ -96,21 +116,23 @@ int AvdDialog::sdcardSize() const void AvdDialog::updateApiLevelComboBox() { SdkPlatformList filteredList; - SdkPlatformList platforms = m_config->sdkTargets(m_minApiLevel); + const SdkPlatformList platforms = m_sdkManager->filteredSdkPlatforms(m_minApiLevel); QString selectedAbi = abi(); - auto hasAbi = [selectedAbi](const SystemImage &image) { - return image.isValid() && (image.abiName == selectedAbi); + auto hasAbi = [selectedAbi](const SystemImage *image) { + return image && image->isValid() && (image->abiName() == selectedAbi); }; - filteredList = Utils::filtered(platforms, [hasAbi](const SdkPlatform &platform) { - return Utils::anyOf(platform.systemImages,hasAbi); + filteredList = Utils::filtered(platforms, [hasAbi](const SdkPlatform *platform) { + return platform && Utils::anyOf(platform->systemImages(), hasAbi); }); m_avdDialog.targetComboBox->clear(); - foreach (const SdkPlatform &platform, filteredList) { - m_avdDialog.targetComboBox->addItem(AndroidConfig::apiLevelNameFor(platform), - QVariant::fromValue(platform)); + for (SdkPlatform *platform: filteredList) { + m_avdDialog.targetComboBox->addItem(platform->displayText(), + QVariant::fromValue(platform)); + m_avdDialog.targetComboBox->setItemData(m_avdDialog.targetComboBox->count() - 1, + platform->descriptionText(), Qt::ToolTipRole); } if (platforms.isEmpty()) { diff --git a/src/plugins/android/avddialog.h b/src/plugins/android/avddialog.h index d22e6af64f5..8a30b613c6b 100644 --- a/src/plugins/android/avddialog.h +++ b/src/plugins/android/avddialog.h @@ -24,7 +24,7 @@ ****************************************************************************/ #pragma once - +#include "androidconfigurations.h" #include "ui_addnewavddialog.h" #include @@ -35,26 +35,28 @@ class AndroidConfig; class SdkPlatform; namespace Internal { - +class AndroidSdkManager; class AvdDialog : public QDialog { Q_OBJECT public: - explicit AvdDialog(int minApiLevel, const QString &targetArch, - const AndroidConfig *config, QWidget *parent = 0); + explicit AvdDialog(int minApiLevel, AndroidSdkManager *sdkManager, const QString &targetArch, + QWidget *parent = 0); - Android::SdkPlatform target() const; + const SdkPlatform *sdkPlatform() const; QString name() const; QString abi() const; int sdcardSize() const; bool isValid() const; + static CreateAvdInfo gatherCreateAVDInfo(QWidget *parent, AndroidSdkManager *sdkManager, + int minApiLevel = 0, QString targetArch = QString()); private: void updateApiLevelComboBox(); bool eventFilter(QObject *obj, QEvent *event); Ui::AddNewAVDDialog m_avdDialog; - const AndroidConfig *m_config; + AndroidSdkManager *m_sdkManager; int m_minApiLevel; QTimer m_hideTipTimer; QRegExp m_allowedNameChars; diff --git a/src/plugins/autotest/qtest/qttestconfiguration.cpp b/src/plugins/autotest/qtest/qttestconfiguration.cpp index 157c9873e58..46a6a3d99f6 100644 --- a/src/plugins/autotest/qtest/qttestconfiguration.cpp +++ b/src/plugins/autotest/qtest/qttestconfiguration.cpp @@ -81,10 +81,8 @@ QStringList QtTestConfiguration::argumentsForTestRunner(QStringList *omitted) co if (qtSettings->logSignalsSlots) arguments << "-vs"; - if (isDebugRunMode()) { - if (qtSettings->noCrashHandler) - arguments << "-nocrashhandler"; - } + if (isDebugRunMode() && qtSettings->noCrashHandler) + arguments << "-nocrashhandler"; return arguments; } diff --git a/src/plugins/autotest/quick/quicktestconfiguration.cpp b/src/plugins/autotest/quick/quicktestconfiguration.cpp index 827b350cfe6..6875207bf76 100644 --- a/src/plugins/autotest/quick/quicktestconfiguration.cpp +++ b/src/plugins/autotest/quick/quicktestconfiguration.cpp @@ -35,6 +35,11 @@ namespace Autotest { namespace Internal { +QuickTestConfiguration::QuickTestConfiguration() +{ + setMixedDebugging(true); +} + TestOutputReader *QuickTestConfiguration::outputReader(const QFutureInterface &fi, QProcess *app) const { @@ -74,6 +79,11 @@ QStringList QuickTestConfiguration::argumentsForTestRunner(QStringList *omitted) const QString &metricsOption = QtTestSettings::metricsTypeToOption(qtSettings->metrics); if (!metricsOption.isEmpty()) arguments << metricsOption; + + if (isDebugRunMode()) { + if (qtSettings->noCrashHandler) + arguments << "-nocrashhandler"; + } return arguments; } diff --git a/src/plugins/autotest/quick/quicktestconfiguration.h b/src/plugins/autotest/quick/quicktestconfiguration.h index ecab2f4a2f3..7eca4cc1f01 100644 --- a/src/plugins/autotest/quick/quicktestconfiguration.h +++ b/src/plugins/autotest/quick/quicktestconfiguration.h @@ -30,10 +30,10 @@ namespace Autotest { namespace Internal { -class QuickTestConfiguration : public TestConfiguration +class QuickTestConfiguration : public DebuggableTestConfiguration { public: - explicit QuickTestConfiguration() {} + QuickTestConfiguration(); TestOutputReader *outputReader(const QFutureInterface &fi, QProcess *app) const override; QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override; diff --git a/src/plugins/autotest/quick/quicktesttreeitem.cpp b/src/plugins/autotest/quick/quicktesttreeitem.cpp index 35202696364..953f31e6618 100644 --- a/src/plugins/autotest/quick/quicktesttreeitem.cpp +++ b/src/plugins/autotest/quick/quicktesttreeitem.cpp @@ -109,6 +109,11 @@ bool QuickTestTreeItem::canProvideTestConfiguration() const } } +bool QuickTestTreeItem::canProvideDebugConfiguration() const +{ + return canProvideTestConfiguration(); +} + TestConfiguration *QuickTestTreeItem::testConfiguration() const { ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); @@ -143,6 +148,14 @@ TestConfiguration *QuickTestTreeItem::testConfiguration() const return config; } +TestConfiguration *QuickTestTreeItem::debugConfiguration() const +{ + QuickTestConfiguration *config = static_cast(testConfiguration()); + if (config) + config->setRunMode(TestRunMode::Debug); + return config; +} + QList QuickTestTreeItem::getAllTestConfigurations() const { QList result; diff --git a/src/plugins/autotest/quick/quicktesttreeitem.h b/src/plugins/autotest/quick/quicktesttreeitem.h index a0e860a52c0..be036a13fb3 100644 --- a/src/plugins/autotest/quick/quicktesttreeitem.h +++ b/src/plugins/autotest/quick/quicktesttreeitem.h @@ -39,7 +39,9 @@ public: QVariant data(int column, int role) const override; Qt::ItemFlags flags(int column) const override; bool canProvideTestConfiguration() const override; + bool canProvideDebugConfiguration() const override; TestConfiguration *testConfiguration() const override; + TestConfiguration *debugConfiguration() const override; QList getAllTestConfigurations() const override; QList getSelectedTestConfigurations() const override; TestTreeItem *find(const TestParseResult *result) override; diff --git a/src/plugins/autotest/testconfiguration.h b/src/plugins/autotest/testconfiguration.h index e35b0f0dd21..e3a0bcea3f9 100644 --- a/src/plugins/autotest/testconfiguration.h +++ b/src/plugins/autotest/testconfiguration.h @@ -117,8 +117,11 @@ public: void setRunMode(TestRunMode mode) { m_runMode = mode; } TestRunMode runMode() const { return m_runMode; } bool isDebugRunMode() const; + void setMixedDebugging(bool enable) { m_mixedDebugging = enable; } + bool mixedDebugging() const { return m_mixedDebugging; } private: TestRunMode m_runMode; + bool m_mixedDebugging = false; }; } // namespace Internal diff --git a/src/plugins/autotest/testrunconfiguration.h b/src/plugins/autotest/testrunconfiguration.h index b0cd1c8e1d2..34ba6b8363b 100644 --- a/src/plugins/autotest/testrunconfiguration.h +++ b/src/plugins/autotest/testrunconfiguration.h @@ -52,10 +52,12 @@ public: initialize("AutoTest.TestRunConfig"); setDefaultDisplayName(tr("AutoTest Debug")); - // disable QmlDebugger that is enabled by default - // might change if debugging QuickTest gets enabled + bool enableQuick = false; + if (auto debuggable = dynamic_cast(config)) + enableQuick = debuggable->mixedDebugging(); + if (auto debugAspect = extraAspect()) - debugAspect->setUseQmlDebugger(false); + debugAspect->setUseQmlDebugger(enableQuick); m_testConfig = config; } diff --git a/src/plugins/help/help.qrc b/src/plugins/help/help.qrc index d2db8d1a6b5..12c1f0803ee 100644 --- a/src/plugins/help/help.qrc +++ b/src/plugins/help/help.qrc @@ -2,8 +2,6 @@ images/find.png images/book.png - images/home.png - images/home@2x.png images/category_help.png images/mode_help.png images/mode_help@2x.png diff --git a/src/plugins/help/helpwidget.cpp b/src/plugins/help/helpwidget.cpp index b0e6cb3daf7..feb6c3ffa43 100644 --- a/src/plugins/help/helpwidget.cpp +++ b/src/plugins/help/helpwidget.cpp @@ -176,7 +176,7 @@ HelpWidget::HelpWidget(const Core::Context &context, WidgetStyle style, QWidget layout->addWidget(Core::Command::toolButtonWithAppendedShortcut(m_switchToHelp, cmd)); } - m_homeAction = new QAction(Icons::HOME_TOOLBAR.icon(), tr("Home"), this); + m_homeAction = new QAction(Utils::Icons::HOME_TOOLBAR.icon(), tr("Home"), this); cmd = Core::ActionManager::registerAction(m_homeAction, Constants::HELP_HOME, context); connect(m_homeAction, &QAction::triggered, this, &HelpWidget::goHome); layout->addWidget(Core::Command::toolButtonWithAppendedShortcut(m_homeAction, cmd)); diff --git a/src/plugins/ios/iosconfigurations.cpp b/src/plugins/ios/iosconfigurations.cpp index 68680911154..4e8dd41aa25 100644 --- a/src/plugins/ios/iosconfigurations.cpp +++ b/src/plugins/ios/iosconfigurations.cpp @@ -210,30 +210,16 @@ static void setupKit(Kit *kit, Core::Id pDeviceType, const ToolChainPair& toolCh SysRootKitInformation::setSysRoot(kit, sdkPath); } -static QVersionNumber findXcodeVersion() +static QVersionNumber findXcodeVersion(const Utils::FileName &developerPath) { - Utils::SynchronousProcess pkgUtilProcess; - Utils::SynchronousProcessResponse resp = - pkgUtilProcess.runBlocking("pkgutil", QStringList("--pkg-info-plist=com.apple.pkg.Xcode")); - if (resp.result == Utils::SynchronousProcessResponse::Finished) { - QDomDocument xcodeVersionDoc; - if (xcodeVersionDoc.setContent(resp.allRawOutput())) { - QDomNodeList nodes = xcodeVersionDoc.elementsByTagName(QStringLiteral("key")); - for (int i = 0; i < nodes.count(); ++i) { - QDomElement elem = nodes.at(i).toElement(); - if (elem.text().compare(QStringLiteral("pkg-version")) == 0) { - QString versionStr = elem.nextSiblingElement().text(); - return QVersionNumber::fromString(versionStr); - } - } - } else { - qCDebug(iosCommonLog) << "Error finding Xcode version. Cannot parse xml output from pkgutil."; - } + FileName xcodeInfo = developerPath.parentDir().appendPath("Info.plist"); + if (xcodeInfo.exists()) { + QSettings settings(xcodeInfo.toString(), QSettings::NativeFormat); + return QVersionNumber::fromString(settings.value("CFBundleShortVersionString").toString()); } else { - qCDebug(iosCommonLog) << "Error finding Xcode version. pkgutil command failed."; + qCDebug(iosCommonLog) << "Error finding Xcode version." << xcodeInfo.toUserOutput() << + "does not exist."; } - - qCDebug(iosCommonLog) << "Error finding Xcode version. Unknow error."; return QVersionNumber(); } @@ -439,7 +425,7 @@ void IosConfigurations::setDeveloperPath(const FileName &devPath) m_instance->updateSimulators(); // Find xcode version. - m_instance->m_xcodeVersion = findXcodeVersion(); + m_instance->m_xcodeVersion = findXcodeVersion(m_instance->m_developerPath); } } } diff --git a/src/plugins/ios/iosrunner.cpp b/src/plugins/ios/iosrunner.cpp index 18264317bb8..7f993865ba6 100644 --- a/src/plugins/ios/iosrunner.cpp +++ b/src/plugins/ios/iosrunner.cpp @@ -486,26 +486,11 @@ void IosDebugSupport::start() setInferiorExecutable(iosRunConfig->localExecutable().toString()); setRemoteChannel("connect://localhost:" + gdbServerPort.toString()); - FileName xcodeInfo = IosConfigurations::developerPath().parentDir().appendPath("Info.plist"); - bool buggyLldb = false; - if (xcodeInfo.exists()) { - QSettings settings(xcodeInfo.toString(), QSettings::NativeFormat); - QStringList version = settings.value(QLatin1String("CFBundleShortVersionString")).toString() - .split('.'); - if (version.value(0).toInt() == 5 && version.value(1, QString::number(1)).toInt() == 0) - buggyLldb = true; - } QString bundlePath = iosRunConfig->bundleDirectory().toString(); bundlePath.chop(4); FileName dsymPath = FileName::fromString(bundlePath.append(".dSYM")); - if (!dsymPath.exists()) { - if (buggyLldb) - TaskHub::addTask(Task::Warning, - tr("Debugging with Xcode 5.0.x can be unreliable without a dSYM. " - "To create one, add a dsymutil deploystep."), - ProjectExplorer::Constants::TASK_CATEGORY_DEPLOYMENT); - } else if (dsymPath.toFileInfo().lastModified() - < QFileInfo(iosRunConfig->localExecutable().toUserOutput()).lastModified()) { + if (dsymPath.exists() && dsymPath.toFileInfo().lastModified() + < QFileInfo(iosRunConfig->localExecutable().toUserOutput()).lastModified()) { TaskHub::addTask(Task::Warning, tr("The dSYM %1 seems to be outdated, it might confuse the debugger.") .arg(dsymPath.toUserOutput()), diff --git a/src/plugins/projectexplorer/foldernavigationwidget.cpp b/src/plugins/projectexplorer/foldernavigationwidget.cpp index f981545c690..4a003ba0283 100644 --- a/src/plugins/projectexplorer/foldernavigationwidget.cpp +++ b/src/plugins/projectexplorer/foldernavigationwidget.cpp @@ -25,6 +25,7 @@ #include "foldernavigationwidget.h" #include "projectexplorer.h" +#include "projectexplorericons.h" #include #include @@ -199,6 +200,7 @@ void FolderNavigationWidget::insertRootDirectory( m_rootSelector->setItemData(index, directory.id, ID_ROLE); m_rootSelector->setItemData(index, directory.sortValue, SORT_ROLE); m_rootSelector->setItemData(index, directory.path.toUserOutput(), Qt::ToolTipRole); + m_rootSelector->setItemIcon(index, directory.icon); if (m_rootSelector->currentIndex() == previousIndex) m_rootSelector->setCurrentIndex(index); if (previousIndex < m_rootSelector->count()) @@ -292,21 +294,26 @@ int FolderNavigationWidget::bestRootForFile(const Utils::FileName &filePath) void FolderNavigationWidget::openItem(const QModelIndex &index) { - if (!index.isValid()) + QTC_ASSERT(index.isValid(), return); + // signal "activate" is also sent when double-clicking folders + // but we don't want to do anything in that case + if (m_fileSystemModel->isDir(index)) return; const QString path = m_fileSystemModel->filePath(index); - if (m_fileSystemModel->isDir(index)) { - const QFileInfo fi = m_fileSystemModel->fileInfo(index); - if (!fi.isReadable() || !fi.isExecutable()) - return; - // Try to find project files in directory and open those. - const QStringList projectFiles = FolderNavigationWidget::projectFilesInDirectory(path); - if (!projectFiles.isEmpty()) - Core::ICore::instance()->openFiles(projectFiles); - } else { - // Open editor - Core::EditorManager::openEditor(path); - } + Core::EditorManager::openEditor(path); +} + +void FolderNavigationWidget::openProjectsInDirectory(const QModelIndex &index) +{ + QTC_ASSERT(index.isValid() && m_fileSystemModel->isDir(index), return); + const QFileInfo fi = m_fileSystemModel->fileInfo(index); + if (!fi.isReadable() || !fi.isExecutable()) + return; + const QString path = m_fileSystemModel->filePath(index); + // Try to find project files in directory and open those. + const QStringList projectFiles = FolderNavigationWidget::projectFilesInDirectory(path); + if (!projectFiles.isEmpty()) + Core::ICore::instance()->openFiles(projectFiles); } void FolderNavigationWidget::contextMenuEvent(QContextMenuEvent *ev) @@ -315,13 +322,14 @@ void FolderNavigationWidget::contextMenuEvent(QContextMenuEvent *ev) // Open current item const QModelIndex current = m_listView->currentIndex(); const bool hasCurrentItem = current.isValid(); - QAction *actionOpen = nullptr; + QAction *actionOpenFile = nullptr; + QAction *actionOpenProjects = nullptr; if (hasCurrentItem) { const QString fileName = m_fileSystemModel->fileName(current); if (m_fileSystemModel->isDir(current)) - actionOpen = menu.addAction(tr("Open Project in \"%1\"").arg(fileName)); + actionOpenProjects = menu.addAction(tr("Open Project in \"%1\"").arg(fileName)); else - actionOpen = menu.addAction(tr("Open \"%1\"").arg(fileName)); + actionOpenFile = menu.addAction(tr("Open \"%1\"").arg(fileName)); } // we need dummy DocumentModel::Entry with absolute file path in it @@ -337,9 +345,10 @@ void FolderNavigationWidget::contextMenuEvent(QContextMenuEvent *ev) return; ev->accept(); - if (action == actionOpen) { // Handle open file. + if (action == actionOpenFile) openItem(current); - } + else if (action == actionOpenProjects) + openProjectsInDirectory(current); } void FolderNavigationWidget::setHiddenFilesFilter(bool filter) @@ -378,11 +387,13 @@ FolderNavigationWidgetFactory::FolderNavigationWidgetFactory() insertRootDirectory({QLatin1String("A.Computer"), 0 /*sortValue*/, FolderNavigationWidget::tr("Computer"), - Utils::FileName()}); + Utils::FileName(), + Icons::DESKTOP_DEVICE_SMALL.icon()}); insertRootDirectory({QLatin1String("A.Home"), 10 /*sortValue*/, FolderNavigationWidget::tr("Home"), - Utils::FileName::fromString(QDir::homePath())}); + Utils::FileName::fromString(QDir::homePath()), + Utils::Icons::HOME.icon()}); updateProjectsDirectoryRoot(); connect(Core::DocumentManager::instance(), &Core::DocumentManager::projectsDirectoryChanged, @@ -465,7 +476,8 @@ void FolderNavigationWidgetFactory::updateProjectsDirectoryRoot() insertRootDirectory({QLatin1String(PROJECTSDIRECTORYROOT_ID), 20 /*sortValue*/, FolderNavigationWidget::tr("Projects"), - Core::DocumentManager::projectsDirectory()}); + Core::DocumentManager::projectsDirectory(), + Utils::Icons::PROJECT.icon()}); } } // namespace Internal diff --git a/src/plugins/projectexplorer/foldernavigationwidget.h b/src/plugins/projectexplorer/foldernavigationwidget.h index 11e2bbc9ec8..efb72828c33 100644 --- a/src/plugins/projectexplorer/foldernavigationwidget.h +++ b/src/plugins/projectexplorer/foldernavigationwidget.h @@ -28,6 +28,7 @@ #include #include +#include #include namespace Core { class IEditor; } @@ -56,6 +57,7 @@ public: int sortValue; QString displayName; Utils::FileName path; + QIcon icon; }; FolderNavigationWidgetFactory(); @@ -105,6 +107,7 @@ private: void setRootDirectory(const Utils::FileName &directory); int bestRootForFile(const Utils::FileName &filePath); void openItem(const QModelIndex &index); + void openProjectsInDirectory(const QModelIndex &index); Utils::NavigationTreeView *m_listView = nullptr; QFileSystemModel *m_fileSystemModel = nullptr; diff --git a/src/plugins/projectexplorer/session.cpp b/src/plugins/projectexplorer/session.cpp index b103bdf132e..42194c2067f 100644 --- a/src/plugins/projectexplorer/session.cpp +++ b/src/plugins/projectexplorer/session.cpp @@ -394,14 +394,19 @@ void SessionManager::addProject(Project *pro) emit m_instance->projectAdded(pro); const auto updateFolderNavigation = [pro] { + const QIcon icon = pro->rootProjectNode() ? pro->rootProjectNode()->icon() : QIcon(); FolderNavigationWidgetFactory::insertRootDirectory({projectFolderId(pro), PROJECT_SORT_VALUE, pro->displayName(), - pro->projectFilePath().parentDir()}); + pro->projectFilePath().parentDir(), + icon}); }; updateFolderNavigation(); configureEditors(pro); - connect(pro, &Project::fileListChanged, [pro](){ configureEditors(pro); }); + connect(pro, &Project::fileListChanged, [pro, updateFolderNavigation]() { + configureEditors(pro); + updateFolderNavigation(); // update icon + }); connect(pro, &Project::displayNameChanged, pro, updateFolderNavigation); if (!startupProject()) diff --git a/src/plugins/qbsprojectmanager/qbsbuildstep.cpp b/src/plugins/qbsprojectmanager/qbsbuildstep.cpp index 063e6bf203b..6dc69eb6582 100644 --- a/src/plugins/qbsprojectmanager/qbsbuildstep.cpp +++ b/src/plugins/qbsprojectmanager/qbsbuildstep.cpp @@ -774,12 +774,14 @@ void QbsBuildStepConfigWidget::applyCachedProperties() tmp.value(Constants::QBS_CONFIG_PROFILE_KEY)); data.insert(Constants::QBS_CONFIG_VARIANT_KEY, tmp.value(Constants::QBS_CONFIG_VARIANT_KEY)); - if (tmp.contains(Constants::QBS_CONFIG_DECLARATIVE_DEBUG_KEY)) - data.insert(Constants::QBS_CONFIG_DECLARATIVE_DEBUG_KEY, - tmp.value(Constants::QBS_CONFIG_DECLARATIVE_DEBUG_KEY)); - if (tmp.contains(Constants::QBS_CONFIG_QUICK_DEBUG_KEY)) - data.insert(Constants::QBS_CONFIG_QUICK_DEBUG_KEY, - tmp.value(Constants::QBS_CONFIG_QUICK_DEBUG_KEY)); + const QStringList additionalSpecialKeys({Constants::QBS_CONFIG_DECLARATIVE_DEBUG_KEY, + Constants::QBS_CONFIG_QUICK_DEBUG_KEY, + Constants::QBS_INSTALL_ROOT_KEY}); + for (const QString &key : additionalSpecialKeys) { + const auto it = tmp.constFind(key); + if (it != tmp.cend()) + data.insert(key, it.value()); + } for (int i = 0; i < m_propertyCache.count(); ++i) { const Property &property = m_propertyCache.at(i); diff --git a/src/plugins/qtsupport/baseqtversion.cpp b/src/plugins/qtsupport/baseqtversion.cpp index e232983f3c9..4ef11bc8ed7 100644 --- a/src/plugins/qtsupport/baseqtversion.cpp +++ b/src/plugins/qtsupport/baseqtversion.cpp @@ -1768,7 +1768,7 @@ FileNameList BaseQtVersion::qtCorePaths() const if (dir.isEmpty()) continue; QDir d(dir); - QFileInfoList infoList = d.entryInfoList(QDir::NoDotAndDotDot); + QFileInfoList infoList = d.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); foreach (const QFileInfo &info, infoList) { const QString file = info.fileName(); if (info.isDir() diff --git a/src/plugins/texteditor/textdocument.cpp b/src/plugins/texteditor/textdocument.cpp index 1bb79b9e83a..9e8befb9391 100644 --- a/src/plugins/texteditor/textdocument.cpp +++ b/src/plugins/texteditor/textdocument.cpp @@ -959,6 +959,7 @@ void TextDocument::removeMark(TextMark *mark) } removeMarkFromMarksCache(mark); + emit markRemoved(mark); mark->setBaseTextDocument(0); updateLayout(); } diff --git a/src/plugins/texteditor/textdocument.h b/src/plugins/texteditor/textdocument.h index 63c1f78a421..eecaa838233 100644 --- a/src/plugins/texteditor/textdocument.h +++ b/src/plugins/texteditor/textdocument.h @@ -144,6 +144,7 @@ signals: void contentsChangedWithPosition(int position, int charsRemoved, int charsAdded); void tabSettingsChanged(); void fontSettingsChanged(); + void markRemoved(TextMark *mark); protected: virtual void applyFontSettings(); diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 0fe63bbc95e..5da5372bd10 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -97,6 +97,7 @@ #include #include #include +#include #include #include #include @@ -396,7 +397,7 @@ public: bool expanded, bool active, bool hovered) const; - void drawLineAnnotation(QPainter &painter, const QTextBlock &block, qreal start); + void drawLineAnnotation(QPainter &painter, const QTextBlock &block, qreal start, const QRect &eventRect); void toggleBlockVisible(const QTextBlock &block); QRect foldBox(); @@ -427,6 +428,7 @@ public: void slotUpdateBlockNotify(const QTextBlock &); void updateTabStops(); void applyFontSettingsDelayed(); + void markRemoved(TextMark *mark); void editorContentsChange(int position, int charsRemoved, int charsAdded); void documentAboutToBeReloaded(); @@ -673,6 +675,8 @@ TextEditorWidgetPrivate::TextEditorWidgetPrivate(TextEditorWidget *parent) TextEditorWidgetPrivate::~TextEditorWidgetPrivate() { + QObject::disconnect(m_document.data(), &TextDocument::markRemoved, + this, &TextEditorWidgetPrivate::markRemoved); q->disconnect(this); delete m_toolBar; } @@ -3201,6 +3205,9 @@ void TextEditorWidgetPrivate::setupDocumentSignals() QObject::connect(m_document.data(), &TextDocument::fontSettingsChanged, this, &TextEditorWidgetPrivate::applyFontSettingsDelayed); + QObject::connect(m_document.data(), &TextDocument::markRemoved, + this, &TextEditorWidgetPrivate::markRemoved); + slotUpdateExtraAreaWidth(); TextEditorSettings *settings = TextEditorSettings::instance(); @@ -3860,7 +3867,7 @@ QRectF TextEditorWidgetPrivate::getLastLineLineRect(const QTextBlock &block) } void TextEditorWidgetPrivate::drawLineAnnotation( - QPainter &painter, const QTextBlock &block, qreal rightMargin) + QPainter &painter, const QTextBlock &block, qreal rightMargin, const QRect &eventRect) { if (!m_displaySettings.m_displayAnnotations) return; @@ -3907,11 +3914,16 @@ void TextEditorWidgetPrivate::drawLineAnnotation( break; // paint annotation - mark->paintAnnotation(painter, &boundingRect, offset, itemOffset / 2); + mark->paintAnnotation(painter, &boundingRect, offset, itemOffset / 2, q->contentOffset()); x = boundingRect.right(); offset = itemOffset / 2; m_annotationRects[block.blockNumber()].append({boundingRect, mark}); } + + QRect updateRect(lineRect.toRect().topRight(), boundingRect.toRect().bottomRight()); + updateRect.setLeft(qMax(0, updateRect.left())); + if (!updateRect.isEmpty() && !eventRect.contains(updateRect)) + q->viewport()->update(updateRect); } void TextEditorWidget::paintEvent(QPaintEvent *e) @@ -4039,7 +4051,6 @@ void TextEditorWidget::paintEvent(QPaintEvent *e) int cursor_cpos = 0; QPen cursor_pen; - d->m_annotationRects.clear(); d->m_searchResultOverlay->clear(); if (!d->m_searchExpr.isEmpty()) { // first pass for the search result overlays @@ -4194,6 +4205,12 @@ void TextEditorWidget::paintEvent(QPaintEvent *e) fs.toTextCharFormat(C_SEARCH_RESULT).background().color(), e->rect()); + { // remove all annotation rects from the cache that where drawn before the first visible block + auto it = d->m_annotationRects.begin(); + auto end = d->m_annotationRects.end(); + while (it != end && it.key() < block.blockNumber()) + it = d->m_annotationRects.erase(it); + } while (block.isValid()) { @@ -4408,6 +4425,9 @@ void TextEditorWidget::paintEvent(QPaintEvent *e) || d->m_blockSelection.positionColumn == d->m_blockSelection.anchorColumn) && blockSelectionCursorRect.isValid()) painter.fillRect(blockSelectionCursorRect, palette().text()); + + d->m_annotationRects.remove(block.blockNumber()); + d->drawLineAnnotation(painter, block, lineX < viewportRect.width() ? lineX : 0, er); } offset.ry() += r.height(); @@ -4427,6 +4447,19 @@ void TextEditorWidget::paintEvent(QPaintEvent *e) block = doc->findBlockByLineNumber(block.firstLineNumber()); } } + + // remove all annotation rects from the cache that where drawn after the last visible block + if (block.isValid()) { + auto it = d->m_annotationRects.begin(); + auto end = d->m_annotationRects.end(); + while (it != end) { + if (it.key() > block.blockNumber()) + it = d->m_annotationRects.erase(it); + else + ++it; + } + } + painter.setPen(context.palette.text().color()); if (backgroundVisible() && !block.isValid() && offset.y() <= er.bottom() @@ -4465,7 +4498,7 @@ void TextEditorWidget::paintEvent(QPaintEvent *e) int lineCount = layout->lineCount(); if (lineCount >= 2 || !nextBlock.isValid()) { painter.save(); - painter.setPen(Qt::lightGray); + painter.setPen(fs.toTextCharFormat(C_VISUAL_WHITESPACE).foreground().color()); for (int i = 0; i < lineCount-1; ++i) { // paint line wrap indicator QTextLine line = layout->lineAt(i); QRectF lineRect = line.naturalTextRect().translated(offset.x(), top); @@ -4554,7 +4587,6 @@ void TextEditorWidget::paintEvent(QPaintEvent *e) painter.restore(); } } - d->drawLineAnnotation(painter, block, lineX < viewportRect.width() ? lineX : 0); block = nextVisibleBlock; top = bottom; @@ -6940,6 +6972,17 @@ void TextEditorWidgetPrivate::applyFontSettingsDelayed() q->triggerPendingUpdates(); } +void TextEditorWidgetPrivate::markRemoved(TextMark *mark) +{ + auto it = m_annotationRects.find(mark->lineNumber() - 1); + if (it == m_annotationRects.end()) + return; + + Utils::erase(it.value(), [mark](AnnotationRect rect) { + return rect.mark == mark; + }); +} + void TextEditorWidget::triggerPendingUpdates() { if (d->m_fontSettingsNeedsApply) diff --git a/src/plugins/texteditor/textmark.cpp b/src/plugins/texteditor/textmark.cpp index d4f9db7c509..89c2385f9af 100644 --- a/src/plugins/texteditor/textmark.cpp +++ b/src/plugins/texteditor/textmark.cpp @@ -122,7 +122,8 @@ void TextMark::paintIcon(QPainter *painter, const QRect &rect) const } void TextMark::paintAnnotation(QPainter &painter, QRectF *annotationRect, - const qreal fadeInOffset, const qreal fadeOutOffset) const + const qreal fadeInOffset, const qreal fadeOutOffset, + const QPointF &contentOffset) const { QString text = lineAnnotation(); if (text.isEmpty()) @@ -136,7 +137,8 @@ void TextMark::paintAnnotation(QPainter &painter, QRectF *annotationRect, markColor, painter.background().color()); painter.save(); - QLinearGradient grad(rects.fadeInRect.topLeft(), rects.fadeInRect.topRight()); + QLinearGradient grad(rects.fadeInRect.topLeft() - contentOffset, + rects.fadeInRect.topRight() - contentOffset); grad.setColorAt(0.0, Qt::transparent); grad.setColorAt(1.0, colors.rectColor); painter.fillRect(rects.fadeInRect, grad); @@ -145,7 +147,8 @@ void TextMark::paintAnnotation(QPainter &painter, QRectF *annotationRect, paintIcon(&painter, rects.iconRect.toAlignedRect()); painter.drawText(rects.textRect, Qt::AlignLeft, rects.text); if (rects.fadeOutRect.isValid()) { - grad = QLinearGradient(rects.fadeOutRect.topLeft(), rects.fadeOutRect.topRight()); + grad = QLinearGradient(rects.fadeOutRect.topLeft() - contentOffset, + rects.fadeOutRect.topRight() - contentOffset); grad.setColorAt(0.0, colors.rectColor); grad.setColorAt(1.0, Qt::transparent); painter.fillRect(rects.fadeOutRect, grad); diff --git a/src/plugins/texteditor/textmark.h b/src/plugins/texteditor/textmark.h index d9c09ee5133..f68f92c11d9 100644 --- a/src/plugins/texteditor/textmark.h +++ b/src/plugins/texteditor/textmark.h @@ -64,7 +64,8 @@ public: virtual void paintIcon(QPainter *painter, const QRect &rect) const; virtual void paintAnnotation(QPainter &painter, QRectF *annotationRect, - const qreal fadeInOffset, const qreal fadeOutOffset) const; + const qreal fadeInOffset, const qreal fadeOutOffset, + const QPointF &contentOffset) const; struct AnnotationRects { QRectF fadeInRect; diff --git a/src/plugins/vcsbase/images/diff.png b/src/plugins/vcsbase/images/diff.png deleted file mode 100644 index b3597f9ff85..00000000000 Binary files a/src/plugins/vcsbase/images/diff.png and /dev/null differ diff --git a/src/plugins/vcsbase/images/diff_arrows.png b/src/plugins/vcsbase/images/diff_arrows.png new file mode 100644 index 00000000000..f3c89ca292d Binary files /dev/null and b/src/plugins/vcsbase/images/diff_arrows.png differ diff --git a/src/plugins/vcsbase/images/diff_arrows@2x.png b/src/plugins/vcsbase/images/diff_arrows@2x.png new file mode 100644 index 00000000000..d2fa19278f6 Binary files /dev/null and b/src/plugins/vcsbase/images/diff_arrows@2x.png differ diff --git a/src/plugins/vcsbase/images/diff_documents.png b/src/plugins/vcsbase/images/diff_documents.png new file mode 100644 index 00000000000..5db2b6ccbfd Binary files /dev/null and b/src/plugins/vcsbase/images/diff_documents.png differ diff --git a/src/plugins/vcsbase/images/diff_documents@2x.png b/src/plugins/vcsbase/images/diff_documents@2x.png new file mode 100644 index 00000000000..cf5d2ce5517 Binary files /dev/null and b/src/plugins/vcsbase/images/diff_documents@2x.png differ diff --git a/src/plugins/vcsbase/images/removesubmitfield.png b/src/plugins/vcsbase/images/removesubmitfield.png deleted file mode 100644 index e4139afc552..00000000000 Binary files a/src/plugins/vcsbase/images/removesubmitfield.png and /dev/null differ diff --git a/src/plugins/vcsbase/images/submit.png b/src/plugins/vcsbase/images/submit.png deleted file mode 100644 index 4f302302b9e..00000000000 Binary files a/src/plugins/vcsbase/images/submit.png and /dev/null differ diff --git a/src/plugins/vcsbase/images/submit_arrow.png b/src/plugins/vcsbase/images/submit_arrow.png new file mode 100644 index 00000000000..5d849210c35 Binary files /dev/null and b/src/plugins/vcsbase/images/submit_arrow.png differ diff --git a/src/plugins/vcsbase/images/submit_arrow@2x.png b/src/plugins/vcsbase/images/submit_arrow@2x.png new file mode 100644 index 00000000000..78064174dbd Binary files /dev/null and b/src/plugins/vcsbase/images/submit_arrow@2x.png differ diff --git a/src/plugins/vcsbase/images/submit_db.png b/src/plugins/vcsbase/images/submit_db.png new file mode 100644 index 00000000000..2796c6576f8 Binary files /dev/null and b/src/plugins/vcsbase/images/submit_db.png differ diff --git a/src/plugins/vcsbase/images/submit_db@2x.png b/src/plugins/vcsbase/images/submit_db@2x.png new file mode 100644 index 00000000000..2bc79f0847a Binary files /dev/null and b/src/plugins/vcsbase/images/submit_db@2x.png differ diff --git a/src/plugins/vcsbase/submitfieldwidget.cpp b/src/plugins/vcsbase/submitfieldwidget.cpp index 58581ad5b8e..77980484c94 100644 --- a/src/plugins/vcsbase/submitfieldwidget.cpp +++ b/src/plugins/vcsbase/submitfieldwidget.cpp @@ -25,6 +25,8 @@ #include "submitfieldwidget.h" +#include + #include #include #include @@ -138,7 +140,7 @@ struct SubmitFieldWidgetPrivate { }; SubmitFieldWidgetPrivate::SubmitFieldWidgetPrivate() : - removeFieldIcon(QLatin1String(":/vcsbase/images/removesubmitfield.png")), + removeFieldIcon(Utils::Icons::BROKEN.icon()), completer(0), hasBrowseButton(false), allowDuplicateFields(false), diff --git a/src/plugins/vcsbase/vcsbase.qbs b/src/plugins/vcsbase/vcsbase.qbs index db5c5a37393..bfaf329f6e4 100644 --- a/src/plugins/vcsbase/vcsbase.qbs +++ b/src/plugins/vcsbase/vcsbase.qbs @@ -74,9 +74,6 @@ QtcPlugin { "vcsplugin.h", "vcsprojectcache.cpp", "vcsprojectcache.h", - "images/diff.png", - "images/removesubmitfield.png", - "images/submit.png", "wizard/vcsconfigurationpage.cpp", "wizard/vcsconfigurationpage.h", "wizard/vcscommandpage.cpp", diff --git a/src/plugins/vcsbase/vcsbase.qrc b/src/plugins/vcsbase/vcsbase.qrc index 4234890c3a5..101764d9f21 100644 --- a/src/plugins/vcsbase/vcsbase.qrc +++ b/src/plugins/vcsbase/vcsbase.qrc @@ -1,8 +1,13 @@ images/category_vcs.png - images/diff.png - images/removesubmitfield.png - images/submit.png + images/diff_arrows.png + images/diff_arrows@2x.png + images/diff_documents.png + images/diff_documents@2x.png + images/submit_arrow.png + images/submit_arrow@2x.png + images/submit_db.png + images/submit_db@2x.png diff --git a/src/plugins/vcsbase/vcsbaseplugin.cpp b/src/plugins/vcsbase/vcsbaseplugin.cpp index 92b2fb44a38..acc3e312ad1 100644 --- a/src/plugins/vcsbase/vcsbaseplugin.cpp +++ b/src/plugins/vcsbase/vcsbaseplugin.cpp @@ -269,8 +269,8 @@ void StateListener::slotStateChanged() if (currentFi.exists()) { // Quick check: Does it look like a patch? - const bool isPatch = state.currentFile.endsWith(QLatin1String(".patch")) - || state.currentFile.endsWith(QLatin1String(".diff")); + const bool isPatch = state.currentFile.endsWith(".patch") + || state.currentFile.endsWith(".diff"); if (isPatch) { // Patch: Figure out a name to display. If it is a temp file, it could be // Codepaster. Use the display name of the editor. @@ -787,9 +787,9 @@ void VcsBasePlugin::setProcessEnvironment(QProcessEnvironment *e, const QString &sshPromptBinary) { if (forceCLocale) - e->insert(QLatin1String("LANG"), QString(QLatin1Char('C'))); + e->insert("LANG", "C"); if (!sshPromptBinary.isEmpty()) - e->insert(QLatin1String("SSH_ASKPASS"), sshPromptBinary); + e->insert("SSH_ASKPASS", sshPromptBinary); } // Run a process synchronously, returning Utils::SynchronousProcessResponse diff --git a/src/plugins/vcsbase/vcsbasesubmiteditor.cpp b/src/plugins/vcsbase/vcsbasesubmiteditor.cpp index a1c01d7a96e..02a4b4783fd 100644 --- a/src/plugins/vcsbase/vcsbasesubmiteditor.cpp +++ b/src/plugins/vcsbase/vcsbasesubmiteditor.cpp @@ -43,6 +43,8 @@ #include #include #include +#include +#include #include #include #include @@ -715,12 +717,20 @@ bool VcsBaseSubmitEditor::runSubmitMessageCheckScript(const QString &checkScript QIcon VcsBaseSubmitEditor::diffIcon() { - return QIcon(QLatin1String(":/vcsbase/images/diff.png")); + using namespace Utils; + return Icon({ + {":/vcsbase/images/diff_documents.png", Theme::PanelTextColorDark}, + {":/vcsbase/images/diff_arrows.png", Theme::IconsStopColor} + }, Icon::Tint).icon(); } QIcon VcsBaseSubmitEditor::submitIcon() { - return QIcon(QLatin1String(":/vcsbase/images/submit.png")); + using namespace Utils; + return Icon({ + {":/vcsbase/images/submit_db.png", Theme::PanelTextColorDark}, + {":/vcsbase/images/submit_arrow.png", Theme::IconsRunColor} + }, Icon::Tint | Icon::PunchEdges).icon(); } // Reduce a list of untracked files reported by a VCS down to the files diff --git a/src/shared/help/helpicons.h b/src/shared/help/helpicons.h index f9429a28150..dd4ebce26a5 100644 --- a/src/shared/help/helpicons.h +++ b/src/shared/help/helpicons.h @@ -30,8 +30,6 @@ namespace Help { namespace Icons { -const Utils::Icon HOME_TOOLBAR({ - {QLatin1String(":/help/images/home.png"), Utils::Theme::IconsBaseColor}}); const Utils::Icon MODE_HELP_CLASSIC( QLatin1String(":/help/images/mode_help.png")); const Utils::Icon MODE_HELP_FLAT({ diff --git a/src/tools/icons/qtcreatoricons.svg b/src/tools/icons/qtcreatoricons.svg index 1703885bc5b..2cfb5042d24 100644 --- a/src/tools/icons/qtcreatoricons.svg +++ b/src/tools/icons/qtcreatoricons.svg @@ -4087,7 +4087,7 @@ sodipodi:nodetypes="cccc" /> + id="src/libs/utils/images/home"> + + + + + + + + + + + + + + + + + + + + + + + + + + + +