projectmodels.cpp 30.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
** 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
Eike Ziller's avatar
Eike Ziller committed
13
14
** conditions see http://www.qt.io/licensing.  For further information
** use the contact form at http://www.qt.io/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18
19
20
21
22
23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
25
26
**
** 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
27
28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
hjk's avatar
hjk committed
30

con's avatar
con committed
31
#include "projectmodels.h"
hjk's avatar
hjk committed
32
33

#include "project.h"
34
#include "projectnodes.h"
dt's avatar
dt committed
35
#include "projectexplorer.h"
36
#include "projecttree.h"
con's avatar
con committed
37
38

#include <coreplugin/fileiconprovider.h>
39
#include <utils/algorithm.h>
hjk's avatar
hjk committed
40

41
42
43
#include <QDebug>
#include <QFileInfo>
#include <QFont>
44
#include <QMimeData>
con's avatar
con committed
45

46
47
48
namespace ProjectExplorer {

using namespace Internal;
con's avatar
con committed
49
50

namespace {
hjk's avatar
hjk committed
51
52

// sorting helper function
dt's avatar
dt committed
53

hjk's avatar
hjk committed
54
55
56
57
58
59
60
61
bool sortNodes(Node *n1, Node *n2)
{
    // Ordering is: project files, project, folder, file

    const NodeType n1Type = n1->nodeType();
    const NodeType n2Type = n2->nodeType();

    // project files
62
63
    FileNode *file1 = dynamic_cast<FileNode*>(n1);
    FileNode *file2 = dynamic_cast<FileNode*>(n2);
hjk's avatar
hjk committed
64
65
66
67
68
    if (file1 && file1->fileType() == ProjectFileType) {
        if (file2 && file2->fileType() == ProjectFileType) {
            const QString fileName1 = QFileInfo(file1->path()).fileName();
            const QString fileName2 = QFileInfo(file2->path()).fileName();

69
            int result = caseFriendlyCompare(fileName1, fileName2);
dt's avatar
dt committed
70
            if (result != 0)
dt's avatar
dt committed
71
                return result < 0;
hjk's avatar
hjk committed
72
73
            else
                return file1 < file2;
con's avatar
con committed
74
        } else {
hjk's avatar
hjk committed
75
76
77
            return true; // project file is before everything else
        }
    } else {
78
        if (file2 && file2->fileType() == ProjectFileType)
hjk's avatar
hjk committed
79
80
            return false;
    }
con's avatar
con committed
81

hjk's avatar
hjk committed
82
83
84
85
86
    // projects
    if (n1Type == ProjectNodeType) {
        if (n2Type == ProjectNodeType) {
            ProjectNode *project1 = static_cast<ProjectNode*>(n1);
            ProjectNode *project2 = static_cast<ProjectNode*>(n2);
con's avatar
con committed
87

88
            int result = caseFriendlyCompare(project1->displayName(), project2->displayName());
dt's avatar
dt committed
89
            if (result != 0)
dt's avatar
dt committed
90
                return result < 0;
91
92
93
94
95

            result = caseFriendlyCompare(project1->path(), project2->path());
            if (result != 0)
                return result < 0;
            return project1 < project2; // sort by pointer value
hjk's avatar
hjk committed
96
97
98
99
100
101
        } else {
           return true; // project is before folder & file
       }
    }
    if (n2Type == ProjectNodeType)
        return false;
con's avatar
con committed
102

103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
    if (n1Type == VirtualFolderNodeType) {
        if (n2Type == VirtualFolderNodeType) {
            VirtualFolderNode *folder1 = static_cast<VirtualFolderNode *>(n1);
            VirtualFolderNode *folder2 = static_cast<VirtualFolderNode *>(n2);

            if (folder1->priority() > folder2->priority())
                return true;
            if (folder1->priority() < folder2->priority())
                return false;
            int result = caseFriendlyCompare(folder1->path(), folder2->path());
            if (result != 0)
                return result < 0;
            else
                return folder1 < folder2;
        } else {
            return true; // virtual folder is before folder
        }
    }

    if (n2Type == VirtualFolderNodeType)
        return false;


hjk's avatar
hjk committed
126
127
128
129
    if (n1Type == FolderNodeType) {
        if (n2Type == FolderNodeType) {
            FolderNode *folder1 = static_cast<FolderNode*>(n1);
            FolderNode *folder2 = static_cast<FolderNode*>(n2);
con's avatar
con committed
130

131
            int result = caseFriendlyCompare(folder1->path(), folder2->path());
dt's avatar
dt committed
132
            if (result != 0)
dt's avatar
dt committed
133
                return result < 0;
hjk's avatar
hjk committed
134
135
136
137
            else
                return folder1 < folder2;
        } else {
            return true; // folder is before file
con's avatar
con committed
138
        }
hjk's avatar
hjk committed
139
140
141
    }
    if (n2Type == FolderNodeType)
        return false;
con's avatar
con committed
142

hjk's avatar
hjk committed
143
144
    // must be file nodes
    {
145
146
147
148
        int result = caseFriendlyCompare(n1->displayName(), n2->displayName());
        if (result != 0)
            return result < 0;

hjk's avatar
hjk committed
149
150
        const QString filePath1 = n1->path();
        const QString filePath2 = n2->path();
con's avatar
con committed
151

hjk's avatar
hjk committed
152
153
        const QString fileName1 = QFileInfo(filePath1).fileName();
        const QString fileName2 = QFileInfo(filePath2).fileName();
con's avatar
con committed
154

155
        result = caseFriendlyCompare(fileName1, fileName2);
dt's avatar
dt committed
156
        if (result != 0) {
dt's avatar
dt committed
157
            return result < 0; // sort by filename
hjk's avatar
hjk committed
158
        } else {
159
            result = caseFriendlyCompare(filePath1, filePath2);
160
            if (result != 0)
dt's avatar
dt committed
161
                return result < 0; // sort by filepath
162
163
164
165

            if (n1->line() != n2->line())
                return n1->line() < n2->line(); // sort by line numbers
            return n1 < n2; // sort by pointer value
con's avatar
con committed
166
167
        }
    }
hjk's avatar
hjk committed
168
    return false;
con's avatar
con committed
169
170
}

hjk's avatar
hjk committed
171
172
} // namespace anon

con's avatar
con committed
173
174
FlatModel::FlatModel(SessionNode *rootNode, QObject *parent)
        : QAbstractItemModel(parent),
175
          m_filterProjects(false),
con's avatar
con committed
176
177
178
179
180
          m_filterGeneratedFiles(true),
          m_rootNode(rootNode),
          m_startupProject(0),
          m_parentFolderForChange(0)
{
181
    ProjectTree *tree = ProjectTree::instance();
con's avatar
con committed
182

183
184
    connect(tree, &ProjectTree::aboutToChangeShowInSimpleTree,
            this, &FlatModel::aboutToShowInSimpleTreeChanged);
185

186
187
    connect(tree, &ProjectTree::showInSimpleTreeChanged,
            this, &FlatModel::showInSimpleTreeChanged);
188

189
190
191
192
    connect(tree, &ProjectTree::foldersAboutToBeAdded,
            this, &FlatModel::foldersAboutToBeAdded);
    connect(tree, &ProjectTree::foldersAdded,
            this, &FlatModel::foldersAdded);
con's avatar
con committed
193

194
195
196
197
    connect(tree, &ProjectTree::foldersAboutToBeRemoved,
            this, &FlatModel::foldersAboutToBeRemoved);
    connect(tree, &ProjectTree::foldersRemoved,
            this, &FlatModel::foldersRemoved);
con's avatar
con committed
198

199
200
201
202
    connect(tree, &ProjectTree::filesAboutToBeAdded,
            this, &FlatModel::filesAboutToBeAdded);
    connect(tree, &ProjectTree::filesAdded,
            this, &FlatModel::filesAdded);
con's avatar
con committed
203

204
205
206
207
    connect(tree, &ProjectTree::filesAboutToBeRemoved,
            this, &FlatModel::filesAboutToBeRemoved);
    connect(tree, &ProjectTree::filesRemoved,
            this, &FlatModel::filesRemoved);
208

209
210
211
212
    connect(tree, &ProjectTree::nodeSortKeyAboutToChange,
            this, &FlatModel::nodeSortKeyAboutToChange);
    connect(tree, &ProjectTree::nodeSortKeyChanged,
            this, &FlatModel::nodeSortKeyChanged);
213

214
215
    connect(tree, &ProjectTree::nodeUpdated,
            this, &FlatModel::nodeUpdated);
con's avatar
con committed
216
217
218
219
220
221
222
223
}

QModelIndex FlatModel::index(int row, int column, const QModelIndex &parent) const
{
    QModelIndex result;
    if (!parent.isValid() && row == 0 && column == 0) { // session
        result = createIndex(0, 0, m_rootNode);
    } else if (parent.isValid() && column == 0) {
224
        FolderNode *parentNode = dynamic_cast<FolderNode*>(nodeForIndex(parent));
dt's avatar
dt committed
225
        Q_ASSERT(parentNode);
con's avatar
con committed
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
        QHash<FolderNode*, QList<Node*> >::const_iterator it = m_childNodes.constFind(parentNode);
        if (it == m_childNodes.constEnd()) {
            fetchMore(parentNode);
            it = m_childNodes.constFind(parentNode);
        }

        if (row < it.value().size())
            result = createIndex(row, 0, it.value().at(row));
    }
//    qDebug() << "index of " << row << column << parent.data(Project::FilePathRole) << " is " << result.data(Project::FilePathRole);
    return result;
}

QModelIndex FlatModel::parent(const QModelIndex &idx) const
{
    QModelIndex parentIndex;
    if (Node *node = nodeForIndex(idx)) {
        FolderNode *parentNode = visibleFolderNode(node->parentFolderNode());
        if (parentNode) {
            FolderNode *grandParentNode = visibleFolderNode(parentNode->parentFolderNode());
            if (grandParentNode) {
                QHash<FolderNode*, QList<Node*> >::const_iterator it = m_childNodes.constFind(grandParentNode);
                if (it == m_childNodes.constEnd()) {
                    fetchMore(grandParentNode);
                    it = m_childNodes.constFind(grandParentNode);
                }
dt's avatar
dt committed
252
                Q_ASSERT(it != m_childNodes.constEnd());
con's avatar
con committed
253
                const int row = it.value().indexOf(parentNode);
dt's avatar
dt committed
254
                Q_ASSERT(row >= 0);
con's avatar
con committed
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
                parentIndex = createIndex(row, 0, parentNode);
            } else {
                // top level node, parent is session
                parentIndex = index(0, 0, QModelIndex());
            }
        }
    }

//    qDebug() << "parent of " << idx.data(Project::FilePathRole) << " is " << parentIndex.data(Project::FilePathRole);

    return parentIndex;
}

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

    if (Node *node = nodeForIndex(index)) {
273
        FolderNode *folderNode = dynamic_cast<FolderNode*>(node);
con's avatar
con committed
274
        switch (role) {
275
276
277
        case Qt::DisplayRole: {
            QString name = node->displayName();

278
279
            if (node->nodeType() == ProjectNodeType
                    && node->parentFolderNode()
280
                    && node->parentFolderNode()->nodeType() == SessionNodeType) {
281
                const QString vcsTopic = static_cast<ProjectNode *>(node)->vcsTopic();
282
283

                if (!vcsTopic.isEmpty())
284
                    name += QLatin1String(" [") + vcsTopic + QLatin1Char(']');
285
286
287
288
289
            }

            result = name;
            break;
        }
con's avatar
con committed
290
        case Qt::EditRole: {
291
            result = QFileInfo(node->path()).fileName();
con's avatar
con committed
292
293
294
            break;
        }
        case Qt::ToolTipRole: {
295
            result = node->tooltip();
con's avatar
con committed
296
297
298
299
300
301
            break;
        }
        case Qt::DecorationRole: {
            if (folderNode)
                result = folderNode->icon();
            else
302
                result = Core::FileIconProvider::icon(node->path());
con's avatar
con committed
303
304
305
306
307
308
309
310
311
            break;
        }
        case Qt::FontRole: {
            QFont font;
            if (node == m_startupProject)
                font.setBold(true);
            result = font;
            break;
        }
312
        case Project::FilePathRole: {
con's avatar
con committed
313
314
315
            result = node->path();
            break;
        }
316
        case Project::EnabledRole: {
317
318
319
            result = node->isEnabled();
            break;
        }
con's avatar
con committed
320
321
322
323
324
325
        }
    }

    return result;
}

dt's avatar
dt committed
326
327
328
329
330
331
332
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
333
334
    Qt::ItemFlags f = Qt::ItemIsSelectable|Qt::ItemIsEnabled;
    if (Node *node = nodeForIndex(index)) {
335
336
        if (node == m_rootNode)
            return 0; // no flags for session node...
337
        if (!dynamic_cast<ProjectNode *>(node)) {
338
            // either folder or file node
339
            if (node->supportedActions(node).contains(Rename))
340
                f = f | Qt::ItemIsEditable;
341
            if (dynamic_cast<FileNode *>(node))
342
                f = f | Qt::ItemIsDragEnabled;
343
344
345
        }
    }
    return f;
dt's avatar
dt committed
346
347
348
349
350
351
352
353
354
}

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

355
356
357
358
359
360
361
362
    Node *node = nodeForIndex(index);

    QString orgFilePath = QFileInfo(node->path()).absoluteFilePath();
    QString dir = QFileInfo(orgFilePath).absolutePath();
    QString newFilePath = dir + QLatin1Char('/') + value.toString();

    ProjectExplorerPlugin::renameFile(node, newFilePath);
    emit renamed(orgFilePath, newFilePath);
dt's avatar
dt committed
363
364
365
    return true;
}

con's avatar
con committed
366
367
368
369
370
371
int FlatModel::rowCount(const QModelIndex &parent) const
{
    int rows = 0;
    if (!parent.isValid()) {
        rows = 1;
    } else {
372
        FolderNode *folderNode = dynamic_cast<FolderNode*>(nodeForIndex(parent));
con's avatar
con committed
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
        if (folderNode && m_childNodes.contains(folderNode))
            rows = m_childNodes.value(folderNode).size();
    }
    return rows;
}

int FlatModel::columnCount(const QModelIndex &/*parent*/) const
{
    return 1;
}

bool FlatModel::hasChildren(const QModelIndex &parent) const
{
    if (!parent.isValid())
        return true;

389
    FolderNode *folderNode = dynamic_cast<FolderNode*>(nodeForIndex(parent));
con's avatar
con committed
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
    if (!folderNode)
        return false;

    QHash<FolderNode*, QList<Node*> >::const_iterator it = m_childNodes.constFind(folderNode);
    if (it == m_childNodes.constEnd()) {
        fetchMore(folderNode);
        it = m_childNodes.constFind(folderNode);
    }
    return !it.value().isEmpty();
}

bool FlatModel::canFetchMore(const QModelIndex & parent) const
{
    if (!parent.isValid()) {
        return false;
    } else {
406
        if (FolderNode *folderNode = dynamic_cast<FolderNode*>(nodeForIndex(parent)))
con's avatar
con committed
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
            return !m_childNodes.contains(folderNode);
        else
            return false;
    }
}

void FlatModel::recursiveAddFolderNodes(FolderNode *startNode, QList<Node *> *list, const QSet<Node *> &blackList) const
{
    foreach (FolderNode *folderNode, startNode->subFolderNodes()) {
        if (folderNode && !blackList.contains(folderNode))
            recursiveAddFolderNodesImpl(folderNode, list, blackList);
    }
}

void FlatModel::recursiveAddFolderNodesImpl(FolderNode *startNode, QList<Node *> *list, const QSet<Node *> &blackList) const
{
    if (!filter(startNode)) {
        if (!blackList.contains(startNode))
            list->append(startNode);
    } else {
        foreach (FolderNode *folderNode, startNode->subFolderNodes()) {
            if (folderNode && !blackList.contains(folderNode))
                recursiveAddFolderNodesImpl(folderNode, list, blackList);
        }
    }
}

void FlatModel::recursiveAddFileNodes(FolderNode *startNode, QList<Node *> *list, const QSet<Node *> &blackList) const
{
    foreach (FolderNode *subFolderNode, startNode->subFolderNodes()) {
        if (!blackList.contains(subFolderNode))
            recursiveAddFileNodes(subFolderNode, list, blackList);
    }
    foreach (Node *node, startNode->fileNodes()) {
        if (!blackList.contains(node) && !filter(node))
            list->append(node);
    }
}

QList<Node*> FlatModel::childNodes(FolderNode *parentNode, const QSet<Node*> &blackList) const
{
    QList<Node*> nodeList;

    if (parentNode->nodeType() == SessionNodeType) {
        SessionNode *sessionNode = static_cast<SessionNode*>(parentNode);
        QList<ProjectNode*> projectList = sessionNode->projectNodes();
        for (int i = 0; i < projectList.size(); ++i) {
            if (!blackList.contains(projectList.at(i)))
                nodeList << projectList.at(i);
        }
    } else {
        recursiveAddFolderNodes(parentNode, &nodeList, blackList);
        recursiveAddFileNodes(parentNode, &nodeList, blackList + nodeList.toSet());
    }
461
    Utils::sort(nodeList, sortNodes);
con's avatar
con committed
462
463
464
465
466
    return nodeList;
}

void FlatModel::fetchMore(FolderNode *folderNode) const
{
dt's avatar
dt committed
467
468
    Q_ASSERT(folderNode);
    Q_ASSERT(!m_childNodes.contains(folderNode));
con's avatar
con committed
469
470
471
472
473
474
475

    QList<Node*> nodeList = childNodes(folderNode);
    m_childNodes.insert(folderNode, nodeList);
}

void FlatModel::fetchMore(const QModelIndex &parent)
{
476
    FolderNode *folderNode = dynamic_cast<FolderNode*>(nodeForIndex(parent));
dt's avatar
dt committed
477
    Q_ASSERT(folderNode);
con's avatar
con committed
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496

    fetchMore(folderNode);
}

void FlatModel::setStartupProject(ProjectNode *projectNode)
{
    if (m_startupProject != projectNode) {
        QModelIndex oldIndex = (m_startupProject ? indexForNode(m_startupProject) : QModelIndex());
        QModelIndex newIndex = (projectNode ? indexForNode(projectNode) : QModelIndex());
        m_startupProject = projectNode;
        if (oldIndex.isValid())
            emit dataChanged(oldIndex, oldIndex);
        if (newIndex.isValid())
            emit dataChanged(newIndex, newIndex);
    }
}

void FlatModel::reset()
{
497
    beginResetModel();
con's avatar
con committed
498
    m_childNodes.clear();
499
    endResetModel();
con's avatar
con committed
500
501
}

502
503
504
505
506
Qt::DropActions FlatModel::supportedDragActions() const
{
    return Qt::MoveAction;
}

507
508
QStringList FlatModel::mimeTypes() const
{
509
    return Utils::FileDropSupport::mimeTypesForFilePaths();
510
511
512
513
}

QMimeData *FlatModel::mimeData(const QModelIndexList &indexes) const
{
514
    auto data = new Utils::FileDropMimeData;
515
516
    foreach (const QModelIndex &index, indexes) {
        Node *node = nodeForIndex(index);
517
        if (dynamic_cast<FileNode *>(node))
518
            data->addFile(node->path());
519
    }
520
    return data;
521
522
}

con's avatar
con committed
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
QModelIndex FlatModel::indexForNode(const Node *node_)
{
    // We assume that we are only called for nodes that are represented

    // we use non-const pointers internally
    Node *node = const_cast<Node*>(node_);
    if (!node)
        return QModelIndex();

    if (node == m_rootNode)
        return createIndex(0, 0, m_rootNode);

    FolderNode *parentNode = visibleFolderNode(node->parentFolderNode());

    // Do we have the parent mapped?
    QHash<FolderNode*, QList<Node*> >::const_iterator it = m_childNodes.constFind(parentNode);
    if (it == m_childNodes.constEnd()) {
        fetchMore(parentNode);
        it = m_childNodes.constFind(parentNode);
    }
    if (it != m_childNodes.constEnd()) {
        const int row = it.value().indexOf(node);
        if (row != -1)
            return createIndex(row, 0, node);
    }
    return QModelIndex();
}

void FlatModel::setProjectFilterEnabled(bool filter)
{
553
554
    if (filter == m_filterProjects)
        return;
con's avatar
con committed
555
556
557
558
559
560
561
562
563
564
    m_filterProjects = filter;
    reset();
}

void FlatModel::setGeneratedFilesFilterEnabled(bool filter)
{
    m_filterGeneratedFiles = filter;
    reset();
}

565
566
567
568
569
570
571
572
573
574
bool FlatModel::projectFilterEnabled()
{
    return m_filterProjects;
}

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

con's avatar
con committed
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
Node *FlatModel::nodeForIndex(const QModelIndex &index) const
{
    if (index.isValid())
        return (Node*)index.internalPointer();
    return 0;
}

/*
  Returns the first folder node in the ancestors
  for the given node that is not filtered
  out by the Flat Model.
*/
FolderNode *FlatModel::visibleFolderNode(FolderNode *node) const
{
    if (!node)
        return 0;

    for (FolderNode *folderNode = node;
         folderNode;
         folderNode = folderNode->parentFolderNode()) {
        if (!filter(folderNode))
            return folderNode;
    }
    return 0;
}

bool FlatModel::filter(Node *node) const
{
    bool isHidden = false;
604
    if (FolderNode *folderNode = dynamic_cast<FolderNode*>(node)) {
605
        if (m_filterProjects)
606
            isHidden = !folderNode->showInSimpleTree();
607
    } else if (FileNode *fileNode = dynamic_cast<FileNode*>(node)) {
con's avatar
con committed
608
609
610
611
612
613
        if (m_filterGeneratedFiles)
            isHidden = fileNode->isGenerated();
    }
    return isHidden;
}

614
615
616
617
618
619
620
621
622
623
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;
}

con's avatar
con committed
624
625
626
627
628
629
630
631
632
633
634
635
636
637
/// slots and all the fun
void FlatModel::added(FolderNode* parentNode, const QList<Node*> &newNodeList)
{
    QModelIndex parentIndex = indexForNode(parentNode);
    // Old  list
    QHash<FolderNode*, QList<Node*> >::const_iterator it = m_childNodes.constFind(parentNode);
    if (it == m_childNodes.constEnd())
        return;
    QList<Node *> oldNodeList = it.value();

    // Compare lists and emit signals, and modify m_childNodes on the fly
    QList<Node *>::const_iterator oldIter = oldNodeList.constBegin();
    QList<Node *>::const_iterator newIter = newNodeList.constBegin();

638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
    Q_ASSERT(isSorted(oldNodeList));
    Q_ASSERT(isSorted(newNodeList));

    QSet<Node *> emptyDifference;
    emptyDifference = oldNodeList.toSet();
    emptyDifference.subtract(newNodeList.toSet());
    if (!emptyDifference.isEmpty()) {
        // This should not happen...
        qDebug() << "FlatModel::added, old Node list should be subset of newNode list, found files in old list which were not part of new list";
        foreach (Node *n, emptyDifference) {
            qDebug()<<n->path();
        }
        Q_ASSERT(false);
    }

con's avatar
con committed
653
654
655
656
657
658
659
660
661
662
663
664
    // optimization, check for old list is empty
    if (oldIter == oldNodeList.constEnd()) {
        // New Node List is empty, nothing added which intrest us
        if (newIter == newNodeList.constEnd())
            return;
        // So all we need to do is easy
        beginInsertRows(parentIndex, 0, newNodeList.size() - 1);
        m_childNodes.insert(parentNode, newNodeList);
        endInsertRows();
        return;
    }

hjk's avatar
hjk committed
665
    while (true) {
con's avatar
con committed
666
        // Skip all that are the same
hjk's avatar
hjk committed
667
        while (*oldIter == *newIter) {
con's avatar
con committed
668
669
670
671
672
673
674
675
676
677
            ++oldIter;
            ++newIter;
            if (oldIter == oldNodeList.constEnd()) {
                // At end of oldNodeList, sweep up rest of newNodeList
                QList<Node *>::const_iterator startOfBlock = newIter;
                newIter = newNodeList.constEnd();
                int pos = oldIter - oldNodeList.constBegin();
                int count = newIter - startOfBlock;
                if (count > 0) {
                    beginInsertRows(parentIndex, pos, pos+count-1);
hjk's avatar
hjk committed
678
                    while (startOfBlock != newIter) {
con's avatar
con committed
679
680
681
682
683
684
685
686
687
688
689
690
                        oldNodeList.insert(pos, *startOfBlock);
                        ++pos;
                        ++startOfBlock;
                    }
                    m_childNodes.insert(parentNode, oldNodeList);
                    endInsertRows();
                }
                return; // Done with the lists, leave the function
            }
        }

        QList<Node *>::const_iterator startOfBlock = newIter;
hjk's avatar
hjk committed
691
        while (*oldIter != *newIter)
con's avatar
con committed
692
693
694
695
696
697
698
699
            ++newIter;
        // startOfBlock is the first that was diffrent
        // newIter points to the new position of oldIter
        // newIter - startOfBlock is number of new items
        // oldIter is the position where those are...
        int pos = oldIter - oldNodeList.constBegin();
        int count = newIter - startOfBlock;
        beginInsertRows(parentIndex, pos, pos + count - 1);
hjk's avatar
hjk committed
700
        while (startOfBlock != newIter) {
con's avatar
con committed
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
            oldNodeList.insert(pos, *startOfBlock);
            ++pos;
            ++startOfBlock;
        }
        m_childNodes.insert(parentNode, oldNodeList);
        endInsertRows();
        oldIter = oldNodeList.constBegin() + pos;
    }
}

void FlatModel::removed(FolderNode* parentNode, const QList<Node*> &newNodeList)
{
    QModelIndex parentIndex = indexForNode(parentNode);
    // Old  list
    QHash<FolderNode*, QList<Node*> >::const_iterator it = m_childNodes.constFind(parentNode);
    if (it == m_childNodes.constEnd())
        return;
718

con's avatar
con committed
719
720
721
722
723
    QList<Node *> oldNodeList = it.value();
    // Compare lists and emit signals, and modify m_childNodes on the fly
    QList<Node *>::const_iterator oldIter = oldNodeList.constBegin();
    QList<Node *>::const_iterator newIter = newNodeList.constBegin();

724
725
726
727
728
729
730
731
732
733
734
735
736
737
    Q_ASSERT(isSorted(newNodeList));

    QSet<Node *> emptyDifference;
    emptyDifference = newNodeList.toSet();
    emptyDifference.subtract(oldNodeList.toSet());
    if (!emptyDifference.isEmpty()) {
        // This should not happen...
        qDebug() << "FlatModel::removed, new Node list should be subset of oldNode list, found files in new list which were not part of old list";
        foreach (Node *n, emptyDifference) {
            qDebug()<<n->path();
        }
        Q_ASSERT(false);
    }

con's avatar
con committed
738
739
740
741
742
743
744
745
746
747
748
749
    // optimization, check for new list is empty
    if (newIter == newNodeList.constEnd()) {
        // New Node List is empty, everything removed
        if (oldIter == oldNodeList.constEnd())
            return;
        // So all we need to do is easy
        beginRemoveRows(parentIndex, 0, oldNodeList.size() - 1);
        m_childNodes.insert(parentNode, newNodeList);
        endRemoveRows();
        return;
    }

hjk's avatar
hjk committed
750
    while (true) {
con's avatar
con committed
751
        // Skip all that are the same
hjk's avatar
hjk committed
752
        while (*oldIter == *newIter) {
con's avatar
con committed
753
754
755
756
757
758
759
760
761
762
            ++oldIter;
            ++newIter;
            if (newIter == newNodeList.constEnd()) {
                // At end of newNodeList, sweep up rest of oldNodeList
                QList<Node *>::const_iterator startOfBlock = oldIter;
                oldIter = oldNodeList.constEnd();
                int pos = startOfBlock - oldNodeList.constBegin();
                int count = oldIter - startOfBlock;
                if (count > 0) {
                    beginRemoveRows(parentIndex, pos, pos+count-1);
hjk's avatar
hjk committed
763
                    while (startOfBlock != oldIter) {
con's avatar
con committed
764
765
766
767
768
769
770
771
772
773
774
775
                        ++startOfBlock;
                        oldNodeList.removeAt(pos);
                    }

                    m_childNodes.insert(parentNode, oldNodeList);
                    endRemoveRows();
                }
                return; // Done with the lists, leave the function
            }
        }

        QList<Node *>::const_iterator startOfBlock = oldIter;
hjk's avatar
hjk committed
776
        while (*oldIter != *newIter)
con's avatar
con committed
777
778
779
780
781
782
783
784
            ++oldIter;
        // startOfBlock is the first that was diffrent
        // oldIter points to the new position of newIter
        // oldIter - startOfBlock is number of new items
        // newIter is the position where those are...
        int pos = startOfBlock - oldNodeList.constBegin();
        int count = oldIter - startOfBlock;
        beginRemoveRows(parentIndex, pos, pos + count - 1);
hjk's avatar
hjk committed
785
        while (startOfBlock != oldIter) {
con's avatar
con committed
786
787
788
789
790
791
792
793
794
            ++startOfBlock;
            oldNodeList.removeAt(pos);
        }
        m_childNodes.insert(parentNode, oldNodeList);
        endRemoveRows();
        oldIter = oldNodeList.constBegin() + pos;
    }
}

795
void FlatModel::aboutToShowInSimpleTreeChanged(FolderNode* node)
796
797
798
799
800
801
802
803
804
805
{
    if (!m_filterProjects)
        return;
    FolderNode *folder = visibleFolderNode(node->parentFolderNode());
    QList<Node *> newNodeList = childNodes(folder, QSet<Node *>() << node);
    removed(folder, newNodeList);

    QList<Node *> staleFolders;
    recursiveAddFolderNodesImpl(node, &staleFolders);
    foreach (Node *n, staleFolders)
806
        if (FolderNode *fn = dynamic_cast<FolderNode *>(n))
807
808
809
            m_childNodes.remove(fn);
}

810
void FlatModel::showInSimpleTreeChanged(FolderNode *node)
811
812
813
814
815
816
817
818
819
{
    if (!m_filterProjects)
        return;
    // we are only interested if we filter
    FolderNode *folder = visibleFolderNode(node->parentFolderNode());
    QList<Node *> newNodeList = childNodes(folder);
    added(folder, newNodeList);
}

con's avatar
con committed
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
void FlatModel::foldersAboutToBeAdded(FolderNode *parentFolder, const QList<FolderNode*> &newFolders)
{
    Q_UNUSED(newFolders)
    m_parentFolderForChange = parentFolder;
}

void FlatModel::foldersAdded()
{
    // First found out what the folder is that we are adding the files to
    FolderNode *folderNode = visibleFolderNode(m_parentFolderForChange);

    // Now get the new list for that folder
    QList<Node *> newNodeList = childNodes(folderNode);

    added(folderNode, newNodeList);
}

void FlatModel::foldersAboutToBeRemoved(FolderNode *parentFolder, const QList<FolderNode*> &staleFolders)
{
    QSet<Node *> blackList;
hjk's avatar
hjk committed
840
    foreach (FolderNode *node, staleFolders)
con's avatar
con committed
841
842
843
844
845
        blackList.insert(node);

    FolderNode *folderNode = visibleFolderNode(parentFolder);
    QList<Node *> newNodeList = childNodes(folderNode, blackList);

846
    removed(folderNode, newNodeList);
con's avatar
con committed
847
848
849
850
851
    removeFromCache(staleFolders);
}

void FlatModel::removeFromCache(QList<FolderNode *> list)
{
hjk's avatar
hjk committed
852
    foreach (FolderNode *fn, list) {
con's avatar
con committed
853
854
855
856
857
        removeFromCache(fn->subFolderNodes());
        m_childNodes.remove(fn);
    }
}

858
859
void FlatModel::changedSortKey(FolderNode *folderNode, Node *node)
{
860
861
862
    if (!m_childNodes.contains(folderNode))
        return; // The directory was not yet mapped, so there is no need to sort it.

863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
    QList<Node *> nodes = m_childNodes.value(folderNode);
    int oldIndex = nodes.indexOf(node);

    nodes.removeAt(oldIndex);
    QList<Node *>::iterator newPosIt = qLowerBound(nodes.begin(), nodes.end(), node, sortNodes);
    int newIndex = newPosIt - nodes.begin();

    if (newIndex == oldIndex)
        return;

    nodes.insert(newPosIt, node);

    QModelIndex parentIndex = indexForNode(folderNode);
    if (newIndex > oldIndex)
        ++newIndex; // see QAbstractItemModel::beginMoveRows
    beginMoveRows(parentIndex, oldIndex, oldIndex, parentIndex, newIndex);
    m_childNodes[folderNode] = nodes;
    endMoveRows();
}

con's avatar
con committed
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
void FlatModel::foldersRemoved()
{
    // Do nothing
}

void FlatModel::filesAboutToBeAdded(FolderNode *folder, const QList<FileNode*> &newFiles)
{
    Q_UNUSED(newFiles)
    m_parentFolderForChange = folder;
}

void FlatModel::filesAdded()
{
    // First find out what the folder is that we are adding the files to
    FolderNode *folderNode = visibleFolderNode(m_parentFolderForChange);

    // Now get the new List for that folder
    QList<Node *> newNodeList = childNodes(folderNode);
    added(folderNode, newNodeList);
}

void FlatModel::filesAboutToBeRemoved(FolderNode *folder, const QList<FileNode*> &staleFiles)
{
    // First found out what the folder (that is the project) is that we are adding the files to
    FolderNode *folderNode = visibleFolderNode(folder);

    QSet<Node *> blackList;
910
    foreach (Node *node, staleFiles)
con's avatar
con committed
911
912
913
914
915
916
917
918
919
920
921
        blackList.insert(node);

    // Now get the new List for that folder
    QList<Node *> newNodeList = childNodes(folderNode, blackList);
    removed(folderNode, newNodeList);
}

void FlatModel::filesRemoved()
{
    // Do nothing
}
922

923
924
925
926
927
928
929
930
931
932
933
void FlatModel::nodeSortKeyAboutToChange(Node *node)
{
    m_nodeForSortKeyChange = node;
}

void FlatModel::nodeSortKeyChanged()
{
    FolderNode *folderNode = visibleFolderNode(m_nodeForSortKeyChange->parentFolderNode());
    changedSortKey(folderNode, m_nodeForSortKeyChange);
}

934
935
936
937
938
939
void FlatModel::nodeUpdated(Node *node)
{
    QModelIndex idx = indexForNode(node);
    emit dataChanged(idx, idx);
}

940
941
942
943
944
945
946
947
948
949
950
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);
}

}
951
952

} // namespace ProjectExplorer
953