1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "projectmodels.h"
37 #include "projectexplorerconstants.h"
38 #include "projectnodes.h"
39 #include "projectexplorer.h"
41 #include <coreplugin/fileiconprovider.h>
42 #include <utils/qtcassert.h>
44 #include <QtCore/QDebug>
45 #include <QtCore/QFileInfo>
47 #include <QtGui/QFont>
48 #include <QtGui/QIcon>
50 using namespace ProjectExplorer;
51 using namespace ProjectExplorer::Internal;
52 using Core::FileIconProvider;
56 // sorting helper function
58 int fileNameCompare(const QString &a, const QString &b)
60 int result = a.compare(b, Qt::CaseInsensitive);
63 return a.compare(b, Qt::CaseSensitive);
66 bool sortNodes(Node *n1, Node *n2)
68 // Ordering is: project files, project, folder, file
70 const NodeType n1Type = n1->nodeType();
71 const NodeType n2Type = n2->nodeType();
74 FileNode *file1 = qobject_cast<FileNode*>(n1);
75 FileNode *file2 = qobject_cast<FileNode*>(n2);
76 if (file1 && file1->fileType() == ProjectFileType) {
77 if (file2 && file2->fileType() == ProjectFileType) {
78 const QString fileName1 = QFileInfo(file1->path()).fileName();
79 const QString fileName2 = QFileInfo(file2->path()).fileName();
81 int result = fileNameCompare(fileName1, fileName2);
87 return true; // project file is before everything else
90 if (file2 && file2->fileType() == ProjectFileType) {
96 if (n1Type == ProjectNodeType) {
97 if (n2Type == ProjectNodeType) {
98 ProjectNode *project1 = static_cast<ProjectNode*>(n1);
99 ProjectNode *project2 = static_cast<ProjectNode*>(n2);
101 int result = fileNameCompare(project1->displayName(), project2->displayName());
105 return project1 < project2; // sort by pointer value
107 return true; // project is before folder & file
110 if (n2Type == ProjectNodeType)
113 if (n1Type == FolderNodeType) {
114 if (n2Type == FolderNodeType) {
115 FolderNode *folder1 = static_cast<FolderNode*>(n1);
116 FolderNode *folder2 = static_cast<FolderNode*>(n2);
118 int result = fileNameCompare(folder1->path(), folder2->path());
122 return folder1 < folder2;
124 return true; // folder is before file
127 if (n2Type == FolderNodeType)
130 // must be file nodes
132 const QString filePath1 = n1->path();
133 const QString filePath2 = n2->path();
135 const QString fileName1 = QFileInfo(filePath1).fileName();
136 const QString fileName2 = QFileInfo(filePath2).fileName();
138 int result = fileNameCompare(fileName1, fileName2);
140 return result < 0; // sort by filename
142 result = fileNameCompare(filePath1, filePath2);
144 return result < 0; // sort by filepath
146 return n1 < n2; // sort by pointer value
155 FlatModel::FlatModel(SessionNode *rootNode, QObject *parent)
156 : QAbstractItemModel(parent),
157 m_filterProjects(false),
158 m_filterGeneratedFiles(true),
159 m_rootNode(rootNode),
161 m_parentFolderForChange(0)
163 NodesWatcher *watcher = new NodesWatcher(this);
164 m_rootNode->registerWatcher(watcher);
166 connect(watcher, SIGNAL(aboutToChangeHasBuildTargets(ProjectExplorer::ProjectNode*)),
167 this, SLOT(aboutToHasBuildTargetsChanged(ProjectExplorer::ProjectNode*)));
169 connect(watcher, SIGNAL(hasBuildTargetsChanged(ProjectExplorer::ProjectNode*)),
170 this, SLOT(hasBuildTargetsChanged(ProjectExplorer::ProjectNode*)));
172 connect(watcher, SIGNAL(foldersAboutToBeAdded(FolderNode *, const QList<FolderNode*> &)),
173 this, SLOT(foldersAboutToBeAdded(FolderNode *, const QList<FolderNode*> &)));
174 connect(watcher, SIGNAL(foldersAdded()),
175 this, SLOT(foldersAdded()));
177 connect(watcher, SIGNAL(foldersAboutToBeRemoved(FolderNode *, const QList<FolderNode*> &)),
178 this, SLOT(foldersAboutToBeRemoved(FolderNode *, const QList<FolderNode*> &)));
179 connect(watcher, SIGNAL(foldersRemoved()),
180 this, SLOT(foldersRemoved()));
182 connect(watcher, SIGNAL(filesAboutToBeAdded(FolderNode *,const QList<FileNode*> &)),
183 this, SLOT(filesAboutToBeAdded(FolderNode *, const QList<FileNode *> &)));
184 connect(watcher, SIGNAL(filesAdded()),
185 this, SLOT(filesAdded()));
187 connect(watcher, SIGNAL(filesAboutToBeRemoved(FolderNode *, const QList<FileNode*> &)),
188 this, SLOT(filesAboutToBeRemoved(FolderNode *, const QList<FileNode*> &)));
189 connect(watcher, SIGNAL(filesRemoved()),
190 this, SLOT(filesRemoved()));
193 QModelIndex FlatModel::index(int row, int column, const QModelIndex &parent) const
196 if (!parent.isValid() && row == 0 && column == 0) { // session
197 result = createIndex(0, 0, m_rootNode);
198 } else if (parent.isValid() && column == 0) {
199 FolderNode *parentNode = qobject_cast<FolderNode*>(nodeForIndex(parent));
200 Q_ASSERT(parentNode);
201 QHash<FolderNode*, QList<Node*> >::const_iterator it = m_childNodes.constFind(parentNode);
202 if (it == m_childNodes.constEnd()) {
203 fetchMore(parentNode);
204 it = m_childNodes.constFind(parentNode);
207 if (row < it.value().size())
208 result = createIndex(row, 0, it.value().at(row));
210 // qDebug() << "index of " << row << column << parent.data(Project::FilePathRole) << " is " << result.data(Project::FilePathRole);
214 QModelIndex FlatModel::parent(const QModelIndex &idx) const
216 QModelIndex parentIndex;
217 if (Node *node = nodeForIndex(idx)) {
218 FolderNode *parentNode = visibleFolderNode(node->parentFolderNode());
220 FolderNode *grandParentNode = visibleFolderNode(parentNode->parentFolderNode());
221 if (grandParentNode) {
222 QHash<FolderNode*, QList<Node*> >::const_iterator it = m_childNodes.constFind(grandParentNode);
223 if (it == m_childNodes.constEnd()) {
224 fetchMore(grandParentNode);
225 it = m_childNodes.constFind(grandParentNode);
227 Q_ASSERT(it != m_childNodes.constEnd());
228 const int row = it.value().indexOf(parentNode);
230 parentIndex = createIndex(row, 0, parentNode);
232 // top level node, parent is session
233 parentIndex = index(0, 0, QModelIndex());
238 // qDebug() << "parent of " << idx.data(Project::FilePathRole) << " is " << parentIndex.data(Project::FilePathRole);
243 QVariant FlatModel::data(const QModelIndex &index, int role) const
247 if (Node *node = nodeForIndex(index)) {
248 FolderNode *folderNode = qobject_cast<FolderNode*>(node);
250 case Qt::DisplayRole:
253 result = folderNode->displayName();
255 result = QFileInfo(node->path()).fileName(); //TODO cache that?
258 case Qt::ToolTipRole: {
259 result = QDir::toNativeSeparators(node->path());
262 case Qt::DecorationRole: {
264 result = folderNode->icon();
266 result = FileIconProvider::instance()->icon(QFileInfo(node->path()));
271 if (node == m_startupProject)
276 case ProjectExplorer::Project::FilePathRole: {
277 result = node->path();
286 Qt::ItemFlags FlatModel::flags(const QModelIndex &index) const
288 if (!index.isValid())
290 // We claim that everything is editable
291 // That's slightly wrong
292 // We control the only view, and that one does the checks
293 return Qt::ItemIsSelectable|Qt::ItemIsEnabled | Qt::ItemIsEditable;
296 bool FlatModel::setData(const QModelIndex &index, const QVariant &value, int role)
298 if (!index.isValid())
300 if (role != Qt::EditRole)
303 ProjectExplorerPlugin::instance()->renameFile(nodeForIndex(index), value.toString());
307 int FlatModel::rowCount(const QModelIndex &parent) const
310 if (!parent.isValid()) {
313 FolderNode *folderNode = qobject_cast<FolderNode*>(nodeForIndex(parent));
314 if (folderNode && m_childNodes.contains(folderNode))
315 rows = m_childNodes.value(folderNode).size();
320 int FlatModel::columnCount(const QModelIndex &/*parent*/) const
325 bool FlatModel::hasChildren(const QModelIndex &parent) const
327 if (!parent.isValid())
330 FolderNode *folderNode = qobject_cast<FolderNode*>(nodeForIndex(parent));
334 QHash<FolderNode*, QList<Node*> >::const_iterator it = m_childNodes.constFind(folderNode);
335 if (it == m_childNodes.constEnd()) {
336 fetchMore(folderNode);
337 it = m_childNodes.constFind(folderNode);
339 return !it.value().isEmpty();
342 bool FlatModel::canFetchMore(const QModelIndex & parent) const
344 if (!parent.isValid()) {
347 if (FolderNode *folderNode = qobject_cast<FolderNode*>(nodeForIndex(parent)))
348 return !m_childNodes.contains(folderNode);
354 void FlatModel::recursiveAddFolderNodes(FolderNode *startNode, QList<Node *> *list, const QSet<Node *> &blackList) const
356 foreach (FolderNode *folderNode, startNode->subFolderNodes()) {
357 if (folderNode && !blackList.contains(folderNode))
358 recursiveAddFolderNodesImpl(folderNode, list, blackList);
362 void FlatModel::recursiveAddFolderNodesImpl(FolderNode *startNode, QList<Node *> *list, const QSet<Node *> &blackList) const
364 if (!filter(startNode)) {
365 if (!blackList.contains(startNode))
366 list->append(startNode);
368 foreach (FolderNode *folderNode, startNode->subFolderNodes()) {
369 if (folderNode && !blackList.contains(folderNode))
370 recursiveAddFolderNodesImpl(folderNode, list, blackList);
375 void FlatModel::recursiveAddFileNodes(FolderNode *startNode, QList<Node *> *list, const QSet<Node *> &blackList) const
377 foreach (FolderNode *subFolderNode, startNode->subFolderNodes()) {
378 if (!blackList.contains(subFolderNode))
379 recursiveAddFileNodes(subFolderNode, list, blackList);
381 foreach (Node *node, startNode->fileNodes()) {
382 if (!blackList.contains(node) && !filter(node))
387 QList<Node*> FlatModel::childNodes(FolderNode *parentNode, const QSet<Node*> &blackList) const
389 QList<Node*> nodeList;
391 if (parentNode->nodeType() == SessionNodeType) {
392 SessionNode *sessionNode = static_cast<SessionNode*>(parentNode);
393 QList<ProjectNode*> projectList = sessionNode->projectNodes();
394 for (int i = 0; i < projectList.size(); ++i) {
395 if (!blackList.contains(projectList.at(i)))
396 nodeList << projectList.at(i);
399 recursiveAddFolderNodes(parentNode, &nodeList, blackList);
400 recursiveAddFileNodes(parentNode, &nodeList, blackList + nodeList.toSet());
402 qSort(nodeList.begin(), nodeList.end(), sortNodes);
406 void FlatModel::fetchMore(FolderNode *folderNode) const
408 Q_ASSERT(folderNode);
409 Q_ASSERT(!m_childNodes.contains(folderNode));
411 QList<Node*> nodeList = childNodes(folderNode);
412 m_childNodes.insert(folderNode, nodeList);
415 void FlatModel::fetchMore(const QModelIndex &parent)
417 FolderNode *folderNode = qobject_cast<FolderNode*>(nodeForIndex(parent));
418 Q_ASSERT(folderNode);
420 fetchMore(folderNode);
423 void FlatModel::setStartupProject(ProjectNode *projectNode)
425 if (m_startupProject != projectNode) {
426 QModelIndex oldIndex = (m_startupProject ? indexForNode(m_startupProject) : QModelIndex());
427 QModelIndex newIndex = (projectNode ? indexForNode(projectNode) : QModelIndex());
428 m_startupProject = projectNode;
429 if (oldIndex.isValid())
430 emit dataChanged(oldIndex, oldIndex);
431 if (newIndex.isValid())
432 emit dataChanged(newIndex, newIndex);
436 void FlatModel::reset()
438 m_childNodes.clear();
439 QAbstractItemModel::reset();
442 QModelIndex FlatModel::indexForNode(const Node *node_)
444 // We assume that we are only called for nodes that are represented
446 // we use non-const pointers internally
447 Node *node = const_cast<Node*>(node_);
449 return QModelIndex();
451 if (node == m_rootNode)
452 return createIndex(0, 0, m_rootNode);
454 FolderNode *parentNode = visibleFolderNode(node->parentFolderNode());
456 // Do we have the parent mapped?
457 QHash<FolderNode*, QList<Node*> >::const_iterator it = m_childNodes.constFind(parentNode);
458 if (it == m_childNodes.constEnd()) {
459 fetchMore(parentNode);
460 it = m_childNodes.constFind(parentNode);
462 if (it != m_childNodes.constEnd()) {
463 const int row = it.value().indexOf(node);
465 return createIndex(row, 0, node);
467 return QModelIndex();
470 void FlatModel::setProjectFilterEnabled(bool filter)
472 if (filter == m_filterProjects)
474 m_filterProjects = filter;
478 void FlatModel::setGeneratedFilesFilterEnabled(bool filter)
480 m_filterGeneratedFiles = filter;
484 bool FlatModel::projectFilterEnabled()
486 return m_filterProjects;
489 bool FlatModel::generatedFilesFilterEnabled()
491 return m_filterGeneratedFiles;
494 Node *FlatModel::nodeForIndex(const QModelIndex &index) const
497 return (Node*)index.internalPointer();
502 Returns the first folder node in the ancestors
503 for the given node that is not filtered
504 out by the Flat Model.
506 FolderNode *FlatModel::visibleFolderNode(FolderNode *node) const
511 for (FolderNode *folderNode = node;
513 folderNode = folderNode->parentFolderNode()) {
514 if (!filter(folderNode))
520 bool FlatModel::filter(Node *node) const
522 bool isHidden = false;
523 if (node->nodeType() == SessionNodeType) {
525 } else if (ProjectNode *projectNode = qobject_cast<ProjectNode*>(node)) {
526 if (m_filterProjects && projectNode->parentFolderNode() != m_rootNode)
527 isHidden = !projectNode->hasBuildTargets();
528 } else if (node->nodeType() == FolderNodeType) {
529 if (m_filterProjects)
531 } else if (FileNode *fileNode = qobject_cast<FileNode*>(node)) {
532 if (m_filterGeneratedFiles)
533 isHidden = fileNode->isGenerated();
538 bool isSorted(const QList<Node *> &nodes)
540 int size = nodes.size();
541 for (int i = 0; i < size -1; ++i) {
542 if (!sortNodes(nodes.at(i), nodes.at(i+1)))
548 /// slots and all the fun
549 void FlatModel::added(FolderNode* parentNode, const QList<Node*> &newNodeList)
551 QModelIndex parentIndex = indexForNode(parentNode);
553 QHash<FolderNode*, QList<Node*> >::const_iterator it = m_childNodes.constFind(parentNode);
554 if (it == m_childNodes.constEnd())
556 QList<Node *> oldNodeList = it.value();
558 // Compare lists and emit signals, and modify m_childNodes on the fly
559 QList<Node *>::const_iterator oldIter = oldNodeList.constBegin();
560 QList<Node *>::const_iterator newIter = newNodeList.constBegin();
562 Q_ASSERT(isSorted(oldNodeList));
563 Q_ASSERT(isSorted(newNodeList));
565 QSet<Node *> emptyDifference;
566 emptyDifference = oldNodeList.toSet();
567 emptyDifference.subtract(newNodeList.toSet());
568 if (!emptyDifference.isEmpty()) {
569 // This should not happen...
570 qDebug() << "FlatModel::added, old Node list should be subset of newNode list, found files in old list which were not part of new list";
571 foreach (Node *n, emptyDifference) {
577 // optimization, check for old list is empty
578 if (oldIter == oldNodeList.constEnd()) {
579 // New Node List is empty, nothing added which intrest us
580 if (newIter == newNodeList.constEnd())
582 // So all we need to do is easy
583 beginInsertRows(parentIndex, 0, newNodeList.size() - 1);
584 m_childNodes.insert(parentNode, newNodeList);
590 // Skip all that are the same
591 while (*oldIter == *newIter) {
594 if (oldIter == oldNodeList.constEnd()) {
595 // At end of oldNodeList, sweep up rest of newNodeList
596 QList<Node *>::const_iterator startOfBlock = newIter;
597 newIter = newNodeList.constEnd();
598 int pos = oldIter - oldNodeList.constBegin();
599 int count = newIter - startOfBlock;
601 beginInsertRows(parentIndex, pos, pos+count-1);
602 while (startOfBlock != newIter) {
603 oldNodeList.insert(pos, *startOfBlock);
607 m_childNodes.insert(parentNode, oldNodeList);
610 return; // Done with the lists, leave the function
614 QList<Node *>::const_iterator startOfBlock = newIter;
615 while (*oldIter != *newIter)
617 // startOfBlock is the first that was diffrent
618 // newIter points to the new position of oldIter
619 // newIter - startOfBlock is number of new items
620 // oldIter is the position where those are...
621 int pos = oldIter - oldNodeList.constBegin();
622 int count = newIter - startOfBlock;
623 beginInsertRows(parentIndex, pos, pos + count - 1);
624 while (startOfBlock != newIter) {
625 oldNodeList.insert(pos, *startOfBlock);
629 m_childNodes.insert(parentNode, oldNodeList);
631 oldIter = oldNodeList.constBegin() + pos;
635 void FlatModel::removed(FolderNode* parentNode, const QList<Node*> &newNodeList)
637 QModelIndex parentIndex = indexForNode(parentNode);
639 QHash<FolderNode*, QList<Node*> >::const_iterator it = m_childNodes.constFind(parentNode);
640 if (it == m_childNodes.constEnd())
643 QList<Node *> oldNodeList = it.value();
644 // Compare lists and emit signals, and modify m_childNodes on the fly
645 QList<Node *>::const_iterator oldIter = oldNodeList.constBegin();
646 QList<Node *>::const_iterator newIter = newNodeList.constBegin();
648 Q_ASSERT(isSorted(oldNodeList));
649 Q_ASSERT(isSorted(newNodeList));
651 QSet<Node *> emptyDifference;
652 emptyDifference = newNodeList.toSet();
653 emptyDifference.subtract(oldNodeList.toSet());
654 if (!emptyDifference.isEmpty()) {
655 // This should not happen...
656 qDebug() << "FlatModel::removed, new Node list should be subset of oldNode list, found files in new list which were not part of old list";
657 foreach (Node *n, emptyDifference) {
663 // optimization, check for new list is empty
664 if (newIter == newNodeList.constEnd()) {
665 // New Node List is empty, everything removed
666 if (oldIter == oldNodeList.constEnd())
668 // So all we need to do is easy
669 beginRemoveRows(parentIndex, 0, oldNodeList.size() - 1);
670 m_childNodes.insert(parentNode, newNodeList);
676 // Skip all that are the same
677 while (*oldIter == *newIter) {
680 if (newIter == newNodeList.constEnd()) {
681 // At end of newNodeList, sweep up rest of oldNodeList
682 QList<Node *>::const_iterator startOfBlock = oldIter;
683 oldIter = oldNodeList.constEnd();
684 int pos = startOfBlock - oldNodeList.constBegin();
685 int count = oldIter - startOfBlock;
687 beginRemoveRows(parentIndex, pos, pos+count-1);
688 while (startOfBlock != oldIter) {
690 oldNodeList.removeAt(pos);
693 m_childNodes.insert(parentNode, oldNodeList);
696 return; // Done with the lists, leave the function
700 QList<Node *>::const_iterator startOfBlock = oldIter;
701 while (*oldIter != *newIter)
703 // startOfBlock is the first that was diffrent
704 // oldIter points to the new position of newIter
705 // oldIter - startOfBlock is number of new items
706 // newIter is the position where those are...
707 int pos = startOfBlock - oldNodeList.constBegin();
708 int count = oldIter - startOfBlock;
709 beginRemoveRows(parentIndex, pos, pos + count - 1);
710 while (startOfBlock != oldIter) {
712 oldNodeList.removeAt(pos);
714 m_childNodes.insert(parentNode, oldNodeList);
716 oldIter = oldNodeList.constBegin() + pos;
720 void FlatModel::aboutToHasBuildTargetsChanged(ProjectExplorer::ProjectNode* node)
722 if (!m_filterProjects)
724 FolderNode *folder = visibleFolderNode(node->parentFolderNode());
725 QList<Node *> newNodeList = childNodes(folder, QSet<Node *>() << node);
726 removed(folder, newNodeList);
728 QList<Node *> staleFolders;
729 recursiveAddFolderNodesImpl(node, &staleFolders);
730 foreach (Node *n, staleFolders)
731 if (FolderNode *fn = qobject_cast<FolderNode *>(n))
732 m_childNodes.remove(fn);
735 void FlatModel::hasBuildTargetsChanged(ProjectExplorer::ProjectNode *node)
737 if (!m_filterProjects)
739 // we are only interested if we filter
740 FolderNode *folder = visibleFolderNode(node->parentFolderNode());
741 QList<Node *> newNodeList = childNodes(folder);
742 added(folder, newNodeList);
745 void FlatModel::foldersAboutToBeAdded(FolderNode *parentFolder, const QList<FolderNode*> &newFolders)
748 m_parentFolderForChange = parentFolder;
751 void FlatModel::foldersAdded()
753 // First found out what the folder is that we are adding the files to
754 FolderNode *folderNode = visibleFolderNode(m_parentFolderForChange);
756 // Now get the new list for that folder
757 QList<Node *> newNodeList = childNodes(folderNode);
759 added(folderNode, newNodeList);
762 void FlatModel::foldersAboutToBeRemoved(FolderNode *parentFolder, const QList<FolderNode*> &staleFolders)
764 QSet<Node *> blackList;
765 foreach (FolderNode *node, staleFolders)
766 blackList.insert(node);
768 FolderNode *folderNode = visibleFolderNode(parentFolder);
769 QList<Node *> newNodeList = childNodes(folderNode, blackList);
771 removed(folderNode, newNodeList);
772 removeFromCache(staleFolders);
775 void FlatModel::removeFromCache(QList<FolderNode *> list)
777 foreach (FolderNode *fn, list) {
778 removeFromCache(fn->subFolderNodes());
779 m_childNodes.remove(fn);
783 void FlatModel::foldersRemoved()
788 void FlatModel::filesAboutToBeAdded(FolderNode *folder, const QList<FileNode*> &newFiles)
791 m_parentFolderForChange = folder;
794 void FlatModel::filesAdded()
796 // First find out what the folder is that we are adding the files to
797 FolderNode *folderNode = visibleFolderNode(m_parentFolderForChange);
799 // Now get the new List for that folder
800 QList<Node *> newNodeList = childNodes(folderNode);
801 added(folderNode, newNodeList);
804 void FlatModel::filesAboutToBeRemoved(FolderNode *folder, const QList<FileNode*> &staleFiles)
806 // First found out what the folder (that is the project) is that we are adding the files to
807 FolderNode *folderNode = visibleFolderNode(folder);
809 QSet<Node *> blackList;
810 foreach(Node *node, staleFiles)
811 blackList.insert(node);
813 // Now get the new List for that folder
814 QList<Node *> newNodeList = childNodes(folderNode, blackList);
815 removed(folderNode, newNodeList);
818 void FlatModel::filesRemoved()