projectmodels.cpp 12.6 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3 4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
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
** 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
12 13 14
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
15
**
16 17 18 19 20 21 22
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
con's avatar
con committed
23
**
hjk's avatar
hjk committed
24
****************************************************************************/
hjk's avatar
hjk committed
25

con's avatar
con committed
26
#include "projectmodels.h"
hjk's avatar
hjk committed
27 28

#include "project.h"
29
#include "projectnodes.h"
dt's avatar
dt committed
30
#include "projectexplorer.h"
31
#include "projecttree.h"
32
#include "session.h"
con's avatar
con committed
33 34

#include <coreplugin/fileiconprovider.h>
35
#include <utils/algorithm.h>
36
#include <utils/dropsupport.h>
hjk's avatar
hjk committed
37

38 39 40
#include <QDebug>
#include <QFileInfo>
#include <QFont>
41
#include <QMimeData>
42
#include <QLoggingCategory>
con's avatar
con committed
43

44 45
using namespace Utils;

46 47 48
namespace ProjectExplorer {

using namespace Internal;
con's avatar
con committed
49

50
static bool sortNodes(const Node *n1, const Node *n2)
hjk's avatar
hjk committed
51
{
52 53 54
    if (n1->priority() > n2->priority())
        return true;
    if (n1->priority() < n2->priority())
hjk's avatar
hjk committed
55
        return false;
con's avatar
con committed
56

57 58 59
    const int displayNameResult = caseFriendlyCompare(n1->displayName(), n2->displayName());
    if (displayNameResult != 0)
        return displayNameResult < 0;
con's avatar
con committed
60

61 62 63 64 65
    const int filePathResult = caseFriendlyCompare(n1->filePath().toString(),
                                 n2->filePath().toString());
    if (filePathResult != 0)
        return filePathResult < 0;
    return n1 < n2; // sort by pointer value
con's avatar
con committed
66 67
}

68 69 70 71
static bool sortWrapperNodes(const WrapperNode *w1, const WrapperNode *w2)
{
    return sortNodes(w1->m_node, w2->m_node);
}
hjk's avatar
hjk committed
72

73 74
FlatModel::FlatModel(QObject *parent)
    : TreeModel<WrapperNode, WrapperNode>(new WrapperNode(SessionManager::sessionNode()), parent)
con's avatar
con committed
75
{
76 77 78
    m_timer.setInterval(200);
    connect(&m_timer, &QTimer::timeout, this, &FlatModel::doUpdate);

79
    ProjectTree *tree = ProjectTree::instance();
80 81
    connect(tree, &ProjectTree::dataChanged, this, &FlatModel::update);
    connect(tree, &ProjectTree::nodeUpdated, this, &FlatModel::nodeUpdated);
con's avatar
con committed
82

83 84 85
    SessionManager *sm = SessionManager::instance();
    connect(sm, &SessionManager::projectRemoved, this, &FlatModel::update);
    connect(sm, &SessionManager::startupProjectChanged, this, &FlatModel::startupProjectChanged);
con's avatar
con committed
86

87 88 89
    connect(sm, &SessionManager::sessionLoaded, this, &FlatModel::loadExpandData);
    connect(sm, &SessionManager::aboutToSaveSession, this, &FlatModel::saveExpandData);
    connect(sm, &SessionManager::projectAdded, this, &FlatModel::handleProjectAdded);
90
    update();
con's avatar
con committed
91 92
}

93
void FlatModel::setView(QTreeView *view)
con's avatar
con committed
94
{
95 96 97 98 99 100 101 102 103 104 105 106 107
    QTC_CHECK(!m_view);
    m_view = view;
    connect(m_view, &QTreeView::expanded, this, &FlatModel::onExpanded);
    connect(m_view, &QTreeView::collapsed, this, &FlatModel::onCollapsed);
}

void FlatModel::startupProjectChanged(Project *project)
{
    ProjectNode *projectNode = project ? project->rootProjectNode() : nullptr;
    if (m_startupProject == projectNode)
        return;
    m_startupProject = projectNode;
    layoutChanged();
con's avatar
con committed
108 109 110 111 112 113 114
}

QVariant FlatModel::data(const QModelIndex &index, int role) const
{
    QVariant result;

    if (Node *node = nodeForIndex(index)) {
115
        FolderNode *folderNode = node->asFolderNode();
con's avatar
con committed
116
        switch (role) {
117 118
        case Qt::DisplayRole: {
            QString name = node->displayName();
119
            if (node->nodeType() == NodeType::Project
120
                    && node->parentFolderNode()
121
                    && node->parentFolderNode()->nodeType() == NodeType::Session) {
122
                const QString vcsTopic = static_cast<ProjectNode *>(node)->vcsTopic();
123 124

                if (!vcsTopic.isEmpty())
125
                    name += QLatin1String(" [") + vcsTopic + QLatin1Char(']');
126 127 128 129 130
            }

            result = name;
            break;
        }
con's avatar
con committed
131
        case Qt::EditRole: {
132
            result = node->filePath().fileName();
con's avatar
con committed
133 134 135
            break;
        }
        case Qt::ToolTipRole: {
136
            result = node->tooltip();
con's avatar
con committed
137 138 139 140 141 142
            break;
        }
        case Qt::DecorationRole: {
            if (folderNode)
                result = folderNode->icon();
            else
143
                result = Core::FileIconProvider::icon(node->filePath().toString());
con's avatar
con committed
144 145 146 147 148 149 150 151 152
            break;
        }
        case Qt::FontRole: {
            QFont font;
            if (node == m_startupProject)
                font.setBold(true);
            result = font;
            break;
        }
153
        case Project::FilePathRole: {
154
            result = node->filePath().toString();
con's avatar
con committed
155 156
            break;
        }
157
        case Project::EnabledRole: {
158 159 160
            result = node->isEnabled();
            break;
        }
con's avatar
con committed
161 162 163 164 165 166
        }
    }

    return result;
}

dt's avatar
dt committed
167 168 169 170 171 172 173
Qt::ItemFlags FlatModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return 0;
    // We claim that everything is editable
    // That's slightly wrong
    // We control the only view, and that one does the checks
174
    Qt::ItemFlags f = Qt::ItemIsSelectable|Qt::ItemIsEnabled|Qt::ItemIsDragEnabled;
175
    if (Node *node = nodeForIndex(index)) {
176
        if (!node->asProjectNode()) {
177
            // either folder or file node
178
            if (node->supportedActions(node).contains(Rename))
179 180 181 182
                f = f | Qt::ItemIsEditable;
        }
    }
    return f;
dt's avatar
dt committed
183 184 185 186 187 188 189 190 191
}

bool FlatModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!index.isValid())
        return false;
    if (role != Qt::EditRole)
        return false;

192
    Node *node = nodeForIndex(index);
193
    QTC_ASSERT(node, return false);
194

195
    Utils::FileName orgFilePath = node->filePath();
196
    Utils::FileName newFilePath = orgFilePath.parentDir().appendPath(value.toString());
197

198
    ProjectExplorerPlugin::renameFile(node, newFilePath.toString());
199
    emit renamed(orgFilePath, newFilePath);
dt's avatar
dt committed
200 201 202
    return true;
}

203
void FlatModel::update()
con's avatar
con committed
204
{
205
    m_timer.start(300);
con's avatar
con committed
206 207
}

208
void FlatModel::doUpdate()
con's avatar
con committed
209
{
210 211
    m_timer.stop();
    rebuildModel();
con's avatar
con committed
212 213
}

214
void FlatModel::rebuildModel()
con's avatar
con committed
215
{
216
    QSet<Node *> seen;
con's avatar
con committed
217

218
    rootItem()->removeChildren();
219 220 221 222 223
    for (Node *node : SessionManager::sessionNode()->nodes()) {
        if (ProjectNode *projectNode = node->asProjectNode()) {
            if (!seen.contains(projectNode))
                addProjectNode(rootItem(), projectNode, &seen);
        }
con's avatar
con committed
224
    }
225
    rootItem()->sortChildren(&sortWrapperNodes);
con's avatar
con committed
226

227 228 229 230 231 232 233
    forAllItems([this](WrapperNode *node) {
        const QString path = node->m_node->filePath().toString();
        const QString displayName = node->m_node->displayName();
        ExpandData ed(path, displayName);
        if (m_toExpand.contains(ed))
            m_view->expand(node->index());
    });
con's avatar
con committed
234 235
}

236
void FlatModel::onCollapsed(const QModelIndex &idx)
con's avatar
con committed
237
{
238
    m_toExpand.remove(expandDataForNode(nodeForIndex(idx)));
con's avatar
con committed
239 240
}

241
void FlatModel::onExpanded(const QModelIndex &idx)
con's avatar
con committed
242
{
243
    m_toExpand.insert(expandDataForNode(nodeForIndex(idx)));
con's avatar
con committed
244 245
}

246
ExpandData FlatModel::expandDataForNode(const Node *node) const
con's avatar
con committed
247
{
248 249 250 251
    QTC_ASSERT(node, return ExpandData());
    const QString path = node->filePath().toString();
    const QString displayName = node->displayName();
    return ExpandData(path, displayName);
con's avatar
con committed
252 253
}

254
void FlatModel::handleProjectAdded(Project *project)
con's avatar
con committed
255
{
256 257 258 259 260 261
    Node *node = project->rootProjectNode();
    m_toExpand.insert(expandDataForNode(node));
    if (WrapperNode *wrapper = wrapperForNode(node)) {
        wrapper->forFirstLevelChildren([this](WrapperNode *child) {
            m_toExpand.insert(expandDataForNode(child->m_node));
        });
con's avatar
con committed
262
    }
263
    doUpdate();
con's avatar
con committed
264 265
}

266
void FlatModel::loadExpandData()
con's avatar
con committed
267
{
268 269 270
    const QList<QVariant> data = SessionManager::value("ProjectTree.ExpandData").value<QList<QVariant>>();
    m_toExpand = Utils::transform<QSet>(data, [](const QVariant &v) { return ExpandData::fromSettings(v); });
    m_toExpand.remove(ExpandData());
con's avatar
con committed
271 272
}

273
void FlatModel::saveExpandData()
con's avatar
con committed
274
{
275 276 277
    // TODO if there are multiple ProjectTreeWidgets, the last one saves the data
    QList<QVariant> data = Utils::transform<QList>(m_toExpand, &ExpandData::toSettings);
    SessionManager::setValue(QLatin1String("ProjectTree.ExpandData"), data);
con's avatar
con committed
278 279
}

280
void FlatModel::addProjectNode(WrapperNode *parent, ProjectNode *projectNode, QSet<Node *> *seen)
con's avatar
con committed
281
{
282 283 284 285
    seen->insert(projectNode);
    auto node = new WrapperNode(projectNode);
    parent->appendChild(node);
    addFolderNode(node, projectNode, seen);
286 287 288 289 290
    for (Node *subNode : projectNode->nodes()) {
        if (ProjectNode *subProjectNode = subNode->asProjectNode()) {
            if (!seen->contains(subProjectNode))
                addProjectNode(node, subProjectNode, seen);
        }
con's avatar
con committed
291
    }
292
    node->sortChildren(&sortWrapperNodes);
con's avatar
con committed
293 294
}

295
void FlatModel::addFolderNode(WrapperNode *parent, FolderNode *folderNode, QSet<Node *> *seen)
con's avatar
con committed
296
{
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
    const QList<FolderNode *> subFolderNodes = folderNode->folderNodes();
    for (FolderNode *subFolderNode : subFolderNodes) {
        if (!filter(subFolderNode) && !seen->contains(subFolderNode)) {
            seen->insert(subFolderNode);
            auto node = new WrapperNode(subFolderNode);
            parent->appendChild(node);
            addFolderNode(node, subFolderNode, seen);
            node->sortChildren(&sortWrapperNodes);
        } else {
            addFolderNode(parent, subFolderNode, seen);
        }
    }
    const QList<FileNode *> fileNodes = folderNode->fileNodes();
    for (FileNode *fileNode : fileNodes) {
        if (!filter(fileNode) && !seen->contains(fileNode)) {
            seen->insert(fileNode);
            parent->appendChild(new WrapperNode(fileNode));
        }
    }
con's avatar
con committed
316 317
}

318 319 320 321 322
Qt::DropActions FlatModel::supportedDragActions() const
{
    return Qt::MoveAction;
}

323 324
QStringList FlatModel::mimeTypes() const
{
325
    return Utils::DropSupport::mimeTypesForFilePaths();
326 327 328 329
}

QMimeData *FlatModel::mimeData(const QModelIndexList &indexes) const
{
330
    auto data = new Utils::DropMimeData;
331
    foreach (const QModelIndex &index, indexes) {
332 333 334 335 336
        if (Node *node = nodeForIndex(index)) {
            if (node->asFileNode())
                data->addFile(node->filePath().toString());
            data->addValue(QVariant::fromValue(node));
        }
337
    }
338
    return data;
339 340
}

341
WrapperNode *FlatModel::wrapperForNode(const Node *node) const
con's avatar
con committed
342
{
343 344 345 346
    return findNonRooItem([this, node](WrapperNode *item) {
        return item->m_node == node;
    });
}
con's avatar
con committed
347

348 349 350 351
QModelIndex FlatModel::indexForNode(const Node *node) const
{
    WrapperNode *wrapper = wrapperForNode(node);
    return wrapper ? indexForItem(wrapper) : QModelIndex();
con's avatar
con committed
352 353 354 355
}

void FlatModel::setProjectFilterEnabled(bool filter)
{
356 357
    if (filter == m_filterProjects)
        return;
con's avatar
con committed
358
    m_filterProjects = filter;
359
    rebuildModel();
con's avatar
con committed
360 361 362 363 364
}

void FlatModel::setGeneratedFilesFilterEnabled(bool filter)
{
    m_filterGeneratedFiles = filter;
365
    rebuildModel();
con's avatar
con committed
366 367
}

368 369 370 371 372 373 374 375 376 377
bool FlatModel::projectFilterEnabled()
{
    return m_filterProjects;
}

bool FlatModel::generatedFilesFilterEnabled()
{
    return m_filterGeneratedFiles;
}

con's avatar
con committed
378 379
Node *FlatModel::nodeForIndex(const QModelIndex &index) const
{
380 381
    WrapperNode *flatNode = itemForIndex(index);
    return flatNode ? flatNode->m_node : nullptr;
con's avatar
con committed
382 383 384 385 386
}

bool FlatModel::filter(Node *node) const
{
    bool isHidden = false;
387
    if (FolderNode *folderNode = node->asFolderNode()) {
388
        if (m_filterProjects)
389
            isHidden = !folderNode->showInSimpleTree();
390
    } else if (FileNode *fileNode = node->asFileNode()) {
con's avatar
con committed
391 392 393 394 395 396
        if (m_filterGeneratedFiles)
            isHidden = fileNode->isGenerated();
    }
    return isHidden;
}

397 398 399 400 401 402
const QLoggingCategory &FlatModel::logger()
{
    static QLoggingCategory logger("qtc.projectexplorer.flatmodel");
    return logger;
}

403 404 405 406 407 408 409 410 411 412
bool isSorted(const QList<Node *> &nodes)
{
    int size = nodes.size();
    for (int i = 0; i < size -1; ++i) {
        if (!sortNodes(nodes.at(i), nodes.at(i+1)))
            return false;
    }
    return true;
}

413
void FlatModel::nodeUpdated(Node *)
414
{
415
    update();
416 417
}

418 419 420 421 422 423 424 425 426 427 428
namespace Internal {

int caseFriendlyCompare(const QString &a, const QString &b)
{
    int result = a.compare(b, Qt::CaseInsensitive);
    if (result != 0)
        return result;
    return a.compare(b, Qt::CaseSensitive);
}

}
429 430

} // namespace ProjectExplorer
431