/**************************************************************************** ** ** 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_sdkManager, this)), m_ui(new Ui::AndroidSdkManagerWidget) { QTC_CHECK(sdkManager); m_ui->setupUi(this); 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); } 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::onApplyButton() { QTC_ASSERT(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, installSdkPaths, uninstallSdkPaths; for (auto package : packagesToUpdate) { QString str = QString(" %1").arg(package->descriptionText()); if (package->state() == AndroidSdkPackage::Installed) { uninstallSdkPaths << package->sdkStylePath(); uninstallPackages << str; } else { installSdkPaths << package->sdkStylePath(); 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; // User agreed with the selection. Begin packages install/uninstall emit updatingSdk(); switchView(Operations); m_formatter->appendMessage(tr("Updating 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::onUpdatePackages() { if (m_sdkManager->isBusy()) { m_formatter->appendMessage(tr("\nSDK Manager is busy."), Utils::StdErrFormat); return; } switchView(Operations); m_formatter->appendMessage(tr("Updating installed packages\n"), Utils::NormalMessageFormat); addPackageFuture(m_sdkManager->updateAll()); } 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); auto breakLine = [](const QString &line) { return line.endsWith("\n") ? line : line + "\n";}; AndroidSdkManager::OperationOutput result = m_currentOperation->resultAt(index); if (!result.stdError.isEmpty()) m_formatter->appendMessage(breakLine(result.stdError), Utils::StdErrFormat); if (!result.stdOutput.isEmpty()) m_formatter->appendMessage(breakLine(result.stdOutput), Utils::StdOutFormat); } 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(); } } void AndroidSdkManagerWidget::notifyOperationFinished() { if (!m_currentOperation || m_currentOperation->isFinished()) { QMessageBox::information(this, tr("Android SDK Changes"), tr("Android SDK operations finished."), QMessageBox::Ok); switchView(PackageListing); m_ui->operationProgress->setValue(0); m_sdkManager->reloadPackages(true); emit updatingSdkFinished(); } } void AndroidSdkManagerWidget::packageFutureFinished() { if (!m_currentOperation) { qCDebug(androidSdkMgrUiLog) << "Invalid State. No active operation."; return; } else if (m_currentOperation->isCanceled()) { m_formatter->appendMessage(tr("Operation cancelled.\n"), Utils::StdErrFormat); } m_ui->operationProgress->setValue(100); m_currentOperation->deleteLater(); m_currentOperation = nullptr; notifyOperationFinished(); } void AndroidSdkManagerWidget::cancelPendingOperations() { if (!m_sdkManager->isBusy()) { m_formatter->appendMessage(tr("\nNo pending operations to cancel...\n"), Utils::NormalMessageFormat); return; } m_formatter->appendMessage(tr("\nCancelling pending operations...\n"), Utils::NormalMessageFormat); m_sdkManager->cancelOperatons(); } void AndroidSdkManagerWidget::switchView(AndroidSdkManagerWidget::View view) { m_ui->viewStack->setCurrentWidget(view == PackageListing ? m_ui->packagesStack : m_ui->outputStack); m_formatter->clear(); m_ui->outputEdit->clear(); } AndroidSdkManagerWidget::View AndroidSdkManagerWidget::currentView() const { return m_ui->viewStack->currentWidget() == m_ui->packagesStack ? PackageListing : Operations; } 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