diff options
author | Daniel Teske <daniel.teske@digia.com> | 2014-05-13 13:06:30 +0200 |
---|---|---|
committer | Daniel Teske <daniel.teske@digia.com> | 2014-05-13 15:35:36 +0200 |
commit | 5969c01fa625fa41f0b45907c31c411792c4ffee (patch) | |
tree | 9db7484d394e7030aaa160dd20198ffe7a58e623 | |
parent | 4743217eac61bb7af7f04787d2558493ac390823 (diff) | |
download | qt-creator-5969c01fa625fa41f0b45907c31c411792c4ffee.tar.gz |
ProjectExtensionsPage: Rework project combo box
Show a actual tree in the combobox.
Task-number: QTCREATORBUG-12002
Change-Id: I22b62f444923193972109a096bc6eef26a31bf9f
Reviewed-by: Nikolai Kosjar <nikolai.kosjar@digia.com>
Reviewed-by: Daniel Teske <daniel.teske@digia.com>
-rw-r--r-- | src/libs/utils/treeviewcombobox.cpp | 67 | ||||
-rw-r--r-- | src/libs/utils/treeviewcombobox.h | 5 | ||||
-rw-r--r-- | src/plugins/projectexplorer/addnewmodel.cpp | 213 | ||||
-rw-r--r-- | src/plugins/projectexplorer/addnewmodel.h | 88 | ||||
-rw-r--r-- | src/plugins/projectexplorer/projectexplorer.pro | 6 | ||||
-rw-r--r-- | src/plugins/projectexplorer/projectfilewizardextension.cpp | 400 | ||||
-rw-r--r-- | src/plugins/projectexplorer/projectfilewizardextension.h | 4 | ||||
-rw-r--r-- | src/plugins/projectexplorer/projectwizardpage.cpp | 70 | ||||
-rw-r--r-- | src/plugins/projectexplorer/projectwizardpage.h | 17 | ||||
-rw-r--r-- | src/plugins/projectexplorer/projectwizardpage.ui | 9 |
10 files changed, 593 insertions, 286 deletions
diff --git a/src/libs/utils/treeviewcombobox.cpp b/src/libs/utils/treeviewcombobox.cpp index 804b0e6eb8..dc6e5c5fbd 100644 --- a/src/libs/utils/treeviewcombobox.cpp +++ b/src/libs/utils/treeviewcombobox.cpp @@ -57,18 +57,41 @@ TreeViewComboBox::TreeViewComboBox(QWidget *parent) m_view->viewport()->installEventFilter(this); } +QModelIndex TreeViewComboBox::indexAbove(QModelIndex index) +{ + do + index = m_view->indexAbove(index); + while (index.isValid() && !(model()->flags(index) & Qt::ItemIsSelectable)); + return index; +} + +QModelIndex TreeViewComboBox::indexBelow(QModelIndex index) +{ + do + index = m_view->indexBelow(index); + while (index.isValid() && !(model()->flags(index) & Qt::ItemIsSelectable)); + return index; +} + +QModelIndex TreeViewComboBox::lastIndex(const QModelIndex index) +{ + if (index.isValid() && !m_view->isExpanded(index)) + return index; + + int rows = m_view->model()->rowCount(index); + if (rows == 0) + return index; + return lastIndex(m_view->model()->index(rows - 1, 0, index)); +} + void TreeViewComboBox::wheelEvent(QWheelEvent *e) { QModelIndex index = m_view->currentIndex(); - if (e->delta() > 0) { - do - index = m_view->indexAbove(index); - while (index.isValid() && !(model()->flags(index) & Qt::ItemIsSelectable)); - } else if (e->delta() < 0) { - do - index = m_view->indexBelow(index); - while (index.isValid() && !(model()->flags(index) & Qt::ItemIsSelectable)); - } + if (e->delta() > 0) + index = indexAbove(index); + else if (e->delta() < 0) + index = indexBelow(index); + e->accept(); if (!index.isValid()) return; @@ -79,8 +102,34 @@ void TreeViewComboBox::wheelEvent(QWheelEvent *e) emit activated(index.row()); } +void TreeViewComboBox::keyPressEvent(QKeyEvent *e) +{ + if (e->key() == Qt::Key_Up || e->key() == Qt::Key_PageUp) { + setCurrentIndex(indexAbove(m_view->currentIndex())); + } else if (e->key() == Qt::Key_Down || e->key() == Qt::Key_PageDown) { + setCurrentIndex(indexBelow(m_view->currentIndex())); + } else if (e->key() == Qt::Key_Home) { + QModelIndex index = m_view->model()->index(0, 0); + if (index.isValid() && !model()->flags(index) & Qt::ItemIsSelectable) + index = indexBelow(index); + setCurrentIndex(index); + } else if (e->key() == Qt::Key_End) { + QModelIndex index = lastIndex(m_view->rootIndex()); + if (index.isValid() && !model()->flags(index) & Qt::ItemIsSelectable) + index = indexAbove(index); + setCurrentIndex(index); + } else { + QComboBox::keyPressEvent(e); + return; + } + + e->accept(); +} + void TreeViewComboBox::setCurrentIndex(const QModelIndex &index) { + if (!index.isValid()) + return; setRootModelIndex(model()->parent(index)); QComboBox::setCurrentIndex(index.row()); setRootModelIndex(QModelIndex()); diff --git a/src/libs/utils/treeviewcombobox.h b/src/libs/utils/treeviewcombobox.h index f2dabb5792..543a237d92 100644 --- a/src/libs/utils/treeviewcombobox.h +++ b/src/libs/utils/treeviewcombobox.h @@ -52,6 +52,7 @@ public: TreeViewComboBox(QWidget *parent = 0); void wheelEvent(QWheelEvent *e); + void keyPressEvent(QKeyEvent *e); void setCurrentIndex(const QModelIndex &index); bool eventFilter(QObject* object, QEvent* event); void showPopup(); @@ -60,6 +61,10 @@ public: TreeViewComboBoxView *view() const; private: + QModelIndex indexBelow(QModelIndex index); + QModelIndex indexAbove(QModelIndex index); + QModelIndex lastIndex(const QModelIndex index); + TreeViewComboBoxView *m_view; bool m_skipNextHide; }; diff --git a/src/plugins/projectexplorer/addnewmodel.cpp b/src/plugins/projectexplorer/addnewmodel.cpp new file mode 100644 index 0000000000..ff07bdb93e --- /dev/null +++ b/src/plugins/projectexplorer/addnewmodel.cpp @@ -0,0 +1,213 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** 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. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "addnewmodel.h" + +#include "projectexplorer.h" + +using namespace ProjectExplorer; +using namespace ProjectExplorer::Internal; + +AddNewTree::AddNewTree(const QString &displayName) + : m_parent(0), + m_children(QList<AddNewTree *>()), + m_displayName(displayName), + m_node(0), + m_canAdd(true), + m_priority(-1) +{ + +} + +AddNewTree::AddNewTree(FolderNode *node, QList<AddNewTree *> children, const QString &displayName) + : m_parent(0), + m_children(children), + m_displayName(displayName), + m_node(0), + m_canAdd(false), + m_priority(-1) +{ + if (node) + m_toolTip = ProjectExplorerPlugin::directoryFor(node); + foreach (AddNewTree *child, m_children) + child->m_parent = this; +} + +AddNewTree::AddNewTree(FolderNode *node, QList<AddNewTree *> children, const FolderNode::AddNewInformation &info) + : m_parent(0), + m_children(children), + m_displayName(info.displayName), + m_node(node), + m_canAdd(true), + m_priority(info.priority) +{ + if (node) + m_toolTip = ProjectExplorerPlugin::directoryFor(node); + foreach (AddNewTree *child, m_children) + child->m_parent = this; +} + +AddNewTree::~AddNewTree() +{ + qDeleteAll(m_children); +} + +AddNewTree *AddNewTree::parent() const +{ + return m_parent; +} + +QList<AddNewTree *> AddNewTree::children() const +{ + return m_children; +} + +bool AddNewTree::canAdd() const +{ + return m_canAdd; +} + +QString AddNewTree::displayName() const +{ + return m_displayName; +} + +QString AddNewTree::toolTip() const +{ + return m_toolTip; +} + +FolderNode *AddNewTree::node() const +{ + return m_node; +} + +int AddNewTree::priority() const +{ + return m_priority; +} + +AddNewModel::AddNewModel(AddNewTree *root) + : m_root(root) +{ + +} + +AddNewModel::~AddNewModel() +{ + delete m_root; +} + +int AddNewModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) + return m_root->children().size(); + AddNewTree *tree = static_cast<AddNewTree *>(parent.internalPointer()); + return tree->children().size(); +} + +int AddNewModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return 1; +} + +QModelIndex AddNewModel::index(int row, int column, const QModelIndex &parent) const +{ + if (column != 0) + return QModelIndex(); + if (!parent.isValid()) { + if (row >= 0 && row < m_root->children().size()) + return createIndex(row, column, m_root->children().at(row)); + return QModelIndex(); + } + AddNewTree *tree = static_cast<AddNewTree *>(parent.internalPointer()); + if (row >= 0 && row < tree->children().size()) + return createIndex(row, column, tree->children().at(row)); + return QModelIndex(); +} + +QModelIndex AddNewModel::parent(const QModelIndex &child) const +{ + if (!child.isValid()) + return QModelIndex(); + AddNewTree *childTree = static_cast<AddNewTree *>(child.internalPointer()); + if (childTree == m_root) + return QModelIndex(); + AddNewTree *parent = childTree->parent(); + if (parent == m_root) + return QModelIndex(); + AddNewTree *grandparent = parent->parent(); + for (int i = 0; i < grandparent->children().size(); ++i) { + if (grandparent->children().at(i) == parent) + return createIndex(i, 0, parent); + } + return QModelIndex(); +} + +QVariant AddNewModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + AddNewTree *tree = static_cast<AddNewTree *>(index.internalPointer()); + if (role == Qt::DisplayRole) + return tree->displayName(); + else if (role == Qt::ToolTipRole) + return tree->toolTip(); + return QVariant(); +} + +Qt::ItemFlags AddNewModel::flags(const QModelIndex &index) const +{ + AddNewTree *tree = static_cast<AddNewTree *>(index.internalPointer()); + if (tree && tree->canAdd()) + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; + return Qt::NoItemFlags; +} + +FolderNode *AddNewModel::nodeForIndex(const QModelIndex &index) const +{ + if (!index.isValid()) + return m_root->node(); + AddNewTree *tree = static_cast<AddNewTree *>(index.internalPointer()); + return tree->node(); +} + +QModelIndex AddNewModel::indexForTree(AddNewTree *tree) const +{ + if (!tree) + return index(0, 0, QModelIndex()); + AddNewTree *parent = tree->parent(); + if (!parent) + return QModelIndex(); + for (int i = 0; i < parent->children().size(); ++i) + if (parent->children().at(i) == tree) + return createIndex(i, 0, tree); + return QModelIndex(); +} diff --git a/src/plugins/projectexplorer/addnewmodel.h b/src/plugins/projectexplorer/addnewmodel.h new file mode 100644 index 0000000000..9d4bdb7d06 --- /dev/null +++ b/src/plugins/projectexplorer/addnewmodel.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** 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. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef ADDNEWMODEL_H +#define ADDNEWMODEL_H + +#include "projectnodes.h" + +#include <QAbstractItemModel> + +namespace ProjectExplorer { +class FolderNode; + +namespace Internal { + +class AddNewTree +{ +public: + AddNewTree(const QString &displayName); + AddNewTree(FolderNode *node, QList<AddNewTree *> children, const QString &displayName); + AddNewTree(FolderNode *node, QList<AddNewTree *> children, const FolderNode::AddNewInformation &info); + ~AddNewTree(); + + AddNewTree *parent() const; + QList<AddNewTree *> children() const; + + bool canAdd() const; + QString displayName() const; + QString toolTip() const; + FolderNode *node() const; + int priority() const; +private: + AddNewTree *m_parent; + QList<AddNewTree *> m_children; + QString m_displayName; + QString m_toolTip; + FolderNode *m_node; + bool m_canAdd; + int m_priority; +}; + +class AddNewModel : public QAbstractItemModel +{ +public: + AddNewModel(AddNewTree *root); + ~AddNewModel(); + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex parent(const QModelIndex &child) const; + QVariant data(const QModelIndex &index, int role) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + + FolderNode *nodeForIndex(const QModelIndex &index) const; + QModelIndex indexForTree(AddNewTree *tree) const; +private: + AddNewTree *m_root; +}; +} +} + +#endif // ADDNEWMODEL_H diff --git a/src/plugins/projectexplorer/projectexplorer.pro b/src/plugins/projectexplorer/projectexplorer.pro index 02a2d121f7..3e70b00e06 100644 --- a/src/plugins/projectexplorer/projectexplorer.pro +++ b/src/plugins/projectexplorer/projectexplorer.pro @@ -145,7 +145,8 @@ HEADERS += projectexplorer.h \ customparser.h \ customparserconfigdialog.h \ ipotentialkit.h \ - selectablefilesmodel.h + selectablefilesmodel.h \ + addnewmodel.h SOURCES += projectexplorer.cpp \ abi.cpp \ @@ -277,7 +278,8 @@ SOURCES += projectexplorer.cpp \ customparser.cpp \ customparserconfigdialog.cpp \ ipotentialkit.cpp \ - selectablefilesmodel.cpp + selectablefilesmodel.cpp \ + addnewmodel.cpp FORMS += processstep.ui \ editorsettingspropertiespage.ui \ diff --git a/src/plugins/projectexplorer/projectfilewizardextension.cpp b/src/plugins/projectexplorer/projectfilewizardextension.cpp index 124e00eda0..a9fd4a1881 100644 --- a/src/plugins/projectexplorer/projectfilewizardextension.cpp +++ b/src/plugins/projectexplorer/projectfilewizardextension.cpp @@ -33,6 +33,7 @@ #include "projectnodes.h" #include "nodesvisitor.h" #include "projectwizardpage.h" +#include "addnewmodel.h" #include <utils/qtcassert.h> #include <utils/stringutils.h> @@ -85,134 +86,168 @@ enum { debugExtension = 0 }; namespace ProjectExplorer { -typedef QList<FolderNode *> FolderNodeList; -typedef QList<ProjectNode *> ProjectNodeList; - namespace Internal { -// AddNewFileNodesVisitor: Retrieve all folders which support AddNew -class AddNewFileNodesVisitor : public NodesVisitor +class BestNodeSelector { public: - AddNewFileNodesVisitor(); - - static FolderNodeList allFolders(); - - virtual void visitProjectNode(ProjectNode *node); - virtual void visitFolderNode(FolderNode *node); - + BestNodeSelector(const QString &commonDirectory, const QStringList &files, Node *contextNode); + void inspect(AddNewTree *tree); + AddNewTree *bestChoice() const; + QString deployingProjects() const; private: - FolderNodeList m_folderNodes; + QString m_commonDirectory; + QStringList m_files; + Node *m_contextNode; + bool m_deploys; + QString m_deployText; + AddNewTree *m_bestChoice; + int m_bestMatchLength; + int m_bestMatchPriority; }; -AddNewFileNodesVisitor::AddNewFileNodesVisitor() -{} - -FolderNodeList AddNewFileNodesVisitor::allFolders() +BestNodeSelector::BestNodeSelector(const QString &commonDirectory, const QStringList &files, Node *contextNode) + : m_commonDirectory(commonDirectory), + m_files(files), + m_contextNode(contextNode), + m_deploys(false), + m_deployText(QCoreApplication::translate("ProjectWizard", "The files are implicitly added to the projects:") + QLatin1Char('\n')), + m_bestChoice(0), + m_bestMatchLength(-1), + m_bestMatchPriority(-1) { - AddNewFileNodesVisitor visitor; - SessionManager::sessionNode()->accept(&visitor); - return visitor.m_folderNodes; -} -void AddNewFileNodesVisitor::visitProjectNode(ProjectNode *node) -{ - visitFolderNode(node); } -void AddNewFileNodesVisitor::visitFolderNode(FolderNode *node) +// Find the project the new files should be added +// If any node deploys the files, then we don't want to add the files. +// Otherwise consider their common path. Either a direct match on the directory +// or the directory with the longest matching path (list containing"/project/subproject1" +// matching common path "/project/subproject1/newuserpath"). +void BestNodeSelector::inspect(AddNewTree *tree) { - const QList<ProjectExplorer::ProjectAction> &list = node->supportedActions(node); - if (list.contains(ProjectExplorer::AddNewFile) && !list.contains(ProjectExplorer::InheritedFromParent)) - m_folderNodes.push_back(node); + FolderNode *node = tree->node(); + if (node->nodeType() == ProjectNodeType) { + if (static_cast<ProjectNode *>(node)->deploysFolder(m_commonDirectory)) { + m_deploys = true; + m_deployText += tree->displayName() + QLatin1Char('\n'); + } + } + if (m_deploys) + return; + const QString projectDirectory = ProjectExplorerPlugin::directoryFor(node); + const int projectDirectorySize = projectDirectory.size(); + if (!m_commonDirectory.startsWith(projectDirectory)) + return; + bool betterMatch = projectDirectorySize > m_bestMatchLength + || (projectDirectorySize == m_bestMatchLength && tree->priority() > m_bestMatchPriority); + if (betterMatch) { + m_bestMatchPriority = tree->priority(); + m_bestMatchLength = projectDirectorySize; + m_bestChoice = tree; + } } -// AddNewProjectNodesVisitor: Retrieve all folders which support AddNewProject -// also checks the path of the added subproject -class AddNewProjectNodesVisitor : public NodesVisitor +AddNewTree *BestNodeSelector::bestChoice() const { -public: - AddNewProjectNodesVisitor(const QString &subProjectPath); - - static ProjectNodeList projectNodes(const QString &subProjectPath); - - virtual void visitProjectNode(ProjectNode *node); - -private: - ProjectNodeList m_projectNodes; - QString m_subProjectPath; -}; - -AddNewProjectNodesVisitor::AddNewProjectNodesVisitor(const QString &subProjectPath) - : m_subProjectPath(subProjectPath) -{} + if (m_deploys) + return 0; + return m_bestChoice; +} -ProjectNodeList AddNewProjectNodesVisitor::projectNodes(const QString &subProjectPath) +QString BestNodeSelector::deployingProjects() const { - AddNewProjectNodesVisitor visitor(subProjectPath); - SessionManager::sessionNode()->accept(&visitor); - return visitor.m_projectNodes; + if (m_deploys) + return m_deployText; + return QString(); } -void AddNewProjectNodesVisitor::visitProjectNode(ProjectNode *node) +static inline AddNewTree *createNoneNode(BestNodeSelector *selector) { - const QList<ProjectExplorer::ProjectAction> &list = node->supportedActions(node); - if (list.contains(ProjectExplorer::AddSubProject) && !list.contains(ProjectExplorer::InheritedFromParent)) - if (m_subProjectPath.isEmpty() || node->canAddSubProject(m_subProjectPath)) - m_projectNodes.push_back(node); + QString displayName = QCoreApplication::translate("ProjectWizard", "<Implicitly Add>"); + if (selector->bestChoice()) + displayName = QCoreApplication::translate("ProjectWizard", "<None>"); + return new AddNewTree(displayName); } -// ProjectEntry: Context entry for a *.pri/*.pro file. Stores name and path -// for quick sort and path search, provides operator<() for maps. -struct FolderEntry { - FolderEntry() : node(0), priority(-1) {} - explicit FolderEntry(FolderNode *node, const QStringList &generatedFiles, Node *contextNode); +static inline AddNewTree *buildAddProjectTree(ProjectNode *root, const QString &projectPath, Node *contextNode, BestNodeSelector *selector) +{ + QList<AddNewTree *> children; + foreach (ProjectNode *pn, root->subProjectNodes()) { + AddNewTree *child = buildAddProjectTree(pn, projectPath, contextNode, selector); + if (child) + children.append(child); + } - int compare(const FolderEntry &rhs) const; + const QList<ProjectExplorer::ProjectAction> &list = root->supportedActions(root); + if (list.contains(ProjectExplorer::AddSubProject) && !list.contains(ProjectExplorer::InheritedFromParent)) { + if (projectPath.isEmpty() || root->canAddSubProject(projectPath)) { + FolderNode::AddNewInformation info = root->addNewInformation(QStringList() << projectPath, contextNode); + AddNewTree *item = new AddNewTree(root, children, info); + selector->inspect(item); + return item; + } + } - FolderNode *node; - QString directory; // For matching against wizards' files, which are native. - QString displayName; - QString baseName; - int priority; -}; + if (children.isEmpty()) + return 0; + return new AddNewTree(root, children, root->displayName()); +} -FolderEntry::FolderEntry(FolderNode *n, const QStringList &generatedFiles, Node *contextNode) : - node(n) +static inline AddNewTree *buildAddProjectTree(SessionNode *root, const QString &projectPath, Node *contextNode, BestNodeSelector *selector) { - FolderNode::AddNewInformation info = node->addNewInformation(generatedFiles, contextNode); - displayName = info.displayName; - const QFileInfo fi(ProjectExplorerPlugin::pathFor(node)); - baseName = fi.baseName(); - priority = info.priority; - directory = ProjectExplorerPlugin::directoryFor(node); + QList<AddNewTree *> children; + foreach (ProjectNode *pn, root->projectNodes()) { + AddNewTree *child = buildAddProjectTree(pn, projectPath, contextNode, selector); + if (child) + children.append(child); + } + children.prepend(createNoneNode(selector)); + return new AddNewTree(root, children, root->displayName()); } -// Sort helper that sorts by base name and puts '*.pro' before '*.pri' -int FolderEntry::compare(const FolderEntry &rhs) const +static inline AddNewTree *buildAddFilesTree(FolderNode *root, const QStringList &files, Node *contextNode, BestNodeSelector *selector) { - if (const int drc = displayName.compare(rhs.displayName)) - return drc; - if (const int drc = directory.compare(rhs.directory)) - return drc; - if (const int brc = baseName.compare(rhs.baseName)) - return brc; - if (priority > rhs.priority) - return -1; - if (priority < rhs.priority) - return 1; - return 0; + QList<AddNewTree *> children; + foreach (FolderNode *fn, root->subFolderNodes()) { + AddNewTree *child = buildAddFilesTree(fn, files, contextNode, selector); + if (child) + children.append(child); + } + + const QList<ProjectExplorer::ProjectAction> &list = root->supportedActions(root); + if (list.contains(ProjectExplorer::AddNewFile) && !list.contains(ProjectExplorer::InheritedFromParent)) { + FolderNode::AddNewInformation info = root->addNewInformation(files, contextNode); + AddNewTree *item = new AddNewTree(root, children, info); + selector->inspect(item); + return item; + } + if (children.isEmpty()) + return 0; + return new AddNewTree(root, children, root->displayName()); } -inline bool operator<(const FolderEntry &pe1, const FolderEntry &pe2) +static inline AddNewTree *buildAddFilesTree(SessionNode *root, const QStringList &files, Node *contextNode, BestNodeSelector *selector) { - return pe1.compare(pe2) < 0; + QList<AddNewTree *> children; + foreach (ProjectNode *pn, root->projectNodes()) { + AddNewTree *child = buildAddFilesTree(pn, files, contextNode, selector); + if (child) + children.append(child); + } + children.prepend(createNoneNode(selector)); + return new AddNewTree(root, children, root->displayName()); } -QDebug operator<<(QDebug d, const FolderEntry &e) +static inline AddNewTree *getChoices(const QStringList &generatedFiles, + IWizard::WizardKind wizardKind, + Node *contextNode, + BestNodeSelector *selector) { - d.nospace() << e.directory << ' ' << e.displayName << ' ' << e.priority; - return d; + if (wizardKind == IWizard::ProjectWizard) + return buildAddProjectTree(SessionManager::sessionNode(), generatedFiles.first(), contextNode, selector); + else + return buildAddFilesTree(SessionManager::sessionNode(), generatedFiles, contextNode, selector); } // --------- ProjectWizardContext @@ -223,7 +258,6 @@ struct ProjectWizardContext QList<IVersionControl*> versionControls; QList<IVersionControl*> activeVersionControls; - QList<FolderEntry> projects; QPointer<ProjectWizardPage> page; // this is managed by the wizard! bool repositoryExists; // Is VCS 'add' sufficient, or should a repository be created? QString commonDirectory; @@ -240,7 +274,6 @@ ProjectWizardContext::ProjectWizardContext() : void ProjectWizardContext::clear() { activeVersionControls.clear(); - projects.clear(); commonDirectory.clear(); page = 0; repositoryExists = false; @@ -258,51 +291,6 @@ ProjectFileWizardExtension::~ProjectFileWizardExtension() delete m_context; } -static QList<FolderEntry> findDeployProject(const QList<FolderEntry> &folders, - QString &commonPath) -{ - QList<FolderEntry> filtered; - foreach (const FolderEntry &folder, folders) - if (folder.node->nodeType() == ProjectNodeType) - if (static_cast<ProjectNode *>(folder.node)->deploysFolder(commonPath)) - filtered << folder; - return filtered; -} - -// Find the project the new files should be added to given their common -// path. Either a direct match on the directory or the directory with -// the longest matching path (list containing"/project/subproject1" matching -// common path "/project/subproject1/newuserpath"). -static int findMatchingProject(const QList<FolderEntry> &projects, - const QString &commonPath) -{ - if (projects.isEmpty() || commonPath.isEmpty()) - return -1; - - const int count = projects.size(); - int bestMatch = -1; - int bestMatchLength = 0; - int bestMatchPriority = -1; - for (int p = 0; p < count; p++) { - // Direct match or better match? (note that the wizards' files are native). - const FolderEntry &entry = projects.at(p); - const QString &projectDirectory = entry.directory; - const int projectDirectorySize = projectDirectory.size(); - if (!commonPath.startsWith(projectDirectory)) - continue; - - bool betterMatch = projectDirectorySize > bestMatchLength - || (projectDirectorySize == bestMatchLength && entry.priority > bestMatchPriority); - - if (betterMatch) { - bestMatchPriority = entry.priority; - bestMatchLength = projectDirectory.size(); - bestMatch = p; - } - } - return bestMatch; -} - static QString generatedProjectFilePath(const QList<GeneratedFile> &files) { foreach (const GeneratedFile &file, files) @@ -315,48 +303,37 @@ void ProjectFileWizardExtension::firstExtensionPageShown( const QList<GeneratedFile> &files, const QVariantMap &extraValues) { - initProjectChoices(files, extraValues); - if (debugExtension) qDebug() << Q_FUNC_INFO << files.size(); - // Parametrize wizard page: find best project to add to, set up files display and - // version control depending on path QStringList fileNames; foreach (const GeneratedFile &f, files) fileNames.push_back(f.path()); m_context->commonDirectory = Utils::commonPath(fileNames); m_context->page->setFilesDisplay(m_context->commonDirectory, fileNames); - // Find best project (Entry at 0 is 'None'). - - int bestProjectIndex = -1; - - QList<FolderEntry> deployingProjects = findDeployProject(m_context->projects, m_context->commonDirectory); - if (!deployingProjects.isEmpty()) { - // Oh we do have someone that deploys it - // then the best match is NONE - // We display a label explaining that and rename <None> to - // <Implicitly Add> - m_context->page->setNoneLabel(tr("<Implicitly Add>")); - - QString text = tr("The files are implicitly added to the projects:"); - text += QLatin1Char('\n'); - foreach (const FolderEntry &project, deployingProjects) { - text += project.displayName; - text += QLatin1Char('\n'); - } - m_context->page->setAdditionalInfo(text); - bestProjectIndex = -1; + QStringList filePaths; + ProjectExplorer::ProjectAction projectAction; + if (m_context->wizard->kind()== IWizard::ProjectWizard) { + projectAction = ProjectExplorer::AddSubProject; + filePaths << generatedProjectFilePath(files); } else { - bestProjectIndex = findMatchingProject(m_context->projects, m_context->commonDirectory); - m_context->page->setNoneLabel(tr("<None>")); + projectAction = ProjectExplorer::AddNewFile; + foreach (const GeneratedFile &gf, files) + filePaths << gf.path(); } - if (bestProjectIndex == -1) - m_context->page->setCurrentProjectIndex(0); - else - m_context->page->setCurrentProjectIndex(bestProjectIndex + 1); + + Node *contextNode = extraValues.value(QLatin1String(Constants::PREFERRED_PROJECT_NODE)).value<Node *>(); + BestNodeSelector selector(m_context->commonDirectory, filePaths, contextNode); + AddNewTree *tree = getChoices(filePaths, m_context->wizard->kind(), contextNode, &selector); + + m_context->page->setAdditionalInfo(selector.deployingProjects()); + + AddNewModel *model = new AddNewModel(tree); + m_context->page->setModel(model); + m_context->page->setBestNode(selector.bestChoice()); + m_context->page->setAddingSubProject(projectAction == ProjectExplorer::AddSubProject); // Store all version controls for later use: if (m_context->versionControls.isEmpty()) { @@ -429,72 +406,6 @@ QList<QWizardPage *> ProjectFileWizardExtension::extensionPages(const IWizard *w return QList<QWizardPage *>() << m_context->page; } - -static inline void getProjectChoicesAndToolTips(QStringList *projectChoicesParam, - QStringList *projectToolTipsParam, - ProjectExplorer::ProjectAction *projectActionParam, - const QList<GeneratedFile> &generatedFiles, - ProjectWizardContext *context, - Node *contextNode) -{ - // Set up project list which remains the same over duration of wizard execution - // As tooltip, set the directory for disambiguation (should someone have - // duplicate base names in differing directories). - //: No project selected - QStringList projectChoices(ProjectFileWizardExtension::tr("<None>")); - QStringList projectToolTips((QString())); - - typedef QMap<FolderEntry, bool> FolderEntryMap; - // Sort by base name and purge duplicated entries (resulting from dependencies) - // via Map. - FolderEntryMap entryMap; - ProjectExplorer::ProjectAction projectAction; - if (context->wizard->kind()== IWizard::ProjectWizard) { - const QString projectFilePath = generatedProjectFilePath(generatedFiles); - projectAction = ProjectExplorer::AddSubProject; - foreach (ProjectNode *pn, AddNewProjectNodesVisitor::projectNodes(projectFilePath)) - entryMap.insert(FolderEntry(pn, QStringList() << projectFilePath, contextNode), true); - - } else { - QStringList filePaths; - foreach (const GeneratedFile &gf, generatedFiles) - filePaths << gf.path(); - - projectAction = ProjectExplorer::AddNewFile; - foreach (FolderNode *fn, AddNewFileNodesVisitor::allFolders()) - entryMap.insert(FolderEntry(fn, filePaths, contextNode), true); - } - - context->projects.clear(); - - // Collect names - const FolderEntryMap::const_iterator cend = entryMap.constEnd(); - for (FolderEntryMap::const_iterator it = entryMap.constBegin(); it != cend; ++it) { - context->projects.push_back(it.key()); - projectChoices.push_back(it.key().displayName); - projectToolTips.push_back(QDir::toNativeSeparators(it.key().directory)); - } - - *projectChoicesParam = projectChoices; - *projectToolTipsParam = projectToolTips; - *projectActionParam = projectAction; -} - -void ProjectFileWizardExtension::initProjectChoices(const QList<GeneratedFile> &generatedFiles, const QVariantMap &extraValues) -{ - QStringList projectChoices; - QStringList projectToolTips; - ProjectExplorer::ProjectAction projectAction; - - getProjectChoicesAndToolTips(&projectChoices, &projectToolTips, &projectAction, - generatedFiles, m_context, - extraValues.value(QLatin1String(Constants::PREFERRED_PROJECT_NODE)).value<Node *>()); - - m_context->page->setProjects(projectChoices); - m_context->page->setProjectToolTips(projectToolTips); - m_context->page->setAddingSubProject(projectAction == ProjectExplorer::AddSubProject); -} - bool ProjectFileWizardExtension::processFiles( const QList<GeneratedFile> &files, bool *removeOpenProjectAttribute, QString *errorMessage) @@ -525,11 +436,9 @@ bool ProjectFileWizardExtension::processProject( QString generatedProject = generatedProjectFilePath(files); - // Add files to folder (Entry at 0 is 'None'). - const int folderIndex = m_context->page->currentProjectIndex() - 1; - if (folderIndex < 0 || folderIndex >= m_context->projects.size()) + FolderNode *folder = m_context->page->currentNode(); + if (!folder) return true; - FolderNode *folder = m_context->projects.at(folderIndex).node; if (m_context->wizard->kind() == IWizard::ProjectWizard) { if (!static_cast<ProjectNode *>(folder)->addSubProjects(QStringList(generatedProject))) { *errorMessage = tr("Failed to add subproject \"%1\"\nto project \"%2\".") @@ -600,11 +509,7 @@ void ProjectFileWizardExtension::applyCodeStyle(GeneratedFile *file) const if (!languageId.isValid()) return; // don't modify files like *.ui *.pro - FolderNode *folder = 0; - const int projectIndex = m_context->page->currentProjectIndex() - 1; - if (projectIndex >= 0 && projectIndex < m_context->projects.size()) - folder = m_context->projects.at(projectIndex).node; - + FolderNode *folder = m_context->page->currentNode(); Project *baseProject = SessionManager::projectForNode(folder); ICodeStylePreferencesFactory *factory = TextEditorSettings::codeStyleFactory(languageId); @@ -632,10 +537,5 @@ void ProjectFileWizardExtension::applyCodeStyle(GeneratedFile *file) const file->setContents(doc.toPlainText()); } -void ProjectFileWizardExtension::setProjectIndex(int i) -{ - m_context->page->setCurrentProjectIndex(i); -} - } // namespace Internal } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/projectfilewizardextension.h b/src/plugins/projectexplorer/projectfilewizardextension.h index daad9cd5ee..bd40f22e2c 100644 --- a/src/plugins/projectexplorer/projectfilewizardextension.h +++ b/src/plugins/projectexplorer/projectfilewizardextension.h @@ -35,6 +35,7 @@ #include <coreplugin/ifilewizardextension.h> namespace ProjectExplorer { +class FolderNode; namespace Internal { @@ -52,14 +53,11 @@ public: bool *removeOpenProjectAttribute, QString *errorMessage); void applyCodeStyle(Core::GeneratedFile *file) const; - void setProjectIndex(int i); - public slots: void firstExtensionPageShown(const QList<Core::GeneratedFile> &files, const QVariantMap &extraValues); void initializeVersionControlChoices(); private: - void initProjectChoices(const QList<Core::GeneratedFile> &generatedFiles, const QVariantMap &extraValues); bool processProject(const QList<Core::GeneratedFile> &files, bool *removeOpenProjectAttribute, QString *errorMessage); bool processVersionControl(const QList<Core::GeneratedFile> &files, QString *errorMessage); diff --git a/src/plugins/projectexplorer/projectwizardpage.cpp b/src/plugins/projectexplorer/projectwizardpage.cpp index e2f1ecc867..1313b45365 100644 --- a/src/plugins/projectexplorer/projectwizardpage.cpp +++ b/src/plugins/projectexplorer/projectwizardpage.cpp @@ -27,6 +27,7 @@ ** ****************************************************************************/ +#include "addnewmodel.h" #include "projectwizardpage.h" #include "ui_projectwizardpage.h" @@ -36,6 +37,7 @@ #include <QDir> #include <QTextStream> +#include <QTreeView> /*! \class ProjectExplorer::Internal::ProjectWizardPage @@ -51,7 +53,8 @@ using namespace Internal; ProjectWizardPage::ProjectWizardPage(QWidget *parent) : QWizardPage(parent), - m_ui(new Ui::WizardPage) + m_ui(new Ui::WizardPage), + m_model(0) { m_ui->setupUi(this); m_ui->vcsManageButton->setText(Core::ICore::msgShowOptionsDialog()); @@ -64,36 +67,71 @@ ProjectWizardPage::ProjectWizardPage(QWidget *parent) : ProjectWizardPage::~ProjectWizardPage() { delete m_ui; + delete m_model; } -void ProjectWizardPage::setProjects(const QStringList &p) +void ProjectWizardPage::setModel(AddNewModel *model) { - m_ui->projectComboBox->clear(); - m_ui->projectComboBox->addItems(p); - m_ui->projectComboBox->setEnabled(p.size() > 1); - m_ui->projectLabel->setEnabled(p.size() > 1); + delete m_model; + m_model = model; + + // TODO see OverViewCombo and OverView for click event filter + m_ui->projectComboBox->setModel(model); + bool enabled = m_model->rowCount(QModelIndex()) > 1; + m_ui->projectComboBox->setEnabled(enabled); + + expandTree(QModelIndex()); } -void ProjectWizardPage::setProjectToolTips(const QStringList &t) +bool ProjectWizardPage::expandTree(const QModelIndex &root) { - m_projectToolTips = t; + bool expand = false; + if (!root.isValid()) // always expand root + expand = true; + + // Check children + int count = m_model->rowCount(root); + for (int i = 0; i < count; ++i) { + if (expandTree(m_model->index(i, 0, root))) + expand = true; + } + + // Apply to self + if (expand) + m_ui->projectComboBox->view()->expand(root); + else + m_ui->projectComboBox->view()->collapse(root); + + // if we are a high priority node, our *parent* needs to be expanded + AddNewTree *tree = static_cast<AddNewTree *>(root.internalPointer()); + if (tree && tree->priority() >= 100) + expand = true; + + return expand; } -void ProjectWizardPage::setAddingSubProject(bool addingSubProject) +void ProjectWizardPage::setBestNode(AddNewTree *tree) { - m_ui->projectLabel->setText(addingSubProject ? - tr("Add as a subproject to project:") - : tr("Add to &project:")); + QModelIndex index = m_model->indexForTree(tree); + m_ui->projectComboBox->setCurrentIndex(index); + + while (index.isValid()) { + m_ui->projectComboBox->view()->expand(index); + index = index.parent(); + } } -int ProjectWizardPage::currentProjectIndex() const +FolderNode *ProjectWizardPage::currentNode() const { - return m_ui->projectComboBox->currentIndex(); + QModelIndex index = m_ui->projectComboBox->view()->currentIndex(); + return m_model->nodeForIndex(index); } -void ProjectWizardPage::setCurrentProjectIndex(int idx) +void ProjectWizardPage::setAddingSubProject(bool addingSubProject) { - m_ui->projectComboBox->setCurrentIndex(idx); + m_ui->projectLabel->setText(addingSubProject ? + tr("Add as a subproject to project:") + : tr("Add to &project:")); } void ProjectWizardPage::setNoneLabel(const QString &label) diff --git a/src/plugins/projectexplorer/projectwizardpage.h b/src/plugins/projectexplorer/projectwizardpage.h index 6a6b86def1..6a92594bc2 100644 --- a/src/plugins/projectexplorer/projectwizardpage.h +++ b/src/plugins/projectexplorer/projectwizardpage.h @@ -32,8 +32,15 @@ #include <QWizardPage> +QT_BEGIN_NAMESPACE +class QTreeView; +QT_END_NAMESPACE + namespace ProjectExplorer { +class FolderNode; namespace Internal { +class AddNewModel; +class AddNewTree; namespace Ui { class WizardPage; } @@ -46,11 +53,9 @@ public: explicit ProjectWizardPage(QWidget *parent = 0); virtual ~ProjectWizardPage(); - void setProjects(const QStringList &); - void setProjectToolTips(const QStringList &); - - int currentProjectIndex() const; - void setCurrentProjectIndex(int); + void setModel(AddNewModel *model); + void setBestNode(ProjectExplorer::Internal::AddNewTree *tree); + FolderNode *currentNode() const; void setNoneLabel(const QString &label); void setAdditionalInfo(const QString &text); @@ -71,9 +76,11 @@ private slots: private: void setProjectToolTip(const QString &); + bool expandTree(const QModelIndex &root); Ui::WizardPage *m_ui; QStringList m_projectToolTips; + AddNewModel *m_model; }; } // namespace Internal diff --git a/src/plugins/projectexplorer/projectwizardpage.ui b/src/plugins/projectexplorer/projectwizardpage.ui index a337f8fc45..e169128bc2 100644 --- a/src/plugins/projectexplorer/projectwizardpage.ui +++ b/src/plugins/projectexplorer/projectwizardpage.ui @@ -67,7 +67,7 @@ </widget> </item> <item row="0" column="1" colspan="2"> - <widget class="QComboBox" name="projectComboBox"> + <widget class="Utils::TreeViewComboBox" name="projectComboBox"> <property name="enabled"> <bool>false</bool> </property> @@ -144,6 +144,13 @@ </item> </layout> </widget> + <customwidgets> + <customwidget> + <class>Utils::TreeViewComboBox</class> + <extends>QComboBox</extends> + <header location="global">utils/treeviewcombobox.h</header> + </customwidget> + </customwidgets> <resources/> <connections/> </ui> |