diff --git a/src/plugins/projectexplorer/addrunconfigdialog.cpp b/src/plugins/projectexplorer/addrunconfigdialog.cpp new file mode 100644 index 00000000000..dacdbac678a --- /dev/null +++ b/src/plugins/projectexplorer/addrunconfigdialog.cpp @@ -0,0 +1,192 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "addrunconfigdialog.h" + +#include "project.h" +#include "target.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Utils; + +namespace ProjectExplorer { +namespace Internal { + +const Qt::ItemDataRole IsCustomRole = Qt::UserRole; + +class CandidateTreeItem : public TreeItem +{ + Q_DECLARE_TR_FUNCTIONS(ProjectExplorer::Internal::AddRunConfigDialog) +public: + CandidateTreeItem(const RunConfigurationCreationInfo &rci, const FileName &projectRoot) + : m_creationInfo(rci), m_projectRoot(projectRoot) + { } + + RunConfigurationCreationInfo creationInfo() const { return m_creationInfo; } + +private: + QVariant data(int column, int role) const override + { + QTC_ASSERT(column < 2, return QVariant()); + if (role == IsCustomRole) + return m_creationInfo.projectFilePath.isEmpty(); + if (column == 0 && role == Qt::DisplayRole) + return m_creationInfo.displayName; + if (column == 1 && role == Qt::DisplayRole) { + FileName displayPath = m_creationInfo.projectFilePath.relativeChildPath(m_projectRoot); + if (displayPath.isEmpty()) { + displayPath = m_creationInfo.projectFilePath; + QTC_CHECK(displayPath.isEmpty()); + } + return displayPath.isEmpty() ? tr("[none]") : displayPath.toUserOutput(); + } + return QVariant(); + } + + const RunConfigurationCreationInfo m_creationInfo; + const FileName m_projectRoot; +}; + +class CandidatesModel : public TreeModel +{ + Q_DECLARE_TR_FUNCTIONS(ProjectExplorer::Internal::AddRunConfigDialog) +public: + CandidatesModel(Target *target, QObject *parent) : TreeModel(parent) + { + setHeader({tr("Name"), tr("Source")}); + for (const RunConfigurationCreationInfo &rci + : RunConfigurationFactory::creatorsForTarget(target)) { + rootItem()->appendChild(new CandidateTreeItem(rci, + target->project()->projectDirectory())); + } + } +}; + +class ProxyModel : public QSortFilterProxyModel +{ +public: + ProxyModel(QObject *parent) : QSortFilterProxyModel(parent) { } + +private: + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override + { + if (source_left.column() == 0) { + // Let's put the fallback candidates last. + const bool leftIsCustom = sourceModel()->data(source_left, IsCustomRole).toBool(); + const bool rightIsCustom = sourceModel()->data(source_right, IsCustomRole).toBool(); + if (leftIsCustom != rightIsCustom) + return rightIsCustom; + } + return QSortFilterProxyModel::lessThan(source_left, source_right); + } +}; + +class CandidatesTreeView : public TreeView +{ +public: + CandidatesTreeView(QWidget *parent) : TreeView(parent) + { + setUniformRowHeights(true); + } + +private: + QSize sizeHint() const override + { + const int width = columnWidth(0) + columnWidth(1); + const int height = qMin(model()->rowCount() + 10, 10) * rowHeight(model()->index(0, 0)) + + header()->sizeHint().height(); + return {width, height}; + } +}; + +AddRunConfigDialog::AddRunConfigDialog(Target *target, QWidget *parent) + : QDialog(parent), m_view(new CandidatesTreeView(this)) +{ + setWindowTitle(tr("Create Run Configuration")); + const auto model = new CandidatesModel(target, this); + const auto proxyModel = new ProxyModel(this); + proxyModel->setSourceModel(model); + const auto filterEdit = new QLineEdit(this); + m_view->setSelectionMode(TreeView::SingleSelection); + m_view->setSelectionBehavior(TreeView::SelectRows); + m_view->setSortingEnabled(true); + m_view->setModel(proxyModel); + m_view->resizeColumnToContents(0); + m_view->resizeColumnToContents(1); + m_view->sortByColumn(0, Qt::AscendingOrder); + const auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Create")); + + connect(filterEdit, &QLineEdit::textChanged, this, [proxyModel](const QString &text) { + proxyModel->setFilterRegExp(QRegExp(text, Qt::CaseInsensitive)); + }); + connect(m_view, &TreeView::doubleClicked, this, [this] { accept(); }); + const auto updateOkButton = [buttonBox, this] { + buttonBox->button(QDialogButtonBox::Ok) + ->setEnabled(m_view->selectionModel()->hasSelection()); + }; + connect(m_view->selectionModel(), &QItemSelectionModel::selectionChanged, this, updateOkButton); + updateOkButton(); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + const auto layout = new QVBoxLayout(this); + const auto filterLayout = new QHBoxLayout; + filterLayout->addWidget(new QLabel(tr("Filter candidates by name:"), this)); + filterLayout->addWidget(filterEdit); + layout->addLayout(filterLayout); + layout->addWidget(m_view); + layout->addWidget(buttonBox); +} + +void AddRunConfigDialog::accept() +{ + const QModelIndexList selected = m_view->selectionModel()->selectedRows(); + QTC_ASSERT(selected.count() == 1, return); + const auto * const proxyModel = static_cast(m_view->model()); + const auto * const model = static_cast(proxyModel->sourceModel()); + const TreeItem * const item = model->itemForIndex(proxyModel->mapToSource(selected.first())); + QTC_ASSERT(item, return); + m_creationInfo = static_cast(item)->creationInfo(); + QTC_ASSERT(m_creationInfo.id.isValid(), return); + QDialog::accept(); +} + +} // namespace Internal +} // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/addrunconfigdialog.h b/src/plugins/projectexplorer/addrunconfigdialog.h new file mode 100644 index 00000000000..2214bed5a11 --- /dev/null +++ b/src/plugins/projectexplorer/addrunconfigdialog.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "runconfiguration.h" + +#include + +namespace Utils { class TreeView; } + +namespace ProjectExplorer { +class Target; + +namespace Internal { + +class AddRunConfigDialog : public QDialog +{ + Q_OBJECT +public: + AddRunConfigDialog(Target *target, QWidget *parent); + + RunConfigurationCreationInfo creationInfo() const { return m_creationInfo; } + +private: + void accept() override; + + Utils::TreeView * const m_view; + RunConfigurationCreationInfo m_creationInfo; +}; + +} // namespace Internal +} // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/projectexplorer.pro b/src/plugins/projectexplorer/projectexplorer.pro index 14033e5c478..c88962827f5 100644 --- a/src/plugins/projectexplorer/projectexplorer.pro +++ b/src/plugins/projectexplorer/projectexplorer.pro @@ -10,6 +10,7 @@ include(../../shared/clang/clang_defines.pri) HEADERS += projectexplorer.h \ abi.h \ abiwidget.h \ + addrunconfigdialog.h \ ansifilterparser.h \ buildinfo.h \ clangparser.h \ @@ -162,6 +163,7 @@ HEADERS += projectexplorer.h \ SOURCES += projectexplorer.cpp \ abi.cpp \ abiwidget.cpp \ + addrunconfigdialog.cpp \ ansifilterparser.cpp \ buildinfo.cpp \ clangparser.cpp \ diff --git a/src/plugins/projectexplorer/projectexplorer.qbs b/src/plugins/projectexplorer/projectexplorer.qbs index 5294cbd7a51..c7a1a903dc1 100644 --- a/src/plugins/projectexplorer/projectexplorer.qbs +++ b/src/plugins/projectexplorer/projectexplorer.qbs @@ -24,6 +24,7 @@ Project { "abi.cpp", "abi.h", "abiwidget.cpp", "abiwidget.h", "abstractprocessstep.cpp", "abstractprocessstep.h", + "addrunconfigdialog.cpp", "addrunconfigdialog.h", "allprojectsfilter.cpp", "allprojectsfilter.h", "allprojectsfind.cpp", "allprojectsfind.h", "ansifilterparser.cpp", "ansifilterparser.h", diff --git a/src/plugins/projectexplorer/runconfiguration.cpp b/src/plugins/projectexplorer/runconfiguration.cpp index 12a6ab56868..d6cd2dd464c 100644 --- a/src/plugins/projectexplorer/runconfiguration.cpp +++ b/src/plugins/projectexplorer/runconfiguration.cpp @@ -466,6 +466,7 @@ RunConfigurationFactory::availableCreators(Target *parent) const rci.factory = this; rci.id = m_runConfigBaseId; rci.buildKey = ti.buildKey; + rci.projectFilePath = ti.projectFilePath; rci.displayName = displayName; rci.displayNameUniquifier = ti.displayNameUniquifier; rci.creationMode = ti.isQtcRunnable || !hasAnyQtcRunnable diff --git a/src/plugins/projectexplorer/runconfiguration.h b/src/plugins/projectexplorer/runconfiguration.h index c0443ae07e2..782876dbc94 100644 --- a/src/plugins/projectexplorer/runconfiguration.h +++ b/src/plugins/projectexplorer/runconfiguration.h @@ -235,6 +235,7 @@ public: QString buildKey; QString displayName; QString displayNameUniquifier; + Utils::FileName projectFilePath; CreationMode creationMode = AlwaysCreate; bool useTerminal = false; }; diff --git a/src/plugins/projectexplorer/runsettingspropertiespage.cpp b/src/plugins/projectexplorer/runsettingspropertiespage.cpp index 3b30bf63ad7..59cfd94dd38 100644 --- a/src/plugins/projectexplorer/runsettingspropertiespage.cpp +++ b/src/plugins/projectexplorer/runsettingspropertiespage.cpp @@ -25,6 +25,7 @@ #include "runsettingspropertiespage.h" +#include "addrunconfigdialog.h" #include "buildstepspage.h" #include "deployconfiguration.h" #include "runconfiguration.h" @@ -92,7 +93,7 @@ RunSettingsWidget::RunSettingsWidget(Target *target) : m_runConfigurationCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); m_runConfigurationCombo->setMinimumContentsLength(15); - m_addRunToolButton = new QPushButton(tr("Add"), this); + m_addRunToolButton = new QPushButton(tr("Add..."), this); m_removeRunToolButton = new QPushButton(tr("Remove"), this); m_renameRunButton = new QPushButton(tr("Rename..."), this); m_cloneRunButton = new QPushButton(tr("Clone..."), this); @@ -187,8 +188,6 @@ RunSettingsWidget::RunSettingsWidget(Target *target) : m_runLayout->addLayout(disabledHBox); - m_addRunMenu = new QMenu(m_addRunToolButton); - m_addRunToolButton->setMenu(m_addRunMenu); RunConfiguration *rc = m_target->activeRunConfiguration(); m_runConfigurationCombo->setModel(m_runConfigurationsModel); m_runConfigurationCombo->setCurrentIndex( @@ -200,8 +199,8 @@ RunSettingsWidget::RunSettingsWidget(Target *target) : setConfigurationWidget(rc); - connect(m_addRunMenu, &QMenu::aboutToShow, - this, &RunSettingsWidget::aboutToShowAddMenu); + connect(m_addRunToolButton, &QAbstractButton::clicked, + this, &RunSettingsWidget::showAddRunConfigDialog); connect(m_runConfigurationCombo, static_cast(&QComboBox::currentIndexChanged), this, &RunSettingsWidget::currentRunConfigurationChanged); connect(m_removeRunToolButton, &QAbstractButton::clicked, @@ -225,28 +224,20 @@ RunSettingsWidget::RunSettingsWidget(Target *target) : this, &RunSettingsWidget::activeRunConfigurationChanged); } -void RunSettingsWidget::aboutToShowAddMenu() +void RunSettingsWidget::showAddRunConfigDialog() { - m_addRunMenu->clear(); - QList menuActions; - for (const RunConfigurationCreationInfo &item : - RunConfigurationFactory::creatorsForTarget(m_target)) { - auto action = new QAction(item.displayName, m_addRunMenu); - connect(action, &QAction::triggered, [item, this] { - RunConfiguration *newRC = item.create(m_target); - if (!newRC) - return; - QTC_CHECK(newRC->id() == item.id); - m_target->addRunConfiguration(newRC); - m_target->setActiveRunConfiguration(newRC); - m_removeRunToolButton->setEnabled(m_target->runConfigurations().size() > 1); - }); - menuActions.append(action); - } - - Utils::sort(menuActions, &QAction::text); - foreach (QAction *action, menuActions) - m_addRunMenu->addAction(action); + AddRunConfigDialog dlg(m_target, this); + if (dlg.exec() != QDialog::Accepted) + return; + RunConfigurationCreationInfo rci = dlg.creationInfo(); + QTC_ASSERT(rci.id.isValid(), return); + RunConfiguration *newRC = rci.create(m_target); + if (!newRC) + return; + QTC_CHECK(newRC->id() == rci.id); + m_target->addRunConfiguration(newRC); + m_target->setActiveRunConfiguration(newRC); + m_removeRunToolButton->setEnabled(m_target->runConfigurations().size() > 1); } void RunSettingsWidget::cloneRunConfiguration() diff --git a/src/plugins/projectexplorer/runsettingspropertiespage.h b/src/plugins/projectexplorer/runsettingspropertiespage.h index 1857fea945e..bf86fe56ba2 100644 --- a/src/plugins/projectexplorer/runsettingspropertiespage.h +++ b/src/plugins/projectexplorer/runsettingspropertiespage.h @@ -58,7 +58,7 @@ public: private: void currentRunConfigurationChanged(int index); - void aboutToShowAddMenu(); + void showAddRunConfigDialog(); void cloneRunConfiguration(); void removeRunConfiguration(); void activeRunConfigurationChanged(); @@ -91,7 +91,6 @@ private: NamedWidget *m_deployConfigurationWidget = nullptr; QVBoxLayout *m_deployLayout = nullptr; BuildStepListWidget *m_deploySteps = nullptr; - QMenu *m_addRunMenu; QMenu *m_addDeployMenu; bool m_ignoreChange = false; using RunConfigItem = QPair;