1 /***********************************************************************
2  *
3  * Copyright (C) 2014-2018 wereturtle
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  ***********************************************************************/
19 
20 #ifndef DOCUMENTMANAGER_H
21 #define DOCUMENTMANAGER_H
22 
23 #include <QObject>
24 #include <QWidget>
25 #include <QFutureWatcher>
26 
27 #include "MarkdownEditor.h"
28 #include "Outline.h"
29 #include "DocumentStatistics.h"
30 #include "SessionStatistics.h"
31 #include "TextDocument.h"
32 
33 class QFileSystemWatcher;
34 
35 /**
36  * Manages the life-cycle of a document, facilitating user interaction for
37  * opening, closing, saving, etc.
38  */
39 class DocumentManager : public QObject
40 {
41     Q_OBJECT
42 
43     public:
44         /**
45          * Constructor.  Takes MarkdownEditor as a parameter, which is used
46          * to display the current document to the user.  Also takes the
47          * document statistics as a parameter in order to reset the statistics
48          * when a new file is loaded.
49          */
50         DocumentManager
51         (
52             MarkdownEditor* editor,
53             Outline* outline,
54             DocumentStatistics* documentStats,
55             SessionStatistics* sessionStats,
56             QWidget* parent = 0
57         );
58 
59         /**
60          * Destructor.
61          */
62         virtual ~DocumentManager();
63 
64         /**
65          * Gets the current document that is opened.
66          */
67         TextDocument* getDocument() const;
68 
69         /**
70          * Gets whether auto-save is enabled.
71          */
72         bool getAutoSaveEnabled() const;
73 
74         /**
75          * Gets whether automatic file backup (i.e., creation of a .backup
76          * file at regular intervals), is enabled.
77          */
78         bool getFileBackupEnabled() const;
79 
80         /**
81          * Gets whether tracking the recent file history is enabled.
82          */
83         void setFileHistoryEnabled(bool enabled);
84 
85     signals:
86         /**
87          * Emitted when the document's display name changes, which is useful
88          * for updating the editor's containing window or tab to have the new
89          * document display name.
90          */
91         void documentDisplayNameChanged(const QString& displayName);
92 
93         /**
94          * Emitted when the document's modification state changes.  The
95          * modified parameter will be true if the document has been modified.
96          */
97         void documentModifiedChanged(bool modified);
98 
99         /**
100          * Emitted when an operation on the document has started, such as
101          * when a document is being loaded into the editor either from being
102          * opened or reloaded from disk.  Connect to this signal to notify
103          * the user of a possibly long operation for the document, such as
104          * with a progress bar.  The description parameter will contain
105          * descriptive text to display to the user regarding the operation.
106          */
107         void operationStarted(const QString& description);
108 
109         /**
110          * Emitted to update the status of a document operation begun with
111          * operationStarted(). The description is optional.  If no
112          * description is provided, then the previous description used in
113          * operationStarted() should be used to display status to the user.
114          * Upon receipt of this signal, it is recommended that the GUI
115          * be updated to refresh the status as well as the editor and
116          * any other widgets that might have changed due to a document
117          * operation, so that those changes can be displayed to the user.
118          *
119          * A call to QWidget's update() and qApp->processEvents() will
120          * perform this refresh.
121          */
122         void operationUpdate(const QString& description = QString());
123 
124         /**
125          * Emitted when an operation on the document has finished, such as
126          * when a document has finished loading into the editor either from
127          * being opened or reloaded from disk.  Connect to this signal to notify
128          * the user that a long operation for the document has completed,
129          * such as by removing a progress bar that was previously displayed
130          * when the operation first started.
131          */
132         void operationFinished();
133 
134         /**
135          * Emitted when the document is closed.
136          */
137         void documentClosed();
138 
139     public slots:
140 
141         /**
142          * Sets whether auto-saving of the file is enabled.
143          */
144         void setAutoSaveEnabled(bool enabled);
145 
146         /**
147          * Sets whether a backup file is created (with a .backup extension)
148          * on disk before the document is saved.
149          */
150         void setFileBackupEnabled(bool enabled);
151 
152         /**
153          * Prompts the user for a file path, and loads the document with the
154          * file contents at the selected path.
155          */
156         void open(const QString& filePath = QString());
157 
158         /**
159          * Reopens the last closed file, if any is available in the document
160          * history.
161          */
162         void reopenLastClosedFile();
163 
164         /**
165          * Reloads document from disk contents.  This method does nothing if
166          * the document is new and is not associated with a file on disk.
167          * Note that if the document is modified, this method will discard
168          * changes before reloading.  It is left to the caller to check for
169          * modification and save any changes before calling this method.
170          */
171         void reload();
172 
173         /**
174          * Renames file represented by this document to the given file path.
175          * This method does nothing if the document is new and is not
176          * associated with a file on disk.
177          */
178         void rename();
179 
180         /**
181          * Savse document contents to disk.  This method does nothing if the
182          * document is new and is not associated with a file on disk.
183          */
184         bool save();
185 
186         /**
187          * Prompts the user for a file path location, and saves the document
188          * contents to the selected file path. This method is
189          * also called if the document is new and is now going to be saved to
190          * a file path for the first time.  Future save operations to the same
191          * file path can be achieved by calling save() instead.
192          */
193         bool saveAs();
194 
195         /**
196          * Closes the current file, clearing the editor of text and leaving
197          * only an untitled "new" document in its place.  Note that isNew()
198          * will return true after this method is called.
199          */
200         bool close();
201 
202         /**
203          * Exports the current file, prompting the user for the desired
204          * export format.
205          */
206         void exportFile();
207 
208     private slots:
209 
210         void onDocumentModifiedChanged(bool modified);
211         void onSaveCompleted();
212         void onFileChangedExternally(const QString& path);
213         void autoSaveFile();
214 
215     private:
216         static const QString FILE_CHOOSER_FILTER;
217 
218         QWidget* parentWidget;
219         TextDocument* document;
220         MarkdownEditor* editor;
221         Outline* outline;
222         DocumentStatistics* documentStats;
223         SessionStatistics* sessionStats;
224         QFutureWatcher<QString>* saveFutureWatcher;
225         QFileSystemWatcher* fileWatcher;
226         bool fileHistoryEnabled;
227         bool createBackupOnSave;
228 
229         /*
230          * This flag is used to prevent notifying the user that the document
231          * was modified when the user is the one who modified it by saving.
232          * See code for onFileChangedExternally() for details.
233          */
234         bool saveInProgress;
235 
236         /*
237          * This timer's timeout signal is connected to the autoSaveFile() slot,
238          * which saves the document if it can be saved and has been modified.
239          */
240         QTimer* autoSaveTimer;
241         bool autoSaveEnabled;
242 
243         /*
244          * Boolean flag used to track if the prompt for the file having been
245          * externally modified is already displayed and should not be displayed
246          * again.
247          */
248         bool documentModifiedNotifVisible;
249 
250         /*
251          * Begins asynchronous save operation.  Called by save() and saveAs().
252          */
253         void saveFile();
254 
255         /*
256          * Loads the document with the file contents at the given path.
257          */
258         bool loadFile(const QString& filePath);
259 
260         /*
261          * Sets the file path for the document, such that the file will be
262          * monitored for external changes made to it, and the display name
263          * for the document updated.
264          */
265         void setFilePath(const QString& filePath);
266 
267         /*
268          * Checks if changes need to be saved before an operation
269          * can continue.  The user will be prompted to save if
270          * necessary, and this method will return true if the
271          * operation can proceed.
272          */
273         bool checkSaveChanges();
274 
275         /*
276          * Checks if file on the disk is read only.  This method will return
277          * true if save operation can proceed.
278          */
279         bool checkPermissionsBeforeSave();
280 
281         /*
282          * Saves the given text to the given file path, returning a null
283          * string if successful, otherwise an error message.  Note that this
284          * method is intended to be run in a separate thread from the main
285          * Qt event loop, and should thus never interact with any widgets.
286          */
287         QString saveToDisk
288         (
289             const QString& filePath,
290             const QString& text,
291             bool createBackup
292         ) const;
293 
294         /*
295          * Creates a backup file with a ".backup" extension of the file having
296          * the specified path.  Note that this method is intended to be run in
297          * a separate thread from the main Qt event loop, and should thus never
298          * interact with any widgets.
299          */
300         void backupFile(const QString& filePath) const;
301 };
302 
303 #endif // DOCUMENTMANAGER_H
304 
305