ProjectExplorer: Rework mode main window

The existing solution with the special-style horizontal
kit selector comes from a time when there was typically
one, at most four targets. Today's setup can easily
reach half a dozen targets with several toolchain versions
each and can't be sensibly handled with the overflowing
horizontal bar.

This here replaces the horizontal kit selector bar as
well as the top level project "tab bar" with a normal
tree view. All targets are visible (but possibly disabled)
at once, and can be enabled/disabled using the context
menu on the tree items.

Change-Id: I1ce7401ca96109bf34bc8c0ae19d265e5845aa88
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
hjk
2016-07-22 15:53:01 +02:00
committed by hjk
parent d258d47b42
commit 2459652234
43 changed files with 1300 additions and 3156 deletions

View File

@@ -25,345 +25,393 @@
#include "projectwindow.h"
#include "doubletabwidget.h"
#include "panelswidget.h"
#include "kit.h"
#include "kitmanager.h"
#include "panelswidget.h"
#include "project.h"
#include "projectexplorer.h"
#include "projectpanelfactory.h"
#include "session.h"
#include "target.h"
#include "targetsettingspanel.h"
#include <coreplugin/coreicons.h>
#include <coreplugin/icore.h>
#include <coreplugin/idocument.h>
#include <extensionsystem/pluginmanager.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <QStackedWidget>
#include <utils/navigationtreeview.h>
#include <utils/qtcassert.h>
#include <utils/styledbar.h>
#include <utils/treemodel.h>
#include <QApplication>
#include <QComboBox>
#include <QDockWidget>
#include <QHeaderView>
#include <QMenu>
#include <QStyledItemDelegate>
#include <QTreeView>
#include <QVBoxLayout>
using namespace ProjectExplorer;
using namespace ProjectExplorer::Internal;
///
// ProjectWindow
///
using namespace Core;
using namespace Utils;
ProjectWindow::ProjectWindow(QWidget *parent)
: QWidget(parent),
m_ignoreChange(false),
m_currentWidget(0)
namespace ProjectExplorer {
namespace Internal {
// The first tree level, i.e. projects.
class ProjectItem : public TreeItem
{
// Setup overall layout:
auto viewLayout = new QVBoxLayout(this);
viewLayout->setMargin(0);
viewLayout->setSpacing(0);
public:
explicit ProjectItem(Project *project) : m_project(project)
{
QTC_ASSERT(m_project, return);
foreach (ProjectPanelFactory *factory, ProjectPanelFactory::factories())
appendChild(factory->createSelectorItem(m_project));
}
m_tabWidget = new DoubleTabWidget(this);
viewLayout->addWidget(m_tabWidget);
QVariant data(int column, int role) const override
{
switch (role) {
case Qt::DisplayRole:
return m_project->displayName();
// Setup our container for the contents:
m_centralWidget = new QStackedWidget(this);
viewLayout->addWidget(m_centralWidget);
case ProjectDisplayNameRole:
return m_project->displayName();
// Connections
connect(m_tabWidget, &DoubleTabWidget::currentIndexChanged,
this, &ProjectWindow::showProperties);
case Qt::DecorationRole: {
QVariant icon;
forSecondLevelChildren<TreeItem *>([this, &icon](TreeItem *item) {
QVariant sicon = item->data(0, Qt::DecorationRole);
if (sicon.isValid())
icon = sicon;
});
return icon;
}
case Qt::FontRole: {
QFont font;
font.setBold(m_project == SessionManager::startupProject());
return font;
}
case ActiveWidgetRole:
case ActiveIndexRole:
if (0 <= m_currentPanelIndex && m_currentPanelIndex < childCount())
return childAt(m_currentPanelIndex)->data(column, role);
}
return QVariant();
}
bool setData(int column, const QVariant &data, int role) override
{
Q_UNUSED(column)
if (role == ItemActivaterRole) {
// Possible called from child item.
TreeItem *item = data.value<TreeItem *>();
m_currentPanelIndex = children().indexOf(item);
SessionManager::setStartupProject(m_project);
// Bubble up.
parent()->setData(0, QVariant::fromValue(static_cast<TreeItem *>(this)), role);
return true;
}
return false;
}
Project *project() const { return m_project; }
private:
int m_currentPanelIndex = 0;
Project * const m_project;
};
class RootItem : public TypedTreeItem<ProjectItem>
{
public:
QVariant data(int column, int role) const override
{
if (role == ActiveWidgetRole) {
if (0 <= m_currentProjectIndex && m_currentProjectIndex < childCount())
return childAt(m_currentProjectIndex)->data(column, role);
}
return QVariant();
}
bool setData(int column, const QVariant &data, int role) override
{
Q_UNUSED(column)
if (role == ItemActivaterRole) {
// Possible called from child item.
if (TreeItem *t = data.value<TreeItem *>())
m_currentProjectIndex = children().indexOf(t);
updateAll();
return true;
}
return false;
}
int m_currentProjectIndex = -1;
};
//
// SelectorModel
//
class SelectorModel
: public LeveledTreeModel<RootItem, ProjectItem, TreeItem>
{
Q_OBJECT
public:
SelectorModel(QObject *parent)
: LeveledTreeModel<RootItem, ProjectItem, TreeItem>(parent)
{
setRootItem(new RootItem);
setHeader({ ProjectWindow::tr("Projects") });
}
signals:
void needPanelUpdate();
};
//
// SelectorDelegate
//
class SelectorDelegate : public QStyledItemDelegate
{
public:
SelectorDelegate() {}
QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const override
{
QSize s = QStyledItemDelegate::sizeHint(option, index);
auto model = static_cast<const SelectorModel *>(index.model());
TreeItem *item = model->itemForIndex(index);
if (item && item->level() == 2)
s = QSize(s.width(), 3 * s.height());
return s;
}
void paint(QPainter *painter,
const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
auto model = static_cast<const SelectorModel *>(index.model());
TreeItem *item = model->itemForIndex(index);
QStyleOptionViewItem opt = option;
if (item && item->level() == 2) {
opt.font.setBold(true);
opt.font.setPointSizeF(opt.font.pointSizeF() * 1.2);
}
QStyledItemDelegate::paint(painter, opt, index);
}
};
//
// SelectorTree
//
class SelectorTree : public NavigationTreeView
{
public:
SelectorTree()
{
setWindowTitle("Project Kit Selector");
header()->hide();
setExpandsOnDoubleClick(false);
setHeaderHidden(true);
setItemsExpandable(false); // No user interaction.
setRootIsDecorated(false);
setUniformRowHeights(false); // sic!
setSelectionMode(QAbstractItemView::SingleSelection);
setSelectionBehavior(QAbstractItemView::SelectRows);
setEditTriggers(QAbstractItemView::NoEditTriggers);
setActivationMode(SingleClickActivation);
setObjectName("ProjectNavigation");
setContextMenuPolicy(Qt::CustomContextMenu);
}
};
//
// ProjectWindow
//
ProjectWindow::ProjectWindow()
{
setBackgroundRole(QPalette::Base);
m_selectorModel = new SelectorModel(this);
connect(m_selectorModel, &SelectorModel::needPanelUpdate,
this, &ProjectWindow::updatePanel);
m_selectorTree = new SelectorTree;
m_selectorTree->setModel(m_selectorModel);
m_selectorTree->setItemDelegate(new SelectorDelegate);
connect(m_selectorTree, &QAbstractItemView::activated,
this, &ProjectWindow::itemActivated);
m_projectSelection = new QComboBox;
m_projectSelection->setModel(m_selectorModel);
// m_projectSelection->setProperty("hideicon", true);
// m_projectSelection->setProperty("notelideasterisk", true);
// m_projectSelection->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
connect(m_projectSelection, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated),
this, &ProjectWindow::projectSelected, Qt::QueuedConnection);
auto styledBar = new StyledBar; // The black blob on top of the side bar
styledBar->setObjectName("ProjectModeStyledBar");
auto styledBarLayout = new QHBoxLayout(styledBar);
styledBarLayout->setContentsMargins(0, 0, 0, 0);
styledBarLayout->addWidget(m_projectSelection);
auto selectorView = new QWidget; // Black blob + Project tree + Combobox below.
selectorView->setObjectName("ProjectSelector"); // Needed for dock widget state saving
selectorView->setWindowTitle(tr("Project Selector"));
selectorView->setAutoFillBackground(true);
auto innerLayout = new QVBoxLayout;
innerLayout->setContentsMargins(14, 0, 14, 0);
//innerLayout->addWidget(m_projectSelection);
innerLayout->addWidget(m_selectorTree);
auto selectorLayout = new QVBoxLayout(selectorView);
selectorLayout->setContentsMargins(0, 0, 0, 0);
selectorLayout->addWidget(styledBar);
selectorLayout->addLayout(innerLayout);
m_selectorDock = addDockForWidget(selectorView, true);
addDockWidget(Qt::LeftDockWidgetArea, m_selectorDock);
SessionManager *sessionManager = SessionManager::instance();
connect(sessionManager, &SessionManager::projectAdded,
this, &ProjectWindow::registerProject);
connect(sessionManager, &SessionManager::aboutToRemoveProject,
this, &ProjectWindow::deregisterProject);
connect(sessionManager, &SessionManager::startupProjectChanged,
this, &ProjectWindow::startupProjectChanged);
connect(sessionManager, &SessionManager::projectDisplayNameChanged,
this, &ProjectWindow::projectDisplayNameChanged);
// Update properties to empty project for now:
showProperties(-1, -1);
connect(m_selectorTree, &QWidget::customContextMenuRequested,
this, &ProjectWindow::openContextMenu);
}
void ProjectWindow::aboutToShutdown()
void ProjectWindow::openContextMenu(const QPoint &pos)
{
showProperties(-1, -1);
auto menu = new QMenu;
menu->setAttribute(Qt::WA_DeleteOnClose);
QModelIndex index = m_selectorTree->indexAt(pos);
m_selectorModel->setData(index, QVariant::fromValue(menu), ContextMenuItemAdderRole);
if (menu->actions().isEmpty())
delete menu;
else
menu->popup(m_selectorTree->mapToGlobal(pos));
}
void ProjectWindow::removedTarget(Target *)
void ProjectWindow::contextMenuEvent(QContextMenuEvent *event)
{
auto p = qobject_cast<Project *>(sender());
QTC_ASSERT(p, return);
if (p->targets().isEmpty())
projectUpdated(p);
}
void ProjectWindow::projectUpdated(Project *project)
{
// Called after a project was configured
int currentIndex = m_tabWidget->currentIndex();
int oldSubIndex = m_tabWidget->currentSubIndex();
removeCurrentWidget();
int newSubIndex = m_cache.recheckFactories(project, oldSubIndex);
if (newSubIndex == -1)
newSubIndex = 0;
m_tabWidget->setSubTabs(currentIndex, m_cache.tabNames(project));
m_ignoreChange = true;
m_tabWidget->setCurrentIndex(currentIndex, newSubIndex);
m_ignoreChange = false;
QWidget *widget = m_cache.widgetFor(project, newSubIndex);
if (widget) {
m_currentWidget = widget;
m_centralWidget->addWidget(m_currentWidget);
m_centralWidget->setCurrentWidget(m_currentWidget);
m_currentWidget->show();
}
}
void ProjectWindow::projectDisplayNameChanged(Project *project)
{
int index = m_cache.indexForProject(project);
if (index < 0)
return;
m_ignoreChange = true;
bool isCurrentIndex = m_tabWidget->currentIndex() == index;
int subIndex = m_tabWidget->currentSubIndex();
QStringList subTabs = m_tabWidget->subTabs(index);
m_tabWidget->removeTab(index);
m_cache.sort();
int newIndex = m_cache.indexForProject(project);
m_tabWidget->insertTab(newIndex, project->displayName(), project->projectFilePath().toString(), subTabs);
if (isCurrentIndex)
m_tabWidget->setCurrentIndex(newIndex, subIndex);
m_ignoreChange = false;
Q_UNUSED(event)
// Do nothing to avoid creation of the dock window selection menu.
}
void ProjectWindow::registerProject(Project *project)
{
if (m_cache.isRegistered(project))
return;
QTC_ASSERT(itemForProject(project) == nullptr, return);
m_cache.registerProject(project);
m_tabWidget->insertTab(m_cache.indexForProject(project),
project->displayName(),
project->projectFilePath().toString(),
m_cache.tabNames(project));
auto newTab = new ProjectItem(project);
connect(project, &Project::removedTarget, this, &ProjectWindow::removedTarget);
}
m_selectorModel->rootItem()->appendChild(newTab);
bool ProjectWindow::deregisterProject(Project *project)
{
int index = m_cache.indexForProject(project);
if (index == -1)
return false;
disconnect(project, &Project::removedTarget, this, &ProjectWindow::removedTarget);
QVector<QWidget *> deletedWidgets = m_cache.deregisterProject(project);
if (deletedWidgets.contains(m_currentWidget))
m_currentWidget = 0;
m_tabWidget->removeTab(index);
return true;
}
void ProjectWindow::startupProjectChanged(Project *p)
{
int index = m_cache.indexForProject(p);
if (index != -1)
m_tabWidget->setCurrentIndex(index);
}
void ProjectWindow::showProperties(int index, int subIndex)
{
if (m_ignoreChange)
return;
removeCurrentWidget();
Project *project = m_cache.projectFor(index);
if (!project) {
return;
}
QWidget *widget = m_cache.widgetFor(project, subIndex);
if (widget) {
m_currentWidget = widget;
m_centralWidget->addWidget(m_currentWidget);
m_centralWidget->setCurrentWidget(m_currentWidget);
m_currentWidget->show();
if (hasFocus()) // we get assigned focus from setFocusToCurrentMode, pass that on
m_currentWidget->setFocus();
}
SessionManager::setStartupProject(project);
}
void ProjectWindow::removeCurrentWidget()
{
if (m_currentWidget) {
m_centralWidget->removeWidget(m_currentWidget);
m_currentWidget->hide();
m_currentWidget = 0;
}
}
// WidgetCache
void WidgetCache::registerProject(Project *project)
{
QTC_ASSERT(!isRegistered(project), return);
QList<ProjectPanelFactory *> fac = ProjectPanelFactory::factories();
int factorySize = fac.size();
ProjectInfo info;
info.project = project;
info.widgets.resize(factorySize);
info.supports.resize(factorySize);
for (int i = 0; i < factorySize; ++i)
info.supports[i] = fac.at(i)->supports(project);
m_projects.append(info);
sort();
}
QVector<QWidget *> WidgetCache::deregisterProject(Project *project)
{
QTC_ASSERT(isRegistered(project), return QVector<QWidget *>());
int index = indexForProject(project);
ProjectInfo info = m_projects.at(index);
QVector<QWidget *> deletedWidgets = info.widgets;
qDeleteAll(info.widgets);
m_projects.removeAt(index);
return deletedWidgets;
}
QStringList WidgetCache::tabNames(Project *project) const
{
int index = indexForProject(project);
if (index == -1)
return QStringList();
QList<ProjectPanelFactory *> fac = ProjectPanelFactory::factories();
ProjectInfo info = m_projects.at(index);
int end = info.supports.size();
QStringList names;
for (int i = 0; i < end; ++i)
if (info.supports.at(i))
names << fac.at(i)->displayName();
return names;
}
int WidgetCache::factoryIndex(int projectIndex, int supportsIndex) const
{
QList<ProjectPanelFactory *> fac = ProjectPanelFactory::factories();
int end = fac.size();
const ProjectInfo &info = m_projects.at(projectIndex);
for (int i = 0; i < end; ++i) {
if (info.supports.at(i)) {
if (supportsIndex == 0)
return i;
else
--supportsIndex;
}
}
return -1;
}
QWidget *WidgetCache::widgetFor(Project *project, int supportsIndex)
{
int projectIndex = indexForProject(project);
if (projectIndex == -1)
return 0;
QList<ProjectPanelFactory *> fac = ProjectPanelFactory::factories();
int factoryIdx = factoryIndex(projectIndex, supportsIndex);
if (factoryIdx < 0 ||factoryIdx >= m_projects.at(projectIndex).widgets.size())
return 0;
if (!m_projects.at(projectIndex).widgets.at(factoryIdx))
m_projects[projectIndex].widgets[factoryIdx] = fac.at(factoryIdx)->createWidget(project);
return m_projects.at(projectIndex).widgets.at(factoryIdx);
}
bool WidgetCache::isRegistered(Project *project) const
{
return Utils::anyOf(m_projects, [&project](ProjectInfo pinfo) {
return pinfo.project == project;
});
}
int WidgetCache::indexForProject(Project *project) const
{
return Utils::indexOf(m_projects, [&project](ProjectInfo pinfo) {
return pinfo.project == project;
});
}
Project *WidgetCache::projectFor(int projectIndex) const
{
if (projectIndex < 0 || projectIndex >= m_projects.size())
return nullptr;
return m_projects.at(projectIndex).project;
}
void WidgetCache::sort()
{
Utils::sort(m_projects, [](const ProjectInfo &a, const ProjectInfo &b) -> bool {
QString aName = a.project->displayName();
QString bName = b.project->displayName();
if (aName == bName) {
Utils::FileName aPath = a.project->projectFilePath();
Utils::FileName bPath = b.project->projectFilePath();
if (aPath == bPath)
return a.project < b.project;
else
return aPath < bPath;
} else {
// FIXME: Add a TreeModel::insert(item, comparator)
m_selectorModel->rootItem()->sortChildren([this](const ProjectItem *a, const ProjectItem *b) {
Project *pa = a->project();
Project *pb = b->project();
QString aName = pa->displayName();
QString bName = pb->displayName();
if (aName != bName)
return aName < bName;
}
Utils::FileName aPath = pa->projectFilePath();
Utils::FileName bPath = pb->projectFilePath();
if (aPath != bPath)
return aPath < bPath;
return pa < pb;
});
m_selectorTree->expandAll();
}
void ProjectWindow::deregisterProject(Project *project)
{
delete m_selectorModel->takeItem(itemForProject(project));
}
void ProjectWindow::startupProjectChanged(Project *project)
{
if (ProjectItem *projectItem = itemForProject(project)) {
int index = projectItem->parent()->children().indexOf(projectItem);
QTC_ASSERT(index != -1, return);
m_projectSelection->setCurrentIndex(index);
m_selectorModel->rootItem()->m_currentProjectIndex = index;
m_selectorTree->update();
m_selectorTree->setRootIndex(m_selectorModel->indexForItem(m_selectorModel->rootItem()->childAt(index)));
m_selectorTree->expandAll();
QModelIndex activeIndex = projectItem->data(0, ActiveIndexRole).value<QModelIndex>();
m_selectorTree->selectionModel()->setCurrentIndex(activeIndex, QItemSelectionModel::SelectCurrent);
updatePanel();
}
}
void ProjectWindow::projectSelected(int index)
{
auto projectItem = m_selectorModel->rootItem()->childAt(index);
QTC_ASSERT(projectItem, return);
SessionManager::setStartupProject(projectItem->project());
}
void ProjectWindow::itemActivated(const QModelIndex &index)
{
m_selectorModel->setData(index, QVariant(), ItemActivaterRole);
updatePanel();
}
void ProjectWindow::updatePanel()
{
if (QWidget *widget = centralWidget()) {
takeCentralWidget();
widget->hide(); // Don't delete.
}
RootItem *rootItem = m_selectorModel->rootItem();
if (QWidget *widget = rootItem->data(0, ActiveWidgetRole).value<QWidget *>()) {
setCentralWidget(widget);
widget->show();
if (hasFocus()) // we get assigned focus from setFocusToCurrentMode, pass that on
widget->setFocus();
}
}
ProjectItem *ProjectWindow::itemForProject(Project *project) const
{
return m_selectorModel->findFirstLevelItem([project](ProjectItem *item) {
return item->project() == project;
});
}
int WidgetCache::recheckFactories(Project *project, int oldSupportsIndex)
{
int projectIndex = indexForProject(project);
int factoryIdx = factoryIndex(projectIndex, oldSupportsIndex);
ProjectInfo &info = m_projects[projectIndex];
QList<ProjectPanelFactory *> fac = ProjectPanelFactory::factories();
int end = fac.size();
for (int i = 0; i < end; ++i) {
info.supports[i] = fac.at(i)->supports(project);
if (!info.supports.at(i)) {
delete info.widgets.at(i);
info.widgets[i] = nullptr;
}
}
if (factoryIdx < 0)
return -1;
if (!info.supports.at(factoryIdx))
return -1;
int newIndex = 0;
for (int i = 0; i < factoryIdx; ++i) {
if (info.supports.at(i))
++newIndex;
}
return newIndex;
}
} // namespace Internal
} // namespace ProjectExplorer
#include "projectwindow.moc"