projecttreewidget.cpp 17.3 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 117
        : QWidget(parent),
          m_explorer(ProjectExplorerPlugin::instance()),
          m_view(0),
          m_model(0),
          m_filterProjectsAction(0),
118
          m_autoSync(false),
119
          m_autoExpand(true)
con's avatar
con committed
120
{
hjk's avatar
hjk committed
121 122
    m_model = new FlatModel(SessionManager::sessionNode(), this);
    Project *pro = SessionManager::startupProject();
123 124
    if (pro)
        m_model->setStartupProject(pro->rootProjectNode());
125
    NodesWatcher *watcher = new NodesWatcher(this);
hjk's avatar
hjk committed
126
    SessionManager::sessionNode()->registerWatcher(watcher);
127

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

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

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

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

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

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

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

186
    setAutoSynchronization(true);
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 239

    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;
240 241
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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