Files
qt-creator/src/plugins/projectexplorer/projecttree.cpp
Ulf Hermann ea0d3f9ac5 ProjectExplorer: Delete all the widgets when deleting the project tree
Leaving them around is dangerous as they rely on the tree to be valid.
When unloading the plugin the ProjectTree instance is deleted.

Change-Id: Iea6524b0c57c469045cb35f3cd2f376e579b8a57
Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
2016-05-30 12:42:16 +00:00

561 lines
17 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 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 "projecttree.h"
#include "projecttreewidget.h"
#include "session.h"
#include "project.h"
#include "projectnodes.h"
#include "projectexplorerconstants.h"
#include "nodesvisitor.h"
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <coreplugin/icore.h>
#include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/idocument.h>
#include <coreplugin/infobar.h>
#include <coreplugin/vcsmanager.h>
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/documentmanager.h>
#include <coreplugin/navigationwidget.h>
#include <coreplugin/modemanager.h>
#include <QApplication>
#include <QMenu>
#include <QTimer>
namespace {
const char EXTERNAL_FILE_WARNING[] = "ExternalFile";
}
using namespace Utils;
namespace ProjectExplorer {
using namespace Internal;
ProjectTree *ProjectTree::s_instance = nullptr;
ProjectTree::ProjectTree(QObject *parent) : QObject(parent)
{
s_instance = this;
connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged,
this, &ProjectTree::documentManagerCurrentFileChanged);
connect(qApp, &QApplication::focusChanged,
this, &ProjectTree::focusChanged);
connect(SessionManager::instance(), &SessionManager::projectAdded,
this, &ProjectTree::sessionChanged);
connect(SessionManager::instance(), &SessionManager::projectRemoved,
this, &ProjectTree::sessionChanged);
connect(SessionManager::instance(), &SessionManager::startupProjectChanged,
this, &ProjectTree::sessionChanged);
}
ProjectTree::~ProjectTree()
{
QTC_ASSERT(s_instance == this, return);
s_instance = nullptr;
}
void ProjectTree::aboutToShutDown()
{
disconnect(qApp, &QApplication::focusChanged,
s_instance, &ProjectTree::focusChanged);
s_instance->update(0, 0);
qDeleteAll(s_instance->m_projectTreeWidgets);
QTC_CHECK(s_instance->m_projectTreeWidgets.isEmpty());
}
ProjectTree *ProjectTree::instance()
{
return s_instance;
}
Project *ProjectTree::currentProject()
{
return s_instance->m_currentProject;
}
Node *ProjectTree::currentNode()
{
return s_instance->m_currentNode;
}
void ProjectTree::registerWidget(ProjectTreeWidget *widget)
{
s_instance->m_projectTreeWidgets.append(widget);
if (hasFocus(widget))
s_instance->updateFromProjectTreeWidget(widget);
}
void ProjectTree::unregisterWidget(ProjectTreeWidget *widget)
{
s_instance->m_projectTreeWidgets.removeOne(widget);
if (hasFocus(widget))
s_instance->updateFromDocumentManager();
}
void ProjectTree::nodeChanged(ProjectTreeWidget *widget)
{
if (hasFocus(widget))
s_instance->updateFromProjectTreeWidget(widget);
}
void ProjectTree::focusChanged()
{
s_instance->updateFromFocus();
}
void ProjectTree::updateFromFocus(bool invalidCurrentNode)
{
ProjectTreeWidget *focus = m_focusForContextMenu;
if (!focus)
focus = Utils::findOrDefault(m_projectTreeWidgets, &ProjectTree::hasFocus);
if (focus)
updateFromProjectTreeWidget(focus);
else
updateFromDocumentManager(invalidCurrentNode);
}
void ProjectTree::updateFromProjectTreeWidget(ProjectTreeWidget *widget)
{
Node *currentNode = widget->currentNode();
Project *project = projectForNode(currentNode);
update(currentNode, project);
}
void ProjectTree::documentManagerCurrentFileChanged()
{
updateFromFocus();
}
Project *ProjectTree::projectForNode(Node *node)
{
if (!node)
return nullptr;
FolderNode *rootProjectNode = node->asFolderNode();
if (!rootProjectNode)
rootProjectNode = node->parentFolderNode();
while (rootProjectNode && rootProjectNode->parentFolderNode() != SessionManager::sessionNode())
rootProjectNode = rootProjectNode->parentFolderNode();
Q_ASSERT(rootProjectNode);
return Utils::findOrDefault(SessionManager::projects(), Utils::equal(&Project::rootProjectNode, rootProjectNode));
}
void ProjectTree::updateFromDocumentManager(bool invalidCurrentNode)
{
Core::IDocument *document = Core::EditorManager::currentDocument();
const FileName fileName = document ? document->filePath() : FileName();
Node *currentNode = nullptr;
if (!invalidCurrentNode && m_currentNode && m_currentNode->filePath() == fileName)
currentNode = m_currentNode;
else
currentNode = ProjectTreeWidget::nodeForFile(fileName);
updateFromNode(currentNode);
}
void ProjectTree::updateFromNode(Node *node)
{
Project *project;
if (node)
project = projectForNode(node);
else
project = SessionManager::startupProject();
update(node, project);
foreach (ProjectTreeWidget *widget, m_projectTreeWidgets)
widget->sync(node);
}
void ProjectTree::update(Node *node, Project *project)
{
bool changedProject = project != m_currentProject;
bool changedNode = node != m_currentNode;
if (changedProject) {
if (m_currentProject) {
disconnect(m_currentProject, &Project::projectContextUpdated,
this, &ProjectTree::updateContext);
disconnect(m_currentProject, &Project::projectLanguagesUpdated,
this, &ProjectTree::updateContext);
}
m_currentProject = project;
if (m_currentProject) {
connect(m_currentProject, &Project::projectContextUpdated,
this, &ProjectTree::updateContext);
connect(m_currentProject, &Project::projectLanguagesUpdated,
this, &ProjectTree::updateContext);
}
}
if (!node && Core::EditorManager::currentDocument()) {
connect(Core::EditorManager::currentDocument(), &Core::IDocument::changed,
this, &ProjectTree::updateExternalFileWarning,
Qt::UniqueConnection);
}
if (changedNode) {
m_currentNode = node;
emit currentNodeChanged(m_currentNode, project);
}
if (changedProject) {
emit currentProjectChanged(m_currentProject);
sessionChanged();
updateContext();
}
}
void ProjectTree::sessionChanged()
{
if (m_currentProject)
Core::DocumentManager::setDefaultLocationForNewFiles(m_currentProject->projectDirectory().toString());
else if (SessionManager::startupProject())
Core::DocumentManager::setDefaultLocationForNewFiles(SessionManager::startupProject()->projectDirectory().toString());
else
Core::DocumentManager::setDefaultLocationForNewFiles(QString());
updateFromFocus();
}
void ProjectTree::updateContext()
{
Core::Context oldContext;
oldContext.add(m_lastProjectContext);
Core::Context newContext;
if (m_currentProject) {
newContext.add(m_currentProject->projectContext());
newContext.add(m_currentProject->projectLanguages());
m_lastProjectContext = newContext;
} else {
m_lastProjectContext = Core::Context();
}
Core::ICore::updateAdditionalContexts(oldContext, newContext);
}
void ProjectTree::emitNodeUpdated(Node *node)
{
if (!isInNodeHierarchy(node))
return;
emit nodeUpdated(node);
}
void ProjectTree::emitAboutToChangeShowInSimpleTree(FolderNode *node)
{
if (!isInNodeHierarchy(node))
return;
emit aboutToChangeShowInSimpleTree(node);
}
void ProjectTree::emitShowInSimpleTreeChanged(FolderNode *node)
{
if (!isInNodeHierarchy(node))
return;
emit showInSimpleTreeChanged(node);
}
void ProjectTree::emitFoldersAboutToBeAdded(FolderNode *parentFolder, const QList<FolderNode *> &newFolders)
{
if (!isInNodeHierarchy(parentFolder))
return;
m_foldersAdded = newFolders;
emit foldersAboutToBeAdded(parentFolder, newFolders);
}
void ProjectTree::emitFoldersAdded(FolderNode *folder)
{
if (!isInNodeHierarchy(folder))
return;
emit foldersAdded();
if (Utils::anyOf(m_projectTreeWidgets, &ProjectTreeWidget::hasFocus))
return;
if (!m_currentNode) {
Core::IDocument *document = Core::EditorManager::currentDocument();
const FileName fileName = document ? document->filePath() : FileName();
FindNodesForFileVisitor findNodes(fileName);
foreach (FolderNode *fn, m_foldersAdded)
fn->accept(&findNodes);
Node *bestNode = ProjectTreeWidget::mostExpandedNode(findNodes.nodes());
if (!bestNode)
return;
updateFromNode(bestNode);
}
m_foldersAdded.clear();
}
void ProjectTree::emitFoldersAboutToBeRemoved(FolderNode *parentFolder, const QList<FolderNode *> &staleFolders)
{
if (!isInNodeHierarchy(parentFolder))
return;
Node *n = ProjectTree::currentNode();
while (n) {
if (FolderNode *fn = n->asFolderNode()) {
if (staleFolders.contains(fn)) {
ProjectNode *pn = n->projectNode();
// Make sure the node we are switching too isn't going to be removed also
while (staleFolders.contains(pn))
pn = pn->parentFolderNode()->projectNode();
m_resetCurrentNodeFolder = true;
break;
}
}
n = n->parentFolderNode();
}
emit foldersAboutToBeRemoved(parentFolder, staleFolders);
}
void ProjectTree::emitFoldersRemoved(FolderNode *folder)
{
if (!isInNodeHierarchy(folder))
return;
emit foldersRemoved();
if (m_resetCurrentNodeFolder) {
updateFromFocus(true);
m_resetCurrentNodeFolder = false;
}
}
void ProjectTree::emitFilesAboutToBeAdded(FolderNode *folder, const QList<FileNode *> &newFiles)
{
if (!isInNodeHierarchy(folder))
return;
m_filesAdded = newFiles;
emit filesAboutToBeAdded(folder, newFiles);
}
void ProjectTree::emitFilesAdded(FolderNode *folder)
{
if (!isInNodeHierarchy(folder))
return;
emit filesAdded();
if (Utils::anyOf(m_projectTreeWidgets, &ProjectTreeWidget::hasFocus))
return;
if (!m_currentNode) {
Core::IDocument *document = Core::EditorManager::currentDocument();
const FileName fileName = document ? document->filePath() : FileName();
int index = Utils::indexOf(m_filesAdded, Utils::equal(&FileNode::filePath, fileName));
if (index == -1)
return;
updateFromNode(m_filesAdded.at(index));
}
m_filesAdded.clear();
}
void ProjectTree::emitFilesAboutToBeRemoved(FolderNode *folder, const QList<FileNode *> &staleFiles)
{
if (!isInNodeHierarchy(folder))
return;
if (m_currentNode)
if (FileNode *fileNode = m_currentNode->asFileNode())
if (staleFiles.contains(fileNode))
m_resetCurrentNodeFile = true;
emit filesAboutToBeRemoved(folder, staleFiles);
}
void ProjectTree::emitFilesRemoved(FolderNode *folder)
{
if (!isInNodeHierarchy(folder))
return;
emit filesRemoved();
if (m_resetCurrentNodeFile) {
updateFromFocus(true);
m_resetCurrentNodeFile = false;
}
}
void ProjectTree::emitNodeSortKeyAboutToChange(Node *node)
{
if (!isInNodeHierarchy(node))
return;
emit nodeSortKeyAboutToChange(node);
}
void ProjectTree::emitNodeSortKeyChanged(Node *node)
{
if (!isInNodeHierarchy(node))
return;
emit nodeSortKeyChanged();
}
void ProjectTree::collapseAll()
{
if (m_focusForContextMenu)
m_focusForContextMenu->collapseAll();
}
void ProjectTree::updateExternalFileWarning()
{
auto document = qobject_cast<Core::IDocument *>(sender());
if (!document || document->filePath().isEmpty())
return;
Core::InfoBar *infoBar = document->infoBar();
Core::Id externalFileId(EXTERNAL_FILE_WARNING);
if (!document->isModified()) {
infoBar->removeInfo(externalFileId);
return;
}
if (!infoBar->canInfoBeAdded(externalFileId))
return;
const FileName fileName = document->filePath();
const QList<Project *> projects = SessionManager::projects();
if (projects.isEmpty())
return;
foreach (Project *project, projects) {
FileName projectDir = project->projectDirectory();
if (projectDir.isEmpty())
continue;
if (fileName.isChildOf(projectDir))
return;
// External file. Test if it under the same VCS
QString topLevel;
if (Core::VcsManager::findVersionControlForDirectory(projectDir.toString(), &topLevel)
&& fileName.isChildOf(FileName::fromString(topLevel))) {
return;
}
}
infoBar->addInfo(Core::InfoBarEntry(externalFileId,
tr("<b>Warning:</b> This file is outside the project directory."),
Core::InfoBarEntry::GlobalSuppressionEnabled));
}
bool ProjectTree::hasFocus(ProjectTreeWidget *widget)
{
return widget
&& ((widget->focusWidget() && widget->focusWidget()->hasFocus())
|| s_instance->m_focusForContextMenu == widget);
}
void ProjectTree::showContextMenu(ProjectTreeWidget *focus, const QPoint &globalPos, Node *node)
{
QMenu *contextMenu = nullptr;
if (!node)
node = SessionManager::sessionNode();
if (node->nodeType() != SessionNodeType) {
Project *project = SessionManager::projectForNode(node);
emit s_instance->aboutToShowContextMenu(project, node);
switch (node->nodeType()) {
case ProjectNodeType:
if (node->parentFolderNode() == SessionManager::sessionNode())
contextMenu = Core::ActionManager::actionContainer(Constants::M_PROJECTCONTEXT)->menu();
else
contextMenu = Core::ActionManager::actionContainer(Constants::M_SUBPROJECTCONTEXT)->menu();
break;
case VirtualFolderNodeType:
case FolderNodeType:
contextMenu = Core::ActionManager::actionContainer(Constants::M_FOLDERCONTEXT)->menu();
break;
case FileNodeType:
contextMenu = Core::ActionManager::actionContainer(Constants::M_FILECONTEXT)->menu();
break;
default:
qWarning("ProjectExplorerPlugin::showContextMenu - Missing handler for node type");
}
} else { // session item
emit s_instance->aboutToShowContextMenu(nullptr, node);
contextMenu = Core::ActionManager::actionContainer(Constants::M_SESSIONCONTEXT)->menu();
}
if (contextMenu && contextMenu->actions().count() > 0) {
contextMenu->popup(globalPos);
s_instance->m_focusForContextMenu = focus;
connect(contextMenu, &QMenu::aboutToHide,
s_instance, &ProjectTree::hideContextMenu,
Qt::ConnectionType(Qt::UniqueConnection | Qt::QueuedConnection));
}
}
void ProjectTree::highlightProject(Project *project, const QString &message)
{
Core::ModeManager::activateMode(Core::Constants::MODE_EDIT);
Core::NavigationWidget *navigation = Core::NavigationWidget::instance();
// Shows and focusses a project tree
QWidget *widget = navigation->activateSubWidget(ProjectExplorer::Constants::PROJECTTREE_ID);
if (auto *projectTreeWidget = qobject_cast<ProjectTreeWidget *>(widget))
projectTreeWidget->showMessage(project->rootProjectNode(), message);
}
void ProjectTree::hideContextMenu()
{
m_focusForContextMenu = nullptr;
}
bool ProjectTree::isInNodeHierarchy(Node *n)
{
Node *sessionNode = SessionManager::sessionNode();
do {
if (n == sessionNode)
return true;
n = n->parentFolderNode();
} while (n);
return false;
}
} // namespace ProjectExplorer