Android: Android SDK manager user interface

Task-number: QTCREATORBUG-18978
Change-Id: I421ea66fcd4f3cf38e6cfd3be58a35b3f9204c6f
Reviewed-by: BogDan Vatra <bogdan@kdab.com>
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
This commit is contained in:
Vikas Pachdha
2017-09-18 13:48:00 +02:00
parent 4b1429de55
commit 476b133e91
10 changed files with 1381 additions and 140 deletions

View File

@@ -51,7 +51,9 @@ HEADERS += \
androidavdmanager.h \
androidrunconfigurationwidget.h \
adbcommandswidget.h \
androidsdkpackage.h
androidsdkpackage.h \
androidsdkmodel.h \
androidsdkmanagerwidget.h
SOURCES += \
androidconfigurations.cpp \
@@ -96,7 +98,9 @@ SOURCES += \
androidavdmanager.cpp \
androidrunconfigurationwidget.cpp \
adbcommandswidget.cpp \
androidsdkpackage.cpp
androidsdkpackage.cpp \
androidsdkmodel.cpp \
androidsdkmanagerwidget.cpp
FORMS += \
androidsettingswidget.ui \
@@ -106,7 +110,8 @@ FORMS += \
androiddeployqtwidget.ui \
androidbuildapkwidget.ui \
androidrunconfigurationwidget.ui \
adbcommandswidget.ui
adbcommandswidget.ui \
androidsdkmanagerwidget.ui
RESOURCES = android.qrc

View File

@@ -0,0 +1,364 @@
/****************************************************************************
**
** 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 "androidsdkmodel.h"
#include "utils/outputformatter.h"
#include "utils/runextensions.h"
#include "utils/qtcassert.h"
#include "utils/utilsicons.h"
#include <QLoggingCategory>
#include <QMessageBox>
#include <QProcess>
#include <QSortFilterProxyModel>
namespace {
Q_LOGGING_CATEGORY(androidSdkMgrUiLog, "qtc.android.sdkManagerUi")
}
namespace Android {
namespace Internal {
using namespace std::placeholders;
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(const 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)
{
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);
}
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);
}
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<const AndroidSdkPackage *> 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<AndroidSdkManager::OperationOutput>
&future)
{
QTC_ASSERT(!m_currentOperation, return);
if (!future.isFinished() || !future.isCanceled()) {
m_currentOperation = new QFutureWatcher<AndroidSdkManager::OperationOutput>;
m_currentOperation->setFuture(future);
connect(m_currentOperation,
&QFutureWatcher<AndroidSdkManager::OperationOutput>::resultReadyAt,
this, &AndroidSdkManagerWidget::onOperationResult);
connect(m_currentOperation, &QFutureWatcher<AndroidSdkManager::OperationOutput>::finished,
this, &AndroidSdkManagerWidget::packageFutureFinished);
connect(m_currentOperation,
&QFutureWatcher<AndroidSdkManager::OperationOutput>::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;
}
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);
}
} // namespace Internal
} // namespace Android

View File

@@ -0,0 +1,87 @@
/****************************************************************************
**
** 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 <QWidget>
#include <QFutureWatcher>
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
};
public:
AndroidSdkManagerWidget(const AndroidConfig &config, AndroidSdkManager *sdkManager,
QWidget *parent = nullptr);
~AndroidSdkManagerWidget();
void setSdkManagerControlsEnabled(bool enable);
signals:
void updatingSdk();
void updatingSdkFinished();
private:
void onApplyButton();
void onUpdatePackages();
void onCancel();
void onNativeSdkManager();
void onOperationResult(int index);
void addPackageFuture(const QFuture<AndroidSdkManager::OperationOutput> &future);
void notifyOperationFinished();
void packageFutureFinished();
void cancelPendingOperations();
void switchView(View view);
View currentView() const;
const AndroidConfig &m_androidConfig;
AndroidSdkManager *m_sdkManager = nullptr;
AndroidSdkModel *m_sdkModel = nullptr;
Ui::AndroidSdkManagerWidget *m_ui = nullptr;
Utils::OutputFormatter *m_formatter = nullptr;
QFutureWatcher<AndroidSdkManager::OperationOutput> *m_currentOperation = nullptr;
};
} // namespace Internal
} // namespace Android

View File

@@ -0,0 +1,272 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Android::Internal::AndroidSdkManagerWidget</class>
<widget class="QWidget" name="Android::Internal::AndroidSdkManagerWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>664</width>
<height>396</height>
</rect>
</property>
<property name="windowTitle">
<string>Android SDK Manager</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>-1</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QStackedWidget" name="viewStack">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="packagesStack">
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<number>4</number>
</property>
<item row="0" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QCheckBox" name="expandCheck">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Expand All</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="warningIconLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="Utils::ElidingLabel" name="warningLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>SDK manger is not available with the current version of SDK tools. Use native SDK manager.</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QTreeView" name="packagesView">
<property name="indentation">
<number>20</number>
</property>
<attribute name="headerCascadingSectionResizes">
<bool>false</bool>
</attribute>
</widget>
</item>
<item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="updateInstalledButton">
<property name="text">
<string>Update Installed</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="applySelectionButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Apply</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="packagesTypeGroup">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>Show Packages</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>12</number>
</property>
<item>
<widget class="QRadioButton" name="showAvailableRadio">
<property name="text">
<string>Available</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="showInstalledRadio">
<property name="text">
<string>Installed</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="showAllRadio">
<property name="text">
<string>All</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="nativeSdkManagerButton">
<property name="text">
<string>Native SDK Manager...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="outputStack">
<layout class="QGridLayout" name="gridLayout_2">
<item row="2" column="1">
<widget class="QPushButton" name="cancelButton">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item row="2" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0" colspan="2">
<widget class="QPlainTextEdit" name="outputEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QProgressBar" name="operationProgress">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>0</number>
</property>
<property name="textVisible">
<bool>true</bool>
</property>
<property name="invertedAppearance">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Utils::ElidingLabel</class>
<extends>QLabel</extends>
<header>utils/elidinglabel.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>packagesView</tabstop>
<tabstop>showAllRadio</tabstop>
<tabstop>showInstalledRadio</tabstop>
<tabstop>showAvailableRadio</tabstop>
<tabstop>outputEdit</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,316 @@
/****************************************************************************
**
** 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 <QIcon>
namespace Android {
namespace Internal {
const int packageColCount = 4;
AndroidSdkModel::AndroidSdkModel(AndroidSdkManager *sdkManager, QObject *parent)
: QAbstractItemModel(parent),
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<AndroidSdkPackage *>(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 *>(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<const AndroidSdkPackage *>(ip);
if (package->type() == AndroidSdkPackage::SystemImagePackage) {
auto image = static_cast<const SystemImage *>(package);
int row = m_sdkPlatforms.indexOf(const_cast<SdkPlatform *>(image->platform()));
if (row > -1)
return createIndex(row + 1, 0);
} else if (package->type() == AndroidSdkPackage::SdkPlatformPackage) {
int row = m_sdkPlatforms.indexOf(static_cast<const SdkPlatform *>(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<const AndroidSdkPackage *>(index.internalPointer());
QString apiLevelStr;
if (p->type() == AndroidSdkPackage::SdkPlatformPackage)
apiLevelStr = QString::number(static_cast<const SdkPlatform *>(p)->apiLevel());
if (p->type() == AndroidSdkPackage::SystemImagePackage)
apiLevelStr = QString::number(static_cast<const SystemImage *>(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<int, QByteArray> AndroidSdkModel::roleNames() const
{
QHash <int, QByteArray> 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<const AndroidSdkPackage *>(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<const AndroidSdkPackage *>(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;
}
QList<const AndroidSdkPackage *> 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<SdkPlatform *>(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

View File

@@ -0,0 +1,83 @@
/****************************************************************************
**
** 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 <QAbstractItemModel>
#include <memory>
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(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<int, QByteArray> roleNames() const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
QList<const AndroidSdkPackage *> userSelection() const;
void resetSelection();
private:
void clearContainers();
void refreshData();
private:
AndroidSdkManager *m_sdkManager;
QList<const SdkPlatform *> m_sdkPlatforms;
QList<const AndroidSdkPackage *> m_tools;
QSet<const AndroidSdkPackage *> m_changeState;
};
} // namespace Internal
} // namespace Android

View File

@@ -183,7 +183,19 @@ QVersionNumber SdkPlatform::version() const
void SdkPlatform::addSystemImage(SystemImage *image)
{
m_systemImages.append(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);
}

View File

@@ -33,6 +33,7 @@
#include "androidavdmanager.h"
#include "androidsdkmanager.h"
#include "avddialog.h"
#include "androidsdkmanagerwidget.h"
#include <utils/qtcassert.h>
#include <utils/environment.h>
@@ -124,24 +125,35 @@ public:
data.m_valid = valid;
data.m_iconLabel->setPixmap(data.m_valid ? Utils::Icons::OK.pixmap() :
Utils::Icons::BROKEN.pixmap());
bool ok = allRowsOk();
m_detailsWidget->setIcon(ok ? Utils::Icons::OK.icon() :
Utils::Icons::CRITICAL.icon());
m_detailsWidget->setSummaryText(ok ? m_validText : m_invalidText);
updateUi();
}
bool allRowsOk() const
bool rowsOk(QList<int> keys) const
{
for (auto itr = m_validationData.cbegin(); itr != m_validationData.cend(); ++itr) {
if (!itr.value().m_valid)
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<int, RowData> m_validationData;
};
@@ -218,6 +230,21 @@ AndroidSettingsWidget::AndroidSettingsWidget(QWidget *parent)
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<int, QString> javaValidationPoints;
javaValidationPoints[JavaPathExistsRow] = tr("JDK path exists.");
@@ -283,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);
@@ -293,14 +320,20 @@ 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();
validateSdk();
// 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();
}
@@ -521,14 +554,23 @@ void AndroidSettingsWidget::updateUI()
{
auto javaSummaryWidget = static_cast<SummaryWidget *>(m_ui->javaDetailsWidget->widget());
auto androidSummaryWidget = static_cast<SummaryWidget *>(m_ui->androidDetailsWidget->widget());
m_ui->AVDManagerFrame->setEnabled(javaSummaryWidget->allRowsOk()
&& androidSummaryWidget->allRowsOk());
m_ui->javaDetailsWidget->setState(javaSummaryWidget->allRowsOk() ?
Utils::DetailsWidget::Collapsed :
Utils::DetailsWidget::Expanded);
m_ui->androidDetailsWidget->setState(androidSummaryWidget->allRowsOk() ?
Utils::DetailsWidget::Collapsed :
Utils::DetailsWidget::Expanded);
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();
}

View File

@@ -42,6 +42,8 @@ QT_END_NAMESPACE
namespace Android {
namespace Internal {
class AndroidSdkManagerWidget;
class AndroidAvdManager;
class AvdModel: public QAbstractTableModel
@@ -98,6 +100,7 @@ private:
void disableAvdControls();
Ui_AndroidSettingsWidget *m_ui;
AndroidSdkManagerWidget *m_sdkManagerWidget = nullptr;
AndroidConfig m_androidConfig;
AvdModel m_AVDModel;
QFutureWatcher<CreateAvdInfo> m_futureWatcher;

View File

@@ -6,14 +6,26 @@
<rect>
<x>0</x>
<y>0</y>
<width>938</width>
<height>728</height>
<width>1131</width>
<height>826</height>
</rect>
</property>
<property name="windowTitle">
<string>Android Configuration</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item>
<widget class="QGroupBox" name="javaSettingsGroup">
<property name="minimumSize">
@@ -175,122 +187,167 @@
<widget class="Utils::DetailsWidget" name="kitWarningDetails" native="true"/>
</item>
<item>
<widget class="QFrame" name="AVDManagerFrame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
<widget class="QTabWidget" name="managerTabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="3" column="1">
<widget class="QPushButton" name="AVDStartPushButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Start...</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="AVDManagerLabel">
<property name="text">
<string>AVD Manager</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="DataPartitionSizeLable">
<property name="text">
<string>System/data partition size:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="DataPartitionSizeSpinBox">
<property name="suffix">
<string> Mb</string>
</property>
<property name="maximum">
<number>99999</number>
</property>
<property name="value">
<number>1024</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="manageAVDPushButton">
<property name="text">
<string>Start AVD Manager...</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="AVDRemovePushButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="AVDAddPushButton">
<property name="text">
<string>Add...</string>
</property>
</widget>
</item>
<item row="1" column="0" rowspan="4">
<widget class="QTableView" name="AVDTableView">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="textElideMode">
<enum>Qt::ElideMiddle</enum>
</property>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item row="4" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>129</height>
</size>
</property>
</spacer>
</item>
</layout>
<widget class="QWidget" name="avdManagerTab">
<attribute name="title">
<string>AVD Manager</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTableView" name="AVDTableView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="textElideMode">
<enum>Qt::ElideMiddle</enum>
</property>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="DataPartitionSizeLable">
<property name="text">
<string>System/data partition size:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="DataPartitionSizeSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string> Mb</string>
</property>
<property name="maximum">
<number>99999</number>
</property>
<property name="value">
<number>1024</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="AVDAddPushButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Add...</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>8</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="AVDRemovePushButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="AVDStartPushButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Start...</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="nativeAvdManagerButton">
<property name="text">
<string>Native AVD Manager...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="sdkManagerTab">
<attribute name="title">
<string>SDK Manager</string>
</attribute>
</widget>
</widget>
</item>
</layout>
@@ -299,7 +356,7 @@
<customwidget>
<class>Utils::PathChooser</class>
<extends>QWidget</extends>
<header>utils/pathchooser.h</header>
<header location="global">utils/pathchooser.h</header>
<container>1</container>
</customwidget>
<customwidget>