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