projecttreewidget.cpp 17.1 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>
Eike Ziller's avatar
Eike Ziller committed
43
#include <coreplugin/find/treeviewfind.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
        m_context = new IContext(this);
        m_context->setContext(Context(ProjectExplorer::Constants::C_PROJECT_TREE));
92
        m_context->setWidget(this);
93
        ICore::addContextObject(m_context);
dt's avatar
dt committed
94 95 96
    }
    ~ProjectTreeView()
    {
97
        ICore::removeContextObject(m_context);
dt's avatar
dt committed
98
        delete m_context;
con's avatar
con committed
99
    }
dt's avatar
dt committed
100 101

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

/*!
  /class ProjectTreeWidget

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

Robert Loehning's avatar
Robert Loehning committed
126 127 128 129
    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
130 131 132

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

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

Leena Miettinen's avatar
Leena Miettinen committed
143
    m_filterProjectsAction = new QAction(tr("Simplify Tree"), this);
con's avatar
con committed
144 145 146 147
    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
148
    m_filterGeneratedFilesAction = new QAction(tr("Hide Generated Files"), this);
con's avatar
con committed
149 150 151 152 153 154 155
    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
156 157 158 159 160 161
    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
162 163 164

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

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

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

183
    setAutoSynchronization(true);
184 185 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

    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;
237 238
}

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

void ProjectTreeWidget::loadExpandData()
{
    m_autoExpand = true;
hjk's avatar
hjk committed
247
    QStringList data = SessionManager::value(QLatin1String("ProjectTree.ExpandData")).toStringList();
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
    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
266
    SessionManager::setValue(QLatin1String("ProjectTree.ExpandData"), data);
267 268 269 270 271 272 273 274 275 276 277 278 279
}

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);
    }
}

280 281 282
void ProjectTreeWidget::foldersAboutToBeRemoved(FolderNode *, const QList<FolderNode*> &list)
{
    Node *n = m_explorer->currentNode();
283
    while (n) {
284 285
        if (FolderNode *fn = qobject_cast<FolderNode *>(n)) {
            if (list.contains(fn)) {
286 287 288 289 290
                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();
                m_explorer->setCurrentNode(pn);
291 292 293 294 295 296 297 298 299 300
                break;
            }
        }
        n = n->parentFolderNode();
    }
}

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

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

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

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

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

    m_autoSync = sync;

    if (debug)
        qDebug() << (m_autoSync ? "Enabling auto synchronization" : "Disabling auto synchronization");
    if (m_autoSync) {
Robert Loehning's avatar
Robert Loehning committed
332 333
        connect(m_explorer, SIGNAL(currentNodeChanged(ProjectExplorer::Node*,ProjectExplorer::Project*)),
                this, SLOT(setCurrentItem(ProjectExplorer::Node*,ProjectExplorer::Project*)));
con's avatar
con committed
334
        if (syncNow)
335
            setCurrentItem(m_explorer->currentNode(), ProjectExplorerPlugin::currentProject());
con's avatar
con committed
336
    } else {
Robert Loehning's avatar
Robert Loehning committed
337 338
        disconnect(m_explorer, SIGNAL(currentNodeChanged(ProjectExplorer::Node*,ProjectExplorer::Project*)),
                this, SLOT(setCurrentItem(ProjectExplorer::Node*,ProjectExplorer::Project*)));
con's avatar
con committed
339 340 341
    }
}

342 343 344 345 346
void ProjectTreeWidget::collapseAll()
{
    m_view->collapseAll();
}

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

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

359
    if (!project)
con's avatar
con committed
360 361 362 363
        return;

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

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

con's avatar
con committed
375 376 377 378 379
}

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

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

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

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
424
    for (int i = 0; i < m_model->rowCount(sessionIndex); ++i)
con's avatar
con committed
425 426
        m_view->expand(m_model->index(i, 0, sessionIndex));

427
    setCurrentItem(m_explorer->currentNode(), ProjectExplorerPlugin::currentProject());
con's avatar
con committed
428 429 430 431 432
}

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

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);
}

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

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


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

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

    QToolButton *filter = new QToolButton;
478
    filter->setIcon(QIcon(QLatin1String(Core::Constants::ICON_FILTER)));
Leena Miettinen's avatar
Leena Miettinen committed
479
    filter->setToolTip(tr("Filter Tree"));
con's avatar
con committed
480
    filter->setPopupMode(QToolButton::InstantPopup);
Eike Ziller's avatar
Eike Ziller committed
481
    filter->setProperty("noArrow", true);
con's avatar
con committed
482 483 484 485 486
    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
487
    n.dockToolBarWidgets << filter << ptw->toggleSync();
con's avatar
con committed
488 489 490
    return n;
}

491 492 493 494
void ProjectTreeWidgetFactory::saveSettings(int position, QWidget *widget)
{
    ProjectTreeWidget *ptw = qobject_cast<ProjectTreeWidget *>(widget);
    Q_ASSERT(ptw);
495
    QSettings *settings = ICore::settings();
496 497 498 499
    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());
500 501 502 503 504 505
}

void ProjectTreeWidgetFactory::restoreSettings(int position, QWidget *widget)
{
    ProjectTreeWidget *ptw = qobject_cast<ProjectTreeWidget *>(widget);
    Q_ASSERT(ptw);
506
    QSettings *settings = ICore::settings();
507 508 509 510
    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());
511
}