iOS: Remove simulator management from settings page

Simulators can be managed via Xcode, which must be installed anyway. Re-
implementing this functionality is not useful, error-prone, and a
maintenance burden. Point users to the corresponding Xcode documentation
instead.

Add a button for updating the list of simulators in the run configuration
settings (which didn't update when simulators were changed in Xcode).

Change-Id: I5a861f21851bb866d45a703f46bb20ed5df960e8
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
This commit is contained in:
Eike Ziller
2024-04-03 15:58:07 +02:00
parent 0be8e6ed01
commit e8d6e4fc34
12 changed files with 35 additions and 940 deletions

View File

@@ -2,7 +2,6 @@ add_qtc_plugin(Ios
DEPENDS QmlDebug Qt::Xml
PLUGIN_DEPENDS Core Debugger ProjectExplorer QmakeProjectManager CMakeProjectManager
SOURCES
createsimulatordialog.cpp createsimulatordialog.h
devicectlutils.cpp
devicectlutils.h
ios.qrc
@@ -23,8 +22,6 @@ add_qtc_plugin(Ios
iostoolhandler.cpp iostoolhandler.h
iostr.h
simulatorcontrol.cpp simulatorcontrol.h
simulatorinfomodel.cpp simulatorinfomodel.h
simulatoroperationdialog.cpp simulatoroperationdialog.h
)
extend_qtc_plugin(Ios

View File

@@ -1,172 +0,0 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "createsimulatordialog.h"
#include "iostr.h"
#include "simulatorcontrol.h"
#include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/layoutbuilder.h>
#include <QApplication>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
namespace Ios::Internal {
CreateSimulatorDialog::CreateSimulatorDialog(QWidget *parent)
: QDialog(parent)
{
resize(320, 160);
setWindowTitle(Tr::tr("Create Simulator"));
m_nameEdit = new QLineEdit(this);
m_deviceTypeCombo = new QComboBox(this);
m_runtimeCombo = new QComboBox(this);
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
using namespace Layouting;
Column {
Form {
Tr::tr("Simulator name:"), m_nameEdit, br,
Tr::tr("Device type:"), m_deviceTypeCombo, br,
Tr::tr("OS version:"), m_runtimeCombo, br,
},
buttonBox
}.attachTo(this);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
const auto enableOk = [this, buttonBox] {
buttonBox->button(QDialogButtonBox::Ok)->setEnabled(
!m_nameEdit->text().isEmpty() &&
m_deviceTypeCombo->currentIndex() > 0 &&
m_runtimeCombo->currentIndex() > 0);
};
connect(m_nameEdit, &QLineEdit::textChanged, this, enableOk);
connect(m_runtimeCombo, &QComboBox::currentIndexChanged, this, enableOk);
connect(m_deviceTypeCombo, &QComboBox::currentIndexChanged, this, [this, enableOk] {
populateRuntimes(m_deviceTypeCombo->currentData().value<DeviceTypeInfo>());
enableOk();
});
m_futureSync.addFuture(Utils::onResultReady(SimulatorControl::updateDeviceTypes(), this,
&CreateSimulatorDialog::populateDeviceTypes));
QFuture<QList<RuntimeInfo>> runtimesfuture = SimulatorControl::updateRuntimes();
Utils::onResultReady(runtimesfuture, this, [this](const QList<RuntimeInfo> &runtimes) {
m_runtimes = runtimes;
});
m_futureSync.addFuture(runtimesfuture);
populateRuntimes(DeviceTypeInfo());
}
CreateSimulatorDialog::~CreateSimulatorDialog() = default;
/*!
Returns the simulator name entered by user.
*/
QString CreateSimulatorDialog::name() const
{
return m_nameEdit->text();
}
/*!
Returns the simulator runtime (OS version) selected by user.
Though the runtimes are filtered by the selected device type but the runtime camppatibility is
not checked. i.e. User can select the Runtime iOS 10.2 for iPhone 4 but the combination is not
possible as iOS 10.2 is not compatible with iPhone 4. In this case the command to create
simulator shall fail with an error message describing the compatibility.
*/
RuntimeInfo CreateSimulatorDialog::runtime() const
{
return m_runtimeCombo->currentData().value<RuntimeInfo>();
}
/*!
Returns the selected device type.
*/
DeviceTypeInfo CreateSimulatorDialog::deviceType() const
{
return m_deviceTypeCombo->currentData().value<DeviceTypeInfo>();
}
/*!
Populates the devices types. Similar device types are grouped together.
*/
void CreateSimulatorDialog::populateDeviceTypes(const QList<DeviceTypeInfo> &deviceTypes)
{
m_deviceTypeCombo->clear();
m_deviceTypeCombo->addItem(Tr::tr("None"));
if (deviceTypes.isEmpty())
return;
m_deviceTypeCombo->insertSeparator(1);
auto addItems = [this, deviceTypes](const QString &filter) {
const auto filteredTypes = Utils::filtered(deviceTypes, [filter](const DeviceTypeInfo &type){
return type.name.contains(filter, Qt::CaseInsensitive);
});
for (auto type : filteredTypes) {
m_deviceTypeCombo->addItem(type.name, QVariant::fromValue<DeviceTypeInfo>(type));
}
return filteredTypes.count();
};
if (addItems(QStringLiteral("iPhone")) > 0)
m_deviceTypeCombo->insertSeparator(m_deviceTypeCombo->count());
if (addItems(QStringLiteral("iPad")) > 0)
m_deviceTypeCombo->insertSeparator(m_deviceTypeCombo->count());
if (addItems(QStringLiteral("TV")) > 0)
m_deviceTypeCombo->insertSeparator(m_deviceTypeCombo->count());
addItems(QStringLiteral("Watch"));
}
/*!
Populates the available runtimes. Though the runtimes are filtered by the selected device type
but the runtime camppatibility is not checked. i.e. User can select the Runtime iOS 10.2 for
iPhone 4 but the combination is not possible as iOS 10.2 is not compatible with iPhone 4. In
this case the command to create simulator shall fail with an error message describing the
compatibility issue.
*/
void CreateSimulatorDialog::populateRuntimes(const DeviceTypeInfo &deviceType)
{
m_runtimeCombo->clear();
m_runtimeCombo->addItem(Tr::tr("None"));
if (deviceType.name.isEmpty())
return;
m_runtimeCombo->insertSeparator(1);
auto addItems = [this](const QString &filter) {
const auto filteredTypes = Utils::filtered(m_runtimes, [filter](const RuntimeInfo &runtime){
return runtime.name.contains(filter, Qt::CaseInsensitive);
});
for (auto runtime : filteredTypes) {
m_runtimeCombo->addItem(runtime.name, QVariant::fromValue<RuntimeInfo>(runtime));
}
};
if (deviceType.name.contains(QStringLiteral("iPhone")))
addItems(QStringLiteral("iOS"));
else if (deviceType.name.contains(QStringLiteral("iPad")))
addItems(QStringLiteral("iOS"));
else if (deviceType.name.contains(QStringLiteral("TV")))
addItems(QStringLiteral("tvOS"));
else if (deviceType.name.contains(QStringLiteral("Watch")))
addItems(QStringLiteral("watchOS"));
}
} // Ios::Internal

View File

@@ -1,46 +0,0 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <utils/futuresynchronizer.h>
#include <QDialog>
QT_BEGIN_NAMESPACE
class QComboBox;
class QLineEdit;
QT_END_NAMESPACE
namespace Ios::Internal {
class DeviceTypeInfo;
class RuntimeInfo;
/*!
A dialog to select the iOS Device type and the runtime for a new
iOS simulator device.
*/
class CreateSimulatorDialog : public QDialog
{
public:
explicit CreateSimulatorDialog(QWidget *parent = nullptr);
~CreateSimulatorDialog() override;
QString name() const;
RuntimeInfo runtime() const;
DeviceTypeInfo deviceType() const;
private:
void populateDeviceTypes(const QList<DeviceTypeInfo> &deviceTypes);
void populateRuntimes(const DeviceTypeInfo &deviceType);
QList<RuntimeInfo> m_runtimes;
QLineEdit *m_nameEdit;
QComboBox *m_deviceTypeCombo;
QComboBox *m_runtimeCombo;
Utils::FutureSynchronizer m_futureSync; // Keep me last
};
} // Ios::Internal

View File

@@ -69,7 +69,6 @@ const bool IgnoreAllDevicesDefault = false;
const char SettingsGroup[] = "IosConfigurations";
const char ignoreAllDevicesKey[] = "IgnoreAllDevices";
const char screenshotDirPathKey[] = "ScreeshotDirPath";
const char provisioningTeamsTag[] = "IDEProvisioningTeams";
const char freeTeamTag[] = "isFreeProvisioningTeam";
@@ -343,19 +342,6 @@ void IosConfigurations::setIgnoreAllDevices(bool ignoreDevices)
}
}
void IosConfigurations::setScreenshotDir(const FilePath &path)
{
if (m_instance->m_screenshotDir != path) {
m_instance->m_screenshotDir = path;
m_instance->save();
}
}
FilePath IosConfigurations::screenshotDir()
{
return m_instance->m_screenshotDir;
}
FilePath IosConfigurations::developerPath()
{
return m_instance->m_developerPath;
@@ -366,20 +352,11 @@ QVersionNumber IosConfigurations::xcodeVersion()
return m_instance->m_xcodeVersion;
}
static FilePath defaultScreenshotDirPath()
{
return FilePath::fromUserInput(
QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).constFirst());
}
void IosConfigurations::save()
{
QtcSettings *settings = Core::ICore::settings();
settings->beginGroup(SettingsGroup);
settings->setValueWithDefault(ignoreAllDevicesKey, m_ignoreAllDevices, IgnoreAllDevicesDefault);
settings->setValueWithDefault(screenshotDirPathKey,
m_screenshotDir.toSettings(),
defaultScreenshotDirPath().toSettings());
settings->endGroup();
}
@@ -396,11 +373,6 @@ void IosConfigurations::load()
QtcSettings *settings = Core::ICore::settings();
settings->beginGroup(SettingsGroup);
m_ignoreAllDevices = settings->value(ignoreAllDevicesKey, IgnoreAllDevicesDefault).toBool();
m_screenshotDir = FilePath::fromSettings(settings->value(screenshotDirPathKey));
if (!m_screenshotDir.isWritableDir())
m_screenshotDir = defaultScreenshotDirPath();
settings->endGroup();
}

View File

@@ -78,8 +78,6 @@ public:
static void initialize();
static bool ignoreAllDevices();
static void setIgnoreAllDevices(bool ignoreDevices);
static void setScreenshotDir(const Utils::FilePath &path);
static Utils::FilePath screenshotDir();
static Utils::FilePath developerPath();
static QVersionNumber xcodeVersion();
static Utils::FilePath lldbPath();
@@ -103,7 +101,6 @@ private:
void loadProvisioningData(bool notify = true);
Utils::FilePath m_developerPath;
Utils::FilePath m_screenshotDir;
QVersionNumber m_xcodeVersion;
bool m_ignoreAllDevices;
QFileSystemWatcher *m_provisioningDataWatcher = nullptr;

View File

@@ -21,10 +21,11 @@
#include <projectexplorer/target.h>
#include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/filepath.h>
#include <utils/layoutbuilder.h>
#include <utils/qtcprocess.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <QAction>
#include <QApplication>
@@ -33,6 +34,7 @@
#include <QLabel>
#include <QLineEdit>
#include <QList>
#include <QPushButton>
#include <QVariant>
#include <QWidget>
@@ -342,12 +344,25 @@ void IosDeviceTypeAspect::addToLayout(Layouting::LayoutItem &parent)
m_deviceTypeLabel = new QLabel(Tr::tr("Device type:"));
parent.addItems({m_deviceTypeLabel, m_deviceTypeComboBox});
m_updateButton = new QPushButton(Tr::tr("Update"));
parent.addItems({m_deviceTypeLabel, m_deviceTypeComboBox, m_updateButton, Layouting::st});
updateValues();
connect(m_deviceTypeComboBox, &QComboBox::currentIndexChanged,
this, &IosDeviceTypeAspect::setDeviceTypeIndex);
connect(m_updateButton, &QPushButton::clicked, this, [this] {
m_updateButton->setEnabled(false);
Utils::onFinished(
QFuture<void>(SimulatorControl::updateAvailableSimulators(this)),
this,
[this](QFuture<void>) {
m_updateButton->setEnabled(true);
m_deviceTypeModel.clear();
updateValues();
});
});
}
void IosDeviceTypeAspect::setDeviceTypeIndex(int devIndex)
@@ -363,6 +378,7 @@ void IosDeviceTypeAspect::updateValues()
bool showDeviceSelector = deviceType().type != IosDeviceType::IosDevice;
m_deviceTypeLabel->setVisible(showDeviceSelector);
m_deviceTypeComboBox->setVisible(showDeviceSelector);
m_updateButton->setVisible(showDeviceSelector);
if (showDeviceSelector && m_deviceTypeModel.rowCount() == 0) {
const QList<SimulatorInfo> devices = SimulatorControl::availableSimulators();
for (const SimulatorInfo &device : devices) {

View File

@@ -3,8 +3,6 @@
#pragma once
#include "iosconstants.h"
#include "iosconfigurations.h"
#include "iossimulator.h"
#include <projectexplorer/runconfiguration.h>
@@ -12,9 +10,13 @@
#include <utils/fileutils.h>
#include <QComboBox>
#include <QStandardItemModel>
QT_BEGIN_NAMESPACE
class QComboBox;
class QPushButton;
QT_END_NAMESPACE
namespace Ios::Internal {
class IosRunConfiguration;
@@ -58,6 +60,7 @@ private:
QStandardItemModel m_deviceTypeModel;
QLabel *m_deviceTypeLabel = nullptr;
QComboBox *m_deviceTypeComboBox = nullptr;
QPushButton *m_updateButton = nullptr;
};
class IosRunConfiguration : public ProjectExplorer::RunConfiguration

View File

@@ -3,34 +3,17 @@
#include "iossettingspage.h"
#include "createsimulatordialog.h"
#include "iosconfigurations.h"
#include "iosconstants.h"
#include "iostr.h"
#include "simulatorcontrol.h"
#include "simulatorinfomodel.h"
#include "simulatoroperationdialog.h"
#include <coreplugin/dialogs/ioptionspage.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/layoutbuilder.h>
#include <utils/pathchooser.h>
#include <QCheckBox>
#include <QDateTime>
#include <QGroupBox>
#include <QHeaderView>
#include <QInputDialog>
#include <QLabel>
#include <QMessageBox>
#include <QPointer>
#include <QPushButton>
#include <QSortFilterProxyModel>
#include <QTreeView>
using namespace std::placeholders;
@@ -47,43 +30,10 @@ private:
void saveSettings();
void onStart();
void onCreate();
void onReset();
void onRename();
void onDelete();
void onScreenshot();
void onSelectionChanged();
private:
Utils::PathChooser *m_pathWidget;
QPushButton *m_startButton;
QPushButton *m_renameButton;
QPushButton *m_deleteButton;
QPushButton *m_resetButton;
QTreeView *m_deviceView;
QCheckBox *m_deviceAskCheckBox;
};
const int simStartWarnCount = 4;
static SimulatorInfoList selectedSimulators(const QTreeView *deviceTreeView)
{
SimulatorInfoList list;
QItemSelectionModel *selectionModel = deviceTreeView->selectionModel();
for (QModelIndex index: selectionModel->selectedRows())
list << deviceTreeView->model()->data(index, Qt::UserRole).value<SimulatorInfo>();
return list;
}
static void onSimOperation(const SimulatorInfo &simInfo,
SimulatorOperationDialog *dlg,
const QString &contextStr,
const SimulatorControl::Response &response)
{
dlg->addMessage(simInfo, response, contextStr);
}
IosSettingsWidget::IosSettingsWidget()
{
setWindowTitle(Tr::tr("iOS Configuration"));
@@ -91,43 +41,14 @@ IosSettingsWidget::IosSettingsWidget()
m_deviceAskCheckBox = new QCheckBox(Tr::tr("Ask about devices not in developer mode"));
m_deviceAskCheckBox->setChecked(!IosConfigurations::ignoreAllDevices());
m_renameButton = new QPushButton(Tr::tr("Rename"));
m_renameButton->setEnabled(false);
m_renameButton->setToolTip(Tr::tr("Rename a simulator device."));
m_deleteButton = new QPushButton(Tr::tr("Delete"));
m_deleteButton->setEnabled(false);
m_deleteButton->setToolTip(Tr::tr("Delete simulator devices."));
m_resetButton = new QPushButton(Tr::tr("Reset"));
m_resetButton->setEnabled(false);
m_resetButton->setToolTip(Tr::tr("Reset contents and settings of simulator devices."));
auto createButton = new QPushButton(Tr::tr("Create"));
createButton->setToolTip(Tr::tr("Create a new simulator device."));
m_startButton = new QPushButton(Tr::tr("Start"));
m_startButton->setEnabled(false);
m_startButton->setToolTip(Tr::tr("Start simulator devices."));
auto proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(new SimulatorInfoModel(this));
m_deviceView = new QTreeView;
m_deviceView->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
m_deviceView->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_deviceView->setSelectionBehavior(QAbstractItemView::SelectRows);
m_deviceView->setSortingEnabled(true);
m_deviceView->setModel(proxyModel);
m_deviceView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
m_pathWidget = new Utils::PathChooser;
m_pathWidget->setExpectedKind(Utils::PathChooser::ExistingDirectory);
m_pathWidget->lineEdit()->setReadOnly(true);
m_pathWidget->setFilePath(IosConfigurations::screenshotDir());
m_pathWidget->addButton(Tr::tr("Screenshot"), this,
std::bind(&IosSettingsWidget::onScreenshot, this));
auto xcodeLabel = new QLabel(
Tr::tr("Configure available simulator devices in <a href=\"%1\">Xcode</a>.")
.arg("https://developer.apple.com/documentation/xcode/"
"running-your-app-in-simulator-or-on-a-device/"
"#Configure-the-list-of-simulated-devices"));
xcodeLabel->setOpenExternalLinks(true);
// clang-format off
using namespace Layouting;
Column {
Group {
@@ -136,33 +57,11 @@ IosSettingsWidget::IosSettingsWidget()
},
Group {
title(Tr::tr("Simulator")),
Column {
Row {
m_deviceView,
Column {
createButton,
st, // FIXME: Better some fixed space?
m_startButton,
m_renameButton,
m_resetButton,
m_deleteButton,
Row { xcodeLabel }
},
st
},
},
hr,
Row { Tr::tr("Screenshot directory:"), m_pathWidget }
}
}
}.attachTo(this);
connect(m_startButton, &QPushButton::clicked, this, &IosSettingsWidget::onStart);
connect(createButton, &QPushButton::clicked, this, &IosSettingsWidget::onCreate);
connect(m_renameButton, &QPushButton::clicked, this, &IosSettingsWidget::onRename);
connect(m_resetButton, &QPushButton::clicked, this, &IosSettingsWidget::onReset);
connect(m_deleteButton, &QPushButton::clicked, this, &IosSettingsWidget::onDelete);
connect(m_deviceView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &IosSettingsWidget::onSelectionChanged);
// clang-format on
}
IosSettingsWidget::~IosSettingsWidget() = default;
@@ -173,229 +72,9 @@ void IosSettingsWidget::apply()
IosConfigurations::updateAutomaticKitList();
}
/*!
Called on start button click. Selected simulator devices are started. Multiple devices can be
started simultaneously provided they in shutdown state.
*/
void IosSettingsWidget::onStart()
{
const SimulatorInfoList simulatorInfoList = selectedSimulators(m_deviceView);
if (simulatorInfoList.isEmpty())
return;
if (simulatorInfoList.count() > simStartWarnCount) {
const QString message =
Tr::tr("You are trying to launch %n simulators simultaneously. This "
"will take significant system resources. Do you really want to "
"continue?", "", simulatorInfoList.count());
const int buttonCode = QMessageBox::warning(this, Tr::tr("Simulator Start"), message,
QMessageBox::Ok | QMessageBox::Abort,
QMessageBox::Abort);
if (buttonCode == QMessageBox::Abort)
return;
}
QPointer<SimulatorOperationDialog> statusDialog = new SimulatorOperationDialog(this);
statusDialog->setAttribute(Qt::WA_DeleteOnClose);
statusDialog->addMessage(Tr::tr("Starting %n simulator device(s)...", "", simulatorInfoList.count()),
Utils::NormalMessageFormat);
QList<QFuture<void>> futureList;
for (const SimulatorInfo &info : simulatorInfoList) {
if (!info.isShutdown()) {
statusDialog->addMessage(Tr::tr("Cannot start simulator (%1, %2) in current state: %3.")
.arg(info.name)
.arg(info.runtimeName)
.arg(info.state),
Utils::StdErrFormat);
} else {
futureList << QFuture<void>(Utils::onResultReady(
SimulatorControl::startSimulator(info.identifier), this,
std::bind(onSimOperation, info, statusDialog, Tr::tr("simulator start"), _1)));
}
}
statusDialog->addFutures(futureList);
statusDialog->exec(); // Modal dialog returns only when all the operations are done or cancelled.
}
/*!
Called on create button click. User is presented with the create simulator dialog and with the
selected options a new device is created.
*/
void IosSettingsWidget::onCreate()
{
QPointer<SimulatorOperationDialog> statusDialog = new SimulatorOperationDialog(this);
statusDialog->setAttribute(Qt::WA_DeleteOnClose);
statusDialog->addMessage(Tr::tr("Creating simulator device..."), Utils::NormalMessageFormat);
const auto onSimulatorCreate = [statusDialog](const QString &name,
const SimulatorControl::Response &response) {
if (response) {
statusDialog->addMessage(Tr::tr("Simulator device (%1) created.\nUDID: %2")
.arg(name)
.arg(response->simUdid),
Utils::StdOutFormat);
} else {
statusDialog->addMessage(Tr::tr("Simulator device (%1) creation failed.\nError: %2")
.arg(name)
.arg(response.error()),
Utils::StdErrFormat);
}
};
CreateSimulatorDialog createDialog(this);
if (createDialog.exec() == QDialog::Accepted) {
QFuture<void> f = QFuture<void>(Utils::onResultReady(SimulatorControl::createSimulator(
createDialog.name(), createDialog.deviceType(), createDialog.runtime()),
this, std::bind(onSimulatorCreate, createDialog.name(), _1)));
statusDialog->addFutures({ f });
statusDialog->exec(); // Modal dialog returns only when all the operations are done or cancelled.
}
}
/*!
Called on reset button click. Contents and settings of the selected devices are erased. Multiple
devices can be erased simultaneously provided they in shutdown state.
*/
void IosSettingsWidget::onReset()
{
const SimulatorInfoList simulatorInfoList = selectedSimulators(m_deviceView);
if (simulatorInfoList.isEmpty())
return;
const int userInput = QMessageBox::question(this, Tr::tr("Reset"),
Tr::tr("Do you really want to reset the contents and settings"
" of the %n selected device(s)?", "",
simulatorInfoList.count()));
if (userInput == QMessageBox::No)
return;
QPointer<SimulatorOperationDialog> statusDialog = new SimulatorOperationDialog(this);
statusDialog->setAttribute(Qt::WA_DeleteOnClose);
statusDialog->addMessage(Tr::tr("Resetting contents and settings..."),
Utils::NormalMessageFormat);
QList<QFuture<void>> futureList;
for (const SimulatorInfo &info : simulatorInfoList) {
futureList << QFuture<void>(Utils::onResultReady(
SimulatorControl::resetSimulator(info.identifier), this,
std::bind(onSimOperation, info, statusDialog, Tr::tr("simulator reset"), _1)));
}
statusDialog->addFutures(futureList);
statusDialog->exec(); // Modal dialog returns only when all the operations are done or cancelled.
}
/*!
Called on rename button click. Selected device is renamed. Only one device can be renamed at a
time. Rename button is disabled on multi-selection.
*/
void IosSettingsWidget::onRename()
{
const SimulatorInfoList simulatorInfoList = selectedSimulators(m_deviceView);
if (simulatorInfoList.isEmpty() || simulatorInfoList.count() > 1)
return;
const SimulatorInfo &simInfo = simulatorInfoList.at(0);
const QString newName = QInputDialog::getText(this, Tr::tr("Rename %1").arg(simInfo.name),
Tr::tr("Enter new name:"));
if (newName.isEmpty())
return;
QPointer<SimulatorOperationDialog> statusDialog = new SimulatorOperationDialog(this);
statusDialog->setAttribute(Qt::WA_DeleteOnClose);
statusDialog->addMessage(Tr::tr("Renaming simulator device..."), Utils::NormalMessageFormat);
QFuture<void> f = QFuture<void>(Utils::onResultReady(
SimulatorControl::renameSimulator(simInfo.identifier, newName), this,
std::bind(onSimOperation, simInfo, statusDialog, Tr::tr("simulator rename"), _1)));
statusDialog->addFutures({f});
statusDialog->exec(); // Modal dialog returns only when all the operations are done or cancelled.
}
/*!
Called on delete button click. Selected devices are deleted. Multiple devices can be deleted
simultaneously provided they in shutdown state.
*/
void IosSettingsWidget::onDelete()
{
const SimulatorInfoList simulatorInfoList = selectedSimulators(m_deviceView);
if (simulatorInfoList.isEmpty())
return;
const int userInput =
QMessageBox::question(this, Tr::tr("Delete Device"),
Tr::tr("Do you really want to delete the %n selected "
"device(s)?", "", simulatorInfoList.count()));
if (userInput == QMessageBox::No)
return;
QPointer<SimulatorOperationDialog> statusDialog = new SimulatorOperationDialog(this);
statusDialog->setAttribute(Qt::WA_DeleteOnClose);
statusDialog->addMessage(Tr::tr("Deleting %n simulator device(s)...", "", simulatorInfoList.count()),
Utils::NormalMessageFormat);
QList<QFuture<void>> futureList;
for (const SimulatorInfo &info : simulatorInfoList) {
futureList << QFuture<void>(Utils::onResultReady(
SimulatorControl::deleteSimulator(info.identifier), this,
std::bind(onSimOperation, info, statusDialog, Tr::tr("simulator delete"), _1)));
}
statusDialog->addFutures(futureList);
statusDialog->exec(); // Modal dialog returns only when all the operations are done or cancelled.
}
/*!
Called on screenshot button click. Screenshot of the selected devices are saved to the selected
path. Screenshot from multiple devices can be taken simultaneously provided they in booted state.
*/
void IosSettingsWidget::onScreenshot()
{
const SimulatorInfoList simulatorInfoList = selectedSimulators(m_deviceView);
if (simulatorInfoList.isEmpty())
return;
const auto generatePath = [this](const SimulatorInfo &info) {
const QString fileName = QString("%1_%2_%3.png").arg(info.name).arg(info.runtimeName)
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_HH-mm-ss-z")).replace(' ', '_');
return m_pathWidget->filePath().pathAppended(fileName).toString();
};
QPointer<SimulatorOperationDialog> statusDialog = new SimulatorOperationDialog(this);
statusDialog->setAttribute(Qt::WA_DeleteOnClose);
statusDialog->addMessage(Tr::tr("Capturing screenshots from %n device(s)...", "",
simulatorInfoList.count()), Utils::NormalMessageFormat);
QList<QFuture<void>> futureList;
for (const SimulatorInfo &info : simulatorInfoList) {
futureList << QFuture<void>(Utils::onResultReady(
SimulatorControl::takeSceenshot(info.identifier, generatePath(info)), this,
std::bind(onSimOperation, info, statusDialog, Tr::tr("simulator screenshot"), _1)));
}
statusDialog->addFutures(futureList);
statusDialog->exec(); // Modal dialog returns only when all the operations are done or cancelled.
}
void IosSettingsWidget::onSelectionChanged()
{
const SimulatorInfoList infoList = selectedSimulators(m_deviceView);
const bool hasRunning = Utils::anyOf(infoList, [](const SimulatorInfo &info) {
return info.isBooted();
});
const bool hasShutdown = Utils::anyOf(infoList, [](const SimulatorInfo &info) {
return info.isShutdown();
});
m_startButton->setEnabled(hasShutdown);
m_deleteButton->setEnabled(hasShutdown);
m_resetButton->setEnabled(hasShutdown);
m_renameButton->setEnabled(infoList.count() == 1 && hasShutdown);
m_pathWidget->buttonAtIndex(1)->setEnabled(hasRunning); // Screenshot button
}
void IosSettingsWidget::saveSettings()
{
IosConfigurations::setIgnoreAllDevices(!m_deviceAskCheckBox->isChecked());
IosConfigurations::setScreenshotDir(m_pathWidget->filePath());
}
// IosSettingsPage

View File

@@ -1,142 +0,0 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "simulatorinfomodel.h"
#include "iostr.h"
#include <utils/algorithm.h>
#include <utils/async.h>
#include <QTimer>
namespace Ios::Internal {
using namespace std::placeholders;
const int colCount = 3;
const int nameCol = 0;
const int runtimeCol = 1;
const int stateCol = 2;
const int deviceUpdateInterval = 1000; // Update simulator state every 1 sec.
SimulatorInfoModel::SimulatorInfoModel(QObject *parent) :
QAbstractItemModel(parent)
{
requestSimulatorInfo();
auto updateTimer = new QTimer(this);
connect(updateTimer, &QTimer::timeout, this, &SimulatorInfoModel::requestSimulatorInfo);
updateTimer->setInterval(deviceUpdateInterval);
updateTimer->start();
}
QVariant SimulatorInfoModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return {};
const SimulatorInfo &simInfo = m_simList[index.row()];
if (role == Qt::EditRole || role == Qt::DisplayRole) {
switch (index.column()) {
case nameCol:
return simInfo.name;
case runtimeCol:
return simInfo.runtimeName;
case stateCol:
return simInfo.state;
default:
return "";
}
} else if (role == Qt::ToolTipRole) {
return Tr::tr("UDID: %1").arg(simInfo.identifier);
} else if (role == Qt::UserRole) {
return QVariant::fromValue<SimulatorInfo>(simInfo);
}
return {};
}
int SimulatorInfoModel::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid())
return m_simList.count();
return 0;
}
int SimulatorInfoModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return colCount;
}
QVariant SimulatorInfoModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Vertical || section > colCount)
return {};
if (role == Qt::DisplayRole) {
switch (section) {
case nameCol:
return Tr::tr("Simulator Name");
case runtimeCol:
return Tr::tr("Runtime");
case stateCol:
return Tr::tr("Current State");
default:
return {};
}
}
return {};
}
QModelIndex SimulatorInfoModel::index(int row, int column, const QModelIndex &parent) const
{
return hasIndex(row, column, parent) ? createIndex(row, column) : QModelIndex();
}
QModelIndex SimulatorInfoModel::parent(const QModelIndex &) const
{
return {};
}
void SimulatorInfoModel::requestSimulatorInfo()
{
m_fetchFuture.flushFinishedFutures();
if (!m_fetchFuture.isEmpty())
return; // Ignore the request if the last request is still pending.
m_fetchFuture.addFuture(Utils::onResultReady(SimulatorControl::updateAvailableSimulators(this),
this, &SimulatorInfoModel::populateSimulators));
}
void SimulatorInfoModel::populateSimulators(const SimulatorInfoList &simulatorList)
{
if (m_simList.isEmpty() || m_simList.count() != simulatorList.count()) {
// Reset the model in case of addition or deletion.
beginResetModel();
m_simList = simulatorList;
endResetModel();
} else {
// update the rows with data chagne. e.g. state changes.
auto newItr = simulatorList.cbegin();
int start = -1, end = -1;
std::list<std::pair<int, int>> updatedIndexes;
for (auto itr = m_simList.cbegin(); itr < m_simList.cend(); ++itr, ++newItr) {
if (*itr == *newItr) {
if (end != -1)
updatedIndexes.emplace_back(start, end - 1);
start = std::distance(m_simList.cbegin(), itr);
end = -1;
} else {
end = std::distance(m_simList.cbegin(), itr);
}
}
m_simList = simulatorList;
for (auto pair: updatedIndexes)
emit dataChanged(index(pair.first,0), index(pair.second, colCount - 1));
}
}
} // Ios::Internal

View File

@@ -1,37 +0,0 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "simulatorcontrol.h"
#include <utils/futuresynchronizer.h>
#include <QAbstractListModel>
namespace Ios::Internal {
using SimulatorInfoList = QList<SimulatorInfo>;
class SimulatorInfoModel : public QAbstractItemModel
{
public:
SimulatorInfoModel(QObject *parent = nullptr);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent) const override;
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 &) const override;
private:
void requestSimulatorInfo();
void populateSimulators(const SimulatorInfoList &simulatorList);
Utils::FutureSynchronizer m_fetchFuture;
SimulatorInfoList m_simList;
};
} // Ios::Internal

View File

@@ -1,125 +0,0 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "simulatoroperationdialog.h"
#include "iostr.h"
#include <utils/layoutbuilder.h>
#include <utils/outputformatter.h>
#include <utils/qtcassert.h>
#include <QDialogButtonBox>
#include <QFutureWatcher>
#include <QLoggingCategory>
#include <QPlainTextEdit>
#include <QProgressBar>
#include <QPushButton>
namespace Ios::Internal {
static Q_LOGGING_CATEGORY(iosCommon, "qtc.ios.common", QtWarningMsg)
SimulatorOperationDialog::SimulatorOperationDialog(QWidget *parent) :
// TODO: Maximize buttong only because of QTBUG-41932
QDialog(parent,Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMaximizeButtonHint)
{
resize(580, 320);
setModal(true);
setWindowTitle(Tr::tr("Simulator Operation Status"));
auto messageEdit = new QPlainTextEdit;
messageEdit->setReadOnly(true);
m_progressBar = new QProgressBar;
m_progressBar->setMaximum(0);
m_progressBar->setValue(-1);
m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
m_formatter = new Utils::OutputFormatter;
m_formatter->setPlainTextEdit(messageEdit);
using namespace Layouting;
Column {
messageEdit,
m_progressBar,
m_buttonBox
}.attachTo(this);
connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
SimulatorOperationDialog::~SimulatorOperationDialog()
{
// Cancel all pending futures.
const auto futureWatchList = m_futureWatchList;
for (auto watcher : futureWatchList) {
if (!watcher->isFinished())
watcher->cancel();
}
// wait for futures to finish
for (auto watcher : futureWatchList) {
if (!watcher->isFinished())
watcher->waitForFinished();
delete watcher;
}
delete m_formatter;
}
void SimulatorOperationDialog::addFutures(const QList<QFuture<void> > &futureList)
{
for (auto future : futureList) {
if (!future.isFinished() || !future.isCanceled()) {
auto watcher = new QFutureWatcher<void>;
connect(watcher, &QFutureWatcherBase::finished, this, [this, watcher] {
m_futureWatchList.removeAll(watcher);
watcher->deleteLater();
updateInputs();
});
watcher->setFuture(future);
m_futureWatchList << watcher;
}
}
updateInputs();
}
void SimulatorOperationDialog::addMessage(const QString &message, Utils::OutputFormat format)
{
m_formatter->appendMessage(message + "\n\n", format);
}
void SimulatorOperationDialog::addMessage(const SimulatorInfo &siminfo,
const SimulatorControl::Response &response,
const QString &context)
{
if (response) {
QTC_CHECK(siminfo.identifier == response->simUdid);
addMessage(Tr::tr("%1, %2\nOperation %3 completed successfully.").arg(siminfo.name)
.arg(siminfo.runtimeName).arg(context), Utils::StdOutFormat);
} else {
QString erroMsg = response.error();
QString message = Tr::tr("%1, %2\nOperation %3 failed.\nUDID: %4\nError: %5").arg(siminfo.name)
.arg(siminfo.runtimeName).arg(context).arg(siminfo.identifier)
.arg(erroMsg.isEmpty() ? Tr::tr("Unknown") : erroMsg);
addMessage(message, Utils::StdErrFormat);
qCDebug(iosCommon) << message;
}
}
void SimulatorOperationDialog::updateInputs()
{
bool enableOk = m_futureWatchList.isEmpty();
m_buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(!enableOk);
m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enableOk);
if (enableOk) {
addMessage(Tr::tr("Done."), Utils::NormalMessageFormat);
m_progressBar->setMaximum(1); // Stop progress bar.
}
}
} // Ios::Internal

View File

@@ -1,47 +0,0 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "simulatorcontrol.h"
#include <utils/outputformat.h>
#include <QDialog>
#include <QFuture>
#include <QList>
QT_BEGIN_NAMESPACE
class QDialogButtonBox;
class QProgressBar;
QT_END_NAMESPACE
namespace Utils { class OutputFormatter; }
namespace Ios::Internal {
class SimulatorOperationDialog : public QDialog
{
public:
explicit SimulatorOperationDialog(QWidget *parent = nullptr);
~SimulatorOperationDialog() override;
public:
void addFutures(const QList<QFuture<void> > &futureList);
void addMessage(const QString &message, Utils::OutputFormat format);
void addMessage(const SimulatorInfo &siminfo,
const SimulatorControl::Response &response,
const QString &context);
private:
void updateInputs();
Utils::OutputFormatter *m_formatter = nullptr;
QList<QFutureWatcher<void> *> m_futureWatchList;
QProgressBar *m_progressBar;
QDialogButtonBox *m_buttonBox;
};
} // Ios::Internal