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
        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
    layout->addWidget(Core::ItemViewFind::createSearchableWrapper(
139 140
                          m_view, ItemViewFind::DarkColored,
                          ItemViewFind::FetchMoreWhileSearching));
con's avatar
con committed
141 142 143
    layout->setContentsMargins(0, 0, 0, 0);
    setLayout(layout);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

void ProjectTreeWidget::setAutoSynchronization(bool sync, bool syncNow)
{
324
    m_toggleSync->setChecked(sync);
con's avatar
con committed
325 326 327 328 329 330 331 332
    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
333 334
        connect(m_explorer, SIGNAL(currentNodeChanged(ProjectExplorer::Node*,ProjectExplorer::Project*)),
                this, SLOT(setCurrentItem(ProjectExplorer::Node*,ProjectExplorer::Project*)));
con's avatar
con committed
335
        if (syncNow)
336
            setCurrentItem(m_explorer->currentNode(), ProjectExplorerPlugin::currentProject());
con's avatar
con committed
337
    } else {
Robert Loehning's avatar
Robert Loehning committed
338 339
        disconnect(m_explorer, SIGNAL(currentNodeChanged(ProjectExplorer::Node*,ProjectExplorer::Project*)),
                this, SLOT(setCurrentItem(ProjectExplorer::Node*,ProjectExplorer::Project*)));
con's avatar
con committed
340 341 342
    }
}

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

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

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

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

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

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

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

void ProjectTreeWidget::handleCurrentItemChange(const QModelIndex &current)
{
    Node *node = m_model->nodeForIndex(current);
dt's avatar
dt committed
381
    // node might be 0. that's okay
con's avatar
con committed
382 383 384 385 386 387 388 389 390 391
    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);
392
    m_explorer->showContextMenu(this, m_view->mapToGlobal(pos), node);
con's avatar
con committed
393 394 395 396 397 398
}

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

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

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

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

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

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

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


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

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

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

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

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