1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 namespace juce
27 {
28 
FileBasedDocument(const String & fileExtension_,const String & fileWildcard_,const String & openFileDialogTitle_,const String & saveFileDialogTitle_)29 FileBasedDocument::FileBasedDocument (const String& fileExtension_,
30                                       const String& fileWildcard_,
31                                       const String& openFileDialogTitle_,
32                                       const String& saveFileDialogTitle_)
33     : fileExtension (fileExtension_),
34       fileWildcard (fileWildcard_),
35       openFileDialogTitle (openFileDialogTitle_),
36       saveFileDialogTitle (saveFileDialogTitle_)
37 {
38 }
39 
~FileBasedDocument()40 FileBasedDocument::~FileBasedDocument()
41 {
42 }
43 
44 //==============================================================================
setChangedFlag(bool hasChanged)45 void FileBasedDocument::setChangedFlag (bool hasChanged)
46 {
47     if (changedSinceSave != hasChanged)
48     {
49         changedSinceSave = hasChanged;
50         sendChangeMessage();
51     }
52 }
53 
changed()54 void FileBasedDocument::changed()
55 {
56     changedSinceSave = true;
57     sendChangeMessage();
58 }
59 
60 //==============================================================================
setFile(const File & newFile)61 void FileBasedDocument::setFile (const File& newFile)
62 {
63     if (documentFile != newFile)
64     {
65         documentFile = newFile;
66         changed();
67     }
68 }
69 
70 //==============================================================================
loadFrom(const File & newFile,bool showMessageOnFailure,bool showWaitCursor)71 Result FileBasedDocument::loadFrom (const File& newFile, bool showMessageOnFailure, bool showWaitCursor)
72 {
73     if (showWaitCursor)
74         MouseCursor::showWaitCursor();
75 
76     auto oldFile = documentFile;
77     documentFile = newFile;
78 
79     auto result = Result::fail (TRANS("The file doesn't exist"));
80 
81     if (newFile.existsAsFile())
82     {
83         result = loadDocument (newFile);
84 
85         if (result.wasOk())
86         {
87             setChangedFlag (false);
88 
89             if (showWaitCursor)
90                 MouseCursor::hideWaitCursor();
91 
92             setLastDocumentOpened (newFile);
93             return result;
94         }
95     }
96 
97     documentFile = oldFile;
98 
99     if (showWaitCursor)
100         MouseCursor::hideWaitCursor();
101 
102     if (showMessageOnFailure)
103         AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
104                                           TRANS("Failed to open file..."),
105                                           TRANS("There was an error while trying to load the file: FLNM")
106                                               .replace ("FLNM", "\n" + newFile.getFullPathName())
107                                             + "\n\n"
108                                             + result.getErrorMessage());
109 
110     return result;
111 }
112 
113 #if JUCE_MODAL_LOOPS_PERMITTED
loadFromUserSpecifiedFile(const bool showMessageOnFailure)114 Result FileBasedDocument::loadFromUserSpecifiedFile (const bool showMessageOnFailure)
115 {
116     FileChooser fc (openFileDialogTitle,
117                     getLastDocumentOpened(),
118                     fileWildcard);
119 
120     if (fc.browseForFileToOpen())
121         return loadFrom (fc.getResult(), showMessageOnFailure);
122 
123     return Result::fail (TRANS("User cancelled"));
124 }
125 
askToOverwriteFile(const File & newFile)126 static bool askToOverwriteFile (const File& newFile)
127 {
128     return AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
129                                             TRANS("File already exists"),
130                                             TRANS("There's already a file called: FLNM")
131                                                 .replace ("FLNM", newFile.getFullPathName())
132                                              + "\n\n"
133                                              + TRANS("Are you sure you want to overwrite it?"),
134                                             TRANS("Overwrite"),
135                                             TRANS("Cancel"));
136 }
137 
138 //==============================================================================
save(bool askUserForFileIfNotSpecified,bool showMessageOnFailure)139 FileBasedDocument::SaveResult FileBasedDocument::save (bool askUserForFileIfNotSpecified,
140                                                        bool showMessageOnFailure)
141 {
142     return saveAs (documentFile,
143                    false,
144                    askUserForFileIfNotSpecified,
145                    showMessageOnFailure);
146 }
147 
saveAs(const File & newFile,bool warnAboutOverwritingExistingFiles,bool askUserForFileIfNotSpecified,bool showMessageOnFailure,bool showWaitCursor)148 FileBasedDocument::SaveResult FileBasedDocument::saveAs (const File& newFile,
149                                                          bool warnAboutOverwritingExistingFiles,
150                                                          bool askUserForFileIfNotSpecified,
151                                                          bool showMessageOnFailure,
152                                                          bool showWaitCursor)
153 {
154     if (newFile == File())
155     {
156         if (askUserForFileIfNotSpecified)
157             return saveAsInteractive (true);
158 
159         // can't save to an unspecified file
160         jassertfalse;
161         return failedToWriteToFile;
162     }
163 
164     if (warnAboutOverwritingExistingFiles
165           && newFile.exists()
166           && ! askToOverwriteFile (newFile))
167         return userCancelledSave;
168 
169     if (showWaitCursor)
170         MouseCursor::showWaitCursor();
171 
172     auto oldFile = documentFile;
173     documentFile = newFile;
174 
175     auto result = saveDocument (newFile);
176 
177     if (result.wasOk())
178     {
179         setChangedFlag (false);
180 
181         if (showWaitCursor)
182             MouseCursor::hideWaitCursor();
183 
184         sendChangeMessage(); // because the filename may have changed
185         return savedOk;
186     }
187 
188     documentFile = oldFile;
189 
190     if (showWaitCursor)
191         MouseCursor::hideWaitCursor();
192 
193     if (showMessageOnFailure)
194         AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
195                                           TRANS("Error writing to file..."),
196                                           TRANS("An error occurred while trying to save \"DCNM\" to the file: FLNM")
197                                             .replace ("DCNM", getDocumentTitle())
198                                             .replace ("FLNM", "\n" + newFile.getFullPathName())
199                                            + "\n\n"
200                                            + result.getErrorMessage());
201 
202     sendChangeMessage(); // because the filename may have changed
203     return failedToWriteToFile;
204 }
205 
saveIfNeededAndUserAgrees()206 FileBasedDocument::SaveResult FileBasedDocument::saveIfNeededAndUserAgrees()
207 {
208     if (! hasChangedSinceSaved())
209         return savedOk;
210 
211     auto r = AlertWindow::showYesNoCancelBox (AlertWindow::QuestionIcon,
212                                               TRANS("Closing document..."),
213                                               TRANS("Do you want to save the changes to \"DCNM\"?")
214                                                    .replace ("DCNM", getDocumentTitle()),
215                                               TRANS("Save"),
216                                               TRANS("Discard changes"),
217                                               TRANS("Cancel"));
218 
219     if (r == 1)  // save changes
220         return save (true, true);
221 
222     if (r == 2)  // discard changes
223         return savedOk;
224 
225     return userCancelledSave;
226 }
227 
getSuggestedSaveAsFile(const File & defaultFile)228 File FileBasedDocument::getSuggestedSaveAsFile (const File& defaultFile)
229 {
230     return defaultFile.withFileExtension (fileExtension).getNonexistentSibling (true);
231 }
232 
saveAsInteractive(bool warnAboutOverwritingExistingFiles)233 FileBasedDocument::SaveResult FileBasedDocument::saveAsInteractive (bool warnAboutOverwritingExistingFiles)
234 {
235     auto f = documentFile.existsAsFile() ? documentFile : getLastDocumentOpened();
236 
237     auto legalFilename = File::createLegalFileName (getDocumentTitle());
238 
239     if (legalFilename.isEmpty())
240         legalFilename = "unnamed";
241 
242     if (f.existsAsFile() || f.getParentDirectory().isDirectory())
243         f = f.getSiblingFile (legalFilename);
244     else
245         f = File::getSpecialLocation (File::userDocumentsDirectory).getChildFile (legalFilename);
246 
247     f = getSuggestedSaveAsFile (f);
248 
249     FileChooser fc (saveFileDialogTitle, f, fileWildcard);
250 
251     if (fc.browseForFileToSave (warnAboutOverwritingExistingFiles))
252     {
253         auto chosen = fc.getResult();
254 
255         if (chosen.getFileExtension().isEmpty())
256         {
257             chosen = chosen.withFileExtension (fileExtension);
258 
259             if (chosen.exists() && ! askToOverwriteFile (chosen))
260                 return userCancelledSave;
261         }
262 
263         setLastDocumentOpened (chosen);
264         return saveAs (chosen, false, false, true);
265     }
266 
267     return userCancelledSave;
268 }
269 #endif
270 
271 } // namespace juce
272