1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "documentmodel.h"
27 #include "documentmodel_p.h"
28
29 #include "ieditor.h"
30 #include <coreplugin/documentmanager.h>
31 #include <coreplugin/idocument.h>
32
33 #include <utils/algorithm.h>
34 #include <utils/dropsupport.h>
35 #include <utils/hostosinfo.h>
36 #include <utils/qtcassert.h>
37 #include <utils/utilsicons.h>
38
39 #include <QAbstractItemModel>
40 #include <QDir>
41 #include <QIcon>
42 #include <QMimeData>
43 #include <QSet>
44 #include <QUrl>
45
46 using namespace Utils;
47
48 static Core::Internal::DocumentModelPrivate *d;
49
50 namespace Core {
51 namespace Internal {
52
53 namespace {
compare(const DocumentModel::Entry * e1,const DocumentModel::Entry * e2)54 bool compare(const DocumentModel::Entry *e1, const DocumentModel::Entry *e2)
55 {
56 // Pinned files should go at the top.
57 if (e1->pinned != e2->pinned)
58 return e1->pinned;
59
60 const int cmp = e1->plainDisplayName().localeAwareCompare(e2->plainDisplayName());
61 return (cmp < 0) || (cmp == 0 && e1->fileName() < e2->fileName());
62 }
63
64 // Return a pair of indices. The first is the index that needs to be removed or -1 if no removal
65 // is necessary. The second is the index to add the entry into, or -1 if no addition is necessary.
66 // If the entry does not need to be moved, then (-1, -1) will be returned as no action is needed.
positionEntry(const QList<DocumentModel::Entry * > & list,DocumentModel::Entry * entry)67 std::pair<int, int> positionEntry(const QList<DocumentModel::Entry *> &list,
68 DocumentModel::Entry *entry)
69 {
70 const int to_remove = list.indexOf(entry);
71
72 const QList<DocumentModel::Entry *> toSort
73 = Utils::filtered(list, [entry](DocumentModel::Entry *e) { return e != entry; });
74
75 const auto begin = std::begin(toSort);
76 const auto end = std::end(toSort);
77 const auto to_insert
78 = static_cast<int>(std::distance(begin, std::lower_bound(begin, end, entry, &compare)));
79 if (to_remove == to_insert)
80 return std::make_pair(-1, -1);
81 return std::make_pair(to_remove, to_insert);
82 }
83 } // namespace
84
~DocumentModelPrivate()85 DocumentModelPrivate::~DocumentModelPrivate()
86 {
87 qDeleteAll(m_entries);
88 }
89
columnCount(const QModelIndex & parent) const90 int DocumentModelPrivate::columnCount(const QModelIndex &parent) const
91 {
92 if (!parent.isValid())
93 return 2;
94 return 0;
95 }
96
rowCount(const QModelIndex & parent) const97 int DocumentModelPrivate::rowCount(const QModelIndex &parent) const
98 {
99 if (!parent.isValid())
100 return m_entries.count() + 1/*<no document>*/;
101 return 0;
102 }
103
addEntry(DocumentModel::Entry * entry)104 void DocumentModelPrivate::addEntry(DocumentModel::Entry *entry)
105 {
106 const Utils::FilePath filePath = entry->fileName();
107
108 // replace a non-loaded entry (aka 'suspended') if possible
109 DocumentModel::Entry *previousEntry = DocumentModel::entryForFilePath(filePath);
110 if (previousEntry) {
111 const bool replace = !entry->isSuspended && previousEntry->isSuspended;
112 if (replace) {
113 previousEntry->isSuspended = false;
114 delete previousEntry->document;
115 previousEntry->document = entry->document;
116 connect(previousEntry->document, &IDocument::changed,
117 this, [this, document = previousEntry->document] { itemChanged(document); });
118 }
119 delete entry;
120 entry = nullptr;
121 disambiguateDisplayNames(previousEntry);
122 return;
123 }
124
125 auto positions = positionEntry(m_entries, entry);
126 // Do not remove anything (new entry), insert somewhere:
127 QTC_CHECK(positions.first == -1 && positions.second >= 0);
128
129 int row = positions.second + 1/*<no document>*/;
130 beginInsertRows(QModelIndex(), row, row);
131 m_entries.insert(positions.second, entry);
132 disambiguateDisplayNames(entry);
133 FilePath fixedPath = DocumentManager::filePathKey(filePath, DocumentManager::ResolveLinks);
134 if (!fixedPath.isEmpty())
135 m_entryByFixedPath[fixedPath] = entry;
136 connect(entry->document, &IDocument::changed, this, [this, document = entry->document] {
137 itemChanged(document);
138 });
139 endInsertRows();
140 }
141
disambiguateDisplayNames(DocumentModel::Entry * entry)142 bool DocumentModelPrivate::disambiguateDisplayNames(DocumentModel::Entry *entry)
143 {
144 const QString displayName = entry->plainDisplayName();
145 int minIdx = -1, maxIdx = -1;
146
147 QList<DynamicEntry> dups;
148
149 for (int i = 0, total = m_entries.count(); i < total; ++i) {
150 DocumentModel::Entry *e = m_entries.at(i);
151 if (e == entry || e->plainDisplayName() == displayName) {
152 e->document->setUniqueDisplayName(QString());
153 dups += DynamicEntry(e);
154 maxIdx = i;
155 if (minIdx < 0)
156 minIdx = i;
157 }
158 }
159
160 const int dupsCount = dups.count();
161 if (dupsCount == 0)
162 return false;
163
164 if (dupsCount > 1) {
165 int serial = 0;
166 int count = 0;
167 // increase uniqueness unless no dups are left
168 forever {
169 bool seenDups = false;
170 for (int i = 0; i < dupsCount - 1; ++i) {
171 DynamicEntry &e = dups[i];
172 const Utils::FilePath myFileName = e->document->filePath();
173 if (e->document->isTemporary() || myFileName.isEmpty() || count > 10) {
174 // path-less entry, append number
175 e.setNumberedName(++serial);
176 continue;
177 }
178 for (int j = i + 1; j < dupsCount; ++j) {
179 DynamicEntry &e2 = dups[j];
180 if (e->displayName().compare(e2->displayName(), Utils::HostOsInfo::fileNameCaseSensitivity()) == 0) {
181 const Utils::FilePath otherFileName = e2->document->filePath();
182 if (otherFileName.isEmpty())
183 continue;
184 seenDups = true;
185 e2.disambiguate();
186 if (j > maxIdx)
187 maxIdx = j;
188 }
189 }
190 if (seenDups) {
191 e.disambiguate();
192 ++count;
193 break;
194 }
195 }
196 if (!seenDups)
197 break;
198 }
199 }
200
201 emit dataChanged(index(minIdx + 1, 0), index(maxIdx + 1, 0));
202 return true;
203 }
204
setPinned(DocumentModel::Entry * entry,bool pinned)205 void DocumentModelPrivate::setPinned(DocumentModel::Entry *entry, bool pinned)
206 {
207 if (entry->pinned == pinned)
208 return;
209
210 entry->pinned = pinned;
211 // Ensure that this entry is re-sorted in the list of open documents
212 // now that its pinned state has changed.
213 d->itemChanged(entry->document);
214 }
215
lockedIcon()216 QIcon DocumentModelPrivate::lockedIcon()
217 {
218 const static QIcon icon = Utils::Icons::LOCKED.icon();
219 return icon;
220 }
221
pinnedIcon()222 QIcon DocumentModelPrivate::pinnedIcon()
223 {
224 const static QIcon icon = Utils::Icons::PINNED.icon();
225 return icon;
226 }
227
indexOfFilePath(const Utils::FilePath & filePath) const228 Utils::optional<int> DocumentModelPrivate::indexOfFilePath(const Utils::FilePath &filePath) const
229 {
230 if (filePath.isEmpty())
231 return Utils::nullopt;
232 const FilePath fixedPath = DocumentManager::filePathKey(filePath, DocumentManager::ResolveLinks);
233 const int index = m_entries.indexOf(m_entryByFixedPath.value(fixedPath));
234 if (index < 0)
235 return Utils::nullopt;
236 return index;
237 }
238
removeDocument(int idx)239 void DocumentModelPrivate::removeDocument(int idx)
240 {
241 if (idx < 0)
242 return;
243 QTC_ASSERT(idx < m_entries.size(), return);
244 int row = idx + 1/*<no document>*/;
245 beginRemoveRows(QModelIndex(), row, row);
246 DocumentModel::Entry *entry = m_entries.takeAt(idx);
247 endRemoveRows();
248
249 const FilePath fixedPath = DocumentManager::filePathKey(entry->fileName(),
250 DocumentManager::ResolveLinks);
251 if (!fixedPath.isEmpty())
252 m_entryByFixedPath.remove(fixedPath);
253 disconnect(entry->document, &IDocument::changed, this, nullptr);
254 disambiguateDisplayNames(entry);
255 delete entry;
256 }
257
indexOfDocument(IDocument * document) const258 Utils::optional<int> DocumentModelPrivate::indexOfDocument(IDocument *document) const
259 {
260 const int index = Utils::indexOf(m_entries, [&document](DocumentModel::Entry *entry) {
261 return entry->document == document;
262 });
263 if (index < 0)
264 return Utils::nullopt;
265 return index;
266 }
267
flags(const QModelIndex & index) const268 Qt::ItemFlags DocumentModelPrivate::flags(const QModelIndex &index) const
269 {
270 const DocumentModel::Entry *e = DocumentModel::entryAtRow(index.row());
271 if (!e || e->fileName().isEmpty())
272 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
273 return Qt::ItemIsDragEnabled | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
274 }
275
mimeData(const QModelIndexList & indexes) const276 QMimeData *DocumentModelPrivate::mimeData(const QModelIndexList &indexes) const
277 {
278 auto data = new Utils::DropMimeData;
279 foreach (const QModelIndex &index, indexes) {
280 const DocumentModel::Entry *e = DocumentModel::entryAtRow(index.row());
281 if (!e || e->fileName().isEmpty())
282 continue;
283 data->addFile(e->fileName().toString());
284 }
285 return data;
286 }
287
index(int row,int column,const QModelIndex & parent) const288 QModelIndex DocumentModelPrivate::index(int row, int column, const QModelIndex &parent) const
289 {
290 Q_UNUSED(parent)
291 if (column < 0 || column > 1 || row < 0 || row >= m_entries.count() + 1/*<no document>*/)
292 return QModelIndex();
293 return createIndex(row, column);
294 }
295
supportedDragActions() const296 Qt::DropActions DocumentModelPrivate::supportedDragActions() const
297 {
298 return Qt::MoveAction;
299 }
300
mimeTypes() const301 QStringList DocumentModelPrivate::mimeTypes() const
302 {
303 return Utils::DropSupport::mimeTypesForFilePaths();
304 }
305
data(const QModelIndex & index,int role) const306 QVariant DocumentModelPrivate::data(const QModelIndex &index, int role) const
307 {
308 if (!index.isValid() || (index.column() != 0 && role < Qt::UserRole))
309 return QVariant();
310 const DocumentModel::Entry *entry = DocumentModel::entryAtRow(index.row());
311 if (!entry) {
312 // <no document> entry
313 switch (role) {
314 case Qt::DisplayRole:
315 return tr("<no document>");
316 case Qt::ToolTipRole:
317 return tr("No document is selected.");
318 default:
319 return QVariant();
320 }
321 }
322 switch (role) {
323 case Qt::DisplayRole: {
324 QString name = entry->displayName();
325 if (entry->document->isModified())
326 name += QLatin1Char('*');
327 return name;
328 }
329 case Qt::DecorationRole:
330 if (entry->document->isFileReadOnly())
331 return lockedIcon();
332 if (entry->pinned)
333 return pinnedIcon();
334 return QVariant();
335 case Qt::ToolTipRole:
336 return entry->fileName().isEmpty() ? entry->displayName() : entry->fileName().toUserOutput();
337 default:
338 break;
339 }
340 return QVariant();
341 }
342
itemChanged(IDocument * document)343 void DocumentModelPrivate::itemChanged(IDocument *document)
344 {
345 const Utils::optional<int> idx = indexOfDocument(document);
346 if (!idx)
347 return;
348 const FilePath fixedPath = DocumentManager::filePathKey(document->filePath(),
349 DocumentManager::ResolveLinks);
350 DocumentModel::Entry *entry = m_entries.at(idx.value());
351 bool found = false;
352 // The entry's fileName might have changed, so find the previous fileName that was associated
353 // with it and remove it, then add the new fileName.
354 for (auto it = m_entryByFixedPath.begin(), end = m_entryByFixedPath.end(); it != end; ++it) {
355 if (it.value() == entry) {
356 found = true;
357 if (it.key() != fixedPath) {
358 m_entryByFixedPath.remove(it.key());
359 if (!fixedPath.isEmpty())
360 m_entryByFixedPath[fixedPath] = entry;
361 }
362 break;
363 }
364 }
365 if (!found && !fixedPath.isEmpty())
366 m_entryByFixedPath[fixedPath] = entry;
367
368 if (!disambiguateDisplayNames(m_entries.at(idx.value()))) {
369 QModelIndex mindex = index(idx.value() + 1/*<no document>*/, 0);
370 emit dataChanged(mindex, mindex);
371 }
372
373 // Make sure the entries stay sorted:
374 auto positions = positionEntry(m_entries, entry);
375 if (positions.first >= 0 && positions.second >= 0) {
376 // Entry did move: update its position.
377
378 // Account for the <no document> entry.
379 static const int noDocumentEntryOffset = 1;
380 const int fromIndex = positions.first + noDocumentEntryOffset;
381 const int toIndex = positions.second + noDocumentEntryOffset;
382 // Account for the weird requirements of beginMoveRows().
383 const int effectiveToIndex = toIndex > fromIndex ? toIndex + 1 : toIndex;
384 beginMoveRows(QModelIndex(), fromIndex, fromIndex, QModelIndex(), effectiveToIndex);
385
386 m_entries.move(fromIndex - 1, toIndex - 1);
387
388 endMoveRows();
389 } else {
390 // Nothing to remove or add: The entry did not move.
391 QTC_CHECK(positions.first == -1 && positions.second == -1);
392 }
393 }
394
addEditor(IEditor * editor,bool * isNewDocument)395 void DocumentModelPrivate::addEditor(IEditor *editor, bool *isNewDocument)
396 {
397 if (!editor)
398 return;
399
400 QList<IEditor *> &editorList = d->m_editors[editor->document()];
401 bool isNew = editorList.isEmpty();
402 if (isNewDocument)
403 *isNewDocument = isNew;
404 editorList << editor;
405 if (isNew) {
406 auto entry = new DocumentModel::Entry;
407 entry->document = editor->document();
408 d->addEntry(entry);
409 }
410 }
411
412 /*!
413 \class Core::DocumentModel
414 \inmodule QtCreator
415 \internal
416 */
417
418 /*!
419 \class Core::DocumentModel::Entry
420 \inmodule QtCreator
421 \internal
422 */
423
addSuspendedDocument(const QString & fileName,const QString & displayName,Id id)424 DocumentModel::Entry *DocumentModelPrivate::addSuspendedDocument(const QString &fileName,
425 const QString &displayName,
426 Id id)
427 {
428 QTC_CHECK(id.isValid());
429 auto entry = new DocumentModel::Entry;
430 entry->document = new IDocument;
431 entry->document->setFilePath(Utils::FilePath::fromString(fileName));
432 if (!displayName.isEmpty())
433 entry->document->setPreferredDisplayName(displayName);
434 entry->document->setId(id);
435 entry->isSuspended = true;
436 d->addEntry(entry);
437 return entry;
438 }
439
firstSuspendedEntry()440 DocumentModel::Entry *DocumentModelPrivate::firstSuspendedEntry()
441 {
442 return Utils::findOrDefault(d->m_entries, [](DocumentModel::Entry *entry) { return entry->isSuspended; });
443 }
444
445 /*!
446 Removes an editor from the list of open editors for its entry. If the editor is the last
447 one, the entry is put into suspended state.
448 Returns the affected entry.
449 */
removeEditor(IEditor * editor)450 DocumentModel::Entry *DocumentModelPrivate::removeEditor(IEditor *editor)
451 {
452 QTC_ASSERT(editor, return nullptr);
453 IDocument *document = editor->document();
454 QTC_ASSERT(d->m_editors.contains(document), return nullptr);
455 d->m_editors[document].removeAll(editor);
456 DocumentModel::Entry *entry = DocumentModel::entryForDocument(document);
457 QTC_ASSERT(entry, return nullptr);
458 if (d->m_editors.value(document).isEmpty()) {
459 d->m_editors.remove(document);
460 entry->document = new IDocument;
461 entry->document->setFilePath(document->filePath());
462 entry->document->setPreferredDisplayName(document->preferredDisplayName());
463 entry->document->setUniqueDisplayName(document->uniqueDisplayName());
464 entry->document->setId(document->id());
465 entry->isSuspended = true;
466 }
467 return entry;
468 }
469
removeEntry(DocumentModel::Entry * entry)470 void DocumentModelPrivate::removeEntry(DocumentModel::Entry *entry)
471 {
472 // For non suspended entries, we wouldn't know what to do with the associated editors
473 QTC_ASSERT(entry->isSuspended, return);
474 int index = d->m_entries.indexOf(entry);
475 d->removeDocument(index);
476 }
477
removeAllSuspendedEntries(PinnedFileRemovalPolicy pinnedFileRemovalPolicy)478 void DocumentModelPrivate::removeAllSuspendedEntries(PinnedFileRemovalPolicy pinnedFileRemovalPolicy)
479 {
480 for (int i = d->m_entries.count()-1; i >= 0; --i) {
481 const DocumentModel::Entry *entry = d->m_entries.at(i);
482 if (!entry->isSuspended)
483 continue;
484
485 if (pinnedFileRemovalPolicy == DoNotRemovePinnedFiles && entry->pinned)
486 continue;
487
488 int row = i + 1/*<no document>*/;
489 d->beginRemoveRows(QModelIndex(), row, row);
490 delete d->m_entries.takeAt(i);
491 d->endRemoveRows();
492 }
493 QSet<QString> displayNames;
494 foreach (DocumentModel::Entry *entry, d->m_entries) {
495 const QString displayName = entry->plainDisplayName();
496 if (displayNames.contains(displayName))
497 continue;
498 displayNames.insert(displayName);
499 d->disambiguateDisplayNames(entry);
500 }
501 }
502
DynamicEntry(DocumentModel::Entry * e)503 DocumentModelPrivate::DynamicEntry::DynamicEntry(DocumentModel::Entry *e) :
504 entry(e),
505 pathComponents(0)
506 {
507 }
508
operator ->() const509 DocumentModel::Entry *DocumentModelPrivate::DynamicEntry::operator->() const
510 {
511 return entry;
512 }
513
disambiguate()514 void DocumentModelPrivate::DynamicEntry::disambiguate()
515 {
516 const QString display = entry->fileName().fileNameWithPathComponents(++pathComponents);
517 entry->document->setUniqueDisplayName(display);
518 }
519
setNumberedName(int number)520 void DocumentModelPrivate::DynamicEntry::setNumberedName(int number)
521 {
522 entry->document->setUniqueDisplayName(QStringLiteral("%1 (%2)")
523 .arg(entry->document->displayName())
524 .arg(number));
525 }
526
527 } // Internal
528
Entry()529 DocumentModel::Entry::Entry() :
530 document(nullptr),
531 isSuspended(false),
532 pinned(false)
533 {
534 }
535
~Entry()536 DocumentModel::Entry::~Entry()
537 {
538 if (isSuspended)
539 delete document;
540 }
541
542 DocumentModel::DocumentModel() = default;
543
init()544 void DocumentModel::init()
545 {
546 d = new Internal::DocumentModelPrivate;
547 }
548
destroy()549 void DocumentModel::destroy()
550 {
551 delete d;
552 }
553
lockedIcon()554 QIcon DocumentModel::lockedIcon()
555 {
556 return Internal::DocumentModelPrivate::lockedIcon();
557 }
558
model()559 QAbstractItemModel *DocumentModel::model()
560 {
561 return d;
562 }
563
fileName() const564 Utils::FilePath DocumentModel::Entry::fileName() const
565 {
566 return document->filePath();
567 }
568
displayName() const569 QString DocumentModel::Entry::displayName() const
570 {
571 return document->displayName();
572 }
573
plainDisplayName() const574 QString DocumentModel::Entry::plainDisplayName() const
575 {
576 return document->plainDisplayName();
577 }
578
id() const579 Id DocumentModel::Entry::id() const
580 {
581 return document->id();
582 }
583
editorsForDocument(IDocument * document)584 QList<IEditor *> DocumentModel::editorsForDocument(IDocument *document)
585 {
586 return d->m_editors.value(document);
587 }
588
editorsForOpenedDocuments()589 QList<IEditor *> DocumentModel::editorsForOpenedDocuments()
590 {
591 return editorsForDocuments(openedDocuments());
592 }
593
editorsForDocuments(const QList<IDocument * > & documents)594 QList<IEditor *> DocumentModel::editorsForDocuments(const QList<IDocument *> &documents)
595 {
596 QList<IEditor *> result;
597 foreach (IDocument *document, documents)
598 result += d->m_editors.value(document);
599 return result;
600 }
601
indexOfDocument(IDocument * document)602 Utils::optional<int> DocumentModel::indexOfDocument(IDocument *document)
603 {
604 return d->indexOfDocument(document);
605 }
606
indexOfFilePath(const Utils::FilePath & filePath)607 Utils::optional<int> DocumentModel::indexOfFilePath(const Utils::FilePath &filePath)
608 {
609 return d->indexOfFilePath(filePath);
610 }
611
entryForDocument(IDocument * document)612 DocumentModel::Entry *DocumentModel::entryForDocument(IDocument *document)
613 {
614 return Utils::findOrDefault(d->m_entries,
615 [&document](Entry *entry) { return entry->document == document; });
616 }
617
entryForFilePath(const Utils::FilePath & filePath)618 DocumentModel::Entry *DocumentModel::entryForFilePath(const Utils::FilePath &filePath)
619 {
620 const Utils::optional<int> index = d->indexOfFilePath(filePath);
621 if (!index)
622 return nullptr;
623 return d->m_entries.at(*index);
624 }
625
openedDocuments()626 QList<IDocument *> DocumentModel::openedDocuments()
627 {
628 return d->m_editors.keys();
629 }
630
documentForFilePath(const Utils::FilePath & filePath)631 IDocument *DocumentModel::documentForFilePath(const Utils::FilePath &filePath)
632 {
633 const Utils::optional<int> index = d->indexOfFilePath(filePath);
634 if (!index)
635 return nullptr;
636 return d->m_entries.at(*index)->document;
637 }
638
editorsForFilePath(const Utils::FilePath & filePath)639 QList<IEditor *> DocumentModel::editorsForFilePath(const Utils::FilePath &filePath)
640 {
641 IDocument *document = documentForFilePath(filePath);
642 if (document)
643 return editorsForDocument(document);
644 return QList<IEditor *>();
645 }
646
entryAtRow(int row)647 DocumentModel::Entry *DocumentModel::entryAtRow(int row)
648 {
649 int entryIndex = row - 1/*<no document>*/;
650 if (entryIndex < 0)
651 return nullptr;
652 return d->m_entries[entryIndex];
653 }
654
entryCount()655 int DocumentModel::entryCount()
656 {
657 return d->m_entries.count();
658 }
659
rowOfDocument(IDocument * document)660 Utils::optional<int> DocumentModel::rowOfDocument(IDocument *document)
661 {
662 if (!document)
663 return 0 /*<no document>*/;
664 const Utils::optional<int> index = indexOfDocument(document);
665 if (index)
666 return *index + 1/*correction for <no document>*/;
667 return Utils::nullopt;
668 }
669
entries()670 QList<DocumentModel::Entry *> DocumentModel::entries()
671 {
672 return d->m_entries;
673 }
674
675 } // namespace Core
676