projecttreewidget.cpp 17.2 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8 9 10 11 12 13 14
** 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.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17 18 19 20 21 22 23 24 25
** 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
con's avatar
con committed
26 27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
hjk's avatar
hjk committed
29

con's avatar
con committed
30
#include "projecttreewidget.h"
hjk's avatar
hjk committed
31

con's avatar
con committed
32
#include "projectexplorer.h"
33
#include "projectnodes.h"
34 35
#include "project.h"
#include "session.h"
con's avatar
con committed
36 37
#include "projectmodels.h"

38
#include <coreplugin/actionmanager/command.h>
39
#include <coreplugin/coreconstants.h>
con's avatar
con committed
40 41
#include <coreplugin/icore.h>
#include <coreplugin/editormanager/editormanager.h>
42
#include <coreplugin/editormanager/ieditor.h>
Daniel Teske's avatar
Daniel Teske committed
43
#include <coreplugin/find/itemviewfind.h>
dt's avatar
dt committed
44

45
#include <utils/navigationtreeview.h>
46
#include <utils/algorithm.h>
hjk's avatar
hjk committed
47

48 49
#include <QDebug>
#include <QSettings>
con's avatar
con committed
50

51
#include <QStyledItemDelegate>
52 53 54 55
#include <QVBoxLayout>
#include <QToolButton>
#include <QAction>
#include <QMenu>
con's avatar
con committed
56

57
using namespace Core;
con's avatar
con committed
58 59 60
using namespace ProjectExplorer;
using namespace ProjectExplorer::Internal;

61 62
QList<ProjectTreeWidget *> ProjectTreeWidget::m_projectTreeWidgets;

con's avatar
con committed
63
namespace {
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80

class ProjectTreeItemDelegate : public QStyledItemDelegate
{
public:
    ProjectTreeItemDelegate(QObject *parent) : QStyledItemDelegate(parent)
    { }

    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
    {
        QStyleOptionViewItem opt = option;
        if (!index.data(ProjectExplorer::Project::EnabledRole).toBool())
            opt.state &= ~QStyle::State_Enabled;
        QStyledItemDelegate::paint(painter, opt, index);
    }
};

bool debug = false;
con's avatar
con committed
81 82
}

83
class ProjectTreeView : public Utils::NavigationTreeView
con's avatar
con committed
84 85 86 87 88 89
{
public:
    ProjectTreeView()
    {
        setEditTriggers(QAbstractItemView::EditKeyPressed);
        setContextMenuPolicy(Qt::CustomContextMenu);
90 91
        setDragEnabled(true);
        setDragDropMode(QAbstractItemView::DragOnly);
92 93
        m_context = new IContext(this);
        m_context->setContext(Context(ProjectExplorer::Constants::C_PROJECT_TREE));
94
        m_context->setWidget(this);
95
        ICore::addContextObject(m_context);
dt's avatar
dt committed
96 97 98
    }
    ~ProjectTreeView()
    {
99
        ICore::removeContextObject(m_context);
dt's avatar
dt committed
100
        delete m_context;
con's avatar
con committed
101
    }
dt's avatar
dt committed
102 103

private:
104
    IContext *m_context;
con's avatar
con committed
105 106 107 108 109 110 111
};

/*!
  /class ProjectTreeWidget

  Shows the projects in form of a tree.
  */
hjk's avatar
hjk committed
112
ProjectTreeWidget::ProjectTreeWidget(QWidget *parent)
con's avatar
con committed
113 114 115 116
        : QWidget(parent),
          m_view(0),
          m_model(0),
          m_filterProjectsAction(0),
117
          m_autoSync(false),
118
          m_autoExpand(true)
con's avatar
con committed
119
{
hjk's avatar
hjk committed
120 121
    m_model = new FlatModel(SessionManager::sessionNode(), this);
    Project *pro = SessionManager::startupProject();
122 123
    if (pro)
        m_model->setStartupProject(pro->rootProjectNode());
124
    NodesWatcher *watcher = new NodesWatcher(this);
hjk's avatar
hjk committed
125
    SessionManager::sessionNode()->registerWatcher(watcher);
126

Robert Loehning's avatar
Robert Loehning committed
127 128 129 130
    connect(watcher, SIGNAL(foldersAboutToBeRemoved(FolderNode*,QList<FolderNode*>)),
            this, SLOT(foldersAboutToBeRemoved(FolderNode*,QList<FolderNode*>)));
    connect(watcher, SIGNAL(filesAboutToBeRemoved(FolderNode*,QList<FileNode*>)),
            this, SLOT(filesAboutToBeRemoved(FolderNode*,QList<FileNode*>)));
con's avatar
con committed
131 132 133

    m_view = new ProjectTreeView;
    m_view->setModel(m_model);
134
    m_view->setItemDelegate(new ProjectTreeItemDelegate(this));
con's avatar
con committed
135 136 137 138
    setFocusProxy(m_view);
    initView();

    QVBoxLayout *layout = new QVBoxLayout();
Daniel Teske's avatar
Daniel Teske committed
139
    layout->addWidget(Core::ItemViewFind::createSearchableWrapper(
140 141
                          m_view, ItemViewFind::DarkColored,
                          ItemViewFind::FetchMoreWhileSearching));
con's avatar
con committed
142 143 144
    layout->setContentsMargins(0, 0, 0, 0);
    setLayout(layout);

Leena Miettinen's avatar
Leena Miettinen committed
145
    m_filterProjectsAction = new QAction(tr("Simplify Tree"), this);
con's avatar
con committed
146 147 148 149
    m_filterProjectsAction->setCheckable(true);
    m_filterProjectsAction->setChecked(false); // default is the traditional complex tree
    connect(m_filterProjectsAction, SIGNAL(toggled(bool)), this, SLOT(setProjectFilter(bool)));

Leena Miettinen's avatar
Leena Miettinen committed
150
    m_filterGeneratedFilesAction = new QAction(tr("Hide Generated Files"), this);
con's avatar
con committed
151 152 153 154 155 156 157
    m_filterGeneratedFilesAction->setCheckable(true);
    m_filterGeneratedFilesAction->setChecked(true);
    connect(m_filterGeneratedFilesAction, SIGNAL(toggled(bool)), this, SLOT(setGeneratedFilesFilter(bool)));

    // connections
    connect(m_model, SIGNAL(modelReset()),
            this, SLOT(initView()));
Robert Loehning's avatar
Robert Loehning committed
158 159 160 161 162 163
    connect(m_view, SIGNAL(activated(QModelIndex)),
            this, SLOT(openItem(QModelIndex)));
    connect(m_view->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
            this, SLOT(handleCurrentItemChange(QModelIndex)));
    connect(m_view, SIGNAL(customContextMenuRequested(QPoint)),
            this, SLOT(showContextMenu(QPoint)));
hjk's avatar
hjk committed
164 165 166

    QObject *sessionManager = SessionManager::instance();
    connect(sessionManager, SIGNAL(singleProjectAdded(ProjectExplorer::Project*)),
Robert Loehning's avatar
Robert Loehning committed
167
            this, SLOT(handleProjectAdded(ProjectExplorer::Project*)));
hjk's avatar
hjk committed
168
    connect(sessionManager, SIGNAL(startupProjectChanged(ProjectExplorer::Project*)),
Robert Loehning's avatar
Robert Loehning committed
169
            this, SLOT(startupProjectChanged(ProjectExplorer::Project*)));
con's avatar
con committed
170

hjk's avatar
hjk committed
171
    connect(sessionManager, SIGNAL(aboutToLoadSession(QString)),
172
            this, SLOT(disableAutoExpand()));
hjk's avatar
hjk committed
173
    connect(sessionManager, SIGNAL(sessionLoaded(QString)),
174
            this, SLOT(loadExpandData()));
hjk's avatar
hjk committed
175
    connect(sessionManager, SIGNAL(aboutToSaveSession()),
176 177
            this, SLOT(saveExpandData()));

178
    m_toggleSync = new QToolButton;
179
    m_toggleSync->setIcon(QIcon(QLatin1String(Core::Constants::ICON_LINK)));
180 181 182 183 184
    m_toggleSync->setCheckable(true);
    m_toggleSync->setChecked(autoSynchronization());
    m_toggleSync->setToolTip(tr("Synchronize with Editor"));
    connect(m_toggleSync, SIGNAL(clicked(bool)), this, SLOT(toggleAutoSynchronization()));

185
    setAutoSynchronization(true);
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238

    m_projectTreeWidgets << this;
}

ProjectTreeWidget::~ProjectTreeWidget()
{
    m_projectTreeWidgets.removeOne(this);
}

// returns how many nodes need to be expanded to make node visible
int ProjectTreeWidget::expandedCount(Node *node)
{
    if (m_projectTreeWidgets.isEmpty())
        return 0;
    FlatModel *model = m_projectTreeWidgets.first()->m_model;
    QModelIndex index = model->indexForNode(node);
    if (!index.isValid())
        return 0;

    int count = 0;
    foreach (ProjectTreeWidget *tree, m_projectTreeWidgets) {
        QModelIndex idx = index;
        while (idx.isValid() && idx != tree->m_view->rootIndex()) {
            if (!tree->m_view->isExpanded(idx))
                ++count;
            idx = model->parent(idx);
        }
    }
    return count;
}

Node *ProjectTreeWidget::nodeForFile(const QString &fileName, Project *project)
{
    Node *bestNode = 0;
    int bestNodeExpandCount = INT_MAX;

    foreach (Node *node, SessionManager::nodesForFile(fileName, project)) {
        if (!bestNode) {
            bestNode = node;
            bestNodeExpandCount = ProjectTreeWidget::expandedCount(node->parentFolderNode());
        } else if (node->nodeType() < bestNode->nodeType()) {
            bestNode = node;
            bestNodeExpandCount = ProjectTreeWidget::expandedCount(node->parentFolderNode());
        } else if (node->nodeType() == bestNode->nodeType()) {
            int nodeExpandCount = ProjectTreeWidget::expandedCount(node->parentFolderNode());
            if (nodeExpandCount < bestNodeExpandCount) {
                bestNode = node;
                bestNodeExpandCount = ProjectTreeWidget::expandedCount(node->parentFolderNode());
            }
        }
    }

    return bestNode;
239 240
}

241 242 243 244 245 246 247 248
void ProjectTreeWidget::disableAutoExpand()
{
    m_autoExpand = false;
}

void ProjectTreeWidget::loadExpandData()
{
    m_autoExpand = true;
hjk's avatar
hjk committed
249
    QStringList data = SessionManager::value(QLatin1String("ProjectTree.ExpandData")).toStringList();
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
    recursiveLoadExpandData(m_view->rootIndex(), data.toSet());
}

void ProjectTreeWidget::recursiveLoadExpandData(const QModelIndex &index, const QSet<QString> &data)
{
    if (data.contains(m_model->nodeForIndex(index)->path())) {
        m_view->expand(index);
        int count = m_model->rowCount(index);
        for (int i = 0; i < count; ++i)
            recursiveLoadExpandData(index.child(i, 0), data);
    }
}

void ProjectTreeWidget::saveExpandData()
{
    QStringList data;
    recursiveSaveExpandData(m_view->rootIndex(), &data);
    // TODO if there are multiple ProjectTreeWidgets, the last one saves the data
hjk's avatar
hjk committed
268
    SessionManager::setValue(QLatin1String("ProjectTree.ExpandData"), data);
269 270 271 272 273 274 275 276 277 278 279 280 281
}

void ProjectTreeWidget::recursiveSaveExpandData(const QModelIndex &index, QStringList *data)
{
    Q_ASSERT(data);
    if (m_view->isExpanded(index)) {
        data->append(m_model->nodeForIndex(index)->path());
        int count = m_model->rowCount(index);
        for (int i = 0; i < count; ++i)
            recursiveSaveExpandData(index.child(i, 0), data);
    }
}

282 283
void ProjectTreeWidget::foldersAboutToBeRemoved(FolderNode *, const QList<FolderNode*> &list)
{
284
    Node *n = ProjectExplorerPlugin::currentNode();
285
    while (n) {
286 287
        if (FolderNode *fn = qobject_cast<FolderNode *>(n)) {
            if (list.contains(fn)) {
288 289 290 291
                ProjectNode *pn = n->projectNode();
                // Make sure the node we are switching too isn't going to be removed also
                while (list.contains(pn))
                    pn = pn->parentFolderNode()->projectNode();
292
                ProjectExplorerPlugin::setCurrentNode(pn);
293 294 295 296 297 298 299 300 301
                break;
            }
        }
        n = n->parentFolderNode();
    }
}

void ProjectTreeWidget::filesAboutToBeRemoved(FolderNode *, const QList<FileNode*> &list)
{
302
    if (FileNode *fileNode = qobject_cast<FileNode *>(ProjectExplorerPlugin::currentNode())) {
303
        if (list.contains(fileNode))
304
            ProjectExplorerPlugin::setCurrentNode(fileNode->projectNode());
305 306 307
    }
}

308 309 310
QToolButton *ProjectTreeWidget::toggleSync()
{
    return m_toggleSync;
con's avatar
con committed
311 312 313 314 315 316 317 318 319 320 321 322 323 324
}

void ProjectTreeWidget::toggleAutoSynchronization()
{
    setAutoSynchronization(!m_autoSync);
}

bool ProjectTreeWidget::autoSynchronization() const
{
    return m_autoSync;
}

void ProjectTreeWidget::setAutoSynchronization(bool sync, bool syncNow)
{
325
    m_toggleSync->setChecked(sync);
con's avatar
con committed
326 327 328 329 330 331 332 333
    if (sync == m_autoSync)
        return;

    m_autoSync = sync;

    if (debug)
        qDebug() << (m_autoSync ? "Enabling auto synchronization" : "Disabling auto synchronization");
    if (m_autoSync) {
334 335
        connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::currentNodeChanged,
                this, &ProjectTreeWidget::setCurrentItem);
con's avatar
con committed
336
        if (syncNow)
337
            setCurrentItem(ProjectExplorerPlugin::currentNode(), ProjectExplorerPlugin::currentProject());
con's avatar
con committed
338
    } else {
339 340
        disconnect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::currentNodeChanged,
                   this, &ProjectTreeWidget::setCurrentItem);
con's avatar
con committed
341 342 343
    }
}

344 345 346 347 348
void ProjectTreeWidget::collapseAll()
{
    m_view->collapseAll();
}

con's avatar
con committed
349 350
void ProjectTreeWidget::editCurrentItem()
{
351 352
    if (m_view->selectionModel()->currentIndex().isValid())
        m_view->edit(m_view->selectionModel()->currentIndex());
con's avatar
con committed
353 354 355 356 357
}

void ProjectTreeWidget::setCurrentItem(Node *node, Project *project)
{
    if (debug)
358 359
        qDebug() << "ProjectTreeWidget::setCurrentItem(" << (project ? project->displayName() : QLatin1String("0"))
                 << ", " <<  (node ? node->path() : QLatin1String("0")) << ")";
360

361
    if (!project)
con's avatar
con committed
362 363 364 365
        return;

    const QModelIndex mainIndex = m_model->indexForNode(node);

366 367 368 369 370
    if (mainIndex.isValid()) {
        if (mainIndex != m_view->selectionModel()->currentIndex()) {
            m_view->setCurrentIndex(mainIndex);
            m_view->scrollTo(mainIndex);
        }
371 372 373 374
    } else {
        if (debug)
            qDebug() << "clear selection";
        m_view->clearSelection();
con's avatar
con committed
375
    }
376

con's avatar
con committed
377 378 379 380 381
}

void ProjectTreeWidget::handleCurrentItemChange(const QModelIndex &current)
{
    Node *node = m_model->nodeForIndex(current);
dt's avatar
dt committed
382
    // node might be 0. that's okay
con's avatar
con committed
383 384
    bool autoSync = autoSynchronization();
    setAutoSynchronization(false);
385
    ProjectExplorerPlugin::setCurrentNode(node);
con's avatar
con committed
386 387 388 389 390 391 392
    setAutoSynchronization(autoSync, false);
}

void ProjectTreeWidget::showContextMenu(const QPoint &pos)
{
    QModelIndex index = m_view->indexAt(pos);
    Node *node = m_model->nodeForIndex(index);
393
    ProjectExplorerPlugin::showContextMenu(this, m_view->mapToGlobal(pos), node);
con's avatar
con committed
394 395 396 397 398 399
}

void ProjectTreeWidget::handleProjectAdded(ProjectExplorer::Project *project)
{
    Node *node = project->rootProjectNode();
    QModelIndex idx = m_model->indexForNode(node);
400 401
    if (m_autoExpand) // disabled while session restoring
        m_view->setExpanded(idx, true);
402
    m_view->setCurrentIndex(idx);
con's avatar
con committed
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
}

void ProjectTreeWidget::startupProjectChanged(ProjectExplorer::Project *project)
{
    if (project) {
        ProjectNode *node = project->rootProjectNode();
        m_model->setStartupProject(node);
    } else {
        m_model->setStartupProject(0);
    }
}

void ProjectTreeWidget::initView()
{
    QModelIndex sessionIndex = m_model->index(0, 0);

    // hide root folder
    m_view->setRootIndex(sessionIndex);

    while (m_model->canFetchMore(sessionIndex))
        m_model->fetchMore(sessionIndex);

    // expand top level projects
hjk's avatar
hjk committed
426
    for (int i = 0; i < m_model->rowCount(sessionIndex); ++i)
con's avatar
con committed
427 428
        m_view->expand(m_model->index(i, 0, sessionIndex));

429
    setCurrentItem(ProjectExplorerPlugin::currentNode(), ProjectExplorerPlugin::currentProject());
con's avatar
con committed
430 431 432 433 434
}

void ProjectTreeWidget::openItem(const QModelIndex &mainIndex)
{
    Node *node = m_model->nodeForIndex(mainIndex);
435 436
    if (node->nodeType() != FileNodeType)
        return;
Eike Ziller's avatar
Eike Ziller committed
437
    IEditor *editor = EditorManager::openEditor(node->path());
438
    if (editor && node->line() >= 0)
439
        editor->gotoLine(node->line());
con's avatar
con committed
440 441 442 443 444 445 446 447 448 449 450 451 452 453
}

void ProjectTreeWidget::setProjectFilter(bool filter)
{
    m_model->setProjectFilterEnabled(filter);
    m_filterProjectsAction->setChecked(filter);
}

void ProjectTreeWidget::setGeneratedFilesFilter(bool filter)
{
    m_model->setGeneratedFilesFilterEnabled(filter);
    m_filterGeneratedFilesAction->setChecked(filter);
}

454 455 456 457 458 459 460 461 462 463 464
bool ProjectTreeWidget::generatedFilesFilter()
{
    return m_model->generatedFilesFilterEnabled();
}

bool ProjectTreeWidget::projectFilter()
{
    return m_model->projectFilterEnabled();
}


hjk's avatar
hjk committed
465
ProjectTreeWidgetFactory::ProjectTreeWidgetFactory()
con's avatar
con committed
466
{
467 468 469 470
    setDisplayName(tr("Projects"));
    setPriority(100);
    setId("Projects");
    setActivationSequence(QKeySequence(UseMacShortcuts ? tr("Meta+X") : tr("Alt+X")));
con's avatar
con committed
471 472
}

473
NavigationView ProjectTreeWidgetFactory::createWidget()
con's avatar
con committed
474
{
475
    NavigationView n;
hjk's avatar
hjk committed
476
    ProjectTreeWidget *ptw = new ProjectTreeWidget;
con's avatar
con committed
477 478 479
    n.widget = ptw;

    QToolButton *filter = new QToolButton;
480
    filter->setIcon(QIcon(QLatin1String(Core::Constants::ICON_FILTER)));
Leena Miettinen's avatar
Leena Miettinen committed
481
    filter->setToolTip(tr("Filter Tree"));
con's avatar
con committed
482
    filter->setPopupMode(QToolButton::InstantPopup);
Eike Ziller's avatar
Eike Ziller committed
483
    filter->setProperty("noArrow", true);
con's avatar
con committed
484 485 486 487 488
    QMenu *filterMenu = new QMenu(filter);
    filterMenu->addAction(ptw->m_filterProjectsAction);
    filterMenu->addAction(ptw->m_filterGeneratedFilesAction);
    filter->setMenu(filterMenu);

Roberto Raggi's avatar
Roberto Raggi committed
489
    n.dockToolBarWidgets << filter << ptw->toggleSync();
con's avatar
con committed
490 491 492
    return n;
}

493 494 495 496
void ProjectTreeWidgetFactory::saveSettings(int position, QWidget *widget)
{
    ProjectTreeWidget *ptw = qobject_cast<ProjectTreeWidget *>(widget);
    Q_ASSERT(ptw);
497
    QSettings *settings = ICore::settings();
498 499 500 501
    const QString baseKey = QLatin1String("ProjectTreeWidget.") + QString::number(position);
    settings->setValue(baseKey + QLatin1String(".ProjectFilter"), ptw->projectFilter());
    settings->setValue(baseKey + QLatin1String(".GeneratedFilter"), ptw->generatedFilesFilter());
    settings->setValue(baseKey + QLatin1String(".SyncWithEditor"), ptw->autoSynchronization());
502 503 504 505 506 507
}

void ProjectTreeWidgetFactory::restoreSettings(int position, QWidget *widget)
{
    ProjectTreeWidget *ptw = qobject_cast<ProjectTreeWidget *>(widget);
    Q_ASSERT(ptw);
508
    QSettings *settings = ICore::settings();
509 510 511 512
    const QString baseKey = QLatin1String("ProjectTreeWidget.") + QString::number(position);
    ptw->setProjectFilter(settings->value(baseKey + QLatin1String(".ProjectFilter"), false).toBool());
    ptw->setGeneratedFilesFilter(settings->value(baseKey + QLatin1String(".GeneratedFilter"), true).toBool());
    ptw->setAutoSynchronization(settings->value(baseKey +  QLatin1String(".SyncWithEditor"), true).toBool());
513
}