From fbfa825535b52686989ec997766acfc74a4803ed Mon Sep 17 00:00:00 2001 From: Friedemann Kleint <Friedemann.Kleint@nokia.com> Date: Wed, 14 Apr 2010 15:53:35 +0200 Subject: [PATCH] Symbian/Linux: Remove GnuPoc autodetection code and make it possible to configure it in the settings. Split up S60Devices up into a class hierarchy and implement the Autodetected Windows case and the manually configured Linux case separately for code clarity. Same with the settings widgets. Reviewed-by: con --- .../qt-s60/gccetoolchain.cpp | 2 +- .../qt4projectmanager/qt-s60/s60devices.cpp | 333 +++++++++++++----- .../qt4projectmanager/qt-s60/s60devices.h | 99 +++++- .../qt-s60/s60devicespreferencepane.cpp | 330 +++++++++++++++-- .../qt-s60/s60devicespreferencepane.h | 80 ++++- .../qt-s60/s60devicespreferencepane.ui | 159 +++++---- .../qt4projectmanager/qt-s60/s60manager.cpp | 5 +- 7 files changed, 793 insertions(+), 215 deletions(-) diff --git a/src/plugins/qt4projectmanager/qt-s60/gccetoolchain.cpp b/src/plugins/qt4projectmanager/qt-s60/gccetoolchain.cpp index 7ae453be27d..8b73e3633b7 100644 --- a/src/plugins/qt4projectmanager/qt-s60/gccetoolchain.cpp +++ b/src/plugins/qt4projectmanager/qt-s60/gccetoolchain.cpp @@ -51,7 +51,7 @@ static QString gcceCommand(const QString &dir) gcce += QLatin1String(".exe"); #endif const QString rc = env.searchInPath(gcce); - if (rc.isEmpty()) { + if (debug && rc.isEmpty()) { const QString msg = QString::fromLatin1("GCCEToolChain: Unable to locate '%1' in '%2' (GCCE root: '%3')") .arg(gcce, env.value(QLatin1String("PATH")), dir); qWarning("%s", qPrintable(msg)); diff --git a/src/plugins/qt4projectmanager/qt-s60/s60devices.cpp b/src/plugins/qt4projectmanager/qt-s60/s60devices.cpp index 5e159c4c06a..3b4fe3da23b 100644 --- a/src/plugins/qt4projectmanager/qt-s60/s60devices.cpp +++ b/src/plugins/qt4projectmanager/qt-s60/s60devices.cpp @@ -31,6 +31,7 @@ #include "gccetoolchain.h" #include <projectexplorer/environment.h> +#include <coreplugin/icore.h> #include <QtCore/QSettings> #include <QtCore/QXmlStreamReader> @@ -51,16 +52,36 @@ namespace { const char * const DEVICE_DEFAULT = "default"; const char * const DEVICE_EPOCROOT = "epocroot"; const char * const DEVICE_TOOLSROOT = "toolsroot"; + const char * const GNUPOC_SETTINGS_GROUP = "GnuPocSDKs"; + const char * const AUTODETECT_SETTINGS_GROUP = "SymbianSDKs"; + const char * const SDK_QT_ASSOC_SETTINGS_KEY_ROOT = "SymbianSDK"; + const char * const SETTINGS_DEFAULT_SDK_POSTFIX = ",default"; } namespace Qt4ProjectManager { namespace Internal { +static int findDefaultDevice(const QList<S60Devices::Device> &d) +{ + const int count = d.size(); + for (int i = 0; i < count; i++) + if (d.at(i).isDefault) + return i; + return -1; +} + S60Devices::Device::Device() : isDefault(false) { } +bool S60Devices::Device::equals(const Device &rhs) const +{ + return id == rhs.id && name == rhs.name && isDefault == rhs.isDefault + && epocRoot == rhs.epocRoot && toolsRoot == rhs.toolsRoot + && qt == rhs.qt; +} + QString S60Devices::Device::toHtml() const { QString rc; @@ -79,60 +100,180 @@ QString S60Devices::Device::toHtml() const return rc; } -S60Devices::S60Devices(QObject *parent) - : QObject(parent) +// ------------ S60Devices +S60Devices::S60Devices(QObject *parent) : QObject(parent) { } -// GNU-Poc stuff -static const char *epocRootC = "EPOCROOT"; +QList<S60Devices::Device> S60Devices::devices() const +{ + return m_devices; +} -static inline QString msgEnvVarNotSet(const char *var) +void S60Devices::setDevices(const QList<Device> &devices) { - return QString::fromLatin1("The environment variable %1 is not set.").arg(QLatin1String(var)); + if (m_devices != devices) { + m_devices = devices; + // Ensure a default device + if (!m_devices.isEmpty() && findDefaultDevice(m_devices) == -1) + m_devices.front().isDefault = true; + writeSettings(); + emit qtVersionsChanged(); + } } -static inline QString msgEnvVarDirNotExist(const QString &dir, const char *var) +const QList<S60Devices::Device> &S60Devices::devicesList() const { - return QString::fromLatin1("The directory %1 pointed to by the environment variable %2 is not set.").arg(dir, QLatin1String(var)); + return m_devices; } -bool S60Devices::readLinux() +QList<S60Devices::Device> &S60Devices::devicesList() { - // Detect GNUPOC_ROOT/EPOC ROOT - const QByteArray epocRootA = qgetenv(epocRootC); - if (epocRootA.isEmpty()) { - m_errorString = msgEnvVarNotSet(epocRootC); - return false; - } + return m_devices; +} - const QDir epocRootDir(QString::fromLocal8Bit(epocRootA)); - if (!epocRootDir.exists()) { - m_errorString = msgEnvVarDirNotExist(epocRootDir.absolutePath(), epocRootC); - return false; - } +S60Devices *S60Devices::createS60Devices(QObject *parent) +{ + S60Devices *rc = 0; +#ifdef Q_OS_WIN + AutoDetectS60QtDevices *ad = new AutoDetectS60QtDevices(parent); + ad->detectDevices(); + rc = ad; +#else + rc = new GnuPocS60Devices(parent); +#endif + rc->readSettings(); + return rc; +} - // Check Qt - Device device; - device.id = device.name = QLatin1String("GnuPoc"); - device.toolsRoot = device.epocRoot = epocRootDir.absolutePath(); - device.isDefault = true; - m_devices.push_back(device); - return true; +int S60Devices::findById(const QString &id) const +{ + const int count = m_devices.size(); + for (int i = 0; i < count; i++) + if (m_devices.at(i).id == id) + return i; + return -1; } -bool S60Devices::read() +int S60Devices::findByEpocRoot(const QString &er) const { - m_devices.clear(); - m_errorString.clear(); + const int count = m_devices.size(); + for (int i = 0; i < count; i++) + if (m_devices.at(i).epocRoot == er) + return i; + return -1; +} + +S60Devices::Device S60Devices::deviceForId(const QString &id) const +{ + const int index = findById(id); + return index == -1 ? Device() : m_devices.at(index); +} + +S60Devices::Device S60Devices::deviceForEpocRoot(const QString &root) const +{ + const int index = findByEpocRoot(root); + return index == -1 ? Device() : m_devices.at(index); +} + +S60Devices::Device S60Devices::defaultDevice() const +{ + const int index = findDefaultDevice(m_devices); + return index == -1 ? Device() : m_devices.at(index); +} + +QString S60Devices::cleanedRootPath(const QString &deviceRoot) +{ + QString path = deviceRoot; #ifdef Q_OS_WIN - return readWin(); -#else - return readLinux(); + // sbsv2 actually recommends having the DK on a separate drive... + // But qmake breaks when doing that! + if (path.size() > 1 && path.at(1) == QChar(':')) + path = path.mid(2); #endif + + if (!path.size() || path.at(path.size()-1) != '/') + path.append('/'); + return path; +} + +S60Devices::StringStringPairList S60Devices::readSdkQtAssociationSettings(const QSettings *settings, + const QString &group, + int *defaultIndexPtr) +{ + StringStringPairList rc; + // Read out numbered pairs of EpocRoot/QtDir as many as exist + // "SymbianSDK1=/epoc,/qt[,default]". + const QChar separator = QLatin1Char(','); + const QString keyRoot = group + QLatin1Char('/') + QLatin1String(SDK_QT_ASSOC_SETTINGS_KEY_ROOT); + int defaultIndex = -1; + for (int i = 1; ; i++) { + // Split pairs of epocroot/qtdir. + const QVariant valueV = settings->value(keyRoot + QString::number(i)); + if (!valueV.isValid()) + break; + // Check for default postfix + QString value = valueV.toString(); + if (value.endsWith(QLatin1String(SETTINGS_DEFAULT_SDK_POSTFIX))) { + value.truncate(value.size() - qstrlen(SETTINGS_DEFAULT_SDK_POSTFIX)); + defaultIndex = rc.size(); + } + // Split into SDK and Qt + const int separatorPos = value.indexOf(separator); + if (separatorPos == -1) + break; + const QString epocRoot = value.left(separatorPos); + const QString qtDir = value.mid(separatorPos + 1); + rc.push_back(StringStringPair(epocRoot, qtDir)); + } + if (defaultIndexPtr) + *defaultIndexPtr = defaultIndex; + return rc; +} + +void S60Devices::writeSdkQtAssociationSettings(QSettings *settings, const QString &group) const +{ + // Write out as numbered pairs of EpocRoot/QtDir and indicate default + // "SymbianSDK1=/epoc,/qt[,default]". + settings->beginGroup(group); + settings->remove(QString()); // remove all keys + if (const int count = devicesList().size()) { + const QString keyRoot = QLatin1String(SDK_QT_ASSOC_SETTINGS_KEY_ROOT); + const QChar separator = QLatin1Char(','); + for (int i = 0; i < count; i++) { + const QString key = keyRoot + QString::number(i + 1); + QString value = devicesList().at(i).epocRoot; + value += separator; + value += devicesList().at(i).qt; + // Indicate default by postfix ",default" + if (devicesList().at(i).isDefault) + value += QLatin1String(SETTINGS_DEFAULT_SDK_POSTFIX); + settings->setValue(key, value); + } + } + settings->endGroup(); +} + +void S60Devices::readSettings() +{ +} + +void S60Devices::writeSettings() +{ +} + +// ------------------ S60Devices + +AutoDetectS60Devices::AutoDetectS60Devices(QObject *parent) : + S60Devices(parent) +{ +} + +QString AutoDetectS60Devices::errorString() const +{ + return m_errorString; } -// Windows: Get list of paths containing common program data // as pointed to by environment. static QStringList commonProgramFilesPaths() { @@ -147,8 +288,6 @@ static QStringList commonProgramFilesPaths() return rc; } -// Windows EPOC - // Find the "devices.xml" file containing the SDKs static QString devicesXmlFile(QString *errorMessage) { @@ -174,8 +313,10 @@ static QString devicesXmlFile(QString *errorMessage) return QString(); } -bool S60Devices::readWin() +bool AutoDetectS60Devices::detectDevices() { + devicesList().clear(); + m_errorString.clear(); const QString devicesXmlPath = devicesXmlFile(&m_errorString); if (devicesXmlPath.isEmpty()) return false; @@ -210,7 +351,7 @@ bool S60Devices::readWin() } if (device.toolsRoot.isEmpty()) device.toolsRoot = device.epocRoot; - m_devices.append(device); + devicesList().append(device); } } } else { @@ -227,6 +368,35 @@ bool S60Devices::readWin() return true; } +void AutoDetectS60Devices::readSettings() +{ + // Read the associated Qt version from the settings + // and set on the autodetected SDKs. + bool changed = false; + const QSettings *settings = Core::ICore::instance()->settings(); + foreach (const StringStringPair &p, readSdkQtAssociationSettings(settings, QLatin1String(GNUPOC_SETTINGS_GROUP))) { + const int index = findByEpocRoot(p.first); + if (index != -1 && devicesList().at(index).qt != p.second) { + devicesList()[index].qt = p.second; + changed = true; + } + } + if (changed) + emit qtVersionsChanged(); +} + +void AutoDetectS60Devices::writeSettings() +{ + writeSdkQtAssociationSettings(Core::ICore::instance()->settings(), QLatin1String(AUTODETECT_SETTINGS_GROUP)); +} + +// ========== AutoDetectS60QtDevices + +AutoDetectS60QtDevices::AutoDetectS60QtDevices(QObject *parent) : + AutoDetectS60Devices(parent) +{ +} + // Detect a Qt version that is installed into a Symbian SDK static QString detect_SDK_installedQt(const QString &epocRoot) { @@ -263,28 +433,14 @@ static QString detect_SDK_installedQt(const QString &epocRoot) return QDir(QString::fromLatin1(buffer.mid(index, lastIndex-index))).absolutePath(); } -// GnuPoc: Detect a Qt version that is symlinked/below an SDK -// TODO: Find a proper way of doing that -static QString detectGnuPocQt(const QString &epocRoot) -{ - const QFileInfo fi(epocRoot + QLatin1String("/qt")); - if (!fi.exists()) - return QString(); - if (fi.isSymLink()) - return QFileInfo(fi.symLinkTarget()).absoluteFilePath(); - return fi.absoluteFilePath(); -} - -bool S60Devices::detectQtForDevices() +bool AutoDetectS60QtDevices::detectQtForDevices() { bool changed = false; - const int deviceCount = m_devices.size(); + const int deviceCount = devicesList().size(); for (int i = 0; i < deviceCount; ++i) { - Device &device = m_devices[i]; + Device &device = devicesList()[i]; if (device.qt.isEmpty()) { device.qt = detect_SDK_installedQt(device.epocRoot); - if (device.qt.isEmpty()) - device.qt = detectGnuPocQt(device.epocRoot); if (device.qt.isEmpty()) { qWarning("Unable to detect Qt version for '%s'.", qPrintable(device.epocRoot)); } else { @@ -297,59 +453,50 @@ bool S60Devices::detectQtForDevices() return true; } -QString S60Devices::errorString() const -{ - return m_errorString; -} - -QList<S60Devices::Device> S60Devices::devices() const +bool AutoDetectS60QtDevices::detectDevices() { - return m_devices; + return AutoDetectS60Devices::detectDevices() && detectQtForDevices(); } -S60Devices::Device S60Devices::deviceForId(const QString &id) const +// ------- GnuPocS60Devices +GnuPocS60Devices::GnuPocS60Devices(QObject *parent) : + S60Devices(parent) { - foreach (const S60Devices::Device &i, m_devices) { - if (i.id == id) { - return i; - } - } - return Device(); } -S60Devices::Device S60Devices::deviceForEpocRoot(const QString &root) const +S60Devices::Device GnuPocS60Devices::createDevice(const QString &epoc, const QString &qtDir) { - foreach (const S60Devices::Device &i, m_devices) { - if (i.epocRoot == root) { - return i; - } - } - return Device(); + Device device; + device.id = device.name = QLatin1String("GnuPoc"); + device.toolsRoot = device.epocRoot = epoc; + device.qt = qtDir; + return device; } -S60Devices::Device S60Devices::defaultDevice() const +// GnuPoc settings are just the pairs of EpocRoot and Qt Dir. +void GnuPocS60Devices::readSettings() { - foreach (const S60Devices::Device &i, m_devices) { - if (i.isDefault) { - return i; + // Read out numbered pairs of EpocRoot/QtDir as many as exist + // "SymbianSDK1=/epoc,/qt". + devicesList().clear(); + int defaultIndex = 0; + const QSettings *settings = Core::ICore::instance()->settings(); + const StringStringPairList devices =readSdkQtAssociationSettings(settings, QLatin1String(GNUPOC_SETTINGS_GROUP), &defaultIndex); + foreach (const StringStringPair &p, devices) + devicesList().append(createDevice(p.first, p.second)); + // Ensure a default + if (!devicesList().isEmpty()) { + if (defaultIndex >= 0 && defaultIndex < devicesList().size()) { + devicesList()[defaultIndex].isDefault = true; + } else { + devicesList().front().isDefault = true; } } - return Device(); } -QString S60Devices::cleanedRootPath(const QString &deviceRoot) +void GnuPocS60Devices::writeSettings() { - QString path = deviceRoot; -#ifdef Q_OS_WIN - // sbsv2 actually recommends having the DK on a separate drive... - // But qmake breaks when doing that! - if (path.size() > 1 && path.at(1) == QChar(':')) - path = path.mid(2); -#endif - - if (!path.size() || path.at(path.size()-1) != '/') - path.append('/'); - return path; + writeSdkQtAssociationSettings(Core::ICore::instance()->settings(), QLatin1String(GNUPOC_SETTINGS_GROUP)); } // S60ToolChainMixin diff --git a/src/plugins/qt4projectmanager/qt-s60/s60devices.h b/src/plugins/qt4projectmanager/qt-s60/s60devices.h index 4e3657f5527..076c81184c2 100644 --- a/src/plugins/qt4projectmanager/qt-s60/s60devices.h +++ b/src/plugins/qt4projectmanager/qt-s60/s60devices.h @@ -35,20 +35,25 @@ #include <QtCore/QObject> #include <QtCore/QString> #include <QtCore/QList> +#include <QtCore/QPair> QT_BEGIN_NAMESPACE class QDebug; +class QSettings; QT_END_NAMESPACE namespace Qt4ProjectManager { namespace Internal { +// List of S60 devices. class S60Devices : public QObject { Q_OBJECT public: struct Device { Device(); + bool equals(const Device &rhs) const; + QString toHtml() const; QString id; @@ -59,26 +64,106 @@ public: QString qt; }; - S60Devices(QObject *parent = 0); - bool read(); - QString errorString() const; + // Construct a devices object, does autodetection if applicable + // and restores settings. + static S60Devices *createS60Devices(QObject *parent); + QList<Device> devices() const; - bool detectQtForDevices(); + // Set devices, write settings and emit changed signals accordingly. + void setDevices(const QList<Device> &device); + Device deviceForId(const QString &id) const; Device deviceForEpocRoot(const QString &root) const; Device defaultDevice() const; + int findByEpocRoot(const QString &er) const; + static QString cleanedRootPath(const QString &deviceRoot); + signals: void qtVersionsChanged(); +protected: + typedef QPair<QString, QString> StringStringPair; + typedef QList<StringStringPair> StringStringPairList; + + explicit S60Devices(QObject *parent = 0); + + const QList<Device> &devicesList() const; + QList<Device> &devicesList(); + + int findById(const QString &id) const; + + // Helpers to serialize the association of Symbian SDK<->Qt + // to QSettings (pair of SDK/Qt). + static StringStringPairList readSdkQtAssociationSettings(const QSettings *settings, + const QString &group, + int *defaultIndex = 0); + void writeSdkQtAssociationSettings(QSettings *settings, const QString &group) const; + private: - bool readLinux(); - bool readWin(); - QString m_errorString; + virtual void readSettings(); // empty stubs + virtual void writeSettings(); + QList<Device> m_devices; }; +inline bool operator==(const S60Devices::Device &d1, const S60Devices::Device &d2) +{ return d1.equals(d2); } +inline bool operator!=(const S60Devices::Device &d1, const S60Devices::Device &d2) +{ return !d1.equals(d2); } + +// Autodetected Symbian Devices (as parsed from devices.xml file on Windows) +// with a manually set version of Qt. Currently not used, but might be by +// makefile-based builds on Windows. +class AutoDetectS60Devices : public S60Devices +{ + Q_OBJECT +public: + explicit AutoDetectS60Devices(QObject *parent = 0); + virtual bool detectDevices(); + QString errorString() const; + +private: + // Write and restore Qt-SDK associations. + virtual void readSettings(); + virtual void writeSettings(); + + QString m_errorString; +}; + +// Autodetected Symbian-Qt-Devices (with Qt installed +// into the SDK) for ABLD, Raptor. Completely autodetected. +class AutoDetectS60QtDevices : public AutoDetectS60Devices +{ + Q_OBJECT +public: + explicit AutoDetectS60QtDevices(QObject *parent = 0); + // Overwritten to detect associated Qt versions in addition. + virtual bool detectDevices(); + +private: + // No settings as it is completely autodetected. + virtual void readSettings() {} + virtual void writeSettings() {} + + bool detectQtForDevices(); +}; + +// Manually configured Symbian Devices completely based on QSettings. +class GnuPocS60Devices : public S60Devices +{ + Q_OBJECT +public: + explicit GnuPocS60Devices(QObject *parent = 0); + + static Device createDevice(const QString &epoc, const QString &qtDir); + +private: + virtual void readSettings(); + virtual void writeSettings(); +}; + /* Mixin for the toolchains with convenience functions for EPOC * (Windows) and GnuPoc (Linux). */ diff --git a/src/plugins/qt4projectmanager/qt-s60/s60devicespreferencepane.cpp b/src/plugins/qt4projectmanager/qt-s60/s60devicespreferencepane.cpp index db5b54c1d50..cabb8c1eae6 100644 --- a/src/plugins/qt4projectmanager/qt-s60/s60devicespreferencepane.cpp +++ b/src/plugins/qt4projectmanager/qt-s60/s60devicespreferencepane.cpp @@ -34,50 +34,207 @@ #include <qt4projectmanager/qt4projectmanagerconstants.h> #include <utils/qtcassert.h> +#include <coreplugin/coreconstants.h> #include <QtCore/QDir> #include <QtCore/QtDebug> +#include <QtCore/QSharedPointer> -using namespace Qt4ProjectManager; -using namespace Qt4ProjectManager::Internal; +#include <QtGui/QFileDialog> +#include <QtGui/QMessageBox> +#include <QtGui/QIcon> +#include <QtGui/QApplication> +#include <QtGui/QStyle> +#include <QtGui/QStandardItemModel> +#include <QtGui/QStandardItem> -S60DevicesWidget::S60DevicesWidget(QWidget *parent, S60Devices *devices) : +enum { deviceRole = Qt::UserRole + 1 }; + +enum Columns { DefaultColumn, EpocColumn, QtColumn, ColumnCount }; + +typedef QSharedPointer<Qt4ProjectManager::Internal::S60Devices::Device> DevicePtr; +Q_DECLARE_METATYPE(DevicePtr) + +typedef QList<QStandardItem *> StandardItemList; + +namespace Qt4ProjectManager { +namespace Internal { + +static inline DevicePtr deviceFromItem(const QStandardItem *item) +{ + return qvariant_cast<DevicePtr>(item->data(deviceRole)); +} + +// Device model storing a shared pointer to the device as user data. +// Provides a checkable 'default' column which works exclusively. +class S60DevicesModel : public QStandardItemModel { + Q_OBJECT +public: + typedef QList<S60Devices::Device> DeviceList; + + explicit S60DevicesModel(bool defaultColumnCheckable, QObject *parent = 0); + + void setDevices(const DeviceList &list); + DeviceList devices() const; + void appendDevice(const S60Devices::Device &device); + +private slots: + void slotItemChanged(QStandardItem *item); + +private: + const bool m_defaultColumnCheckable; +}; + +S60DevicesModel::S60DevicesModel(bool defaultColumnCheckable, QObject *parent) : + QStandardItemModel(0, ColumnCount, parent), + m_defaultColumnCheckable(defaultColumnCheckable) +{ + QStringList headers; + headers << S60DevicesBaseWidget::tr("Default") + << S60DevicesBaseWidget::tr("SDK Location") + << S60DevicesBaseWidget::tr("Qt Location"); + setHorizontalHeaderLabels(headers); + + if (m_defaultColumnCheckable) + connect(this, SIGNAL(itemChanged(QStandardItem*)), + this, SLOT(slotItemChanged(QStandardItem*))); +} + +void S60DevicesModel::appendDevice(const S60Devices::Device &device) +{ + // Create SDK/Qt column items with shared pointer to entry as data. + const QVariant deviceData = qVariantFromValue(DevicePtr(new S60Devices::Device(device))); + + const Qt::ItemFlags flags = Qt::ItemIsEnabled|Qt::ItemIsSelectable; + + QStandardItem *defaultItem = new QStandardItem; + defaultItem->setCheckable(true); + defaultItem->setCheckState(device.isDefault ? Qt::Checked : Qt::Unchecked); + // Item is only checkable if it is not the default. + Qt::ItemFlags checkFlags = flags; + if (!device.isDefault && m_defaultColumnCheckable) + checkFlags |= Qt::ItemIsUserCheckable; + defaultItem->setFlags(checkFlags); + + defaultItem->setData(deviceData); + + QStandardItem *epocItem = new QStandardItem(QDir::toNativeSeparators(device.epocRoot)); + epocItem->setFlags(flags); + epocItem->setData(deviceData); + + const QString qtDesc = device.qt.isEmpty() ? + S60DevicesModel::tr("No Qt installed") : + QDir::toNativeSeparators(device.qt); + QStandardItem *qtItem = new QStandardItem(qtDesc); + qtItem->setFlags(flags); + qtItem->setData(deviceData); + + const QString tooltip = device.toHtml(); + epocItem->setToolTip(tooltip); + qtItem->setToolTip(tooltip); + + StandardItemList row; + row << defaultItem << epocItem << qtItem; + appendRow(row); +} + +void S60DevicesModel::setDevices(const DeviceList &list) +{ + removeRows(0, rowCount()); + foreach(const S60Devices::Device &device, list) + appendDevice(device); +} + +S60DevicesModel::DeviceList S60DevicesModel::devices() const +{ + S60DevicesModel::DeviceList rc; + const int count = rowCount(); + for (int r = 0; r < count; r++) + rc.push_back(S60Devices::Device(*deviceFromItem(item(r, 0)))); + return rc; +} + +void S60DevicesModel::slotItemChanged(QStandardItem *changedItem) +{ + // Sync all "default" checkmarks. Emulate an exclusive group + // by enabling only the unchecked items (preventing the user from unchecking) + // and uncheck all other items. Protect against recursion. + if (changedItem->column() != DefaultColumn || changedItem->checkState() != Qt::Checked) + return; + const int row = changedItem->row(); + const int count = rowCount(); + for (int r = 0; r < count; r++) { + QStandardItem *rowItem = item(r, DefaultColumn); + if (r == row) { // Prevent uncheck. + rowItem->setFlags(rowItem->flags() & ~Qt::ItemIsUserCheckable); + deviceFromItem(rowItem)->isDefault = true; + } else { + // Uncheck others. + rowItem->setCheckState(Qt::Unchecked); + rowItem->setFlags(rowItem->flags() | Qt::ItemIsUserCheckable); + deviceFromItem(rowItem)->isDefault = false; + } + } +} + +// --------------- S60DevicesBaseWidget +S60DevicesBaseWidget::S60DevicesBaseWidget(unsigned flags, QWidget *parent) : QWidget(parent), m_ui(new Ui::S60DevicesPreferencePane), - m_devices(devices) + m_model(new S60DevicesModel(flags & DeviceDefaultCheckable)) { m_ui->setupUi(this); - connect(m_ui->refreshButton, SIGNAL(clicked()), this, SLOT(updateDevices())); - updateDevicesList(); + m_ui->addButton->setIcon(QIcon(QLatin1String(Core::Constants::ICON_PLUS))); + connect(m_ui->addButton, SIGNAL(clicked()), this, SLOT(addDevice())); + m_ui->removeButton->setIcon(QIcon(QLatin1String(Core::Constants::ICON_MINUS))); + connect(m_ui->removeButton, SIGNAL(clicked()), this, SLOT(removeDevice())); + m_ui->refreshButton->setIcon(qApp->style()->standardIcon(QStyle::SP_BrowserReload)); + connect(m_ui->refreshButton, SIGNAL(clicked()), this, SLOT(refresh())); + m_ui->changeQtButton->setIcon(QIcon(QLatin1String(":/welcome/images/qt_logo.png"))); + connect(m_ui->changeQtButton, SIGNAL(clicked()), this, SLOT(changeQtVersion())); + + m_ui->list->setModel(m_model); + connect(m_ui->list->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(currentChanged(QModelIndex,QModelIndex))); + + m_ui->addButton->setVisible(flags & ShowAddButton); + m_ui->removeButton->setVisible(flags & ShowAddButton); + m_ui->removeButton->setEnabled(false); + m_ui->changeQtButton->setVisible(flags & ShowChangeQtButton); + m_ui->removeButton->setEnabled(false); + m_ui->refreshButton->setVisible(flags & ShowRefreshButton); } -S60DevicesWidget::~S60DevicesWidget() +S60DevicesBaseWidget::~S60DevicesBaseWidget() { delete m_ui; } -void S60DevicesWidget::updateDevices() +QStandardItem *S60DevicesBaseWidget::currentItem() const { - m_devices->read(); - QTC_ASSERT(m_devices->detectQtForDevices(), return); - updateDevicesList(); + // Return the column-0 item. + QModelIndex current = m_ui->list->currentIndex(); + if (current.isValid()) { + if (current.row() != 0) + current = current.sibling(current.row(), 0); + return m_model->itemFromIndex(current); + } + return 0; } -void S60DevicesWidget::updateDevicesList() +S60DevicesBaseWidget::DeviceList S60DevicesBaseWidget::devices() const { - QList<S60Devices::Device> devices = m_devices->devices(); - m_ui->list->clear(); - foreach (const S60Devices::Device &device, devices) { - QStringList columns; - columns << QDir::toNativeSeparators(device.epocRoot) - << (device.qt.isEmpty()?tr("No Qt installed"):QDir::toNativeSeparators(device.qt)); - QTreeWidgetItem *item = new QTreeWidgetItem(columns); - const QString tooltip = device.toHtml(); - item->setToolTip(0, tooltip); - item->setToolTip(1, tooltip); - m_ui->list->addTopLevelItem(item); - } - const QString errorString = m_devices->errorString(); + return m_model->devices(); +} + +void S60DevicesBaseWidget::setDevices(const DeviceList &s60devices, + const QString &errorString) +{ + m_model->setDevices(s60devices); + + for (int c = 0; c < ColumnCount; c++) + m_ui->list->resizeColumnToContents(c); + if (errorString.isEmpty()) { clearErrorLabel(); } else { @@ -85,24 +242,114 @@ void S60DevicesWidget::updateDevicesList() } } -void S60DevicesWidget::setErrorLabel(const QString& t) +void S60DevicesBaseWidget::changeQtVersion() +{ + if (const QStandardItem *item = currentItem()) { + const QString qtDir = promptDirectory(tr("Choose Qt folder")); + if (!qtDir.isEmpty()) { + const DevicePtr device = deviceFromItem(item); + device->qt = qtDir; + } + } +} + +void S60DevicesBaseWidget::removeDevice() +{ + if (const QStandardItem *item = currentItem()) + m_model->removeRows(item->row(), 1); +} + +void S60DevicesBaseWidget::currentChanged(const QModelIndex ¤t, + const QModelIndex & /* previous */) +{ + const bool hasItem = current.isValid(); + m_ui->changeQtButton->setEnabled(hasItem); + m_ui->removeButton->setEnabled(hasItem); +} + +void S60DevicesBaseWidget::setErrorLabel(const QString& t) { m_ui->errorLabel->setText(t); m_ui->errorLabel->setVisible(true); } -void S60DevicesWidget::clearErrorLabel() +void S60DevicesBaseWidget::clearErrorLabel() { m_ui->errorLabel->setVisible(false); } +QString S60DevicesBaseWidget::promptDirectory(const QString &title) +{ + return QFileDialog::getExistingDirectory(this, title); +} + +void S60DevicesBaseWidget::appendDevice(const S60Devices::Device &d) +{ + m_model->appendDevice(d); +} + +int S60DevicesBaseWidget::deviceCount() const +{ + return m_model->rowCount(); +} + +// ============ AutoDetectS60DevicesWidget +AutoDetectS60DevicesWidget::AutoDetectS60DevicesWidget(QWidget *parent, + AutoDetectS60Devices *devices, + bool changeQtVersionEnabled) : + S60DevicesBaseWidget(ShowRefreshButton | (changeQtVersionEnabled ? unsigned(ShowChangeQtButton) : 0u), + parent), + m_devices(devices) +{ + refresh(); +} + +void AutoDetectS60DevicesWidget::refresh() +{ + m_devices->detectDevices(); + setDevices(m_devices->devices(), m_devices->errorString()); +} + +// ============ GnuPocS60DevicesWidget +GnuPocS60DevicesWidget::GnuPocS60DevicesWidget(QWidget *parent) : + S60DevicesBaseWidget(ShowAddButton|ShowRemoveButton|ShowChangeQtButton|DeviceDefaultCheckable, + parent) +{ +} + +void GnuPocS60DevicesWidget::addDevice() +{ + // 1) Prompt for GnuPoc + const QString epocRoot = promptDirectory(tr("Step 1 of 2: Choose GnuPoc folder")); + if (epocRoot.isEmpty()) + return; + // 2) Prompt for Qt. Catch equal inputs just in case someone clicks very rapidly. + QString qtDir; + while (true) { + qtDir = promptDirectory(tr("Step 2 of 2: Choose Qt folder")); + if (qtDir.isEmpty()) + return; + if (qtDir == epocRoot) { + QMessageBox::warning(this, tr("Adding GnuPoc"), + tr("GnuPoc and Qt folders must not be identical.")); + } else { + break; + } + } + // Add a device, make default if first. + S60Devices::Device device = GnuPocS60Devices::createDevice(epocRoot, qtDir); + if (deviceCount() == 0) + device.isDefault = true; + appendDevice(device); +} + +// ================= S60DevicesPreferencePane S60DevicesPreferencePane::S60DevicesPreferencePane(S60Devices *devices, QObject *parent) : Core::IOptionsPage(parent), m_widget(0), m_devices(devices) { - m_devices->read(); } S60DevicesPreferencePane::~S60DevicesPreferencePane() @@ -134,18 +381,43 @@ QIcon S60DevicesPreferencePane::categoryIcon() const return QIcon(Constants::QT_SETTINGS_CATEGORY_ICON); } +S60DevicesBaseWidget *S60DevicesPreferencePane::createWidget(QWidget *parent) const +{ + // Symbian ABLD/Raptor: Qt installed into SDK, cannot change + if (AutoDetectS60QtDevices *aqd = qobject_cast<AutoDetectS60QtDevices *>(m_devices)) + return new AutoDetectS60DevicesWidget(parent, aqd, false); + // Not used yet: Manual association of Qt with auto-detected SDK + if (AutoDetectS60Devices *ad = qobject_cast<AutoDetectS60Devices *>(m_devices)) + return new AutoDetectS60DevicesWidget(parent, ad, true); + if (GnuPocS60Devices *gd = qobject_cast<GnuPocS60Devices*>(m_devices)) { + GnuPocS60DevicesWidget *gw = new GnuPocS60DevicesWidget(parent); + gw->setDevices(gd->devices()); + return gw; + } + return 0; // Huh? +} + QWidget *S60DevicesPreferencePane::createPage(QWidget *parent) { if (m_widget) delete m_widget; - m_widget = new S60DevicesWidget(parent, m_devices); + m_widget = createWidget(parent); + QTC_ASSERT(m_widget, return 0) return m_widget; } void S60DevicesPreferencePane::apply() { + QTC_ASSERT(m_widget, return) + + m_devices->setDevices(m_widget->devices()); } void S60DevicesPreferencePane::finish() { } + +} // namespace Internal +} // namespace Qt4ProjectManager + +#include "s60devicespreferencepane.moc" diff --git a/src/plugins/qt4projectmanager/qt-s60/s60devicespreferencepane.h b/src/plugins/qt4projectmanager/qt-s60/s60devicespreferencepane.h index 312c1efb906..a48ae399510 100644 --- a/src/plugins/qt4projectmanager/qt-s60/s60devicespreferencepane.h +++ b/src/plugins/qt4projectmanager/qt-s60/s60devicespreferencepane.h @@ -30,38 +30,96 @@ #ifndef S60DEVICESPREFERENCEPANE_H #define S60DEVICESPREFERENCEPANE_H +#include "s60devices.h" #include <coreplugin/dialogs/ioptionspage.h> #include <QtCore/QPointer> #include <QtGui/QWidget> +QT_BEGIN_NAMESPACE +class QStandardItem; +class QAbstractButton; +class QModelIndex; +QT_END_NAMESPACE + namespace Qt4ProjectManager { namespace Internal { -class S60Devices; +class AutoDetectS60Devices; +class GnuPocS60Devices; +class S60DevicesModel; namespace Ui { class S60DevicesPreferencePane; } -class S60DevicesWidget : public QWidget +// Base pane listing Symbian SDKs with "Change Qt" version functionality. +class S60DevicesBaseWidget : public QWidget { Q_OBJECT public: - S60DevicesWidget(QWidget *parent, S60Devices *devices); - ~S60DevicesWidget(); + typedef QList<S60Devices::Device> DeviceList; -private slots: - void updateDevices(); + enum Flags { ShowAddButton = 0x1, ShowRemoveButton = 0x2, + ShowChangeQtButton =0x4, ShowRefreshButton = 0x8, + DeviceDefaultCheckable = 0x10 }; + + virtual ~S60DevicesBaseWidget(); + + DeviceList devices() const; + void setDevices(const DeviceList &dl, const QString &errorString = QString()); + +protected: + explicit S60DevicesBaseWidget(unsigned flags, QWidget *parent = 0); -private: - void updateDevicesList(); void setErrorLabel(const QString&); void clearErrorLabel(); + QString promptDirectory(const QString &title); + void appendDevice(const S60Devices::Device &d); + int deviceCount() const; + + QStandardItem *currentItem() const; + +private slots: + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); + void changeQtVersion(); + void removeDevice(); + virtual void addDevice() {} // Default does nothing + virtual void refresh() {} // Default does nothing + +private: Ui::S60DevicesPreferencePane *m_ui; - S60Devices *m_devices; + S60DevicesModel *m_model; }; +// Widget for autodetected SDK's showing a refresh button. +class AutoDetectS60DevicesWidget : public S60DevicesBaseWidget +{ + Q_OBJECT +public: + explicit AutoDetectS60DevicesWidget(QWidget *parent, + AutoDetectS60Devices *devices, + bool changeQtVersionEnabled); + +private slots: + virtual void refresh(); + +private: + AutoDetectS60Devices *m_devices; +}; + +// Widget for manually configured SDK's showing a add/remove buttons. +class GnuPocS60DevicesWidget : public S60DevicesBaseWidget +{ + Q_OBJECT +public: + explicit GnuPocS60DevicesWidget(QWidget *parent = 0); + +private slots: + virtual void addDevice(); +}; + +// Options Pane. class S60DevicesPreferencePane : public Core::IOptionsPage { Q_OBJECT @@ -80,7 +138,9 @@ public: void finish(); private: - QPointer<S60DevicesWidget> m_widget; + S60DevicesBaseWidget *createWidget(QWidget *parent) const; + + QPointer<S60DevicesBaseWidget> m_widget; S60Devices *m_devices; }; diff --git a/src/plugins/qt4projectmanager/qt-s60/s60devicespreferencepane.ui b/src/plugins/qt4projectmanager/qt-s60/s60devicespreferencepane.ui index 6841bd023b1..ccef3f84e14 100644 --- a/src/plugins/qt4projectmanager/qt-s60/s60devicespreferencepane.ui +++ b/src/plugins/qt4projectmanager/qt-s60/s60devicespreferencepane.ui @@ -6,86 +6,101 @@ <rect> <x>0</x> <y>0</y> - <width>274</width> - <height>264</height> + <width>316</width> + <height>241</height> </rect> </property> <property name="windowTitle"> <string>Form</string> </property> - <layout class="QVBoxLayout" name="verticalLayout"> + <layout class="QVBoxLayout" name="verticalLayout_2"> <item> - <widget class="QTreeWidget" name="list"> - <property name="indentation"> - <number>0</number> - </property> - <property name="rootIsDecorated"> - <bool>false</bool> - </property> - <property name="uniformRowHeights"> - <bool>true</bool> - </property> - <property name="columnCount"> - <number>2</number> - </property> - <attribute name="headerCascadingSectionResizes"> - <bool>true</bool> - </attribute> - <attribute name="headerCascadingSectionResizes"> - <bool>true</bool> - </attribute> - <column> - <property name="text"> - <string>SDK Location</string> - </property> - </column> - <column> - <property name="text"> - <string>Qt Location</string> - </property> - </column> + <widget class="QGroupBox" name="groupBox"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QTreeView" name="list"> + <property name="indentation"> + <number>0</number> + </property> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <property name="uniformRowHeights"> + <bool>true</bool> + </property> + <property name="allColumnsShowFocus"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="buttonLayout"> + <item> + <widget class="QToolButton" name="refreshButton"> + <property name="toolTip"> + <string>Refresh</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="addButton"> + <property name="toolTip"> + <string>Add</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="changeQtButton"> + <property name="toolTip"> + <string>Change Qt version</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="removeButton"> + <property name="toolTip"> + <string>Remove</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> + </layout> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="errorLabel"> + <property name="styleSheet"> + <string notr="true">background-color: red;</string> + </property> + <property name="text"> + <string>Error</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + </layout> </widget> </item> - <item> - <widget class="QLabel" name="errorLabel"> - <property name="styleSheet"> - <string notr="true">color: red;</string> - </property> - <property name="text"> - <string>Error</string> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <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="QPushButton" name="refreshButton"> - <property name="text"> - <string>Refresh</string> - </property> - </widget> - </item> - </layout> - </item> </layout> </widget> <resources/> diff --git a/src/plugins/qt4projectmanager/qt-s60/s60manager.cpp b/src/plugins/qt4projectmanager/qt-s60/s60manager.cpp index c88c7786285..fa7b21fb573 100644 --- a/src/plugins/qt4projectmanager/qt-s60/s60manager.cpp +++ b/src/plugins/qt4projectmanager/qt-s60/s60manager.cpp @@ -101,14 +101,13 @@ private: S60Manager *S60Manager::instance() { return m_instance; } S60Manager::S60Manager(QObject *parent) - : QObject(parent), - m_devices(new S60Devices(this)) + : QObject(parent), m_devices(S60Devices::createS60Devices(this)) { m_instance = this; + #ifdef QTCREATOR_WITH_S60 addAutoReleasedObject(new S60DevicesPreferencePane(m_devices, this)); #endif - m_devices->detectQtForDevices(); // Order! addAutoReleasedObject(new S60EmulatorRunConfigurationFactory); addAutoReleasedObject(new RunControlFactory<S60EmulatorRunControl, -- GitLab