forked from qt-creator/qt-creator
ProjectExplorer: Sanitize the "Add run config" UI
This functionality was implemented via a pop-up menu, which was close to unusable if more than a few candidates existed (for example: Qt Creator with autotests enabled). We now use a proper dialog. The list of candidates is sortable, can be filtered and includes information about which project file the target executable comes from. Fixes: QTCREATORBUG-19955 Change-Id: Ife087ad69a7e43e280d13c528d21f94a1ae48d4d Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
192
src/plugins/projectexplorer/addrunconfigdialog.cpp
Normal file
192
src/plugins/projectexplorer/addrunconfigdialog.cpp
Normal file
@@ -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 <utils/itemviews.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/treemodel.h>
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHeaderView>
|
||||
#include <QHBoxLayout>
|
||||
#include <QItemSelectionModel>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QRegExp>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
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<TreeItem, CandidateTreeItem>
|
||||
{
|
||||
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<ProxyModel *>(m_view->model());
|
||||
const auto * const model = static_cast<CandidatesModel *>(proxyModel->sourceModel());
|
||||
const TreeItem * const item = model->itemForIndex(proxyModel->mapToSource(selected.first()));
|
||||
QTC_ASSERT(item, return);
|
||||
m_creationInfo = static_cast<const CandidateTreeItem *>(item)->creationInfo();
|
||||
QTC_ASSERT(m_creationInfo.id.isValid(), return);
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace ProjectExplorer
|
55
src/plugins/projectexplorer/addrunconfigdialog.h
Normal file
55
src/plugins/projectexplorer/addrunconfigdialog.h
Normal file
@@ -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 <QDialog>
|
||||
|
||||
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
|
@@ -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 \
|
||||
|
@@ -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",
|
||||
|
@@ -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
|
||||
|
@@ -235,6 +235,7 @@ public:
|
||||
QString buildKey;
|
||||
QString displayName;
|
||||
QString displayNameUniquifier;
|
||||
Utils::FileName projectFilePath;
|
||||
CreationMode creationMode = AlwaysCreate;
|
||||
bool useTerminal = false;
|
||||
};
|
||||
|
@@ -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<void (QComboBox::*)(int)>(&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<QAction *> 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()
|
||||
|
@@ -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<QWidget *, QLabel *>;
|
||||
|
Reference in New Issue
Block a user