diff --git a/src/plugins/debugger/debuggeractions.cpp b/src/plugins/debugger/debuggeractions.cpp index f462261d96751df26730f56f486d0af9a2d947c5..7a8d7a3c485689a55496b2980eed0d287c53e2b7 100644 --- a/src/plugins/debugger/debuggeractions.cpp +++ b/src/plugins/debugger/debuggeractions.cpp @@ -32,11 +32,14 @@ #include "registerpostmortemaction.h" #endif +#include <projectexplorer/toolchain.h> + #include <utils/savedaction.h> #include <utils/qtcassert.h> #include <utils/pathchooser.h> #include <QtCore/QDebug> +#include <QtCore/QVariant> #include <QtCore/QSettings> #include <QtGui/QAction> @@ -47,6 +50,10 @@ using namespace Utils; +static const char debugModeSettingsGroupC[] = "DebugMode"; +static const char gdbBinariesSettingsGroupC[] = "GdbBinaries"; +static const char debugModeGdbBinaryKeyC[] = "GdbBinary"; + namespace Debugger { namespace Internal { @@ -57,7 +64,7 @@ namespace Internal { ////////////////////////////////////////////////////////////////////////// DebuggerSettings::DebuggerSettings(QObject *parent) - : QObject(parent) + : QObject(parent), m_gdbBinaryToolChainMap(new GdbBinaryToolChainMap) {} DebuggerSettings::~DebuggerSettings() @@ -78,12 +85,61 @@ void DebuggerSettings::readSettings(QSettings *settings) { foreach (SavedAction *item, m_items) item->readSettings(settings); + // Convert gdb binaries from flat settings list (see writeSettings) + // into map ("binary1=gdb,1,2", "binary2=symbian_gdb,3,4"). + m_gdbBinaryToolChainMap->clear(); + const QChar separator = QLatin1Char(','); + const QString keyRoot = QLatin1String(gdbBinariesSettingsGroupC) + QLatin1Char('/') + + QLatin1String(debugModeGdbBinaryKeyC); + for (int i = 1; ; i++) { + const QString value = settings->value(keyRoot + QString::number(i)).toString(); + if (value.isEmpty()) + break; + // Split apart comma-separated binary and its numerical toolchains. + QStringList tokens = value.split(separator); + if (tokens.size() < 2) + break; + const QString binary = tokens.front(); + tokens.pop_front(); + foreach(const QString &t, tokens) + m_gdbBinaryToolChainMap->insert(binary, t.toInt()); + } + // Linux defaults +#ifdef Q_OS_UNIX + if (m_gdbBinaryToolChainMap->isEmpty()) { + const QString gdb = QLatin1String("gdb"); + m_gdbBinaryToolChainMap->insert(gdb, ProjectExplorer::ToolChain::GCC); + m_gdbBinaryToolChainMap->insert(gdb, ProjectExplorer::ToolChain::OTHER); + m_gdbBinaryToolChainMap->insert(gdb, ProjectExplorer::ToolChain::UNKNOWN); + } +#endif } void DebuggerSettings::writeSettings(QSettings *settings) const { foreach (SavedAction *item, m_items) item->writeSettings(settings); + // Convert gdb binaries map into a flat settings list of + // ("binary1=gdb,1,2", "binary2=symbian_gdb,3,4"). It needs to be ASCII for installers + QString lastBinary; + QStringList settingsList; + const QChar separator = QLatin1Char(','); + const GdbBinaryToolChainMap::const_iterator cend = m_gdbBinaryToolChainMap->constEnd(); + for (GdbBinaryToolChainMap::const_iterator it = m_gdbBinaryToolChainMap->constBegin(); it != cend; ++it) { + if (it.key() != lastBinary) { + lastBinary = it.key(); // Start new entry with first toolchain + settingsList.push_back(lastBinary); + } + settingsList.back().append(separator); // Append toolchain to last binary + settingsList.back().append(QString::number(it.value())); + } + settings->beginGroup(QLatin1String(gdbBinariesSettingsGroupC)); + settings->remove(QString()); // remove all keys in group. + const int count = settingsList.size(); + const QString keyRoot = QLatin1String(debugModeGdbBinaryKeyC); + for (int i = 0; i < count; i++) + settings->setValue(keyRoot + QString::number(i + 1), settingsList.at(i)); + settings->endGroup(); } SavedAction *DebuggerSettings::item(int code) const @@ -112,7 +168,7 @@ DebuggerSettings *DebuggerSettings::instance() if (instance) return instance; - const QString debugModeGroup = QLatin1String("DebugMode"); + const QString debugModeGroup = QLatin1String(debugModeSettingsGroupC); instance = new DebuggerSettings; SavedAction *item = 0; @@ -309,14 +365,6 @@ DebuggerSettings *DebuggerSettings::instance() // // Settings // - item = new SavedAction(instance); - item->setSettingsKey(debugModeGroup, QLatin1String("Location")); -#ifdef Q_OS_WIN - item->setDefaultValue(QLatin1String("gdb-i686-pc-mingw32.exe")); -#else - item->setDefaultValue(QLatin1String("gdb")); -#endif - instance->insertItem(GdbLocation, item); item = new SavedAction(instance); item->setSettingsKey(debugModeGroup, QLatin1String("Environment")); diff --git a/src/plugins/debugger/debuggeractions.h b/src/plugins/debugger/debuggeractions.h index e1b4f87abab7db9ceba59cb29b446006c9e74f62..545b346a2fbf4a0d1948a6180711bc66ce0bb659 100644 --- a/src/plugins/debugger/debuggeractions.h +++ b/src/plugins/debugger/debuggeractions.h @@ -31,6 +31,8 @@ #define DEBUGGER_ACTIONS_H #include <QtCore/QHash> +#include <QtCore/QMap> +#include <QtCore/QSharedPointer> QT_BEGIN_NAMESPACE class QAction; @@ -49,9 +51,14 @@ class DebuggerSettings : public QObject { Q_OBJECT public: - DebuggerSettings(QObject *parent = 0); + typedef QMultiMap<QString, int> GdbBinaryToolChainMap; + typedef QSharedPointer<GdbBinaryToolChainMap> GdbBinaryToolChainMapPtr; + + explicit DebuggerSettings(QObject *parent = 0); ~DebuggerSettings(); + GdbBinaryToolChainMapPtr gdbBinaryToolChainMap() const { return m_gdbBinaryToolChainMap; } + void insertItem(int code, Utils::SavedAction *item); Utils::SavedAction *item(int code) const; @@ -65,6 +72,8 @@ public slots: private: QHash<int, Utils::SavedAction *> m_items; + + const GdbBinaryToolChainMapPtr m_gdbBinaryToolChainMap; }; @@ -103,7 +112,6 @@ enum DebuggerActionCode RegisterForPostMortem, // Gdb - GdbLocation, GdbEnvironment, GdbScriptFile, ExecuteCommand, diff --git a/src/plugins/debugger/gdb/gdb.pri b/src/plugins/debugger/gdb/gdb.pri index edf30bac20b131dfb69aafa058146b53de693089..03d7123eef3d7b69c918b05b4fca08d9d9f23f2d 100644 --- a/src/plugins/debugger/gdb/gdb.pri +++ b/src/plugins/debugger/gdb/gdb.pri @@ -2,6 +2,7 @@ HEADERS += \ $$PWD/gdbmi.h \ $$PWD/gdbengine.h \ $$PWD/gdboptionspage.h \ + $$PWD/gdbchooserwidget.h \ $$PWD/trkoptions.h \ $$PWD/trkoptionswidget.h \ $$PWD/trkoptionspage.h \ @@ -20,6 +21,7 @@ SOURCES += \ $$PWD/classicgdbengine.cpp \ $$PWD/pythongdbengine.cpp \ $$PWD/gdboptionspage.cpp \ + $$PWD/gdbchooserwidget.cpp \ $$PWD/trkoptions.cpp \ $$PWD/trkoptionswidget.cpp \ $$PWD/trkoptionspage.cpp \ diff --git a/src/plugins/debugger/gdb/gdbchooserwidget.cpp b/src/plugins/debugger/gdb/gdbchooserwidget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..aa38342d58abeb3c9b3afd02eecfabfcb12ccca8 --- /dev/null +++ b/src/plugins/debugger/gdb/gdbchooserwidget.cpp @@ -0,0 +1,574 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "gdbchooserwidget.h" + +#include <utils/pathchooser.h> +#include <projectexplorer/toolchain.h> +#include <coreplugin/coreconstants.h> + +#include <utils/qtcassert.h> + +#include <QtGui/QTreeView> +#include <QtGui/QStandardItemModel> +#include <QtGui/QStandardItem> +#include <QtGui/QToolButton> +#include <QtGui/QFormLayout> +#include <QtGui/QVBoxLayout> +#include <QtGui/QHBoxLayout> +#include <QtGui/QDialogButtonBox> +#include <QtGui/QMessageBox> +#include <QtGui/QPushButton> +#include <QtGui/QIcon> +#include <QtGui/QGroupBox> +#include <QtGui/QCheckBox> + +#include <QtCore/QDebug> +#include <QtCore/QSet> +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QProcess> + +enum { binaryRole = Qt::UserRole + 1, toolChainRole = Qt::UserRole + 2 }; +enum Columns { binaryColumn, toolChainColumn, ColumnCount }; + +typedef QList<QStandardItem *> StandardItemList; + +Q_DECLARE_METATYPE(QList<int>) + +static QList<int> allGdbToolChains() +{ + QList<int> rc; + rc << ProjectExplorer::ToolChain::GCC +#ifdef Q_OS_WIN + << ProjectExplorer::ToolChain::MinGW + << ProjectExplorer::ToolChain::WINSCW + << ProjectExplorer::ToolChain::GCCE + << ProjectExplorer::ToolChain::RVCT_ARMV5 + << ProjectExplorer::ToolChain::RVCT_ARMV6 +#endif + << ProjectExplorer::ToolChain::GCC_MAEMO +#ifdef Q_OS_UNIX + << ProjectExplorer::ToolChain::GCCE_GNUPOC + << ProjectExplorer::ToolChain::RVCT_ARMV5_GNUPOC +#endif + << ProjectExplorer::ToolChain::OTHER + << ProjectExplorer::ToolChain::UNKNOWN; + return rc; +} + +static inline QString toolChainName(int tc) +{ + return ProjectExplorer::ToolChain::toolChainName(static_cast<ProjectExplorer::ToolChain::ToolChainType>(tc)); +} + +namespace Debugger { +namespace Internal { + +// ----------------------------------------------- + +// Obtain a tooltip for a gdb binary by running --version +static inline QString gdbToolTip(const QString &binary) +{ + QProcess process; + process.start(binary, QStringList(QLatin1String("--version"))); + process.closeWriteChannel(); + if (!process.waitForStarted()) + return GdbChooserWidget::tr("Unable to run '%1': %2").arg(binary, process.errorString()); + process.waitForFinished(); // That should never fail + QString rc = QDir::toNativeSeparators(binary); + rc += QLatin1String("\n\n"); + rc += QString::fromLocal8Bit(process.readAllStandardOutput()); + rc.remove(QLatin1Char('\r')); + return rc; +} + +// GdbBinaryModel: Show gdb binaries and associated toolchains as a list. +// Provides a delayed tooltip listing the gdb version as +// obtained by running it. Provides conveniences for getting/setting the maps and +// for listing the toolchains used and the ones still available. + +class GdbBinaryModel : public QStandardItemModel { +public: + typedef GdbChooserWidget::BinaryToolChainMap BinaryToolChainMap; + + explicit GdbBinaryModel(QObject * parent = 0); + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + // get / set data as map. + BinaryToolChainMap gdbBinaries() const; + void setGdbBinaries(const BinaryToolChainMap &m); + + QString binary(int row) const; + QList<int> toolChains(int row) const; + + QStringList binaries() const; + QList<int> usedToolChains() const; + QSet<int> unusedToolChainSet() const; + QList<int> unusedToolChains() const; + + void append(const QString &binary, const QList<int> &toolChains); + + static void setBinaryItem(QStandardItem *item, const QString &binary); + static void setToolChainItem(QStandardItem *item, const QList<int> &toolChain); +}; + +GdbBinaryModel::GdbBinaryModel(QObject *parent) : + QStandardItemModel(0, ColumnCount, parent) +{ + QStringList headers; + headers << GdbChooserWidget::tr("Binary") << GdbChooserWidget::tr("Toolchains"); + setHorizontalHeaderLabels(headers); +} + +QVariant GdbBinaryModel::data(const QModelIndex &index, int role) const +{ + if (index.isValid() && role == Qt::ToolTipRole) { + // Is there a tooltip set? + const QString itemToolTip = itemFromIndex(index)->toolTip(); + if (!itemToolTip.isEmpty()) + return QVariant(itemToolTip); + // Run the gdb and obtain the tooltip + const QString tooltip = gdbToolTip(binary(index.row())); + // Set on the whole row + item(index.row(), binaryColumn)->setToolTip(tooltip); + item(index.row(), toolChainColumn)->setToolTip(tooltip); + return QVariant(tooltip); + } + return QStandardItemModel::data(index, role); +} + +QStringList GdbBinaryModel::binaries() const +{ + QStringList rc; + const int binaryCount = rowCount(); + for (int b = 0; b < binaryCount; b++) + rc.push_back(binary(b)); + return rc; +} + +QList<int> GdbBinaryModel::usedToolChains() const +{ + // Loop over model and collect all toolchains. + QList<int> rc; + const int binaryCount = rowCount(); + for (int b = 0; b < binaryCount; b++) + foreach(int tc, toolChains(b)) + rc.push_back(tc); + return rc; +} + +QSet<int> GdbBinaryModel::unusedToolChainSet() const +{ + const QSet<int> used = usedToolChains().toSet(); + QSet<int> all = allGdbToolChains().toSet(); + return all.subtract(used); +} + +QList<int> GdbBinaryModel::unusedToolChains() const +{ + QList<int> unused = unusedToolChainSet().toList(); + qSort(unused); + return unused; +} + +GdbBinaryModel::BinaryToolChainMap GdbBinaryModel::gdbBinaries() const +{ + BinaryToolChainMap rc; + const int binaryCount = rowCount(); + for (int r = 0; r < binaryCount; r++) { + const QString bin = binary(r); + foreach(int tc, toolChains(r)) + rc.insert(bin, tc); + } + return rc; +} + +void GdbBinaryModel::setGdbBinaries(const BinaryToolChainMap &m) +{ + removeRows(0, rowCount()); + foreach(const QString &binary, m.uniqueKeys()) + append(binary, m.values(binary)); +} + +QString GdbBinaryModel::binary(int row) const +{ + return item(row, binaryColumn)->data(binaryRole).toString(); +} + +QList<int> GdbBinaryModel::toolChains(int row) const +{ + const QVariant data = item(row, toolChainColumn)->data(toolChainRole); + return qVariantValue<QList<int> >(data); +} + +void GdbBinaryModel::setBinaryItem(QStandardItem *item, const QString &binary) +{ + const QFileInfo fi(binary); + item->setText(fi.isAbsolute() ? fi.fileName() : QDir::toNativeSeparators(binary)); + item->setToolTip(QString());; // clean out delayed tooltip + item->setData(QVariant(binary), binaryRole); + item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable); +} + +void GdbBinaryModel::setToolChainItem(QStandardItem *item, const QList<int> &toolChains) +{ + // Format comma-separated list + const QString toolChainSeparator = QLatin1String(", "); + QString toolChainDesc; + const int count = toolChains.size(); + for (int i = 0; i < count; i++) { + if (i) + toolChainDesc += toolChainSeparator; + toolChainDesc += toolChainName(toolChains.at(i)); + } + + item->setText(toolChainDesc); + item->setToolTip(QString());; // clean out delayed tooltip + item->setData(qVariantFromValue(toolChains), toolChainRole); + item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable); +} + +void GdbBinaryModel::append(const QString &binary, const QList<int> &toolChains) +{ + QStandardItem *binaryItem = new QStandardItem; + QStandardItem *toolChainItem = new QStandardItem; + GdbBinaryModel::setBinaryItem(binaryItem, binary); + GdbBinaryModel::setToolChainItem(toolChainItem, toolChains); + StandardItemList row; + row << binaryItem << toolChainItem; + appendRow(row); +} + +// ----------- GdbChooserWidget +GdbChooserWidget::GdbChooserWidget(QWidget *parent) : + QWidget(parent), + m_treeView(new QTreeView), + m_model(new GdbBinaryModel), + m_addButton(new QToolButton), + m_deleteButton(new QToolButton) +{ + QHBoxLayout *mainHLayout = new QHBoxLayout; + + m_treeView->setRootIsDecorated(false); + m_treeView->setModel(m_model); + m_treeView->setUniformRowHeights(true); + m_treeView->setAllColumnsShowFocus(true); + m_treeView->setSelectionMode(QAbstractItemView::SingleSelection); + connect(m_treeView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(slotCurrentChanged(QModelIndex,QModelIndex))); + connect(m_treeView, SIGNAL(doubleClicked(QModelIndex)), + this, SLOT(slotDoubleClicked(QModelIndex))); + mainHLayout->addWidget(m_treeView); + + m_addButton->setIcon(QIcon(QLatin1String(Core::Constants::ICON_PLUS))); + connect(m_addButton, SIGNAL(clicked()), this, SLOT(slotAdd())); + + m_deleteButton->setIcon(QIcon(QLatin1String(Core::Constants::ICON_MINUS))); + m_deleteButton->setEnabled(false); + connect(m_deleteButton, SIGNAL(clicked()), this, SLOT(slotRemove())); + + QVBoxLayout *vButtonLayout = new QVBoxLayout; + vButtonLayout->addWidget(m_addButton); + vButtonLayout->addWidget(m_deleteButton); + vButtonLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding)); + + mainHLayout->addLayout(vButtonLayout); + setLayout(mainHLayout); +} + +QStandardItem *GdbChooserWidget::currentItem() const +{ + // Return the column-0-item + QModelIndex currentIndex = m_treeView->currentIndex(); + if (!currentIndex.isValid()) + return 0; + if (currentIndex.column() != binaryColumn) + currentIndex = currentIndex.sibling(currentIndex.row(), binaryColumn); + return m_model->itemFromIndex(currentIndex); +} + +void GdbChooserWidget::slotAdd() +{ + // Any toolchains left? + const QList<int> unusedToolChains = m_model->unusedToolChains(); + if (unusedToolChains.isEmpty()) + return; + + // On a binary or no current item: Add binary + toolchain + BinaryToolChainDialog binaryDialog(this); + binaryDialog.setToolChainChoices(unusedToolChains); + if (binaryDialog.exec() != QDialog::Accepted) + return; + // Refuse binaries that already exist + const QString path = binaryDialog.path(); + if (m_model->binaries().contains(path)) { + QMessageBox::warning(this, tr("Duplicate binary"), + tr("The binary '%1' already exists.").arg(path)); + return; + } + // Add binary + toolchain to model + m_model->append(path, binaryDialog.toolChains()); + m_treeView->expandAll(); +} + +void GdbChooserWidget::slotRemove() +{ + if (QStandardItem *item = currentItem()) + removeItem(item); +} + +void GdbChooserWidget::removeItem(QStandardItem *item) +{ + m_model->removeRow(item->row()); +} + +void GdbChooserWidget::slotCurrentChanged(const QModelIndex ¤t, const QModelIndex &) +{ + const bool hasItem = current.isValid() && m_model->itemFromIndex(current); + m_deleteButton->setEnabled(hasItem); +} + +void GdbChooserWidget::slotDoubleClicked(const QModelIndex ¤t) +{ + QTC_ASSERT(current.isValid(), return) + // Show dialog to edit. Make all unused toolchains including the ones + // previously assigned to that binary available. + const int row = current.row(); + const QString oldBinary = m_model->binary(row); + const QList<int> oldToolChains = m_model->toolChains(row); + const QSet<int> toolChainChoices = m_model->unusedToolChainSet().unite(oldToolChains.toSet()); + + BinaryToolChainDialog dialog(this); + dialog.setPath(oldBinary); + dialog.setToolChainChoices(toolChainChoices.toList()); + dialog.setToolChains(oldToolChains); + if (dialog.exec() != QDialog::Accepted) + return; + // Check if anything changed. + const QString newBinary = dialog.path(); + const QList<int> newToolChains = dialog.toolChains(); + if (newBinary == oldBinary && newToolChains == oldToolChains) + return; + + GdbBinaryModel::setBinaryItem(m_model->item(row, binaryColumn), newBinary); + GdbBinaryModel::setToolChainItem(m_model->item(row, toolChainColumn), newToolChains); +} + +GdbChooserWidget::BinaryToolChainMap GdbChooserWidget::gdbBinaries() const +{ + return m_model->gdbBinaries(); +} + +void GdbChooserWidget::setGdbBinaries(const BinaryToolChainMap &m) +{ + m_model->setGdbBinaries(m); + m_treeView->expandAll(); +} + +// -------------- ToolChainSelectorWidget +static const char *toolChainPropertyC = "toolChain"; + +static inline int toolChainOfCheckBox(const QCheckBox *c) +{ + return c->property(toolChainPropertyC).toInt(); +} + +static inline QVBoxLayout *createGroupBox(const QString &title, QVBoxLayout *lt) +{ + QGroupBox *gb = new QGroupBox(title); + QVBoxLayout *gbLayout = new QVBoxLayout; + gb->setLayout(gbLayout); + lt->addWidget(gb); + return gbLayout; +} + +ToolChainSelectorWidget::ToolChainSelectorWidget(QWidget *parent) : + QWidget(parent), m_valid(false) +{ + QVBoxLayout *mainLayout = new QVBoxLayout; + QVBoxLayout *desktopLayout = createGroupBox(tr("Desktop/General"), mainLayout); + QVBoxLayout *symbianLayout = createGroupBox(tr("Symbian"), mainLayout); + QVBoxLayout *maemoLayout = createGroupBox(tr("Maemo"), mainLayout); + + // Group checkboxes into categories + foreach(int tc, allGdbToolChains()) { + switch (tc) { + case ProjectExplorer::ToolChain::GCC: + case ProjectExplorer::ToolChain::MinGW: + case ProjectExplorer::ToolChain::OTHER: + case ProjectExplorer::ToolChain::UNKNOWN: + desktopLayout->addWidget(createToolChainCheckBox(tc)); + break; + case ProjectExplorer::ToolChain::MSVC: + case ProjectExplorer::ToolChain::WINCE: + break; + case ProjectExplorer::ToolChain::WINSCW: + case ProjectExplorer::ToolChain::GCCE: + case ProjectExplorer::ToolChain::RVCT_ARMV5: + case ProjectExplorer::ToolChain::RVCT_ARMV6: + case ProjectExplorer::ToolChain::GCCE_GNUPOC: + case ProjectExplorer::ToolChain::RVCT_ARMV5_GNUPOC: + symbianLayout->addWidget(createToolChainCheckBox(tc)); + break; + case ProjectExplorer::ToolChain::GCC_MAEMO: + maemoLayout->addWidget(createToolChainCheckBox(tc)); + break; + case ProjectExplorer::ToolChain::INVALID: + break; + } + } + setLayout(mainLayout); +} + +QCheckBox *ToolChainSelectorWidget::createToolChainCheckBox(int tc) +{ + // Add checkbox + QCheckBox *cb = new QCheckBox(toolChainName(tc)); + cb->setProperty(toolChainPropertyC, QVariant(tc)); + connect(cb, SIGNAL(stateChanged(int)), this, SLOT(slotCheckStateChanged(int))); + m_checkBoxes.push_back(cb); + return cb; +} + +void ToolChainSelectorWidget::setEnabledToolChains(const QList<int> &enabled) +{ + foreach(QCheckBox *cb, m_checkBoxes) + if (!enabled.contains(toolChainOfCheckBox(cb))) + cb->setEnabled(false); +} + +void ToolChainSelectorWidget::setCheckedToolChains(const QList<int> &checked) +{ + foreach(QCheckBox *cb, m_checkBoxes) + if (checked.contains(toolChainOfCheckBox(cb))) + cb->setChecked(true); + // Trigger 'valid changed' + slotCheckStateChanged(checked.isEmpty() ? Qt::Unchecked : Qt::Checked); +} + +QList<int> ToolChainSelectorWidget::checkedToolChains() const +{ + QList<int> rc; + foreach(const QCheckBox *cb, m_checkBoxes) + if (cb->isChecked()) + rc.push_back(toolChainOfCheckBox(cb)); + return rc; +} + +bool ToolChainSelectorWidget::isValid() const +{ + return m_valid; +} + +void ToolChainSelectorWidget::slotCheckStateChanged(int state) +{ + // Emit signal if valid state changed + const bool newValid = state == Qt::Checked || hasCheckedToolChain(); + if (newValid != m_valid) { + m_valid = newValid; + emit validChanged(m_valid); + } +} + +bool ToolChainSelectorWidget::hasCheckedToolChain() const +{ + foreach(const QCheckBox *cb, m_checkBoxes) + if (cb->isChecked()) + return true; + return false; +} + +// -------------- ToolChainDialog +BinaryToolChainDialog::BinaryToolChainDialog(QWidget *parent) : + QDialog(parent), + m_toolChainSelector(new ToolChainSelectorWidget), + m_mainLayout(new QFormLayout), + m_buttonBox(new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel)), + m_pathChooser(new Utils::PathChooser) +{ + + setModal(true); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + setWindowTitle(tr("Select binary and toolchains")); + + m_pathChooser->setExpectedKind(Utils::PathChooser::Command); + m_pathChooser->setPromptDialogTitle(tr("Gdb binary")); + connect(m_pathChooser, SIGNAL(validChanged()), this, SLOT(slotValidChanged())); + m_mainLayout->addRow(tr("Path:"), m_pathChooser); + + connect(m_toolChainSelector, SIGNAL(validChanged(bool)), this, SLOT(slotValidChanged())); + m_mainLayout->addRow(m_toolChainSelector); + + connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + m_mainLayout->addRow(m_buttonBox); + setLayout(m_mainLayout); + + setOkButtonEnabled(false); + m_pathChooser->setFocus(); +} + +void BinaryToolChainDialog::setToolChainChoices(const QList<int> &tcs) +{ + m_toolChainSelector->setEnabledToolChains(tcs); +} + +void BinaryToolChainDialog::setToolChains(const QList<int> &tcs) +{ + m_toolChainSelector->setCheckedToolChains(tcs); +} + +QList<int> BinaryToolChainDialog::toolChains() const +{ + return m_toolChainSelector->checkedToolChains(); +} + +void BinaryToolChainDialog::setOkButtonEnabled(bool v) +{ + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(v); +} + +void BinaryToolChainDialog::setPath(const QString &p) +{ + m_pathChooser->setPath(p); +} + +QString BinaryToolChainDialog::path() const +{ + return m_pathChooser->path(); +} + +void BinaryToolChainDialog::slotValidChanged() +{ + setOkButtonEnabled(m_pathChooser->isValid() && m_toolChainSelector->isValid()); +} + +} // namespace Internal +} // namespace Debugger diff --git a/src/plugins/debugger/gdb/gdbchooserwidget.h b/src/plugins/debugger/gdb/gdbchooserwidget.h new file mode 100644 index 0000000000000000000000000000000000000000..c428e2bb47ec963c2b19c0296ec0fe30545617ff --- /dev/null +++ b/src/plugins/debugger/gdb/gdbchooserwidget.h @@ -0,0 +1,147 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef GDBCHOOSERWIDGET_H +#define GDBCHOOSERWIDGET_H + +#include <QtCore/QMultiMap> +#include <QtGui/QDialog> + +QT_BEGIN_NAMESPACE +class QTreeView; +class QToolButton; +class QModelIndex; +class QStandardItem; +class QComboBox; +class QDialogButtonBox; +class QFormLayout; +class QCheckBox; +QT_END_NAMESPACE + +namespace Utils { + class PathChooser; +} + +namespace Debugger { +namespace Internal { + +class GdbBinaryModel; + +/* GdbChooserWidget: Shows a list of gdb binary and associated toolchains with + * 'add' and 'remove' buttons. Provides delayed tooltip showing version information. + * Based on a multimap of binaries to toolchain. */ + +class GdbChooserWidget : public QWidget +{ + Q_OBJECT +public: + explicit GdbChooserWidget(QWidget *parent = 0); + + typedef QMultiMap<QString, int> BinaryToolChainMap; + + BinaryToolChainMap gdbBinaries() const; + void setGdbBinaries(const BinaryToolChainMap &m); + +private slots: + void slotAdd(); + void slotRemove(); + void slotCurrentChanged(const QModelIndex ¤t, const QModelIndex & previous); + void slotDoubleClicked(const QModelIndex ¤t); + +private: + void removeItem(QStandardItem *item); + QToolButton *createAddToolMenuButton(); + QStandardItem *currentItem() const; + + QTreeView *m_treeView; + GdbBinaryModel *m_model; + QToolButton *m_addButton; + QToolButton *m_deleteButton; +}; + +// Present toolchains with checkboxes grouped in QGroupBox panes +// and provide valid-handling. Unavailabe toolchains can be grayed +// out using setEnabledToolChains(). +class ToolChainSelectorWidget : public QWidget { + Q_OBJECT +public: + explicit ToolChainSelectorWidget(QWidget *parent = 0); + + void setEnabledToolChains(const QList<int> &); + + void setCheckedToolChains(const QList<int> &); + QList<int> checkedToolChains() const; + + bool isValid() const; + +signals: + void validChanged(bool); + +private slots: + void slotCheckStateChanged(int); + +private: + bool hasCheckedToolChain() const; + QCheckBox *createToolChainCheckBox(int tc); + + QList<QCheckBox*> m_checkBoxes; + bool m_valid; +}; + +// Internal helper dialog for selecting a binary and its +// associated toolchains. +class BinaryToolChainDialog : public QDialog { + Q_OBJECT +public: + explicit BinaryToolChainDialog(QWidget *parent); + + void setToolChainChoices(const QList<int> &); + + void setToolChains(const QList<int> &); + QList<int> toolChains() const; + + void setPath(const QString &); + QString path() const; + +private slots: + void slotValidChanged(); + +private: + void setOkButtonEnabled(bool e); + + ToolChainSelectorWidget *m_toolChainSelector; + QFormLayout *m_mainLayout; + QDialogButtonBox *m_buttonBox; + Utils::PathChooser *m_pathChooser; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // GDBCHOOSERWIDGET_H diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp index e3f1409eab2f0c8ca756a5d1453439c893787a77..7164db24e606bf14685688a6d3cd4e045e4f09db 100644 --- a/src/plugins/debugger/gdb/gdbengine.cpp +++ b/src/plugins/debugger/gdb/gdbengine.cpp @@ -172,7 +172,9 @@ static QByteArray parsePlainConsoleStream(const GdbResponse &response) // /////////////////////////////////////////////////////////////////////// -GdbEngine::GdbEngine(DebuggerManager *manager) : IDebuggerEngine(manager) +GdbEngine::GdbEngine(DebuggerManager *manager) : + IDebuggerEngine(manager), + m_gdbBinaryToolChainMap(DebuggerSettings::instance()->gdbBinaryToolChainMap()) { m_trkOptions = QSharedPointer<TrkOptions>(new TrkOptions); m_trkOptions->fromSettings(Core::ICore::instance()->settings()); @@ -293,7 +295,7 @@ QString GdbEngine::errorMessage(QProcess::ProcessError error) return tr("The Gdb process failed to start. Either the " "invoked program '%1' is missing, or you may have insufficient " "permissions to invoke the program.") - .arg(theDebuggerStringSetting(GdbLocation)); + .arg(m_gdb); case QProcess::Crashed: return tr("The Gdb process crashed some time after starting " "successfully."); @@ -4034,16 +4036,21 @@ void GdbEngine::gotoLocation(const StackFrame &frame, bool setMarker) bool GdbEngine::startGdb(const QStringList &args, const QString &gdb, const QString &settingsIdHint) { - debugMessage(_("STARTING GDB ") + gdb); - m_gdbProc.disconnect(); // From any previous runs - QString location = gdb; - const QByteArray env = qgetenv("QTC_DEBUGGER_PATH"); - if (!env.isEmpty()) - location = QString::fromLatin1(env); - if (location.isEmpty()) - location = theDebuggerStringSetting(GdbLocation); + m_gdb = QString::fromLatin1(qgetenv("QTC_DEBUGGER_PATH")); + if (m_gdb.isEmpty()) + m_gdb = m_gdbBinaryToolChainMap->key(m_startParameters->toolChainType); + if (m_gdb.isEmpty()) + m_gdb = gdb; + if (m_gdb.isEmpty()) { + const ProjectExplorer::ToolChain::ToolChainType toolChain = static_cast<ProjectExplorer::ToolChain::ToolChainType>(m_startParameters->toolChainType); + const QString toolChainName = ProjectExplorer::ToolChain::toolChainName(toolChain); + const QString msg = tr("There is no gdb binary available for '%1'").arg(toolChainName); + handleAdapterStartFailed(msg, GdbOptionsPage::settingsId()); + return false; + } + debugMessage(_("STARTING GDB ") + m_gdb); QStringList gdbArgs; gdbArgs << _("-i"); gdbArgs << _("mi"); @@ -4051,7 +4058,7 @@ bool GdbEngine::startGdb(const QStringList &args, const QString &gdb, const QStr #ifdef Q_OS_WIN // Set python path. By convention, python is located below gdb executable. // Extend the environment set on the process in startAdapter(). - const QFileInfo fi(location); + const QFileInfo fi(m_gdb); bool foundPython = false; if (fi.isAbsolute()) { const QString winPythonVersion = QLatin1String(winPythonVersionC); @@ -4076,8 +4083,8 @@ bool GdbEngine::startGdb(const QStringList &args, const QString &gdb, const QStr } } if (!foundPython) { - debugMessage(_("UNSUPPORTED GDB %1 DOES NOT HAVE PYTHON.").arg(location)); - showStatusMessage(_("Gdb at %1 does not have python.").arg(location)); + debugMessage(_("UNSUPPORTED GDB %1 DOES NOT HAVE PYTHON.").arg(m_gdb)); + showStatusMessage(_("Gdb at %1 does not have python.").arg(m_gdb)); } #endif @@ -4090,11 +4097,11 @@ bool GdbEngine::startGdb(const QStringList &args, const QString &gdb, const QStr connect(&m_gdbProc, SIGNAL(readyReadStandardError()), SLOT(readGdbStandardError())); - m_gdbProc.start(location, gdbArgs); + m_gdbProc.start(m_gdb, gdbArgs); if (!m_gdbProc.waitForStarted()) { const QString msg = tr("Unable to start gdb '%1': %2") - .arg(location, m_gdbProc.errorString()); + .arg(m_gdb, m_gdbProc.errorString()); handleAdapterStartFailed(msg, settingsIdHint); return false; } @@ -4340,7 +4347,7 @@ void GdbEngine::handleAdapterCrashed(const QString &msg) void GdbEngine::addOptionPages(QList<Core::IOptionsPage *> *opts) const { - opts->push_back(new GdbOptionsPage); + opts->push_back(new GdbOptionsPage(m_gdbBinaryToolChainMap)); opts->push_back(new TrkOptionsPage(m_trkOptions)); } diff --git a/src/plugins/debugger/gdb/gdbengine.h b/src/plugins/debugger/gdb/gdbengine.h index b1259d5e4998e759be5ccad95fafada5281df9ad..9bf587a1e2198fbbee839a99831d54354150f9d9 100644 --- a/src/plugins/debugger/gdb/gdbengine.h +++ b/src/plugins/debugger/gdb/gdbengine.h @@ -39,6 +39,7 @@ #include <QtCore/QFutureInterface> #include <QtCore/QHash> #include <QtCore/QMap> +#include <QtCore/QMultiMap> #include <QtCore/QObject> #include <QtCore/QProcess> #include <QtCore/QPoint> @@ -46,6 +47,7 @@ #include <QtCore/QTextCodec> #include <QtCore/QTime> #include <QtCore/QVariant> +#include <QtCore/QSharedPointer> QT_BEGIN_NAMESPACE class QMainWindow; @@ -84,6 +86,9 @@ class GdbEngine : public IDebuggerEngine Q_OBJECT public: + typedef QMultiMap<QString, int> GdbBinaryToolChainMap; + typedef QSharedPointer<GdbBinaryToolChainMap> GdbBinaryToolChainMapPtr; + explicit GdbEngine(DebuggerManager *manager); ~GdbEngine(); @@ -514,8 +519,10 @@ private: ////////// Dumper Management ////////// Q_SLOT void setDebugDebuggingHelpersClassic(const QVariant &on); Q_SLOT void setUseDebuggingHelpers(const QVariant &on); + const GdbBinaryToolChainMapPtr m_gdbBinaryToolChainMap; DebuggingHelperState m_debuggingHelperState; QtDumperHelper m_dumperHelper; + QString m_gdb; private: ////////// Convenience Functions ////////// diff --git a/src/plugins/debugger/gdb/gdboptionspage.cpp b/src/plugins/debugger/gdb/gdboptionspage.cpp index 6747e7aeb34eb8b6baa6c3fa885ecccf31fbab9e..2eca0a4f7d3cce3cf55b8d157a581cbd882bc3e5 100644 --- a/src/plugins/debugger/gdb/gdboptionspage.cpp +++ b/src/plugins/debugger/gdb/gdboptionspage.cpp @@ -38,7 +38,8 @@ namespace Debugger { namespace Internal { -GdbOptionsPage::GdbOptionsPage() +GdbOptionsPage::GdbOptionsPage(const GdbBinaryToolChainMapPtr &binaryToolChainMap) : + m_binaryToolChainMap(binaryToolChainMap) { } @@ -71,14 +72,11 @@ QWidget *GdbOptionsPage::createPage(QWidget *parent) { QWidget *w = new QWidget(parent); m_ui.setupUi(w); - m_ui.gdbLocationChooser->setExpectedKind(Utils::PathChooser::Command); - m_ui.gdbLocationChooser->setPromptDialogTitle(tr("Choose Gdb Location")); + m_ui.gdbChooserWidget->setGdbBinaries(*m_binaryToolChainMap); m_ui.scriptFileChooser->setExpectedKind(Utils::PathChooser::File); m_ui.scriptFileChooser->setPromptDialogTitle(tr("Choose Location of Startup Script File")); m_group.clear(); - m_group.insert(theDebuggerAction(GdbLocation), - m_ui.gdbLocationChooser); m_group.insert(theDebuggerAction(GdbScriptFile), m_ui.scriptFileChooser); m_group.insert(theDebuggerAction(GdbEnvironment), @@ -120,7 +118,7 @@ QWidget *GdbOptionsPage::createPage(QWidget *parent) if (m_searchKeywords.isEmpty()) { // TODO: Add breakpoints, environment? - QTextStream(&m_searchKeywords) << ' ' << m_ui.labelGdbLocation->text() + QTextStream(&m_searchKeywords) << ' ' << QLatin1String("gdb") << ' ' << m_ui.checkBoxSkipKnownFrames->text() << ' ' << m_ui.checkBoxEnableReverseDebugging->text() << ' ' << m_ui.checkBoxUseMessageBoxForSignals->text() @@ -133,6 +131,7 @@ QWidget *GdbOptionsPage::createPage(QWidget *parent) void GdbOptionsPage::apply() { m_group.apply(Core::ICore::instance()->settings()); + *m_binaryToolChainMap = m_ui.gdbChooserWidget->gdbBinaries(); } void GdbOptionsPage::finish() diff --git a/src/plugins/debugger/gdb/gdboptionspage.h b/src/plugins/debugger/gdb/gdboptionspage.h index 7fbf855fe370bd95e16e3b4720462ae67638fb4d..5e67016de7d516b0d7c08e03116e475ea13ba396 100644 --- a/src/plugins/debugger/gdb/gdboptionspage.h +++ b/src/plugins/debugger/gdb/gdboptionspage.h @@ -30,10 +30,13 @@ #ifndef GDBOPTIONSPAGE_H #define GDBOPTIONSPAGE_H +#include "ui_gdboptionspage.h" + #include <coreplugin/dialogs/ioptionspage.h> #include <utils/savedaction.h> -#include "ui_gdboptionspage.h" +#include <QtCore/QSharedPointer> +#include <QtCore/QMultiMap> namespace Debugger { namespace Internal { @@ -42,7 +45,10 @@ class GdbOptionsPage : public Core::IOptionsPage { Q_OBJECT public: - GdbOptionsPage(); + typedef QMultiMap<QString, int> GdbBinaryToolChainMap; + typedef QSharedPointer<GdbBinaryToolChainMap> GdbBinaryToolChainMapPtr; + + explicit GdbOptionsPage(const GdbBinaryToolChainMapPtr &binaryToolChainMap); virtual QString id() const { return settingsId(); } virtual QString displayName() const; @@ -58,6 +64,8 @@ public: static QString settingsId(); private: + const GdbBinaryToolChainMapPtr m_binaryToolChainMap; + Ui::GdbOptionsPage m_ui; Utils::SavedActionSet m_group; QString m_searchKeywords; diff --git a/src/plugins/debugger/gdb/gdboptionspage.ui b/src/plugins/debugger/gdb/gdboptionspage.ui index 451c532974c4a5a51e4de43d97293dc419008810..90deb6b6dd2b7bf17e4e888d647a0962b3c2351b 100644 --- a/src/plugins/debugger/gdb/gdboptionspage.ui +++ b/src/plugins/debugger/gdb/gdboptionspage.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>480</width> - <height>371</height> + <height>475</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout"> @@ -17,6 +17,9 @@ <string>Gdb</string> </property> <layout class="QFormLayout" name="formLayout"> + <property name="fieldGrowthPolicy"> + <enum>QFormLayout::ExpandingFieldsGrow</enum> + </property> <property name="horizontalSpacing"> <number>6</number> </property> @@ -26,18 +29,8 @@ <property name="margin"> <number>9</number> </property> - <item row="0" column="0"> - <widget class="QLabel" name="labelGdbLocation"> - <property name="toolTip"> - <string>This is either a full absolute path leading to the gdb binary you intend to use or the name of a gdb binary that will be searched in your PATH.</string> - </property> - <property name="text"> - <string>Gdb location:</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="Utils::PathChooser" name="gdbLocationChooser"/> + <item row="0" column="0" colspan="2"> + <widget class="Debugger::Internal::GdbChooserWidget" name="gdbChooserWidget" native="true"/> </item> <item row="1" column="0"> <widget class="QLabel" name="labelEnvironment"> @@ -65,44 +58,6 @@ <item row="2" column="1"> <widget class="Utils::PathChooser" name="scriptFileChooser"/> </item> - <item row="5" column="0" colspan="2"> - <widget class="QCheckBox" name="checkBoxEnableReverseDebugging"> - <property name="text"> - <string>Enable reverse debugging</string> - </property> - </widget> - </item> - <item row="6" column="0" colspan="2"> - <widget class="QCheckBox" name="checkBoxSkipKnownFrames"> - <property name="toolTip"> - <string>When this option is checked, 'Step Into' compresses several steps into one in certain situations, leading to 'less noisy' debugging. So will, e.g., the atomic - reference counting code be skipped, and a single 'Step Into' for a signal emission will end up directly in the slot connected to it.</string> - </property> - <property name="text"> - <string>Skip known frames when stepping</string> - </property> - </widget> - </item> - <item row="7" column="0" colspan="2"> - <widget class="QCheckBox" name="checkBoxUseMessageBoxForSignals"> - <property name="text"> - <string>Show a message box when receiving a signal</string> - </property> - </widget> - </item> - <item row="4" column="0" colspan="2"> - <widget class="QCheckBox" name="checkBoxUsePreciseBreakpoints"> - <property name="toolTip"> - <string>When this option is checked, the debugger plugin attempts -to extract full path information for all source files from gdb. This is a -slow process but enables setting breakpoints in files with the same file -name in different directories.</string> - </property> - <property name="text"> - <string>Use full path information to set breakpoints</string> - </property> - </widget> - </item> <item row="3" column="0"> <widget class="QLabel" name="labelGdbWatchdogTimeout"> <property name="text"> @@ -136,6 +91,44 @@ on slow machines. In this case, the value should be increased.</string> </property> </widget> </item> + <item row="4" column="0" colspan="2"> + <widget class="QCheckBox" name="checkBoxUsePreciseBreakpoints"> + <property name="toolTip"> + <string>When this option is checked, the debugger plugin attempts +to extract full path information for all source files from gdb. This is a +slow process but enables setting breakpoints in files with the same file +name in different directories.</string> + </property> + <property name="text"> + <string>Use full path information to set breakpoints</string> + </property> + </widget> + </item> + <item row="5" column="0" colspan="2"> + <widget class="QCheckBox" name="checkBoxEnableReverseDebugging"> + <property name="text"> + <string>Enable reverse debugging</string> + </property> + </widget> + </item> + <item row="6" column="0" colspan="2"> + <widget class="QCheckBox" name="checkBoxSkipKnownFrames"> + <property name="toolTip"> + <string>When this option is checked, 'Step Into' compresses several steps into one in certain situations, leading to 'less noisy' debugging. So will, e.g., the atomic + reference counting code be skipped, and a single 'Step Into' for a signal emission will end up directly in the slot connected to it.</string> + </property> + <property name="text"> + <string>Skip known frames when stepping</string> + </property> + </widget> + </item> + <item row="7" column="0" colspan="2"> + <widget class="QCheckBox" name="checkBoxUseMessageBoxForSignals"> + <property name="text"> + <string>Show a message box when receiving a signal</string> + </property> + </widget> + </item> </layout> </widget> </item> @@ -227,6 +220,12 @@ on slow machines. In this case, the value should be increased.</string> <header location="global">utils/pathchooser.h</header> <container>1</container> </customwidget> + <customwidget> + <class>Debugger::Internal::GdbChooserWidget</class> + <extends>QWidget</extends> + <header>gdb/gdbchooserwidget.h</header> + <container>1</container> + </customwidget> </customwidgets> <resources/> <connections/>