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 "idocument.h"
27 
28 #include <utils/fileutils.h>
29 #include <utils/infobar.h>
30 #include <utils/optional.h>
31 #include <utils/qtcassert.h>
32 
33 #include <QFile>
34 #include <QFileInfo>
35 
36 /*!
37     \class Core::IDocument
38     \inheaderfile coreplugin/idocument.h
39     \inmodule QtCreator
40 
41     \brief The IDocument class describes a document that can be saved and
42     reloaded.
43 
44     The class has two use cases.
45 
46     \section1 Handling External Modifications
47 
48     You can implement IDocument and register instances in DocumentManager to
49     let it handle external modifications of a file. When the file specified with
50     filePath() has changed externally, the DocumentManager asks the
51     corresponding IDocument instance what to do via reloadBehavior(). If that
52     returns \c IDocument::BehaviorAsk, the user is asked if the file should be
53     reloaded from disk. If the user requests the reload or reloadBehavior()
54     returns \c IDocument::BehaviorSilent, the DocumentManager calls reload()
55     to initiate a reload of the file from disk.
56 
57     Core functions: setFilePath(), reload(), reloadBehavior().
58 
59     If the content of the document can change in \QC, diverging from the
60     content on disk: isModified(), save(), isSaveAsAllowed(),
61     fallbackSaveAsPath(), fallbackSaveAsFileName().
62 
63     \section1 Editor Document
64 
65     The most common use case for implementing an IDocument subclass is as a
66     document for an IEditor implementation. Multiple editor instances can work
67     on the same document instance, for example if the document is visible in
68     multiple splits simultaneously. So the IDocument subclass should hold all
69     data that is independent from the specific IEditor instance, for example
70     the content and highlighting information.
71 
72     Each IDocument subclass is only required to work with the corresponding
73     IEditor subclasses that it was designed to work with.
74 
75     An IDocument can either be backed by a file, or solely represent some data
76     in memory. Documents backed by a file are automatically added to the
77     DocumentManager by the EditorManager.
78 
79     Core functions: setId(), isModified(), contents(), setContents().
80 
81     If the content of the document is backed by a file: open(), save(),
82     setFilePath(), mimeType(), shouldAutoSave(), setSuspendAllowed(), and
83     everything from \l{Handling External Modifications}.
84 
85     If the content of the document is not backed by a file:
86     setPreferredDisplayName(), setTemporary().
87 
88     \ingroup mainclasses
89 */
90 
91 /*!
92     \enum IDocument::OpenResult
93 
94     The OpenResult enum describes whether a file was successfully opened.
95 
96     \value Success
97            The file was read successfully and can be handled by this document
98            type.
99     \value ReadError
100            The file could not be opened for reading, either because it does not
101            exist or because of missing permissions.
102     \value CannotHandle
103            This document type could not handle the file content.
104 */
105 
106 /*!
107     \enum IDocument::ReloadSetting
108 
109     \internal
110 */
111 
112 /*!
113     \enum IDocument::ChangeTrigger
114 
115     The ChangeTrigger enum describes whether a file was changed from \QC
116     internally or from the outside.
117 
118     \value TriggerInternal
119            The file was changed by \QC.
120     \value TriggerExternal
121            The file was changed from the outside.
122 
123     \sa IDocument::reloadBehavior()
124 */
125 
126 /*!
127     \enum IDocument::ChangeType
128 
129     The ChangeType enum describes the way in which the file changed.
130 
131     \value TypeContents
132            The contents of the file changed.
133     \value TypeRemoved
134            The file was removed.
135 
136     \sa IDocument::reloadBehavior()
137     \sa IDocument::reload()
138 */
139 
140 /*!
141     \enum IDocument::ReloadFlag
142 
143     The ReloadFlag enum describes if a file should be reloaded from disk.
144 
145     \value FlagReload
146            The file should be reloaded.
147     \value FlagIgnore
148            The file should not be reloaded, but the document state should
149            reflect the change.
150 
151     \sa IDocument::reload()
152 */
153 
154 /*!
155     \fn Core::IDocument::changed()
156 
157     This signal is emitted when the document's meta data, like file name or
158     modified state, changes.
159 
160     \sa isModified()
161     \sa filePath()
162     \sa displayName()
163 */
164 
165 /*!
166     \fn Core::IDocument::contentsChanged()
167 
168     This signal is emitted when the document's content changes.
169 
170     \sa contents()
171 */
172 
173 /*!
174     \fn Core::IDocument::mimeTypeChanged()
175 
176     This signal is emitted when the document content's MIME type changes.
177 
178     \sa mimeType()
179 */
180 
181 /*!
182     \fn Core::IDocument::aboutToReload()
183 
184     This signal is emitted before the document is reloaded from the backing
185     file.
186 
187     \sa reload()
188 */
189 
190 /*!
191     \fn Core::IDocument::reloadFinished(bool success)
192 
193     This signal is emitted after the document is reloaded from the backing
194     file, or if reloading failed.
195 
196     The success state is passed in \a success.
197 
198     \sa reload()
199 */
200 
201 /*!
202     \fn Core::IDocument::filePathChanged(const Utils::FilePath &oldName, const Utils::FilePath &newName)
203 
204     This signal is emitted after the file path changed from \a oldName to \a
205     newName.
206 
207     \sa filePath()
208 */
209 
210 using namespace Utils;
211 
212 namespace Core {
213 namespace Internal {
214 
215 class IDocumentPrivate
216 {
217 public:
~IDocumentPrivate()218     ~IDocumentPrivate()
219     {
220         delete infoBar;
221     }
222 
223     QString mimeType;
224     Utils::FilePath filePath;
225     QString preferredDisplayName;
226     QString uniqueDisplayName;
227     Utils::FilePath autoSavePath;
228     Utils::InfoBar *infoBar = nullptr;
229     Id id;
230     optional<bool> fileIsReadOnly;
231     bool temporary = false;
232     bool hasWriteWarning = false;
233     bool restored = false;
234     bool isSuspendAllowed = false;
235 };
236 
237 } // namespace Internal
238 
239 /*!
240     Creates an IDocument with \a parent.
241 
242     \note Using the \a parent for ownership of the document is generally a
243     bad idea if the IDocument is intended for use with IEditor. It is better to
244     use shared ownership in that case.
245 */
IDocument(QObject * parent)246 IDocument::IDocument(QObject *parent) : QObject(parent),
247     d(new Internal::IDocumentPrivate)
248 {
249 }
250 
251 /*!
252     Destroys the IDocument.
253     If there was an auto save file for this document, it is removed.
254 
255     \sa shouldAutoSave()
256 */
~IDocument()257 IDocument::~IDocument()
258 {
259     removeAutoSaveFile();
260     delete d;
261 }
262 
263 /*!
264     \fn void IDocument::setId(Utils::Id id)
265 
266     Sets the ID for this document type to \a id. This is coupled with the
267     corresponding IEditor implementation and the \l{IEditorFactory::id()}{id()}
268     of the IEditorFactory. If the IDocument implementation only works with a
269     single IEditor type, this is preferably set in the IDocuments's
270     constructor.
271 
272     \sa id()
273 */
setId(Id id)274 void IDocument::setId(Id id)
275 {
276     d->id = id;
277 }
278 
279 /*!
280     Returns the ID for this document type.
281 
282     \sa setId()
283 */
id() const284 Id IDocument::id() const
285 {
286     QTC_CHECK(d->id.isValid());
287     return d->id;
288 }
289 
290 /*!
291     The open() method is used to load the contents of a file when a document is
292     opened in an editor.
293 
294     If the document is opened from an auto save file, \a realFilePath is the
295     name of the auto save file that should be loaded, and \a filePath is the
296     file name of the resulting file. In that case, the contents of the auto
297     save file should be loaded, the file name of the IDocument should be set to
298     \a filePath, and the document state be set to modified.
299 
300     If the editor is opened from a regular file, \a filePath and \a
301     filePath are the same.
302 
303     Use \a errorString to return an error message if this document cannot
304     handle the file contents.
305 
306     Returns whether the file was opened and read successfully.
307 
308     The default implementation does nothing and returns
309     CannotHandle.
310 
311     \sa EditorManager::openEditor()
312     \sa shouldAutoSave()
313     \sa setFilePath()
314 */
open(QString * errorString,const Utils::FilePath & filePath,const Utils::FilePath & realFilePath)315 IDocument::OpenResult IDocument::open(QString *errorString, const Utils::FilePath &filePath, const Utils::FilePath &realFilePath)
316 {
317     Q_UNUSED(errorString)
318     Q_UNUSED(filePath)
319     Q_UNUSED(realFilePath)
320     return OpenResult::CannotHandle;
321 }
322 
323 /*!
324     Saves the contents of the document to the \a filePath on disk.
325 
326     If \a autoSave is \c true, the saving is done for an auto-save, so the
327     document should avoid cleanups or other operations that it does for
328     user-requested saves.
329 
330     Use \a errorString to return an error message if saving failed.
331 
332     Returns whether saving was successful.
333 
334     The default implementation does nothing and returns \c false.
335 
336     \sa shouldAutoSave()
337 */
save(QString * errorString,const Utils::FilePath & filePath,bool autoSave)338 bool IDocument::save(QString *errorString, const Utils::FilePath &filePath, bool autoSave)
339 {
340     Q_UNUSED(errorString)
341     Q_UNUSED(filePath)
342     Q_UNUSED(autoSave)
343     return false;
344 }
345 
346 /*!
347     Returns the current contents of the document. The default implementation
348     returns an empty QByteArray.
349 
350     \sa setContents()
351     \sa contentsChanged()
352 */
contents() const353 QByteArray IDocument::contents() const
354 {
355     return QByteArray();
356 }
357 
358 /*!
359     The setContents() method is for example used by
360     EditorManager::openEditorWithContents() to set the \a contents of this
361     document.
362 
363     Returns whether setting the contents was successful.
364 
365     The default implementation does nothing and returns false.
366 
367     \sa contents()
368     \sa EditorManager::openEditorWithContents()
369 */
setContents(const QByteArray & contents)370 bool IDocument::setContents(const QByteArray &contents)
371 {
372     Q_UNUSED(contents)
373     return false;
374 }
375 
376 /*!
377     Returns the absolute path of the file that this document refers to. May be
378     empty for documents that are not backed by a file.
379 
380     \sa setFilePath()
381 */
filePath() const382 const Utils::FilePath &IDocument::filePath() const
383 {
384     return d->filePath;
385 }
386 
387 /*!
388     The reloadBehavior() method is used by the DocumentManager to ask what to
389     do if the file backing this document has changed on disk.
390 
391     The \a trigger specifies if the change was triggered by some operation in
392     \QC. The \a type specifies if the file changed permissions or contents, or
393     was removed completely.
394 
395     Returns whether the user should be asked or the document should be
396     reloaded silently.
397 
398     The default implementation requests a silent reload if only the permissions
399     changed or if the contents have changed but the \a trigger is internal and
400     the document is not modified.
401 
402     \sa isModified()
403 */
reloadBehavior(ChangeTrigger trigger,ChangeType type) const404 IDocument::ReloadBehavior IDocument::reloadBehavior(ChangeTrigger trigger, ChangeType type) const
405 {
406     if (type == TypeContents && trigger == TriggerInternal && !isModified())
407         return BehaviorSilent;
408     return BehaviorAsk;
409 }
410 
411 /*!
412     Reloads the document from the backing file when that changed on disk.
413 
414     If \a flag is FlagIgnore the file should not actually be loaded, but the
415     document should reflect the change in its \l{isModified()}{modified state}.
416 
417     The \a type specifies whether only the file permissions changed or if the
418     contents of the file changed.
419 
420     Use \a errorString to return an error message, if this document cannot
421     handle the file contents.
422 
423     Returns if the file was reloaded successfully.
424 
425     The default implementation does nothing and returns \c true.
426 
427     Subclasses should emit aboutToReload() before, and reloadFinished() after
428     reloading the file.
429 
430     \sa isModified()
431     \sa aboutToReload()
432     \sa reloadFinished()
433     \sa changed()
434 */
reload(QString * errorString,ReloadFlag flag,ChangeType type)435 bool IDocument::reload(QString *errorString, ReloadFlag flag, ChangeType type)
436 {
437     Q_UNUSED(errorString)
438     Q_UNUSED(flag)
439     Q_UNUSED(type)
440     return true;
441 }
442 
443 /*!
444     Updates the cached information about the read-only status of the backing file.
445 */
checkPermissions()446 void IDocument::checkPermissions()
447 {
448     bool previousReadOnly = d->fileIsReadOnly.value_or(false);
449     if (!filePath().isEmpty()) {
450         d->fileIsReadOnly = !filePath().toFileInfo().isWritable();
451     } else {
452         d->fileIsReadOnly = false;
453     }
454     if (previousReadOnly != *(d->fileIsReadOnly))
455         emit changed();
456 }
457 
458 /*!
459     Returns whether the document should automatically be saved at a user-defined
460     interval.
461 
462     The default implementation returns \c false.
463 */
shouldAutoSave() const464 bool IDocument::shouldAutoSave() const
465 {
466     return false;
467 }
468 
469 /*!
470     Returns whether the document has been modified after it was loaded from a
471     file.
472 
473     The default implementation returns \c false. Re-implementations should emit
474     changed() when this property changes.
475 
476     \sa changed()
477 */
isModified() const478 bool IDocument::isModified() const
479 {
480     return false;
481 }
482 
483 /*!
484     Returns whether the document may be saved under a different file name.
485 
486     The default implementation returns \c false.
487 
488     \sa save()
489 */
isSaveAsAllowed() const490 bool IDocument::isSaveAsAllowed() const
491 {
492     return false;
493 }
494 
495 /*!
496     Returns whether the document may be suspended.
497 
498     The EditorManager can automatically suspend editors and its corresponding
499     documents if the document is backed by a file, is not modified, and is not
500     temporary. Suspended IEditor and IDocument instances are deleted and
501     removed from memory, but are still visually accessible as if the document
502     was still opened in \QC.
503 
504     The default is \c false.
505 
506     \sa setSuspendAllowed()
507     \sa isModified()
508     \sa isTemporary()
509 */
isSuspendAllowed() const510 bool IDocument::isSuspendAllowed() const
511 {
512     return d->isSuspendAllowed;
513 }
514 
515 /*!
516     Sets whether the document may be suspended to \a value.
517 
518     \sa isSuspendAllowed()
519 */
setSuspendAllowed(bool value)520 void IDocument::setSuspendAllowed(bool value)
521 {
522     d->isSuspendAllowed = value;
523 }
524 
525 /*!
526     Returns whether the file backing this document is read-only, or \c false if
527     the document is not backed by a file.
528 */
isFileReadOnly() const529 bool IDocument::isFileReadOnly() const
530 {
531     if (filePath().isEmpty())
532         return false;
533     if (!d->fileIsReadOnly)
534         const_cast<IDocument *>(this)->checkPermissions();
535     return d->fileIsReadOnly.value_or(false);
536 }
537 
538 /*!
539     Returns if the document is temporary, and should for example not be
540     considered when saving or restoring the session state, or added to the recent
541     files list.
542 
543     The default is \c false.
544 
545     \sa setTemporary()
546 */
isTemporary() const547 bool IDocument::isTemporary() const
548 {
549     return d->temporary;
550 }
551 
552 /*!
553     Sets whether the document is \a temporary.
554 
555     \sa isTemporary()
556 */
setTemporary(bool temporary)557 void IDocument::setTemporary(bool temporary)
558 {
559     d->temporary = temporary;
560 }
561 
562 /*!
563     Returns a path to use for the \uicontrol{Save As} file dialog in case the
564     document is not backed by a file.
565 
566     \sa fallbackSaveAsFileName()
567 */
fallbackSaveAsPath() const568 QString IDocument::fallbackSaveAsPath() const
569 {
570     return QString();
571 }
572 
573 /*!
574     Returns a file name to use for the \uicontrol{Save As} file dialog in case
575     the document is not backed by a file.
576 
577     \sa fallbackSaveAsPath()
578 */
fallbackSaveAsFileName() const579 QString IDocument::fallbackSaveAsFileName() const
580 {
581     return QString();
582 }
583 
584 /*!
585     Returns the MIME type of the document content, if applicable.
586 
587     Subclasses should set this with setMimeType() after setting or loading
588     content.
589 
590     The default MIME type is empty.
591 
592     \sa setMimeType()
593     \sa mimeTypeChanged()
594 */
mimeType() const595 QString IDocument::mimeType() const
596 {
597     return d->mimeType;
598 }
599 
600 /*!
601     Sets the MIME type of the document content to \a mimeType.
602 
603     \sa mimeType()
604 */
setMimeType(const QString & mimeType)605 void IDocument::setMimeType(const QString &mimeType)
606 {
607     if (d->mimeType != mimeType) {
608         d->mimeType = mimeType;
609         emit mimeTypeChanged();
610     }
611 }
612 
613 /*!
614     \internal
615 */
autoSave(QString * errorString,const FilePath & filePath)616 bool IDocument::autoSave(QString *errorString, const FilePath &filePath)
617 {
618     if (!save(errorString, filePath, true))
619         return false;
620     d->autoSavePath = filePath;
621     return true;
622 }
623 
624 static const char kRestoredAutoSave[] = "RestoredAutoSave";
625 
626 /*!
627     \internal
628 */
setRestoredFrom(const Utils::FilePath & path)629 void IDocument::setRestoredFrom(const Utils::FilePath &path)
630 {
631     d->autoSavePath = path;
632     d->restored = true;
633     Utils::InfoBarEntry info(Id(kRestoredAutoSave),
634                              tr("File was restored from auto-saved copy. "
635                                 "Select Save to confirm or Revert to Saved to discard changes."));
636     infoBar()->addInfo(info);
637 }
638 
639 /*!
640     \internal
641 */
removeAutoSaveFile()642 void IDocument::removeAutoSaveFile()
643 {
644     if (!d->autoSavePath.isEmpty()) {
645         QFile::remove(d->autoSavePath.toString());
646         d->autoSavePath.clear();
647         if (d->restored) {
648             d->restored = false;
649             infoBar()->removeInfo(Id(kRestoredAutoSave));
650         }
651     }
652 }
653 
654 /*!
655     \internal
656 */
hasWriteWarning() const657 bool IDocument::hasWriteWarning() const
658 {
659     return d->hasWriteWarning;
660 }
661 
662 /*!
663     \internal
664 */
setWriteWarning(bool has)665 void IDocument::setWriteWarning(bool has)
666 {
667     d->hasWriteWarning = has;
668 }
669 
670 /*!
671     Returns the document's Utils::InfoBar, which is shown at the top of an
672     editor.
673 */
infoBar()674 Utils::InfoBar *IDocument::infoBar()
675 {
676     if (!d->infoBar)
677         d->infoBar = new Utils::InfoBar;
678     return d->infoBar;
679 }
680 
681 /*!
682     Sets the absolute \a filePath of the file that backs this document. The
683     default implementation sets the file name and sends the filePathChanged() and
684     changed() signals.
685 
686     \sa filePath()
687     \sa filePathChanged()
688     \sa changed()
689 */
setFilePath(const Utils::FilePath & filePath)690 void IDocument::setFilePath(const Utils::FilePath &filePath)
691 {
692     if (d->filePath == filePath)
693         return;
694     Utils::FilePath oldName = d->filePath;
695     d->filePath = filePath;
696     emit filePathChanged(oldName, d->filePath);
697     emit changed();
698 }
699 
700 /*!
701     Returns the string to display for this document, for example in the
702     \uicontrol{Open Documents} view and the documents drop down.
703 
704     The display name is one of the following, in order:
705 
706     \list 1
707         \li Unique display name set by the document model
708         \li Preferred display name set by the owner
709         \li Base name of the document's file name
710     \endlist
711 
712     \sa setPreferredDisplayName()
713     \sa filePath()
714     \sa changed()
715 */
displayName() const716 QString IDocument::displayName() const
717 {
718     return d->uniqueDisplayName.isEmpty() ? plainDisplayName() : d->uniqueDisplayName;
719 }
720 
721 /*!
722     Sets the preferred display \a name for this document.
723 
724     \sa preferredDisplayName()
725     \sa displayName()
726  */
setPreferredDisplayName(const QString & name)727 void IDocument::setPreferredDisplayName(const QString &name)
728 {
729     if (name == d->preferredDisplayName)
730         return;
731     d->preferredDisplayName = name;
732     emit changed();
733 }
734 
735 /*!
736     Returns the preferred display name for this document.
737 
738     The default preferred display name is empty, which means that the display
739     name is preferably the file name of the file backing this document.
740 
741     \sa setPreferredDisplayName()
742     \sa displayName()
743 */
preferredDisplayName() const744 QString IDocument::preferredDisplayName() const
745 {
746     return d->preferredDisplayName;
747 }
748 
749 /*!
750     \internal
751     Returns displayName without disambiguation.
752  */
plainDisplayName() const753 QString IDocument::plainDisplayName() const
754 {
755     return d->preferredDisplayName.isEmpty() ? d->filePath.fileName() : d->preferredDisplayName;
756 }
757 
758 /*!
759     \internal
760     Sets unique display name for the document. Used by the document model.
761  */
setUniqueDisplayName(const QString & name)762 void IDocument::setUniqueDisplayName(const QString &name)
763 {
764     d->uniqueDisplayName = name;
765 }
766 
767 /*!
768     \internal
769 */
uniqueDisplayName() const770 QString IDocument::uniqueDisplayName() const
771 {
772     return d->uniqueDisplayName;
773 }
774 
775 } // namespace Core
776