From df8fc9b910ad0eba3fdd4c8a4cde76dd0160df13 Mon Sep 17 00:00:00 2001 From: hogliux Date: Wed, 22 Nov 2017 14:03:42 +0000 Subject: [PATCH] FileChoosers: Added a file-chooser save mode where the caller already supplies a temporary file which should be saved. JUCE will automatically move the temporary file to the location selected by the user --- examples/Demo/Source/Demos/DialogsDemo.cpp | 12 ++++- .../filebrowser/juce_FileChooser.cpp | 29 +++++++++--- .../filebrowser/juce_FileChooser.h | 47 ++++++++++++++++--- .../native/juce_android_FileChooser.cpp | 2 +- .../native/juce_ios_FileChooser.mm | 25 +++------- .../native/juce_linux_FileChooser.cpp | 2 +- .../native/juce_mac_FileChooser.mm | 2 +- .../native/juce_win32_FileChooser.cpp | 4 +- 8 files changed, 86 insertions(+), 37 deletions(-) diff --git a/examples/Demo/Source/Demos/DialogsDemo.cpp b/examples/Demo/Source/Demos/DialogsDemo.cpp index b0bb5f9830..9bc4b2268f 100644 --- a/examples/Demo/Source/Demos/DialogsDemo.cpp +++ b/examples/Demo/Source/Demos/DialogsDemo.cpp @@ -317,8 +317,16 @@ private: } else if (type == saveChooser) { + File fileToSave = File::createTempFile ("saveChooserDemo"); + + if (fileToSave.createDirectory().wasOk()) + { + fileToSave = fileToSave.getChildFile ("JUCE.png"); + fileToSave.replaceWithData (BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize); + } + fc = new FileChooser ("Choose a file to save...", - File::getCurrentWorkingDirectory(), + File::getCurrentWorkingDirectory().getChildFile (fileToSave.getFileName()), "*", useNativeVersion); @@ -333,7 +341,7 @@ private: AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon, "File Chooser...", "You picked: " + name); - }); + }, nullptr, fileToSave); } else if (type == directoryChooser) { diff --git a/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp b/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp index 3218e400ca..4504f75e8c 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp +++ b/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp @@ -73,7 +73,7 @@ private: result.add (URL (browserComponent.getSelectedFile (i))); } - owner.finished (result); + owner.finished (result, true); } //============================================================================== @@ -137,12 +137,13 @@ bool FileChooser::browseForMultipleFilesOrDirectories (FilePreviewComponent* pre previewComp); } -bool FileChooser::browseForFileToSave (const bool warnAboutOverwrite) +bool FileChooser::browseForFileToSave (const bool warnAboutOverwrite, + const File& fileWhichShouldBeSaved) { return showDialog (FileBrowserComponent::saveMode | FileBrowserComponent::canSelectFiles | (warnAboutOverwrite ? FileBrowserComponent::warnAboutOverwriting : 0), - nullptr); + nullptr, fileWhichShouldBeSaved); } bool FileChooser::browseForDirectory() @@ -152,8 +153,11 @@ bool FileChooser::browseForDirectory() nullptr); } -bool FileChooser::showDialog (const int flags, FilePreviewComponent* const previewComp) +bool FileChooser::showDialog (const int flags, FilePreviewComponent* const previewComp, + const File& fileWhichShouldBeSaved) { + fileToSave = (flags & FileBrowserComponent::saveMode) != 0 ? fileWhichShouldBeSaved : File(); + FocusRestorer focusRestorer; pimpl = createPimpl (flags, previewComp); @@ -167,7 +171,8 @@ bool FileChooser::showDialog (const int flags, FilePreviewComponent* const previ #endif void FileChooser::launchAsync (int flags, std::function callback, - FilePreviewComponent* previewComp) + FilePreviewComponent* previewComp, + const File& fileWhichShouldBeSaved) { // You must specify a callback when using launchAsync jassert (callback); @@ -175,6 +180,8 @@ void FileChooser::launchAsync (int flags, std::function&&> (callback); pimpl = createPimpl (flags, previewComp); @@ -249,13 +256,23 @@ URL FileChooser::getURLResult() const return results.getFirst(); } -void FileChooser::finished (const Array& asyncResults) +void FileChooser::finished (const Array& asyncResults, bool shouldMove) { std::function callback; std::swap (callback, asyncCallback); results = asyncResults; + if (shouldMove && fileToSave.existsAsFile() && results.size() > 0) + { + // The user either selected multiple files or wants to save the file to a URL + // Both are not supported + jassert (results.size() == 1 && results.getReference (0).isLocalFile()); + + if (! fileToSave.moveFileTo (results.getReference (0).getLocalFile())) + results.clear(); + } + pimpl = nullptr; if (callback) diff --git a/modules/juce_gui_basics/filebrowser/juce_FileChooser.h b/modules/juce_gui_basics/filebrowser/juce_FileChooser.h index 9bf7d168e3..7a26f9cfb4 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileChooser.h +++ b/modules/juce_gui_basics/filebrowser/juce_FileChooser.h @@ -66,7 +66,8 @@ public: @param initialFileOrDirectory the file or directory that should be selected when the dialog box opens. If this parameter is set to File(), a sensible default directory will - be used instead. + be used instead. This parameter is ignored for native + iOS file choosers. @param filePatternsAllowed a set of file patterns to specify which files can be selected - each pattern should be separated by a comma or semi-colon, e.g. "*" or @@ -122,12 +123,22 @@ public: @param warnAboutOverwritingExistingFiles if true, the dialog box will ask the user if they're sure they want to overwrite a file that already exists + @param fileWhichShouldBeSaved if this parameter is specified, then, if the the user + selects a valid location to save the file, fileWhichShouldBeSaved will + automaitcally be moved to the location selected by the user when the user + clicks 'ok'. If you do not specify this parameter, then it is your + responsibility to save your file at the location that is returned from this + file chooser. Typically, when using this parameter, you already write the + file you wish to save to a temporary location and then supply the path to + this file to this parameter. This parameter is required on iOS when using + native file save dialogs but can be used on all other platforms. @returns true if the user chose a file and pressed 'ok', in which case, use the getResult() method to find out what the file was. Returns false if they cancelled instead. @see browseForFileToOpen, browseForDirectory */ - bool browseForFileToSave (bool warnAboutOverwritingExistingFiles); + bool browseForFileToSave (bool warnAboutOverwritingExistingFiles, + const File& fileWhichShouldBeSaved = File()); /** Shows a dialog box to choose a directory. @@ -152,12 +163,24 @@ public: /** Runs a dialog box for the given set of option flags. The flag values used are those in FileBrowserComponent::FileChooserFlags. + @param fileWhichShouldBeSaved if this parameter is specified and saveMode is + specified, then, if the the user selects a valid location to save the file, + fileWhichShouldBeSaved will automaitcally be moved to the location selected + by the user when the user clicks 'ok'. If you do not specify this parameter, + then it is your responsibility to save your file at the location that is + returned from this file chooser. Typically, when using this parameter, + you already write the file you wish to save to a temporary location and + then supply the path to this file to this parameter. This parameter is + required on iOS when using native file save dialogs but can be used on all + other platforms. + @returns true if the user chose a directory and pressed 'ok', in which case, use the getResult() method to find out what they chose. Returns false if they cancelled instead. @see FileBrowserComponent::FileChooserFlags */ - bool showDialog (int flags, FilePreviewComponent* previewComponent); + bool showDialog (int flags, FilePreviewComponent* previewComponent, + const File& fileWhichShouldBeSaved = File()); /** Use this method to launch the file browser window asynchronously. @@ -173,10 +196,22 @@ public: You must ensure that the lifetime of the callback object is longer than the lifetime of the file-chooser. + + @param fileWhichShouldBeSaved if this parameter is specified and saveMode is + specified, then, if the the user selects a valid location to save the file, + fileWhichShouldBeSaved will automaitcally be moved to the location selected + by the user when the user clicks 'ok'. If you do not specify this parameter, + then it is your responsibility to save your file at the location that is + returned from this file chooser. Typically, when using this parameter, + you already write the file you wish to save to a temporary location and + then supply the path to this file to this parameter. This parameter is + required on iOS when using native file save dialogs but can be used on all + other platforms. */ void launchAsync (int flags, std::function, - FilePreviewComponent* previewComponent = nullptr); + FilePreviewComponent* previewComponent = nullptr, + const File& fileWhichShouldBeSaved = File()); //============================================================================== /** Returns the last file that was chosen by one of the browseFor methods. @@ -253,14 +288,14 @@ public: private: //============================================================================== String title, filters; - const File startingFile; + File startingFile, fileToSave; Array results; const bool useNativeDialogBox; const bool treatFilePackagesAsDirs; std::function asyncCallback; //============================================================================== - void finished (const Array&); + void finished (const Array&, bool); //============================================================================== struct Pimpl diff --git a/modules/juce_gui_basics/native/juce_android_FileChooser.cpp b/modules/juce_gui_basics/native/juce_android_FileChooser.cpp index 1ff4ec89e6..c886eabd64 100644 --- a/modules/juce_gui_basics/native/juce_android_FileChooser.cpp +++ b/modules/juce_gui_basics/native/juce_android_FileChooser.cpp @@ -168,7 +168,7 @@ public: } } - owner.finished (chosenURLs); + owner.finished (chosenURLs, true); } static Native* currentFileChooser; diff --git a/modules/juce_gui_basics/native/juce_ios_FileChooser.mm b/modules/juce_gui_basics/native/juce_ios_FileChooser.mm index 2b9d70ca36..1e31a96074 100644 --- a/modules/juce_gui_basics/native/juce_ios_FileChooser.mm +++ b/modules/juce_gui_basics/native/juce_ios_FileChooser.mm @@ -45,25 +45,14 @@ public: if ((flags & FileBrowserComponent::saveMode) != 0) { - auto currentFileOrDirectory = owner.startingFile; + // You must specify the fileWhichShouldBeSaved parameter when using + // the native save dialog on iOS! + jassert (owner.fileToSave.existsAsFile()); - if (! currentFileOrDirectory.existsAsFile()) - { - auto filename = (currentFileOrDirectory.isDirectory() ? "Untitled" : currentFileOrDirectory.getFileName()); - - auto tmpDirectory = File::createTempFile ("iosDummyFiles"); - - if (tmpDirectory.createDirectory().wasOk()) - { - currentFileOrDirectory = tmpDirectory.getChildFile (filename); - currentFileOrDirectory.replaceWithText (""); - } - } - - auto url = [[NSURL alloc] initFileURLWithPath:juceStringToNS (currentFileOrDirectory.getFullPathName())]; + auto url = [[NSURL alloc] initFileURLWithPath:juceStringToNS (owner.fileToSave.getFullPathName())]; controller = [[UIDocumentPickerViewController alloc] initWithURL:url - inMode:UIDocumentPickerModeMoveToService]; + inMode:UIDocumentPickerModeExportToService]; [url release]; } else @@ -158,7 +147,7 @@ private: Array chooserResults; chooserResults.add (URL (nsStringToJuce ([url absoluteString]))); - owner.finished (chooserResults); + owner.finished (chooserResults, false); exitModalState (1); } @@ -166,7 +155,7 @@ private: { Array chooserResults; - owner.finished (chooserResults); + owner.finished (chooserResults, false); exitModalState (0); } diff --git a/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp b/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp index 9155759064..97baca86f6 100644 --- a/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp +++ b/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp @@ -122,7 +122,7 @@ private: if (! shouldKill) { child.waitForProcessToFinish (60 * 1000); - owner.finished (selection); + owner.finished (selection, true); } } diff --git a/modules/juce_gui_basics/native/juce_mac_FileChooser.mm b/modules/juce_gui_basics/native/juce_mac_FileChooser.mm index 55ec816a4b..562770e500 100644 --- a/modules/juce_gui_basics/native/juce_mac_FileChooser.mm +++ b/modules/juce_gui_basics/native/juce_mac_FileChooser.mm @@ -213,7 +213,7 @@ private: } } - owner.finished (chooserResults); + owner.finished (chooserResults, true); } bool shouldShowFilename (const String& filenameToTest) diff --git a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp b/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp index d1c8c5c40f..8f96282dd3 100644 --- a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp +++ b/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp @@ -535,7 +535,7 @@ public: [safeThis] (int) { if (safeThis != nullptr) - safeThis->owner.finished (safeThis->nativeFileChooser->results); + safeThis->owner.finished (safeThis->nativeFileChooser->results, true); })); nativeFileChooser->open (true); @@ -548,7 +548,7 @@ public: exitModalState (nativeFileChooser->results.size() > 0 ? 1 : 0); nativeFileChooser->cancel(); - owner.finished (nativeFileChooser->results); + owner.finished (nativeFileChooser->results, true); } private: