diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index 2a75731f1c..e7bd06426a 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -4,6 +4,31 @@ JUCE breaking changes Develop ======= +Change +------ +The default value of JUCE_MODAL_LOOPS_PERMITTED has been changed from 1 to 0. + +Possible Issues +--------------- +With JUCE_MODAL_LOOPS_PERMITTED set to 0 code that previously relied upon modal +loops will need to be rewritten to use asynchronous versions of the modal +functions. There is no non-modal alternative to +AlterWindow::showNativeDialogBox and the previously modal behaviour of the +MultiDocumentPanel destructor has changed. + +Workaround +---------- +Set JUCE_MODAL_LOOPS_PERMITTED back to 1. + +Rationale +--------- +Modal operations are a frequent source of problems, particularly when used in +plug-ins. On Android modal loops are not possible, so people wanting to target +Android often have an unwelcome surprise when then have to rewrite what they +assumed to be platform independent code. Changing the default addresses these +problems. + + Change ------ The minimum supported C++ standard is now C++14 and the oldest supported diff --git a/examples/Assets/DSPDemos_Common.h b/examples/Assets/DSPDemos_Common.h index 5bb3d966ff..25647e14f1 100644 --- a/examples/Assets/DSPDemos_Common.h +++ b/examples/Assets/DSPDemos_Common.h @@ -590,14 +590,13 @@ private: if (fileChooser != nullptr) return; - SafePointer safeThis (this); - if (! RuntimePermissions::isGranted (RuntimePermissions::readExternalStorage)) { + SafePointer safeThis (this); RuntimePermissions::request (RuntimePermissions::readExternalStorage, [safeThis] (bool granted) mutable { - if (granted) + if (safeThis != nullptr && granted) safeThis->openFile(); }); return; @@ -606,22 +605,19 @@ private: fileChooser.reset (new FileChooser ("Select an audio file...", File(), "*.wav;*.mp3;*.aif")); fileChooser->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles, - [safeThis] (const FileChooser& fc) mutable + [this] (const FileChooser& fc) mutable { - if (safeThis == nullptr) - return; - if (fc.getURLResults().size() > 0) { auto u = fc.getURLResult(); - if (! safeThis->audioFileReader.loadURL (u)) + if (! audioFileReader.loadURL (u)) NativeMessageBox::showOkCancelBox (AlertWindow::WarningIcon, "Error loading file", "Unable to load audio file", nullptr, nullptr); else - safeThis->thumbnailComp.setCurrentURL (u); + thumbnailComp.setCurrentURL (u); } - safeThis->fileChooser = nullptr; + fileChooser = nullptr; }, nullptr); } diff --git a/examples/Audio/AudioPlaybackDemo.h b/examples/Audio/AudioPlaybackDemo.h index 495f2f953d..ea1b6b7bdf 100644 --- a/examples/Audio/AudioPlaybackDemo.h +++ b/examples/Audio/AudioPlaybackDemo.h @@ -498,14 +498,13 @@ private: { if (btn == &chooseFileButton && fileChooser.get() == nullptr) { - SafePointer safeThis (this); - if (! RuntimePermissions::isGranted (RuntimePermissions::readExternalStorage)) { + SafePointer safeThis (this); RuntimePermissions::request (RuntimePermissions::readExternalStorage, [safeThis] (bool granted) mutable { - if (granted) + if (safeThis != nullptr && granted) safeThis->buttonClicked (&safeThis->chooseFileButton); }); return; @@ -516,16 +515,16 @@ private: fileChooser.reset (new FileChooser ("Select an audio file...", File(), "*.wav;*.mp3;*.aif")); fileChooser->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles, - [safeThis] (const FileChooser& fc) mutable + [this] (const FileChooser& fc) mutable { - if (safeThis != nullptr && fc.getURLResults().size() > 0) + if (fc.getURLResults().size() > 0) { auto u = fc.getURLResult(); - safeThis->showAudioResource (std::move (u)); + showAudioResource (std::move (u)); } - safeThis->fileChooser = nullptr; + fileChooser = nullptr; }, nullptr); } else diff --git a/examples/DemoRunner/Builds/Android/app/src/main/assets/DSPDemos_Common.h b/examples/DemoRunner/Builds/Android/app/src/main/assets/DSPDemos_Common.h index 5bb3d966ff..25647e14f1 100644 --- a/examples/DemoRunner/Builds/Android/app/src/main/assets/DSPDemos_Common.h +++ b/examples/DemoRunner/Builds/Android/app/src/main/assets/DSPDemos_Common.h @@ -590,14 +590,13 @@ private: if (fileChooser != nullptr) return; - SafePointer safeThis (this); - if (! RuntimePermissions::isGranted (RuntimePermissions::readExternalStorage)) { + SafePointer safeThis (this); RuntimePermissions::request (RuntimePermissions::readExternalStorage, [safeThis] (bool granted) mutable { - if (granted) + if (safeThis != nullptr && granted) safeThis->openFile(); }); return; @@ -606,22 +605,19 @@ private: fileChooser.reset (new FileChooser ("Select an audio file...", File(), "*.wav;*.mp3;*.aif")); fileChooser->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles, - [safeThis] (const FileChooser& fc) mutable + [this] (const FileChooser& fc) mutable { - if (safeThis == nullptr) - return; - if (fc.getURLResults().size() > 0) { auto u = fc.getURLResult(); - if (! safeThis->audioFileReader.loadURL (u)) + if (! audioFileReader.loadURL (u)) NativeMessageBox::showOkCancelBox (AlertWindow::WarningIcon, "Error loading file", "Unable to load audio file", nullptr, nullptr); else - safeThis->thumbnailComp.setCurrentURL (u); + thumbnailComp.setCurrentURL (u); } - safeThis->fileChooser = nullptr; + fileChooser = nullptr; }, nullptr); } diff --git a/examples/GUI/DialogsDemo.h b/examples/GUI/DialogsDemo.h index 19f4c06598..0cac5293de 100644 --- a/examples/GUI/DialogsDemo.h +++ b/examples/GUI/DialogsDemo.h @@ -141,10 +141,21 @@ public: nativeButton.setButtonText ("Use Native Windows"); nativeButton.onClick = [this] { getLookAndFeel().setUsingNativeAlertWindows (nativeButton.getToggleState()); }; - StringArray windowNames { "Plain Alert Window", "Alert Window With Warning Icon", "Alert Window With Info Icon", "Alert Window With Question Icon", - "OK Cancel Alert Window", "Alert Window With Extra Components", "CalloutBox", "Thread With Progress Window", - "'Load' File Browser", "'Load' File Browser With Image Preview", "'Choose Directory' File Browser", "'Save' File Browser", - "Share Text", "Share Files", "Share Images" }; + StringArray windowNames { "Plain Alert Window", + "Alert Window With Warning Icon", + "Alert Window With Info Icon", + "Alert Window With Question Icon", + "OK Cancel Alert Window", + "Alert Window With Extra Components", + "CalloutBox", + "Thread With Progress Window", + "'Load' File Browser", + "'Load' File Browser With Image Preview", + "'Choose Directory' File Browser", + "'Save' File Browser", + "Share Text", + "Share Files", + "Share Images" }; // warn in case we add any windows jassert (windowNames.size() == numDialogs); @@ -207,11 +218,42 @@ private: OwnedArray windowButtons; ToggleButton nativeButton; - static void alertBoxResultChosen (int result, DialogsDemo*) + struct AlertBoxResultChosen { - AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon, "Alert Box", - "Result code: " + String (result)); - } + void operator() (int result) const noexcept + { + AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon, + "Alert Box", + "Result code: " + String (result)); + } + }; + + struct AsyncAlertBoxResultChosen + { + void operator() (int result) const noexcept + { + auto& aw = *demo.asyncAlertWindow; + + aw.exitModalState (result); + aw.setVisible (false); + + if (result == 0) + { + AlertBoxResultChosen{} (result); + return; + } + + auto optionIndexChosen = aw.getComboBoxComponent ("option")->getSelectedItemIndex(); + auto text = aw.getTextEditorContents ("text"); + + AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon, "Alert Box", + "Result code: " + String (result) + newLine + + "Option index chosen: " + String (optionIndexChosen) + newLine + + "Text: " + text); + } + + DialogsDemo& demo; + }; void showWindow (Component& button, DialogType type) { @@ -232,7 +274,7 @@ private: AlertWindow::showOkCancelBox (AlertWindow::QuestionIcon, "This is an ok/cancel AlertWindow", "And this is the AlertWindow's message. Blah blah blah blah blah blah blah blah blah blah blah blah blah.", {}, {}, {}, - ModalCallbackFunction::forComponent (alertBoxResultChosen, this)); + ModalCallbackFunction::create (AlertBoxResultChosen{})); } else if (type == calloutBoxWindow) { @@ -247,31 +289,16 @@ private: } else if (type == extraComponentsAlertWindow) { - #if JUCE_MODAL_LOOPS_PERMITTED - // Modal loops are extremely dangerous. Do not copy the code below unless you are absolutely - // certain you are aware of all the many complicated things that can go catastrophically - // wrong. Read the documentation for Component::runModalLoop. If you find you are using code - // similar to this you should refactor things to remove it. - - AlertWindow w ("AlertWindow demo..", - "This AlertWindow has a couple of extra components added to show how to add drop-down lists and text entry boxes.", - AlertWindow::QuestionIcon); - - w.addTextEditor ("text", "enter some text here", "text field:"); - w.addComboBox ("option", { "option 1", "option 2", "option 3", "option 4" }, "some options"); - w.addButton ("OK", 1, KeyPress (KeyPress::returnKey, 0, 0)); - w.addButton ("Cancel", 0, KeyPress (KeyPress::escapeKey, 0, 0)); - - if (w.runModalLoop() != 0) // is they picked 'ok' - { - // this is the item they chose in the drop-down list.. - auto optionIndexChosen = w.getComboBoxComponent ("option")->getSelectedItemIndex(); - ignoreUnused (optionIndexChosen); + asyncAlertWindow = std::make_unique ("AlertWindow demo..", + "This AlertWindow has a couple of extra components added to show how to add drop-down lists and text entry boxes.", + AlertWindow::QuestionIcon); - // this is the text they entered.. - auto text = w.getTextEditorContents ("text"); - } - #endif + asyncAlertWindow->addTextEditor ("text", "enter some text here", "text field:"); + asyncAlertWindow->addComboBox ("option", { "option 1", "option 2", "option 3", "option 4" }, "some options"); + asyncAlertWindow->addButton ("OK", 1, KeyPress (KeyPress::returnKey, 0, 0)); + asyncAlertWindow->addButton ("Cancel", 0, KeyPress (KeyPress::escapeKey, 0, 0)); + + asyncAlertWindow->enterModalState (true, ModalCallbackFunction::create (AsyncAlertBoxResultChosen { *this })); } else if (type == progressWindow) { @@ -461,6 +488,7 @@ private: ImagePreviewComponent imagePreview; std::unique_ptr fc; + std::unique_ptr asyncAlertWindow; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DialogsDemo) }; diff --git a/examples/GUI/MDIDemo.h b/examples/GUI/MDIDemo.h index 3818dd6e69..36d91d9988 100644 --- a/examples/GUI/MDIDemo.h +++ b/examples/GUI/MDIDemo.h @@ -110,14 +110,12 @@ public: // not interested in this for now } - #if JUCE_MODAL_LOOPS_PERMITTED File getSuggestedSaveAsFile (const File&) override { return File::getSpecialLocation (File::userDesktopDirectory) .getChildFile (getName()) .withFileExtension ("jnote"); } - #endif private: Value textValueObject; @@ -138,23 +136,19 @@ private: class DemoMultiDocumentPanel : public MultiDocumentPanel { public: - DemoMultiDocumentPanel() {} + DemoMultiDocumentPanel() = default; - ~DemoMultiDocumentPanel() override + void tryToCloseDocumentAsync (Component* component, std::function callback) override { - closeAllDocuments (true); - } - - bool tryToCloseDocument (Component* component) override - { - #if JUCE_MODAL_LOOPS_PERMITTED if (auto* note = dynamic_cast (component)) - return note->saveIfNeededAndUserAgrees() != FileBasedDocument::failedToWriteToFile; - #else - ignoreUnused (component); - #endif - - return true; + { + SafePointer parent { this }; + note->saveIfNeededAndUserAgreesAsync ([parent, callback] (FileBasedDocument::SaveResult result) + { + if (parent != nullptr) + callback (result == FileBasedDocument::savedOk); + }); + } } private: @@ -180,6 +174,16 @@ public: addNoteButton.onClick = [this] { addNote ("Note " + String (multiDocumentPanel.getNumDocuments() + 1), "Hello World!"); }; addAndMakeVisible (addNoteButton); + closeApplicationButton.onClick = [this] + { + multiDocumentPanel.closeAllDocumentsAsync (true, [] (bool allSaved) + { + if (allSaved) + JUCEApplicationBase::quit(); + }); + }; + addAndMakeVisible (closeApplicationButton); + addAndMakeVisible (multiDocumentPanel); multiDocumentPanel.setBackgroundColour (Colours::transparentBlack); @@ -200,8 +204,9 @@ public: auto area = getLocalBounds(); auto buttonArea = area.removeFromTop (28).reduced (2); - addNoteButton .setBounds (buttonArea.removeFromRight (150)); - showInTabsButton.setBounds (buttonArea); + closeApplicationButton.setBounds (buttonArea.removeFromRight (150)); + addNoteButton .setBounds (buttonArea.removeFromRight (150)); + showInTabsButton .setBounds (buttonArea); multiDocumentPanel.setBounds (area); } @@ -235,11 +240,6 @@ public: } private: - ToggleButton showInTabsButton { "Show with tabs" }; - TextButton addNoteButton { "Create a new note" }; - - DemoMultiDocumentPanel multiDocumentPanel; - void updateLayoutMode() { multiDocumentPanel.setLayoutMode (showInTabsButton.getToggleState() ? MultiDocumentPanel::MaximisedWindowsWithTabs @@ -261,5 +261,11 @@ private: createNotesForFiles (files); } + ToggleButton showInTabsButton { "Show with tabs" }; + TextButton addNoteButton { "Create a new note" }, + closeApplicationButton { "Close app" }; + + DemoMultiDocumentPanel multiDocumentPanel; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MDIDemo) }; diff --git a/examples/GUI/OpenGLDemo.h b/examples/GUI/OpenGLDemo.h index bf6cb26836..6c33b46b46 100644 --- a/examples/GUI/OpenGLDemo.h +++ b/examples/GUI/OpenGLDemo.h @@ -1103,25 +1103,25 @@ private: void selectTexture (int itemID) { - #if JUCE_MODAL_LOOPS_PERMITTED if (itemID == 1000) { - auto lastLocation = File::getSpecialLocation (File::userPicturesDirectory); + textureFileChooser = std::make_unique ("Choose an image to open...", + File::getSpecialLocation (File::userPicturesDirectory), + "*.jpg;*.jpeg;*.png;*.gif"); + auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles; - FileChooser fc ("Choose an image to open...", lastLocation, "*.jpg;*.jpeg;*.png;*.gif"); - - if (fc.browseForFileToOpen()) + textureFileChooser->launchAsync (chooserFlags, [this] (const FileChooser& fc) { - lastLocation = fc.getResult(); + if (fc.getResult() == File{}) + return; textures.add (new OpenGLUtils::TextureFromFile (fc.getResult())); updateTexturesList(); textureBox.setSelectedId (textures.size()); - } + }); } else - #endif { if (auto* t = textures[itemID - 1]) demo.setTexture (t); @@ -1135,10 +1135,8 @@ private: for (int i = 0; i < textures.size(); ++i) textureBox.addItem (textures.getUnchecked (i)->name, i + 1); - #if JUCE_MODAL_LOOPS_PERMITTED textureBox.addSeparator(); textureBox.addItem ("Load from a file...", 1000); - #endif } void updateShader() @@ -1208,6 +1206,8 @@ private: OwnedArray textures; + std::unique_ptr textureFileChooser; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DemoControlsOverlay) }; diff --git a/extras/AudioPluginHost/Builds/Android/app/src/main/assets/DSPDemos_Common.h b/extras/AudioPluginHost/Builds/Android/app/src/main/assets/DSPDemos_Common.h index 5bb3d966ff..25647e14f1 100644 --- a/extras/AudioPluginHost/Builds/Android/app/src/main/assets/DSPDemos_Common.h +++ b/extras/AudioPluginHost/Builds/Android/app/src/main/assets/DSPDemos_Common.h @@ -590,14 +590,13 @@ private: if (fileChooser != nullptr) return; - SafePointer safeThis (this); - if (! RuntimePermissions::isGranted (RuntimePermissions::readExternalStorage)) { + SafePointer safeThis (this); RuntimePermissions::request (RuntimePermissions::readExternalStorage, [safeThis] (bool granted) mutable { - if (granted) + if (safeThis != nullptr && granted) safeThis->openFile(); }); return; @@ -606,22 +605,19 @@ private: fileChooser.reset (new FileChooser ("Select an audio file...", File(), "*.wav;*.mp3;*.aif")); fileChooser->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles, - [safeThis] (const FileChooser& fc) mutable + [this] (const FileChooser& fc) mutable { - if (safeThis == nullptr) - return; - if (fc.getURLResults().size() > 0) { auto u = fc.getURLResult(); - if (! safeThis->audioFileReader.loadURL (u)) + if (! audioFileReader.loadURL (u)) NativeMessageBox::showOkCancelBox (AlertWindow::WarningIcon, "Error loading file", "Unable to load audio file", nullptr, nullptr); else - safeThis->thumbnailComp.setCurrentURL (u); + thumbnailComp.setCurrentURL (u); } - safeThis->fileChooser = nullptr; + fileChooser = nullptr; }, nullptr); } diff --git a/extras/AudioPluginHost/Source/UI/MainHostWindow.cpp b/extras/AudioPluginHost/Source/UI/MainHostWindow.cpp index 8695f50601..6ba07140d2 100644 --- a/extras/AudioPluginHost/Source/UI/MainHostWindow.cpp +++ b/extras/AudioPluginHost/Source/UI/MainHostWindow.cpp @@ -189,23 +189,45 @@ void MainHostWindow::tryToQuitApplication() // to flush any GUI events that may have been in transit before the app forces them to // be unloaded new AsyncQuitRetrier(); + return; } - else if (ModalComponentManager::getInstance()->cancelAllModalComponents()) + + if (ModalComponentManager::getInstance()->cancelAllModalComponents()) { new AsyncQuitRetrier(); + return; } - #if JUCE_ANDROID || JUCE_IOS - else if (graphHolder == nullptr || graphHolder->graph->saveDocument (PluginGraph::getDefaultGraphDocumentOnMobile())) - #else - else if (graphHolder == nullptr || graphHolder->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk) - #endif + + if (graphHolder != nullptr) { - // Some plug-ins do not want [NSApp stop] to be called - // before the plug-ins are not deallocated. - graphHolder->releaseGraph(); + auto releaseAndQuit = [this] + { + // Some plug-ins do not want [NSApp stop] to be called + // before the plug-ins are not deallocated. + graphHolder->releaseGraph(); + + JUCEApplication::quit(); + }; + + #if JUCE_ANDROID || JUCE_IOS + if (graphHolder->graph->saveDocument (PluginGraph::getDefaultGraphDocumentOnMobile())) + releaseAndQuit(); + #else + SafePointer parent { this }; + graphHolder->graph->saveIfNeededAndUserAgreesAsync ([parent, releaseAndQuit] (FileBasedDocument::SaveResult r) + { + if (parent == nullptr) + return; + + if (r == FileBasedDocument::savedOk) + releaseAndQuit(); + }); + #endif - JUCEApplication::quit(); + return; } + + JUCEApplication::quit(); } void MainHostWindow::changeListenerCallback (ChangeBroadcaster* changed) @@ -329,9 +351,20 @@ void MainHostWindow::menuItemSelected (int menuItemID, int /*topLevelMenuIndex*/ ->getValue ("recentFilterGraphFiles")); if (graphHolder != nullptr) + { if (auto* graph = graphHolder->graph.get()) - if (graph != nullptr && graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk) - graph->loadFrom (recentFiles.getFile (menuItemID - 100), true); + { + SafePointer parent { this }; + graph->saveIfNeededAndUserAgreesAsync ([parent, recentFiles, menuItemID] (FileBasedDocument::SaveResult r) + { + if (parent == nullptr) + return; + + if (r == FileBasedDocument::savedOk) + parent->graphHolder->graph->loadFrom (recentFiles.getFile (menuItemID - 100), true); + }); + } + } } #endif else if (menuItemID >= 200 && menuItemID < 210) @@ -492,23 +525,43 @@ bool MainHostWindow::perform (const InvocationInfo& info) { #if ! (JUCE_IOS || JUCE_ANDROID) case CommandIDs::newFile: - if (graphHolder != nullptr && graphHolder->graph != nullptr && graphHolder->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk) - graphHolder->graph->newDocument(); + if (graphHolder != nullptr && graphHolder->graph != nullptr) + { + SafePointer parent { this }; + graphHolder->graph->saveIfNeededAndUserAgreesAsync ([parent] (FileBasedDocument::SaveResult r) + { + if (parent == nullptr) + return; + + if (r == FileBasedDocument::savedOk) + parent->graphHolder->graph->newDocument(); + }); + } break; case CommandIDs::open: - if (graphHolder != nullptr && graphHolder->graph != nullptr && graphHolder->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk) - graphHolder->graph->loadFromUserSpecifiedFile (true); + if (graphHolder != nullptr && graphHolder->graph != nullptr) + { + SafePointer parent { this }; + graphHolder->graph->saveIfNeededAndUserAgreesAsync ([parent] (FileBasedDocument::SaveResult r) + { + if (parent == nullptr) + return; + + if (r == FileBasedDocument::savedOk) + parent->graphHolder->graph->loadFromUserSpecifiedFileAsync (true, [] (Result) {}); + }); + } break; case CommandIDs::save: if (graphHolder != nullptr && graphHolder->graph != nullptr) - graphHolder->graph->save (true, true); + graphHolder->graph->saveAsync (true, true, nullptr); break; case CommandIDs::saveAs: if (graphHolder != nullptr && graphHolder->graph != nullptr) - graphHolder->graph->saveAs (File(), true, true, true); + graphHolder->graph->saveAsAsync ({}, true, true, true, nullptr); break; #endif @@ -630,11 +683,22 @@ void MainHostWindow::filesDropped (const StringArray& files, int x, int y) if (graphHolder != nullptr) { #if ! (JUCE_ANDROID || JUCE_IOS) - if (files.size() == 1 && File (files[0]).hasFileExtension (PluginGraph::getFilenameSuffix())) + File firstFile { files[0] }; + + if (files.size() == 1 && firstFile.hasFileExtension (PluginGraph::getFilenameSuffix())) { if (auto* g = graphHolder->graph.get()) - if (g->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk) - g->loadFrom (File (files[0]), true); + { + SafePointer parent; + g->saveIfNeededAndUserAgreesAsync ([parent, g, firstFile] (FileBasedDocument::SaveResult r) + { + if (parent == nullptr) + return; + + if (r == FileBasedDocument::savedOk) + g->loadFrom (firstFile, true); + }); + } } else #endif diff --git a/extras/Projucer/Source/Application/StartPage/jucer_ContentComponents.h b/extras/Projucer/Source/Application/StartPage/jucer_ContentComponents.h index 766773c631..b9f5cf6525 100644 --- a/extras/Projucer/Source/Application/StartPage/jucer_ContentComponents.h +++ b/extras/Projucer/Source/Application/StartPage/jucer_ContentComponents.h @@ -99,25 +99,34 @@ public: { createProjectButton.onClick = [this] { - FileChooser fc ("Save Project", NewProjectWizard::getLastWizardFolder()); + chooser = std::make_unique ("Save Project", NewProjectWizard::getLastWizardFolder()); + auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories; - if (fc.browseForDirectory()) + chooser->launchAsync (flags, [this] (const FileChooser& fc) { auto dir = fc.getResult(); - if (auto project = NewProjectWizard::createNewProject (projectTemplate, - dir.getChildFile (projectNameValue.get().toString()), - projectNameValue.get(), - modulesValue.get(), - exportersValue.get(), - fileOptionsValue.get(), - modulePathValue.getCurrentValue(), - modulePathValue.getWrappedValueWithDefault().isUsingDefault())) + if (dir == File{}) + return; + + SafePointer safeThis { this }; + NewProjectWizard::createNewProject (projectTemplate, + dir.getChildFile (projectNameValue.get().toString()), + projectNameValue.get(), + modulesValue.get(), + exportersValue.get(), + fileOptionsValue.get(), + modulePathValue.getCurrentValue(), + modulePathValue.getWrappedValueWithDefault().isUsingDefault(), + [safeThis, dir] (std::unique_ptr project) { - projectCreatedCallback (std::move (project)); + if (safeThis == nullptr) + return; + + safeThis->projectCreatedCallback (std::move (project)); getAppSettings().lastWizardFolder = dir; - } - } + }); + }); }; addAndMakeVisible (createProjectButton); @@ -150,6 +159,7 @@ public: private: NewProjectTemplates::ProjectTemplate projectTemplate; + std::unique_ptr chooser; std::function)> projectCreatedCallback; ItemHeader header; diff --git a/extras/Projucer/Source/Application/StartPage/jucer_NewProjectWizard.cpp b/extras/Projucer/Source/Application/StartPage/jucer_NewProjectWizard.cpp index e611966779..49cfa72dcd 100644 --- a/extras/Projucer/Source/Application/StartPage/jucer_NewProjectWizard.cpp +++ b/extras/Projucer/Source/Application/StartPage/jucer_NewProjectWizard.cpp @@ -220,63 +220,91 @@ File NewProjectWizard::getLastWizardFolder() return lastFolderFallback; } -std::unique_ptr NewProjectWizard::createNewProject (const NewProjectTemplates::ProjectTemplate& projectTemplate, - const File& targetFolder, const String& name, var modules, var exporters, var fileOptions, - const String& modulePath, bool useGlobalModulePath) +static void displayFailedFilesMessage (const StringArray& failedFiles) +{ + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + TRANS("Errors in Creating Project!"), + TRANS("The following files couldn't be written:") + + "\n\n" + + failedFiles.joinIntoString ("\n", 0, 10)); +} + +template +static void prepareDirectory (const File& targetFolder, Callback&& callback) { StringArray failedFiles; if (! targetFolder.exists()) { if (! targetFolder.createDirectory()) - failedFiles.add (targetFolder.getFullPathName()); + { + displayFailedFilesMessage ({ targetFolder.getFullPathName() }); + return; + } } else if (FileHelpers::containsAnyNonHiddenFiles (targetFolder)) { - if (! AlertWindow::showOkCancelBox (AlertWindow::InfoIcon, - TRANS("New JUCE Project"), - TRANS("You chose the folder:\n\nXFLDRX\n\n").replace ("XFLDRX", targetFolder.getFullPathName()) - + TRANS("This folder isn't empty - are you sure you want to create the project there?") - + "\n\n" - + TRANS("Any existing files with the same names may be overwritten by the new files."))) - { - return nullptr; - } + AlertWindow::showOkCancelBox (AlertWindow::InfoIcon, + TRANS("New JUCE Project"), + TRANS("You chose the folder:\n\nXFLDRX\n\n").replace ("XFLDRX", targetFolder.getFullPathName()) + + TRANS("This folder isn't empty - are you sure you want to create the project there?") + + "\n\n" + + TRANS("Any existing files with the same names may be overwritten by the new files."), + {}, + {}, + nullptr, + ModalCallbackFunction::create ([callback] (int result) + { + if (result != 0) + callback(); + })); + + return; } - auto project = std::make_unique (targetFolder.getChildFile (File::createLegalFileName (name)) - .withFileExtension (Project::projectFileExtension)); + callback(); +} - if (failedFiles.isEmpty()) +void NewProjectWizard::createNewProject (const NewProjectTemplates::ProjectTemplate& projectTemplate, + const File& targetFolder, const String& name, var modules, var exporters, var fileOptions, + const String& modulePath, bool useGlobalModulePath, + std::function)> callback) +{ + prepareDirectory (targetFolder, [=] { + auto project = std::make_unique (targetFolder.getChildFile (File::createLegalFileName (name)) + .withFileExtension (Project::projectFileExtension)); + doBasicProjectSetup (*project, projectTemplate, name); + StringArray failedFiles; + if (addFiles (*project, projectTemplate, name, fileOptions, failedFiles)) { addExporters (*project, *exporters.getArray()); addModules (*project, *modules.getArray(), modulePath, useGlobalModulePath); - if (project->save (false, true) == FileBasedDocument::savedOk) - { - project->setChangedFlag (false); - project->loadFrom (project->getFile(), true); - } - else + auto sharedProject = std::make_shared> (std::move (project)); + (*sharedProject)->saveAsync (false, true, [sharedProject, failedFiles, callback] (FileBasedDocument::SaveResult r) { - failedFiles.add (project->getFile().getFullPathName()); - } + auto uniqueProject = std::move (*sharedProject.get()); + + if (r == FileBasedDocument::savedOk) + { + uniqueProject->setChangedFlag (false); + uniqueProject->loadFrom (uniqueProject->getFile(), true); + callback (std::move (uniqueProject)); + return; + } + + auto failedFilesCopy = failedFiles; + failedFilesCopy.add (uniqueProject->getFile().getFullPathName()); + displayFailedFilesMessage (failedFilesCopy); + }); + + return; } - } - - if (! failedFiles.isEmpty()) - { - AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, - TRANS("Errors in Creating Project!"), - TRANS("The following files couldn't be written:") - + "\n\n" - + failedFiles.joinIntoString ("\n", 0, 10)); - return nullptr; - } - return project; + displayFailedFilesMessage (failedFiles); + }); } diff --git a/extras/Projucer/Source/Application/StartPage/jucer_NewProjectWizard.h b/extras/Projucer/Source/Application/StartPage/jucer_NewProjectWizard.h index 1fe2329014..8fe20df014 100644 --- a/extras/Projucer/Source/Application/StartPage/jucer_NewProjectWizard.h +++ b/extras/Projucer/Source/Application/StartPage/jucer_NewProjectWizard.h @@ -32,7 +32,8 @@ namespace NewProjectWizard { File getLastWizardFolder(); - std::unique_ptr createNewProject (const NewProjectTemplates::ProjectTemplate& projectTemplate, - const File& targetFolder, const String& name, var modules, var exporters, var fileOptions, - const String& modulePath, bool useGlobalModulePath); + void createNewProject (const NewProjectTemplates::ProjectTemplate& projectTemplate, + const File& targetFolder, const String& name, var modules, var exporters, var fileOptions, + const String& modulePath, bool useGlobalModulePath, + std::function)> callback); } diff --git a/extras/Projucer/Source/Application/Windows/jucer_EditorColourSchemeWindowComponent.h b/extras/Projucer/Source/Application/Windows/jucer_EditorColourSchemeWindowComponent.h index d8e0bf749b..74f9d3b213 100644 --- a/extras/Projucer/Source/Application/Windows/jucer_EditorColourSchemeWindowComponent.h +++ b/extras/Projucer/Source/Application/Windows/jucer_EditorColourSchemeWindowComponent.h @@ -184,40 +184,52 @@ private: void saveScheme (bool isExit) { - FileChooser fc ("Select a file in which to save this colour-scheme...", - getAppSettings().appearance.getSchemesFolder() - .getNonexistentChildFile ("Scheme", AppearanceSettings::getSchemeFileSuffix()), - AppearanceSettings::getSchemeFileWildCard()); - - if (fc.browseForFileToSave (true)) + chooser = std::make_unique ("Select a file in which to save this colour-scheme...", + getAppSettings().appearance.getSchemesFolder() + .getNonexistentChildFile ("Scheme", AppearanceSettings::getSchemeFileSuffix()), + AppearanceSettings::getSchemeFileWildCard()); + auto chooserFlags = FileBrowserComponent::saveMode + | FileBrowserComponent::canSelectFiles + | FileBrowserComponent::warnAboutOverwriting; + + chooser->launchAsync (chooserFlags, [this, isExit] (const FileChooser& fc) { + if (fc.getResult() == File{}) + { + if (isExit) + restorePreviousScheme(); + + return; + } + File file (fc.getResult().withFileExtension (AppearanceSettings::getSchemeFileSuffix())); getAppSettings().appearance.writeToFile (file); getAppSettings().appearance.refreshPresetSchemeList(); saveSchemeState(); ProjucerApplication::getApp().selectEditorColourSchemeWithName (file.getFileNameWithoutExtension()); - } - else if (isExit) - { - restorePreviousScheme(); - } + }); } void loadScheme() { - FileChooser fc ("Please select a colour-scheme file to load...", - getAppSettings().appearance.getSchemesFolder(), - AppearanceSettings::getSchemeFileWildCard()); + chooser = std::make_unique ("Please select a colour-scheme file to load...", + getAppSettings().appearance.getSchemesFolder(), + AppearanceSettings::getSchemeFileWildCard()); + auto chooserFlags = FileBrowserComponent::openMode + | FileBrowserComponent::canSelectFiles; - if (fc.browseForFileToOpen()) + chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc) { + if (fc.getResult() == File{}) + return; + if (getAppSettings().appearance.readFromFile (fc.getResult())) { rebuildProperties(); saveSchemeState(); } - } + }); } void lookAndFeelChanged() override @@ -264,6 +276,7 @@ private: appearance.getColourValue (colourNames[i]).setValue (colourValues[i]); } + std::unique_ptr chooser; JUCE_DECLARE_NON_COPYABLE (EditorPanel) }; diff --git a/extras/Projucer/Source/Application/Windows/jucer_PIPCreatorWindowComponent.h b/extras/Projucer/Source/Application/Windows/jucer_PIPCreatorWindowComponent.h index a966372175..3413336f00 100644 --- a/extras/Projucer/Source/Application/Windows/jucer_PIPCreatorWindowComponent.h +++ b/extras/Projucer/Source/Application/Windows/jucer_PIPCreatorWindowComponent.h @@ -73,13 +73,20 @@ public: addAndMakeVisible (createButton); createButton.onClick = [this] { - FileChooser fc ("Save PIP File", - File::getSpecialLocation (File::SpecialLocationType::userDesktopDirectory) - .getChildFile (nameValue.get().toString() + ".h")); - - fc.browseForFileToSave (true); + chooser = std::make_unique ("Save PIP File", + File::getSpecialLocation (File::SpecialLocationType::userDesktopDirectory) + .getChildFile (nameValue.get().toString() + ".h")); + auto flags = FileBrowserComponent::saveMode + | FileBrowserComponent::canSelectFiles + | FileBrowserComponent::warnAboutOverwriting; + + chooser->launchAsync (flags, [this] (const FileChooser& fc) + { + const auto result = fc.getResult(); - createPIPFile (fc.getResult()); + if (result != File{}) + createPIPFile (result); + }); }; pipTree.addListener (this); @@ -333,6 +340,8 @@ private: TextButton createButton { "Create PIP" }; + std::unique_ptr chooser; + //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PIPCreatorWindowComponent) }; diff --git a/extras/Projucer/Source/Application/Windows/jucer_TranslationToolWindowComponent.h b/extras/Projucer/Source/Application/Windows/jucer_TranslationToolWindowComponent.h index 742445c58f..f681ad5bcd 100644 --- a/extras/Projucer/Source/Application/Windows/jucer_TranslationToolWindowComponent.h +++ b/extras/Projucer/Source/Application/Windows/jucer_TranslationToolWindowComponent.h @@ -33,9 +33,9 @@ class TranslationToolComponent : public Component public: TranslationToolComponent() : editorOriginal (documentOriginal, nullptr), - editorPre (documentPre, nullptr), - editorPost (documentPost, nullptr), - editorResult (documentResult, nullptr) + editorPre (documentPre, nullptr), + editorPost (documentPost, nullptr), + editorResult (documentResult, nullptr) { instructionsLabel.setText ( "This utility converts translation files to/from a format that can be passed to automatic translation tools." @@ -114,17 +114,7 @@ public: } private: - CodeDocument documentOriginal, documentPre, documentPost, documentResult; - CodeEditorComponent editorOriginal, editorPre, editorPost, editorResult; - - Label label1, label2, label3, label4; - Label instructionsLabel; - - TextButton generateButton { TRANS("Generate") }; - TextButton scanProjectButton { "Scan project for TRANS macros" }; - TextButton scanFolderButton { "Scan folder for TRANS macros" }; - TextButton loadTranslationButton { "Load existing translation file..."}; - + //============================================================================== void generate() { StringArray preStrings (TranslationHelpers::breakApart (documentPre.getAllContent())); @@ -154,28 +144,35 @@ private: void scanFolder() { - FileChooser fc ("Choose the root folder to search for the TRANS macros", - File(), "*"); + chooser = std::make_unique ("Choose the root folder to search for the TRANS macros", + File(), "*"); + auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories; - if (fc.browseForDirectory()) + chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc) { + if (fc.getResult() == File{}) + return; + StringArray strings; TranslationHelpers::scanFolderForTranslations (strings, fc.getResult()); setPreTranslationText (TranslationHelpers::mungeStrings(strings)); - } + }); } void loadFile() { - FileChooser fc ("Choose a translation file to load", - File(), "*"); + chooser = std::make_unique ("Choose a translation file to load", File(), "*"); + auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles; - if (fc.browseForFileToOpen()) + chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc) { + if (fc.getResult() == File{}) + return; + const LocalisedStrings loadedStrings (fc.getResult(), false); documentOriginal.replaceAllContent (fc.getResult().loadFileAsString().trim()); setPreTranslationText (TranslationHelpers::getPreTranslationText (loadedStrings)); - } + }); } void setPreTranslationText (const String& text) @@ -184,4 +181,18 @@ private: editorPre.grabKeyboardFocus(); editorPre.selectAll(); } + + //============================================================================== + CodeDocument documentOriginal, documentPre, documentPost, documentResult; + CodeEditorComponent editorOriginal, editorPre, editorPost, editorResult; + + Label label1, label2, label3, label4; + Label instructionsLabel; + + TextButton generateButton { TRANS("Generate") }, + scanProjectButton { "Scan project for TRANS macros" }, + scanFolderButton { "Scan folder for TRANS macros" }, + loadTranslationButton { "Load existing translation file..."}; + + std::unique_ptr chooser; }; diff --git a/extras/Projucer/Source/Application/jucer_Application.cpp b/extras/Projucer/Source/Application/jucer_Application.cpp index 5907027c6a..c59c9a1230 100644 --- a/extras/Projucer/Source/Application/jucer_Application.cpp +++ b/extras/Projucer/Source/Application/jucer_Application.cpp @@ -223,8 +223,11 @@ void ProjucerApplication::systemRequestedQuit() } else { - if (closeAllMainWindows()) - quit(); + closeAllMainWindows ([] (bool closedSuccessfully) + { + if (closedSuccessfully) + ProjucerApplication::quit(); + }); } } @@ -251,7 +254,7 @@ void ProjucerApplication::anotherInstanceStarted (const String& commandLine) ArgumentList list ({}, commandLine); for (auto& arg : list.arguments) - openFile (arg.resolveAsFile()); + openFile (arg.resolveAsFile(), nullptr); } } @@ -651,7 +654,7 @@ void ProjucerApplication::findAndLaunchExample (int selectedIndex) // example doesn't exist? jassert (example != File()); - openFile (example); + openFile (example, nullptr); } //============================================================================== @@ -863,7 +866,7 @@ void ProjucerApplication::handleMainMenuCommand (int menuItemID) if (menuItemID >= recentProjectsBaseID && menuItemID < (recentProjectsBaseID + 100)) { // open a file from the "recent files" menu - openFile (settings->recentFiles.getFile (menuItemID - recentProjectsBaseID)); + openFile (settings->recentFiles.getFile (menuItemID - recentProjectsBaseID), nullptr); } else if (menuItemID >= openWindowsBaseID && menuItemID < (openWindowsBaseID + 100)) { @@ -1095,23 +1098,33 @@ void ProjucerApplication::createNewProjectFromClipboard() tempFile.create(); tempFile.appendText (SystemClipboard::getTextFromClipboard()); - String errorString; + auto cleanup = [tempFile] (String errorString) + { + if (errorString.isNotEmpty()) + { + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "Error", errorString); + tempFile.deleteFile(); + } + }; if (! isPIPFile (tempFile)) { - errorString = "Clipboard does not contain a valid PIP."; - } - else if (! openFile (tempFile)) - { - errorString = "Couldn't create project from clipboard contents."; - mainWindowList.closeWindow (mainWindowList.windows.getLast()); + cleanup ("Clipboard does not contain a valid PIP."); + return; } - if (errorString.isNotEmpty()) + WeakReference parent { this }; + openFile (tempFile, [parent, cleanup] (bool openedSuccessfully) { - AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "Error", errorString); - tempFile.deleteFile(); - } + if (parent == nullptr) + return; + + if (! openedSuccessfully) + { + cleanup ("Couldn't create project from clipboard contents."); + parent->mainWindowList.closeWindow (parent->mainWindowList.windows.getLast()); + } + }); } void ProjucerApplication::createNewPIP() @@ -1121,45 +1134,57 @@ void ProjucerApplication::createNewPIP() void ProjucerApplication::askUserToOpenFile() { - FileChooser fc ("Open File"); + chooser = std::make_unique ("Open File"); + auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles; + + chooser->launchAsync (flags, [this] (const FileChooser& fc) + { + const auto result = fc.getResult(); - if (fc.browseForFileToOpen()) - openFile (fc.getResult()); + if (result != File{}) + openFile (result, nullptr); + }); } -bool ProjucerApplication::openFile (const File& file) +void ProjucerApplication::openFile (const File& file, std::function callback) { - return mainWindowList.openFile (file); + mainWindowList.openFile (file, std::move (callback)); } void ProjucerApplication::saveAllDocuments() { - openDocumentManager.saveAll(); + openDocumentManager.saveAllSyncWithoutAsking(); for (int i = 0; i < mainWindowList.windows.size(); ++i) if (auto* pcc = mainWindowList.windows.getUnchecked(i)->getProjectContentComponent()) pcc->refreshProjectTreeFileStatuses(); } -bool ProjucerApplication::closeAllDocuments (OpenDocumentManager::SaveIfNeeded askUserToSave) +void ProjucerApplication::closeAllDocuments (OpenDocumentManager::SaveIfNeeded askUserToSave) { - return openDocumentManager.closeAll (askUserToSave); + openDocumentManager.closeAllAsync (askUserToSave, nullptr); } -bool ProjucerApplication::closeAllMainWindows() +void ProjucerApplication::closeAllMainWindows (std::function callback) { - return mainWindowList.askAllWindowsToClose(); + mainWindowList.askAllWindowsToClose (std::move (callback)); } void ProjucerApplication::closeAllMainWindowsAndQuitIfNeeded() { - if (closeAllMainWindows()) + WeakReference parent; + closeAllMainWindows ([parent] (bool closedSuccessfully) { - #if ! JUCE_MAC - if (mainWindowList.windows.size() == 0) - systemRequestedQuit(); + #if JUCE_MAC + ignoreUnused (parent, closedSuccessfully); + #else + if (parent == nullptr) + return; + + if (closedSuccessfully && parent->mainWindowList.windows.size() == 0) + parent->systemRequestedQuit(); #endif - } + }); } void ProjucerApplication::clearRecentFiles() diff --git a/extras/Projucer/Source/Application/jucer_Application.h b/extras/Projucer/Source/Application/jucer_Application.h index 7c85a9b90d..15a3effc97 100644 --- a/extras/Projucer/Source/Application/jucer_Application.h +++ b/extras/Projucer/Source/Application/jucer_Application.h @@ -66,7 +66,7 @@ public: bool isGUIEditorEnabled() const; //============================================================================== - bool openFile (const File&); + void openFile (const File&, std::function); void showPathsWindow (bool highlightJUCEPath = false); PropertiesFile::Options getPropertyFileOptionsFor (const String& filename, bool isProjectSettings); void selectEditorColourSchemeWithName (const String& schemeName); @@ -119,8 +119,8 @@ private: void createNewPIP(); void askUserToOpenFile(); void saveAllDocuments(); - bool closeAllDocuments (OpenDocumentManager::SaveIfNeeded askUserToSave); - bool closeAllMainWindows(); + void closeAllDocuments (OpenDocumentManager::SaveIfNeeded askUserToSave); + void closeAllMainWindows (std::function); void closeAllMainWindowsAndQuitIfNeeded(); void clearRecentFiles(); @@ -216,6 +216,9 @@ private: int selectedColourSchemeIndex = 0, selectedEditorColourSchemeIndex = 0; + std::unique_ptr chooser; + //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjucerApplication) + JUCE_DECLARE_WEAK_REFERENCEABLE (ProjucerApplication) }; diff --git a/extras/Projucer/Source/Application/jucer_AutoUpdater.cpp b/extras/Projucer/Source/Application/jucer_AutoUpdater.cpp index 7a14e38288..3a3231c956 100644 --- a/extras/Projucer/Source/Application/jucer_AutoUpdater.cpp +++ b/extras/Projucer/Source/Application/jucer_AutoUpdater.cpp @@ -238,12 +238,17 @@ private: void LatestVersionCheckerAndUpdater::askUserForLocationToDownload (const VersionInfo::Asset& asset) { - FileChooser chooser ("Please select the location into which you would like to install the new version", - { getAppSettings().getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get() }); + chooser = std::make_unique ("Please select the location into which you would like to install the new version", + File { getAppSettings().getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get() }); + auto flags = FileBrowserComponent::openMode + | FileBrowserComponent::canSelectDirectories; - if (chooser.browseForDirectory()) + chooser->launchAsync (flags, [this, asset] (const FileChooser& fc) { - auto targetFolder = chooser.getResult(); + auto targetFolder = fc.getResult(); + + if (targetFolder == File{}) + return; // By default we will install into 'targetFolder/JUCE', but we should install into // 'targetFolder' if that is an existing JUCE directory. @@ -259,6 +264,15 @@ void LatestVersionCheckerAndUpdater::askUserForLocationToDownload (const Version auto targetFolderPath = targetFolder.getFullPathName(); + WeakReference parent { this }; + auto callback = ModalCallbackFunction::create ([parent, asset, targetFolder] (int result) + { + if (parent == nullptr || result == 0) + return; + + parent->downloadAndInstall (asset, targetFolder); + }); + if (willOverwriteJuceFolder) { if (targetFolder.getChildFile (".git").isDirectory()) @@ -269,25 +283,32 @@ void LatestVersionCheckerAndUpdater::askUserForLocationToDownload (const Version return; } - if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, "Overwrite Existing JUCE Folder?", - "Do you want to replace the folder\n\n" + targetFolderPath + "\n\nwith the latest version from juce.com?\n\n" - "This will move the existing folder to " + targetFolderPath + "_old.\n\n" - "Replacing the folder that contains the currently running Projucer executable may not work on Windows.")) - { - return; - } + AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, + "Overwrite Existing JUCE Folder?", + "Do you want to replace the folder\n\n" + targetFolderPath + "\n\nwith the latest version from juce.com?\n\n" + "This will move the existing folder to " + targetFolderPath + "_old.\n\n" + "Replacing the folder that contains the currently running Projucer executable may not work on Windows.", + {}, + {}, + nullptr, + callback); + return; } - else if (targetFolder.exists()) + + if (targetFolder.exists()) { - if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, "Existing File Or Directory", - "Do you want to move\n\n" + targetFolderPath + "\n\nto\n\n" + targetFolderPath + "_old?")) - { - return; - } + AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, + "Existing File Or Directory", + "Do you want to move\n\n" + targetFolderPath + "\n\nto\n\n" + targetFolderPath + "_old?", + {}, + {}, + nullptr, + callback); + return; } downloadAndInstall (asset, targetFolder); - } + }); } void LatestVersionCheckerAndUpdater::askUserAboutNewVersion (const String& newVersionString, diff --git a/extras/Projucer/Source/Application/jucer_AutoUpdater.h b/extras/Projucer/Source/Application/jucer_AutoUpdater.h index 1e9e1abbb8..54d4d9a655 100644 --- a/extras/Projucer/Source/Application/jucer_AutoUpdater.h +++ b/extras/Projucer/Source/Application/jucer_AutoUpdater.h @@ -56,4 +56,7 @@ private: std::unique_ptr installer; std::unique_ptr dialogWindow; + std::unique_ptr chooser; + + JUCE_DECLARE_WEAK_REFERENCEABLE (LatestVersionCheckerAndUpdater) }; diff --git a/extras/Projucer/Source/Application/jucer_CommandLine.cpp b/extras/Projucer/Source/Application/jucer_CommandLine.cpp index bd23f67aa8..eff87122f1 100644 --- a/extras/Projucer/Source/Application/jucer_CommandLine.cpp +++ b/extras/Projucer/Source/Application/jucer_CommandLine.cpp @@ -100,13 +100,18 @@ namespace if (fixMissingDependencies) tryToFixMissingModuleDependencies(); - auto error = justSaveResources ? project->saveResourcesOnly() - : project->saveProject(); + const auto onCompletion = [this] (Result result) + { + project.reset(); - project.reset(); + if (result.failed()) + ConsoleApplication::fail ("Error when saving: " + result.getErrorMessage()); + }; - if (error.failed()) - ConsoleApplication::fail ("Error when saving: " + error.getErrorMessage()); + if (justSaveResources) + onCompletion (project->saveResourcesOnly()); + else + project->saveProject (Async::no, nullptr, onCompletion); } } diff --git a/extras/Projucer/Source/Application/jucer_MainWindow.cpp b/extras/Projucer/Source/Application/jucer_MainWindow.cpp index 2707573078..a6a26ac58a 100644 --- a/extras/Projucer/Source/Application/jucer_MainWindow.cpp +++ b/extras/Projucer/Source/Application/jucer_MainWindow.cpp @@ -229,10 +229,15 @@ void MainWindow::closeButtonPressed() ProjucerApplication::getApp().mainWindowList.closeWindow (this); } -bool MainWindow::closeCurrentProject (OpenDocumentManager::SaveIfNeeded askUserToSave) +void MainWindow::closeCurrentProject (OpenDocumentManager::SaveIfNeeded askUserToSave, std::function callback) { if (currentProject == nullptr) - return true; + { + if (callback != nullptr) + callback (true); + + return; + } currentProject->getStoredProperties().setValue (getProjectWindowPosName(), getWindowStateAsString()); @@ -242,27 +247,65 @@ bool MainWindow::closeCurrentProject (OpenDocumentManager::SaveIfNeeded askUserT pcc->hideEditor(); } - if (ProjucerApplication::getApp().openDocumentManager - .closeAllDocumentsUsingProject (*currentProject, askUserToSave)) + SafePointer parent { this }; + ProjucerApplication::getApp().openDocumentManager + .closeAllDocumentsUsingProjectAsync (*currentProject, askUserToSave, [parent, askUserToSave, callback] (bool closedSuccessfully) { - if (askUserToSave == OpenDocumentManager::SaveIfNeeded::no - || (currentProject->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk)) + if (parent == nullptr) + return; + + if (! closedSuccessfully) { - setProject (nullptr); - return true; + if (callback != nullptr) + callback (false); + + return; } - } - return false; + auto setProjectAndCallback = [parent, callback] + { + parent->setProject (nullptr); + + if (callback != nullptr) + callback (true); + }; + + if (askUserToSave == OpenDocumentManager::SaveIfNeeded::no) + { + setProjectAndCallback(); + return; + } + + parent->currentProject->saveIfNeededAndUserAgreesAsync ([parent, setProjectAndCallback, callback] (FileBasedDocument::SaveResult saveResult) + { + if (parent == nullptr) + return; + + if (saveResult == FileBasedDocument::savedOk) + setProjectAndCallback(); + else if (callback != nullptr) + callback (false); + }); + }); } void MainWindow::moveProject (File newProjectFileToOpen, OpenInIDE openInIDE) { - closeCurrentProject (OpenDocumentManager::SaveIfNeeded::no); - openFile (newProjectFileToOpen); + SafePointer parent { this }; + closeCurrentProject (OpenDocumentManager::SaveIfNeeded::no, [parent, newProjectFileToOpen, openInIDE] (bool) + { + if (parent == nullptr) + return; + + parent->openFile (newProjectFileToOpen, [parent, openInIDE] (bool openedSuccessfully) + { + if (parent == nullptr) + return; - if (currentProject != nullptr && openInIDE == OpenInIDE::yes) - ProjucerApplication::getApp().getCommandManager().invokeDirectly (CommandIDs::openInIDE, false); + if (openedSuccessfully && parent->currentProject != nullptr && openInIDE == OpenInIDE::yes) + ProjucerApplication::getApp().getCommandManager().invokeDirectly (CommandIDs::openInIDE, false); + }); + }); } void MainWindow::setProject (std::unique_ptr newProject) @@ -308,44 +351,102 @@ bool MainWindow::canOpenFile (const File& file) const || ProjucerApplication::getApp().openDocumentManager.canOpenFile (file)); } -bool MainWindow::openFile (const File& file) +void MainWindow::openFile (const File& file, std::function callback) { if (file.hasFileExtension (Project::projectFileExtension)) { auto newDoc = std::make_unique (file); auto result = newDoc->loadFrom (file, true); - if (result.wasOk() && closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes)) + if (result.wasOk()) { - setProject (std::move (newDoc)); - currentProject->setChangedFlag (false); + SafePointer parent { this }; + auto sharedDoc = std::make_shared> (std::move (newDoc)); + closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes, [parent, sharedDoc, callback] (bool saveResult) + { + if (parent == nullptr) + return; + + if (saveResult) + { + parent->setProject (std::move (*sharedDoc.get())); + parent->currentProject->setChangedFlag (false); + + parent->createProjectContentCompIfNeeded(); + parent->getProjectContentComponent()->reloadLastOpenDocuments(); - createProjectContentCompIfNeeded(); - getProjectContentComponent()->reloadLastOpenDocuments(); + parent->currentProject->updateDeprecatedProjectSettingsInteractively(); + } - currentProject->updateDeprecatedProjectSettingsInteractively(); + if (callback != nullptr) + callback (saveResult); + }); - return true; + return; } + + if (callback != nullptr) + callback (false); + + return; } - else if (file.exists()) + + if (file.exists()) { - if (isPIPFile (file) && openPIP ({ file })) - return true; + SafePointer parent { this }; + auto createCompAndShowEditor = [parent, file, callback] + { + if (parent != nullptr) + { + parent->createProjectContentCompIfNeeded(); - createProjectContentCompIfNeeded(); - return getProjectContentComponent()->showEditorForFile (file, true); + if (callback != nullptr) + callback (parent->getProjectContentComponent()->showEditorForFile (file, true)); + } + }; + + if (isPIPFile (file)) + { + openPIP (file, [parent, createCompAndShowEditor, callback] (bool openedSuccessfully) + { + if (parent == nullptr) + return; + + if (openedSuccessfully) + { + if (callback != nullptr) + callback (true); + + return; + } + + createCompAndShowEditor(); + }); + + return; + } + + createCompAndShowEditor(); + return; } - return false; + if (callback != nullptr) + callback (false); } -bool MainWindow::openPIP (PIPGenerator generator) +void MainWindow::openPIP (const File& pipFile, std::function callback) { - if (! generator.hasValidPIP()) - return false; + auto generator = std::make_shared (pipFile); - auto generatorResult = generator.createJucerFile(); + if (! generator->hasValidPIP()) + { + if (callback != nullptr) + callback (false); + + return; + } + + auto generatorResult = generator->createJucerFile(); if (generatorResult != Result::ok()) { @@ -353,29 +454,47 @@ bool MainWindow::openPIP (PIPGenerator generator) "PIP Error.", generatorResult.getErrorMessage()); - return false; + if (callback != nullptr) + callback (false); + + return; } - if (! generator.createMainCpp()) + if (! generator->createMainCpp()) { AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "PIP Error.", "Failed to create Main.cpp."); - return false; + if (callback != nullptr) + callback (false); + + return; } - if (! openFile (generator.getJucerFile())) + SafePointer parent { this }; + openFile (generator->getJucerFile(), [parent, generator, callback] (bool openedSuccessfully) { - AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, - "PIP Error.", - "Failed to open .jucer file."); + if (parent == nullptr) + return; - return false; - } + if (! openedSuccessfully) + { + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + "PIP Error.", + "Failed to open .jucer file."); - setupTemporaryPIPProject (generator); - return true; + if (callback != nullptr) + callback (false); + + return; + } + + parent->setupTemporaryPIPProject (*generator); + + if (callback != nullptr) + callback (true); + }); } void MainWindow::setupTemporaryPIPProject (PIPGenerator& generator) @@ -408,15 +527,32 @@ bool MainWindow::isInterestedInFileDrag (const StringArray& filenames) return false; } -void MainWindow::filesDropped (const StringArray& filenames, int /*mouseX*/, int /*mouseY*/) +static void filesDroppedRecursive (Component::SafePointer parent, StringArray filenames) { - for (auto& filename : filenames) - { - const File f (filename); + if (filenames.isEmpty()) + return; - if (canOpenFile (f) && openFile (f)) - break; + auto f = filenames[0]; + filenames.remove (0); + + if (! parent->canOpenFile (f)) + { + filesDroppedRecursive (parent, filenames); + return; } + + parent->openFile (f, [parent, filenames] (bool openedSuccessfully) + { + if (parent == nullptr || ! openedSuccessfully) + return; + + filesDroppedRecursive (parent, filenames); + }); +} + +void MainWindow::filesDropped (const StringArray& filenames, int /*mouseX*/, int /*mouseY*/) +{ + filesDroppedRecursive (this, filenames); } bool MainWindow::shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails, @@ -472,7 +608,7 @@ void MainWindow::showStartPage() jassert (currentProject == nullptr); setContentOwned (new StartPageComponent ([this] (std::unique_ptr&& newProject) { setProject (std::move (newProject)); }, - [this] (const File& exampleFile) { openFile (exampleFile); }), + [this] (const File& exampleFile) { openFile (exampleFile, nullptr); }), true); setResizable (false, false); @@ -580,19 +716,38 @@ void MainWindowList::forceCloseAllWindows() windows.clear(); } -bool MainWindowList::askAllWindowsToClose() +static void askAllWindowsToCloseRecursive (WeakReference parent, std::function callback) { - saveCurrentlyOpenProjectList(); - - while (windows.size() > 0) + if (parent->windows.size() == 0) { - if (! windows[0]->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes)) - return false; + if (callback != nullptr) + callback (true); - windows.remove (0); + return; } - return true; + parent->windows[0]->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes, [parent, callback] (bool closedSuccessfully) + { + if (parent == nullptr) + return; + + if (! closedSuccessfully) + { + if (callback != nullptr) + callback (false); + + return; + } + + parent->windows.remove (0); + askAllWindowsToCloseRecursive (parent, std::move (callback)); + }); +} + +void MainWindowList::askAllWindowsToClose (std::function callback) +{ + saveCurrentlyOpenProjectList(); + askAllWindowsToCloseRecursive (this, std::move (callback)); } void MainWindowList::createWindowIfNoneAreOpen() @@ -613,11 +768,18 @@ void MainWindowList::closeWindow (MainWindow* w) else #endif { - if (w->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes)) + WeakReference parent { this }; + w->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes, [parent, w] (bool closedSuccessfully) { - windows.removeObject (w); - saveCurrentlyOpenProjectList(); - } + if (parent == nullptr) + return; + + if (closedSuccessfully) + { + parent->windows.removeObject (w); + parent->saveCurrentlyOpenProjectList(); + } + }); } } @@ -653,20 +815,31 @@ void MainWindowList::openDocument (OpenDocumentManager::Document* doc, bool grab getFrontmostWindow()->getProjectContentComponent()->showDocument (doc, grabFocus); } -bool MainWindowList::openFile (const File& file, bool openInBackground) +void MainWindowList::openFile (const File& file, std::function callback, bool openInBackground) { if (! file.exists()) - return false; + { + if (callback != nullptr) + callback (false); + + return; + } for (auto* w : windows) { if (w->getProject() != nullptr && w->getProject()->getFile() == file) { w->toFront (true); - return true; + + if (callback != nullptr) + callback (true); + + return; } } + WeakReference parent { this }; + if (file.hasFileExtension (Project::projectFileExtension) || isPIPFile (file)) { @@ -675,23 +848,37 @@ bool MainWindowList::openFile (const File& file, bool openInBackground) auto* w = getOrCreateEmptyWindow(); jassert (w != nullptr); - if (w->openFile (file)) + w->openFile (file, [parent, previousFrontWindow, w, openInBackground, callback] (bool openedSuccessfully) { - w->makeVisible(); - w->setResizable (true, false); - checkWindowBounds (*w); + if (parent == nullptr) + return; - if (openInBackground && previousFrontWindow != nullptr) - previousFrontWindow->toFront (true); + if (openedSuccessfully) + { + w->makeVisible(); + w->setResizable (true, false); + parent->checkWindowBounds (*w); - return true; - } + if (openInBackground && previousFrontWindow != nullptr) + previousFrontWindow->toFront (true); + } + else + { + parent->closeWindow (w); + } + + if (callback != nullptr) + callback (openedSuccessfully); + }); - closeWindow (w); - return false; + return; } - return getFrontmostWindow()->openFile (file); + getFrontmostWindow()->openFile (file, [parent, callback] (bool openedSuccessfully) + { + if (parent != nullptr && callback != nullptr) + callback (openedSuccessfully); + }); } MainWindow* MainWindowList::createNewMainWindow() @@ -841,7 +1028,7 @@ void MainWindowList::reopenLastProjects() for (auto& p : getAppSettings().getLastProjects()) if (p.existsAsFile()) - openFile (p, true); + openFile (p, nullptr, true); } void MainWindowList::sendLookAndFeelChange() diff --git a/extras/Projucer/Source/Application/jucer_MainWindow.h b/extras/Projucer/Source/Application/jucer_MainWindow.h index 3b187fe0ee..ac63ac0e40 100644 --- a/extras/Projucer/Source/Application/jucer_MainWindow.h +++ b/extras/Projucer/Source/Application/jucer_MainWindow.h @@ -53,7 +53,7 @@ public: //============================================================================== bool canOpenFile (const File& file) const; - bool openFile (const File& file); + void openFile (const File& file, std::function callback); void setProject (std::unique_ptr newProject); Project* getProject() const { return currentProject.get(); } @@ -61,7 +61,7 @@ public: void makeVisible(); void restoreWindowPosition(); void updateTitleBarIcon(); - bool closeCurrentProject (OpenDocumentManager::SaveIfNeeded askToSave); + void closeCurrentProject (OpenDocumentManager::SaveIfNeeded askToSave, std::function callback); void moveProject (File newProjectFile, OpenInIDE openInIDE); void showStartPage(); @@ -91,7 +91,7 @@ private: static const char* getProjectWindowPosName() { return "projectWindowPos"; } void createProjectContentCompIfNeeded(); - bool openPIP (PIPGenerator); + void openPIP (const File&, std::function callback); void setupTemporaryPIPProject (PIPGenerator&); void initialiseProjectWindow(); @@ -112,14 +112,14 @@ public: MainWindowList(); void forceCloseAllWindows(); - bool askAllWindowsToClose(); + void askAllWindowsToClose (std::function callback); void closeWindow (MainWindow*); void goToSiblingWindow (MainWindow*, int delta); void createWindowIfNoneAreOpen(); void openDocument (OpenDocumentManager::Document*, bool grabFocus); - bool openFile (const File& file, bool openInBackground = false); + void openFile (const File& file, std::function callback, bool openInBackground = false); MainWindow* createNewMainWindow(); MainWindow* getFrontmostWindow (bool createIfNotFound = true); @@ -142,4 +142,5 @@ private: bool isInReopenLastProjects = false; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindowList) + JUCE_DECLARE_WEAK_REFERENCEABLE (MainWindowList) }; diff --git a/extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.cpp b/extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.cpp index 416b744565..3d74110adb 100644 --- a/extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.cpp +++ b/extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.cpp @@ -53,8 +53,9 @@ public: bool refersToProject (Project& p) const override { return project == &p; } Project* getProject() const override { return project; } bool needsSaving() const override { return false; } - bool save() override { return true; } - bool saveAs() override { return false; } + bool saveSyncWithoutAsking() override { return true; } + void saveAsync (std::function) override {} + void saveAsAsync (std::function) override {} bool hasFileBeenModifiedExternally() override { return fileModificationTime != file.getLastModificationTime(); } void reloadFromFile() override { fileModificationTime = file.getLastModificationTime(); } String getName() const override { return file.getFileName(); } @@ -164,86 +165,201 @@ OpenDocumentManager::Document* OpenDocumentManager::getOpenDocument (int index) return documents.getUnchecked (index); } -FileBasedDocument::SaveResult OpenDocumentManager::saveIfNeededAndUserAgrees (OpenDocumentManager::Document* doc) +void OpenDocumentManager::saveIfNeededAndUserAgrees (OpenDocumentManager::Document* doc, + std::function callback) { if (! doc->needsSaving()) - return FileBasedDocument::savedOk; + { + if (callback != nullptr) + callback (FileBasedDocument::savedOk); - const int r = AlertWindow::showYesNoCancelBox (AlertWindow::QuestionIcon, - TRANS("Closing document..."), - TRANS("Do you want to save the changes to \"") - + doc->getName() + "\"?", - TRANS("Save"), - TRANS("Discard changes"), - TRANS("Cancel")); + return; + } - if (r == 1) // save changes - return doc->save() ? FileBasedDocument::savedOk - : FileBasedDocument::failedToWriteToFile; + WeakReference parent { this }; + AlertWindow::showYesNoCancelBox (AlertWindow::QuestionIcon, + TRANS("Closing document..."), + TRANS("Do you want to save the changes to \"") + + doc->getName() + "\"?", + TRANS("Save"), + TRANS("Discard changes"), + TRANS("Cancel"), + nullptr, + ModalCallbackFunction::create ([parent, doc, callback] (int r) + { + if (parent == nullptr) + return; - if (r == 2) // discard changes - return FileBasedDocument::savedOk; + if (r == 1) + { + doc->saveAsync ([parent, callback] (bool hasSaved) + { + if (parent == nullptr) + return; - return FileBasedDocument::userCancelledSave; -} + if (callback != nullptr) + callback (hasSaved ? FileBasedDocument::savedOk : FileBasedDocument::failedToWriteToFile); + }); + return; + } + if (callback != nullptr) + callback (r == 2 ? FileBasedDocument::savedOk : FileBasedDocument::userCancelledSave); + })); +} -bool OpenDocumentManager::closeDocument (int index, SaveIfNeeded saveIfNeeded) +bool OpenDocumentManager::closeDocumentWithoutSaving (Document* doc) { - if (Document* doc = documents [index]) + if (documents.contains (doc)) { - if (saveIfNeeded == SaveIfNeeded::yes) - if (saveIfNeededAndUserAgrees (doc) != FileBasedDocument::savedOk) - return false; - bool canClose = true; for (int i = listeners.size(); --i >= 0;) - if (DocumentCloseListener* l = listeners[i]) + if (auto* l = listeners[i]) if (! l->documentAboutToClose (doc)) canClose = false; if (! canClose) return false; - documents.remove (index); + documents.removeObject (doc); ProjucerApplication::getCommandManager().commandStatusChanged(); } return true; } -bool OpenDocumentManager::closeDocument (Document* document, SaveIfNeeded saveIfNeeded) +void OpenDocumentManager::closeDocumentAsync (Document* doc, SaveIfNeeded saveIfNeeded, std::function callback) { - return closeDocument (documents.indexOf (document), saveIfNeeded); + if (! documents.contains (doc)) + { + if (callback != nullptr) + callback (true); + + return; + } + + if (saveIfNeeded == SaveIfNeeded::yes) + { + WeakReference parent { this }; + saveIfNeededAndUserAgrees (doc, [parent, doc, callback] (FileBasedDocument::SaveResult result) + { + if (parent == nullptr) + return; + + if (result != FileBasedDocument::savedOk) + { + if (callback != nullptr) + callback (false); + + return; + } + + if (callback != nullptr) + callback (parent->closeDocumentWithoutSaving (doc)); + }); + + return; + } + + if (callback != nullptr) + callback (closeDocumentWithoutSaving (doc)); } -void OpenDocumentManager::closeFile (const File& f, SaveIfNeeded saveIfNeeded) +void OpenDocumentManager::closeFileWithoutSaving (const File& f) { for (int i = documents.size(); --i >= 0;) - if (Document* d = documents[i]) + if (auto* d = documents[i]) if (d->isForFile (f)) - closeDocument (i, saveIfNeeded); + closeDocumentWithoutSaving (d); } -bool OpenDocumentManager::closeAll (SaveIfNeeded askUserToSave) +static void closeLastAsyncRecusrsive (WeakReference parent, + OpenDocumentManager::SaveIfNeeded askUserToSave, + std::function callback) { - for (int i = getNumOpenDocuments(); --i >= 0;) - if (! closeDocument (i, askUserToSave)) - return false; + auto lastIndex = parent->getNumOpenDocuments() - 1; - return true; + if (lastIndex < 0) + { + if (callback != nullptr) + callback (true); + + return; + } + + parent->closeDocumentAsync (parent->getOpenDocument (lastIndex), + askUserToSave, + [parent, askUserToSave, callback] (bool closedSuccessfully) + { + if (parent == nullptr) + return; + + if (! closedSuccessfully) + { + if (callback != nullptr) + callback (false); + + return; + } + + closeLastAsyncRecusrsive (parent, askUserToSave, std::move (callback)); + }); +} + +void OpenDocumentManager::closeAllAsync (SaveIfNeeded askUserToSave, std::function callback) +{ + closeLastAsyncRecusrsive (this, askUserToSave, std::move (callback)); +} + +void OpenDocumentManager::closeLastDocumentUsingProjectRecursive (WeakReference parent, + Project* project, + SaveIfNeeded askUserToSave, + std::function callback) +{ + for (int i = documents.size(); --i >= 0;) + { + if (auto* d = documents[i]) + { + if (d->getProject() == project) + { + closeDocumentAsync (d, askUserToSave, [parent, project, askUserToSave, callback] (bool closedSuccessfully) + { + if (parent == nullptr) + return; + + if (! closedSuccessfully) + { + if (callback != nullptr) + callback (false); + + return; + } + + parent->closeLastDocumentUsingProjectRecursive (parent, project, askUserToSave, std::move (callback)); + }); + + return; + } + } + } + + if (callback != nullptr) + callback (true); +} + +void OpenDocumentManager::closeAllDocumentsUsingProjectAsync (Project& project, SaveIfNeeded askUserToSave, std::function callback) +{ + WeakReference parent { this }; + closeLastDocumentUsingProjectRecursive (parent, &project, askUserToSave, std::move (callback)); } -bool OpenDocumentManager::closeAllDocumentsUsingProject (Project& project, SaveIfNeeded saveIfNeeded) +void OpenDocumentManager::closeAllDocumentsUsingProjectWithoutSaving (Project& project) { for (int i = documents.size(); --i >= 0;) if (Document* d = documents[i]) if (d->refersToProject (project)) - if (! closeDocument (i, saveIfNeeded)) - return false; - - return true; + closeDocumentWithoutSaving (d); } bool OpenDocumentManager::anyFilesNeedSaving() const @@ -255,17 +371,13 @@ bool OpenDocumentManager::anyFilesNeedSaving() const return false; } -bool OpenDocumentManager::saveAll() +void OpenDocumentManager::saveAllSyncWithoutAsking() { for (int i = documents.size(); --i >= 0;) { - if (! documents.getUnchecked (i)->save()) - return false; - - ProjucerApplication::getCommandManager().commandStatusChanged(); + if (documents.getUnchecked (i)->saveSyncWithoutAsking()) + ProjucerApplication::getCommandManager().commandStatusChanged(); } - - return true; } void OpenDocumentManager::reloadModifiedFiles() diff --git a/extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.h b/extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.h index 7b56caca43..bfef885c9a 100644 --- a/extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.h +++ b/extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.h @@ -51,8 +51,9 @@ public: virtual String getType() const = 0; virtual File getFile() const = 0; virtual bool needsSaving() const = 0; - virtual bool save() = 0; - virtual bool saveAs() = 0; + virtual bool saveSyncWithoutAsking() = 0; + virtual void saveAsync (std::function) = 0; + virtual void saveAsAsync (std::function) = 0; virtual bool hasFileBeenModifiedExternally() = 0; virtual void reloadFromFile() = 0; virtual std::unique_ptr createEditor() = 0; @@ -72,14 +73,20 @@ public: bool canOpenFile (const File& file); Document* openFile (Project* project, const File& file); - bool closeDocument (int index, SaveIfNeeded saveIfNeeded); - bool closeDocument (Document* document, SaveIfNeeded saveIfNeeded); - bool closeAll (SaveIfNeeded askUserToSave); - bool closeAllDocumentsUsingProject (Project& project, SaveIfNeeded saveIfNeeded); - void closeFile (const File& f, SaveIfNeeded saveIfNeeded); + + void closeDocumentAsync (Document* document, SaveIfNeeded saveIfNeeded, std::function callback); + bool closeDocumentWithoutSaving (Document* document); + + void closeAllAsync (SaveIfNeeded askUserToSave, std::function callback); + void closeAllDocumentsUsingProjectAsync (Project& project, SaveIfNeeded askUserToSave, std::function callback); + void closeAllDocumentsUsingProjectWithoutSaving (Project& project); + + void closeFileWithoutSaving (const File& f); bool anyFilesNeedSaving() const; - bool saveAll(); - FileBasedDocument::SaveResult saveIfNeededAndUserAgrees (Document* doc); + + void saveAllSyncWithoutAsking(); + void saveIfNeededAndUserAgrees (Document* doc, std::function); + void reloadModifiedFiles(); void fileHasBeenRenamed (const File& oldFile, const File& newFile); @@ -112,11 +119,19 @@ public: private: + //============================================================================== + void closeLastDocumentUsingProjectRecursive (WeakReference, + Project*, + SaveIfNeeded, + std::function); + + //============================================================================== OwnedArray types; OwnedArray documents; Array listeners; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenDocumentManager) + JUCE_DECLARE_WEAK_REFERENCEABLE (OpenDocumentManager) }; //============================================================================== diff --git a/extras/Projucer/Source/CodeEditor/jucer_SourceCodeEditor.cpp b/extras/Projucer/Source/CodeEditor/jucer_SourceCodeEditor.cpp index b15d269dc3..34621588c4 100644 --- a/extras/Projucer/Source/CodeEditor/jucer_SourceCodeEditor.cpp +++ b/extras/Projucer/Source/CodeEditor/jucer_SourceCodeEditor.cpp @@ -98,7 +98,7 @@ static bool writeCodeDocToFile (const File& file, CodeDocument& doc) return temp.overwriteTargetFileWithTemporary(); } -bool SourceCodeDocument::save() +bool SourceCodeDocument::saveSyncWithoutAsking() { if (writeCodeDocToFile (getFile(), getCodeDocument())) { @@ -110,14 +110,28 @@ bool SourceCodeDocument::save() return false; } -bool SourceCodeDocument::saveAs() +void SourceCodeDocument::saveAsync (std::function callback) { - FileChooser fc (TRANS("Save As..."), getFile(), "*"); + callback (saveSyncWithoutAsking()); +} - if (! fc.browseForFileToSave (true)) - return true; +void SourceCodeDocument::saveAsAsync (std::function callback) +{ + chooser = std::make_unique (TRANS("Save As..."), getFile(), "*"); + auto flags = FileBrowserComponent::saveMode + | FileBrowserComponent::canSelectFiles + | FileBrowserComponent::warnAboutOverwriting; + + chooser->launchAsync (flags, [this, callback] (const FileChooser& fc) + { + if (fc.getResult() == File{}) + { + callback (true); + return; + } - return writeCodeDocToFile (fc.getResult(), getCodeDocument()); + callback (writeCodeDocToFile (fc.getResult(), getCodeDocument())); + }); } void SourceCodeDocument::updateLastState (CodeEditorComponent& editor) @@ -642,18 +656,31 @@ void CppCodeEditorComponent::performPopupMenuAction (int menuItemID) void CppCodeEditorComponent::insertComponentClass() { - AlertWindow aw (TRANS ("Insert a new Component class"), - TRANS ("Please enter a name for the new class"), - AlertWindow::NoIcon, nullptr); + asyncAlertWindow = std::make_unique (TRANS ("Insert a new Component class"), + TRANS ("Please enter a name for the new class"), + AlertWindow::NoIcon, + nullptr); - const char* classNameField = "Class Name"; + const String classNameField { "Class Name" }; - aw.addTextEditor (classNameField, String(), String(), false); - aw.addButton (TRANS ("Insert Code"), 1, KeyPress (KeyPress::returnKey)); - aw.addButton (TRANS ("Cancel"), 0, KeyPress (KeyPress::escapeKey)); + asyncAlertWindow->addTextEditor (classNameField, String(), String(), false); + asyncAlertWindow->addButton (TRANS ("Insert Code"), 1, KeyPress (KeyPress::returnKey)); + asyncAlertWindow->addButton (TRANS ("Cancel"), 0, KeyPress (KeyPress::escapeKey)); - while (aw.runModalLoop() != 0) + SafePointer parent { this }; + asyncAlertWindow->enterModalState (true, ModalCallbackFunction::create ([parent, classNameField] (int result) { + if (parent == nullptr) + return; + + auto& aw = *(parent->asyncAlertWindow); + + aw.exitModalState (result); + aw.setVisible (false); + + if (result == 0) + return; + auto className = aw.getTextEditorContents (classNameField).trim(); if (className == build_tools::makeValidIdentifier (className, false, true, false)) @@ -661,8 +688,10 @@ void CppCodeEditorComponent::insertComponentClass() String code (BinaryData::jucer_InlineComponentTemplate_h); code = code.replace ("%%component_class%%", className); - insertTextAtCaret (code); - break; + parent->insertTextAtCaret (code); + return; } - } + + parent->insertComponentClass(); + })); } diff --git a/extras/Projucer/Source/CodeEditor/jucer_SourceCodeEditor.h b/extras/Projucer/Source/CodeEditor/jucer_SourceCodeEditor.h index 35af349ace..e6f5e0fcd6 100644 --- a/extras/Projucer/Source/CodeEditor/jucer_SourceCodeEditor.h +++ b/extras/Projucer/Source/CodeEditor/jucer_SourceCodeEditor.h @@ -80,8 +80,9 @@ public: } void reloadFromFile() override; - bool save() override; - bool saveAs() override; + bool saveSyncWithoutAsking() override; + void saveAsync (std::function) override; + void saveAsAsync (std::function) override; std::unique_ptr createEditor() override; std::unique_ptr createViewer() override { return createEditor(); } @@ -132,6 +133,9 @@ protected: std::unique_ptr lastState; void reloadInternal(); + +private: + std::unique_ptr chooser; }; class GenericCodeEditorComponent; @@ -235,5 +239,7 @@ public: private: void insertComponentClass(); + std::unique_ptr asyncAlertWindow; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CppCodeEditorComponent) }; diff --git a/extras/Projucer/Source/ComponentEditor/Components/jucer_ComponentTypeHandler.h b/extras/Projucer/Source/ComponentEditor/Components/jucer_ComponentTypeHandler.h index ca295ae909..2b2a2bcb7f 100644 --- a/extras/Projucer/Source/ComponentEditor/Components/jucer_ComponentTypeHandler.h +++ b/extras/Projucer/Source/ComponentEditor/Components/jucer_ComponentTypeHandler.h @@ -140,7 +140,7 @@ protected: String colourIdCode, colourName, xmlTagName; }; - OwnedArray colours; + OwnedArray colours; private: JUCE_DECLARE_NON_COPYABLE (ComponentTypeHandler) diff --git a/extras/Projucer/Source/ComponentEditor/Components/jucer_TabbedComponentHandler.h b/extras/Projucer/Source/ComponentEditor/Components/jucer_TabbedComponentHandler.h index 6f1f8503a5..0a777f3802 100644 --- a/extras/Projucer/Source/ComponentEditor/Components/jucer_TabbedComponentHandler.h +++ b/extras/Projucer/Source/ComponentEditor/Components/jucer_TabbedComponentHandler.h @@ -672,13 +672,13 @@ private: m.addItem (i + 1, "Delete tab " + String (i) + ": \"" + names[i] + "\""); - const int r = m.showAt (this); - - if (r > 0) + PopupMenu::Options options{}; + m.showMenuAsync (PopupMenu::Options().withTargetComponent (this), [this] (int r) { - document.perform (new RemoveTabAction (component, *document.getComponentLayout(), r - 1), - "Remove a tab"); - } + if (r > 0) + document.perform (new RemoveTabAction (component, *document.getComponentLayout(), r - 1), + "Remove a tab"); + }); } String getButtonText() const @@ -1131,11 +1131,13 @@ private: m.addItem (1, "Move this tab up", tabIndex > 0); m.addItem (2, "Move this tab down", tabIndex < totalNumTabs - 1); - const int r = m.showAt (this); - - if (r != 0) - document.perform (new MoveTabAction (component, *document.getComponentLayout(), tabIndex, tabIndex + (r == 2 ? 1 : -1)), - "Move a tab"); + PopupMenu::Options options{}; + m.showMenuAsync (PopupMenu::Options().withTargetComponent (this), [this] (int r) + { + if (r != 0) + document.perform (new MoveTabAction (component, *document.getComponentLayout(), tabIndex, tabIndex + (r == 2 ? 1 : -1)), + "Move a tab"); + }); } String getButtonText() const diff --git a/extras/Projucer/Source/ComponentEditor/PaintElements/jucer_ImageResourceProperty.h b/extras/Projucer/Source/ComponentEditor/PaintElements/jucer_ImageResourceProperty.h index d3c2cc1966..0e731bba54 100644 --- a/extras/Projucer/Source/ComponentEditor/PaintElements/jucer_ImageResourceProperty.h +++ b/extras/Projucer/Source/ComponentEditor/PaintElements/jucer_ImageResourceProperty.h @@ -74,14 +74,16 @@ public: { if (newIndex == 0) { - String resource (document.getResources() - .browseForResource ("Select an image file to add as a resource", - "*.jpg;*.jpeg;*.png;*.gif;*.svg", - File(), - String())); - - if (resource.isNotEmpty()) - setResource (resource); + document.getResources() + .browseForResource ("Select an image file to add as a resource", + "*.jpg;*.jpeg;*.png;*.gif;*.svg", + File(), + String(), + [this] (String resource) + { + if (resource.isNotEmpty()) + setResource (resource); + }); } else { diff --git a/extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElement.cpp b/extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElement.cpp index 12d5b1df0b..387d14aeee 100644 --- a/extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElement.cpp +++ b/extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElement.cpp @@ -660,7 +660,6 @@ void PaintElement::updateSiblingComps() } } - void PaintElement::showPopupMenu() { auto* commandManager = &ProjucerApplication::getCommandManager(); @@ -685,5 +684,5 @@ void PaintElement::showPopupMenu() m.addCommandItem (commandManager, StandardApplicationCommandIDs::paste); m.addCommandItem (commandManager, StandardApplicationCommandIDs::del); - m.show(); + m.showMenuAsync ({}); } diff --git a/extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElement.h b/extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElement.h index 5ab7104c8d..cf7b802996 100644 --- a/extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElement.h +++ b/extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElement.h @@ -124,7 +124,7 @@ protected: void siblingComponentsChanged(); - OwnedArray siblingComponents; + OwnedArray siblingComponents; void updateSiblingComps(); diff --git a/extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElementPath.h b/extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElementPath.h index 047d4d9613..b06fdf4d07 100644 --- a/extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElementPath.h +++ b/extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElementPath.h @@ -127,7 +127,7 @@ public: private: friend class PathPoint; friend class PathPointComponent; - OwnedArray points; + OwnedArray points; bool nonZeroWinding; mutable Path path; mutable Rectangle lastPathBounds; diff --git a/extras/Projucer/Source/ComponentEditor/Properties/jucer_PositionPropertyBase.h b/extras/Projucer/Source/ComponentEditor/Properties/jucer_PositionPropertyBase.h index 9a0f482e16..8e21936539 100644 --- a/extras/Projucer/Source/ComponentEditor/Properties/jucer_PositionPropertyBase.h +++ b/extras/Projucer/Source/ComponentEditor/Properties/jucer_PositionPropertyBase.h @@ -63,8 +63,15 @@ public: button.setConnectedEdges (TextButton::ConnectedOnLeft | TextButton::ConnectedOnRight); button.onClick = [this] { - if (showMenu (layout)) - refresh(); // (to clear the text editor if it's got focus) + SafePointer safeThis { this }; + showMenu (layout, [safeThis] (bool shouldRefresh) + { + if (safeThis == nullptr) + return; + + if (shouldRefresh) + safeThis->refresh(); // (to clear the text editor if it's got focus) + }); }; textEditor.reset (new PositionPropLabel (*this)); @@ -173,7 +180,7 @@ public: refresh(); } - bool showMenu (ComponentLayout* compLayout) + void showMenu (ComponentLayout* compLayout, std::function callback) { RelativePositionedRectangle rpr (getPosition()); PositionedRectangle p (rpr.rect); @@ -255,127 +262,135 @@ public: m.addSubMenu ("Relative to", compLayout->getRelativeTargetMenu (component, (int) dimension)); } - WeakReference ref (this); - - const int menuResult = m.showAt (&button); - - if (menuResult == 0 || ref == nullptr) - return false; + SafePointer ref (this); - switch (menuResult) + m.showMenuAsync (PopupMenu::Options().withTargetComponent (&button), + [ref, compLayout, callback, xAnchor, yAnchor, xMode, yMode, sizeW, sizeH, p, rpr] (int menuResult) mutable { - case 10: - if (dimension == componentX) - xMode = PositionedRectangle::absoluteFromParentTopLeft; - else - yMode = PositionedRectangle::absoluteFromParentTopLeft; - break; - - case 11: - if (dimension == componentX) - xMode = PositionedRectangle::absoluteFromParentBottomRight; - else - yMode = PositionedRectangle::absoluteFromParentBottomRight; - break; - - case 12: - if (dimension == componentX) - xMode = PositionedRectangle::absoluteFromParentCentre; - else - yMode = PositionedRectangle::absoluteFromParentCentre; - break; - - case 13: - if (dimension == componentX) - xMode = PositionedRectangle::proportionOfParentSize; - else - yMode = PositionedRectangle::proportionOfParentSize; - break; - - case 14: - if (dimension == componentX) - xAnchor = PositionedRectangle::anchorAtLeftOrTop; - else - yAnchor = PositionedRectangle::anchorAtLeftOrTop; - break; - - case 15: - if (dimension == componentX) - xAnchor = PositionedRectangle::anchorAtCentre; - else - yAnchor = PositionedRectangle::anchorAtCentre; - break; - - case 16: - if (dimension == componentX) - xAnchor = PositionedRectangle::anchorAtRightOrBottom; - else - yAnchor = PositionedRectangle::anchorAtRightOrBottom; - break; - - case 20: - if (dimension == componentWidth) - sizeW = PositionedRectangle::absoluteSize; - else - sizeH = PositionedRectangle::absoluteSize; - break; - - case 21: - if (dimension == componentWidth) - sizeW = PositionedRectangle::proportionalSize; - else - sizeH = PositionedRectangle::proportionalSize; - break; + if (menuResult == 0 || ref == nullptr) + { + callback (false); + return; + } - case 22: - if (dimension == componentWidth) - sizeW = PositionedRectangle::parentSizeMinusAbsolute; - else - sizeH = PositionedRectangle::parentSizeMinusAbsolute; - break; + switch (menuResult) + { + case 10: + if (ref->dimension == componentX) + xMode = PositionedRectangle::absoluteFromParentTopLeft; + else + yMode = PositionedRectangle::absoluteFromParentTopLeft; + break; + + case 11: + if (ref->dimension == componentX) + xMode = PositionedRectangle::absoluteFromParentBottomRight; + else + yMode = PositionedRectangle::absoluteFromParentBottomRight; + break; + + case 12: + if (ref->dimension == componentX) + xMode = PositionedRectangle::absoluteFromParentCentre; + else + yMode = PositionedRectangle::absoluteFromParentCentre; + break; + + case 13: + if (ref->dimension == componentX) + xMode = PositionedRectangle::proportionOfParentSize; + else + yMode = PositionedRectangle::proportionOfParentSize; + break; + + case 14: + if (ref->dimension == componentX) + xAnchor = PositionedRectangle::anchorAtLeftOrTop; + else + yAnchor = PositionedRectangle::anchorAtLeftOrTop; + break; + + case 15: + if (ref->dimension == componentX) + xAnchor = PositionedRectangle::anchorAtCentre; + else + yAnchor = PositionedRectangle::anchorAtCentre; + break; + + case 16: + if (ref->dimension == componentX) + xAnchor = PositionedRectangle::anchorAtRightOrBottom; + else + yAnchor = PositionedRectangle::anchorAtRightOrBottom; + break; + + case 20: + if (ref->dimension == componentWidth) + sizeW = PositionedRectangle::absoluteSize; + else + sizeH = PositionedRectangle::absoluteSize; + break; + + case 21: + if (ref->dimension == componentWidth) + sizeW = PositionedRectangle::proportionalSize; + else + sizeH = PositionedRectangle::proportionalSize; + break; + + case 22: + if (ref->dimension == componentWidth) + sizeW = PositionedRectangle::parentSizeMinusAbsolute; + else + sizeH = PositionedRectangle::parentSizeMinusAbsolute; + break; + + default: + if (ref->allowRelativeOptions && compLayout != nullptr) + compLayout->processRelativeTargetMenuResult (ref->component, (int) ref->dimension, menuResult); + break; + } - default: - if (allowRelativeOptions && compLayout != nullptr) - compLayout->processRelativeTargetMenuResult (component, (int) dimension, menuResult); - break; - } + const auto parentArea = [&]() -> Rectangle + { + if (ref->component->findParentComponentOfClass() != nullptr) + return { ref->component->getParentWidth(), ref->component->getParentHeight() }; - Rectangle parentArea; + if (auto pre = dynamic_cast (ref->component->getParentComponent())) + return pre->getComponentArea(); - if (component->findParentComponentOfClass() != nullptr) - parentArea.setSize (component->getParentWidth(), component->getParentHeight()); - else if (auto pre = dynamic_cast (component->getParentComponent())) - parentArea = pre->getComponentArea(); - else - jassertfalse; + jassertfalse; + return {}; + }(); - int x, xw, y, yh, w, h; - rpr.getRelativeTargetBounds (parentArea, compLayout, x, xw, y, yh, w, h); + int x, xw, y, yh, w, h; + rpr.getRelativeTargetBounds (parentArea, compLayout, x, xw, y, yh, w, h); - PositionedRectangle xyRect (p); - PositionedRectangle whRect (p); + PositionedRectangle xyRect (p); + PositionedRectangle whRect (p); - xyRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH, - Rectangle (x, y, xw, yh)); + xyRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH, + Rectangle (x, y, xw, yh)); - whRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH, - Rectangle (x, y, w, h)); + whRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH, + Rectangle (x, y, w, h)); - p.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH, - Rectangle (x, y, xw, yh)); + p.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH, + Rectangle (x, y, xw, yh)); - p.setX (xyRect.getX()); - p.setY (xyRect.getY()); - p.setWidth (whRect.getWidth()); - p.setHeight (whRect.getHeight()); + p.setX (xyRect.getX()); + p.setY (xyRect.getY()); + p.setWidth (whRect.getWidth()); + p.setHeight (whRect.getHeight()); - if (p != rpr.rect) - { - rpr.rect = p; - setPosition (rpr); - } + if (p != rpr.rect) + { + rpr.rect = p; + ref->setPosition (rpr); + } - return true; + callback (true); + }); } void resized() diff --git a/extras/Projucer/Source/ComponentEditor/UI/jucer_ComponentLayoutEditor.cpp b/extras/Projucer/Source/ComponentEditor/UI/jucer_ComponentLayoutEditor.cpp index 292f670a43..92a5cbb387 100644 --- a/extras/Projucer/Source/ComponentEditor/UI/jucer_ComponentLayoutEditor.cpp +++ b/extras/Projucer/Source/ComponentEditor/UI/jucer_ComponentLayoutEditor.cpp @@ -282,7 +282,7 @@ void ComponentLayoutEditor::mouseDown (const MouseEvent& e) for (int i = 0; i < ObjectTypes::numComponentTypes; ++i) m.addCommandItem (commandManager, JucerCommandIDs::newComponentBase + i); - m.show(); + m.showMenuAsync (PopupMenu::Options()); } else { @@ -387,7 +387,7 @@ bool ComponentLayoutEditor::isInterestedInDragSource (const SourceDetails& dragS void ComponentLayoutEditor::itemDropped (const SourceDetails& dragSourceDetails) { - OwnedArray selectedNodes; + OwnedArray selectedNodes; ProjectContentComponent::getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes); StringArray filenames; diff --git a/extras/Projucer/Source/ComponentEditor/UI/jucer_PaintRoutineEditor.cpp b/extras/Projucer/Source/ComponentEditor/UI/jucer_PaintRoutineEditor.cpp index 43b4b40388..6087349f8b 100644 --- a/extras/Projucer/Source/ComponentEditor/UI/jucer_PaintRoutineEditor.cpp +++ b/extras/Projucer/Source/ComponentEditor/UI/jucer_PaintRoutineEditor.cpp @@ -212,7 +212,7 @@ void PaintRoutineEditor::mouseDown (const MouseEvent& e) for (int i = 0; i < ObjectTypes::numElementTypes; ++i) m.addCommandItem (commandManager, JucerCommandIDs::newElementBase + i); - m.show(); + m.showMenuAsync (PopupMenu::Options()); } else { diff --git a/extras/Projucer/Source/ComponentEditor/UI/jucer_ResourceEditorPanel.cpp b/extras/Projucer/Source/ComponentEditor/UI/jucer_ResourceEditorPanel.cpp index 6c5f8ffa5c..7885bdc589 100644 --- a/extras/Projucer/Source/ComponentEditor/UI/jucer_ResourceEditorPanel.cpp +++ b/extras/Projucer/Source/ComponentEditor/UI/jucer_ResourceEditorPanel.cpp @@ -39,7 +39,7 @@ public: { if (auto* r = document.getResources() [row]) document.getResources().browseForResource ("Select a file to replace this resource", "*", - File (r->originalFilename), r->name); + File (r->originalFilename), r->name, nullptr); }; } @@ -69,7 +69,10 @@ ResourceEditorPanel::ResourceEditorPanel (JucerDocument& doc) delButton ("Delete selected resources") { addAndMakeVisible (addButton); - addButton.onClick = [this] { document.getResources().browseForResource ("Select a file to add as a resource", "*", {}, {}); }; + addButton.onClick = [this] + { + document.getResources().browseForResource ("Select a file to add as a resource", "*", {}, {}, nullptr); + }; addAndMakeVisible (reloadAllButton); reloadAllButton.onClick = [this] { reloadAll(); }; @@ -258,16 +261,12 @@ void ResourceEditorPanel::reloadAll() StringArray failed; for (int i = 0; i < document.getResources().size(); ++i) - { if (! document.getResources().reload (i)) failed.add (document.getResources().getResourceNames() [i]); - } if (failed.size() > 0) - { - AlertWindow::showMessageBox (AlertWindow::WarningIcon, - TRANS("Reloading resources"), - TRANS("The following resources couldn't be reloaded from their original files:\n\n") - + failed.joinIntoString (", ")); - } + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + TRANS("Reloading resources"), + TRANS("The following resources couldn't be reloaded from their original files:\n\n") + + failed.joinIntoString (", ")); } diff --git a/extras/Projucer/Source/ComponentEditor/jucer_BinaryResources.cpp b/extras/Projucer/Source/ComponentEditor/jucer_BinaryResources.cpp index 6a97a4a901..03beab082c 100644 --- a/extras/Projucer/Source/ComponentEditor/jucer_BinaryResources.cpp +++ b/extras/Projucer/Source/ComponentEditor/jucer_BinaryResources.cpp @@ -27,14 +27,6 @@ #include "jucer_JucerDocument.h" //============================================================================== -BinaryResources::BinaryResources() -{ -} - -BinaryResources::~BinaryResources() -{ -} - BinaryResources& BinaryResources::operator= (const BinaryResources& other) { for (auto* r : other.resources) @@ -130,15 +122,20 @@ bool BinaryResources::reload (const int index) File (resources [index]->originalFilename)); } -String BinaryResources::browseForResource (const String& title, - const String& wildcard, - const File& fileToStartFrom, - const String& resourceToReplace) +void BinaryResources::browseForResource (const String& title, + const String& wildcard, + const File& fileToStartFrom, + const String& resourceToReplace, + std::function callback) { - FileChooser fc (title, fileToStartFrom, wildcard); + chooser = std::make_unique (title, fileToStartFrom, wildcard); + auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles; - if (fc.browseForFileToOpen()) + chooser->launchAsync (flags, [this, resourceToReplace, callback] (const FileChooser& fc) { + if (fc.getResult() == File{}) + callback ({}); + String name (resourceToReplace); if (name.isEmpty()) @@ -146,17 +143,15 @@ String BinaryResources::browseForResource (const String& title, if (! add (name, fc.getResult())) { - AlertWindow::showMessageBox (AlertWindow::WarningIcon, - TRANS("Adding Resource"), - TRANS("Failed to load the file!")); + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + TRANS("Adding Resource"), + TRANS("Failed to load the file!")); name.clear(); } - return name; - } - - return {}; + callback (name); + }); } String BinaryResources::findUniqueName (const String& rootName) const diff --git a/extras/Projucer/Source/ComponentEditor/jucer_BinaryResources.h b/extras/Projucer/Source/ComponentEditor/jucer_BinaryResources.h index 5dce1278b8..2f2ef9cd32 100644 --- a/extras/Projucer/Source/ComponentEditor/jucer_BinaryResources.h +++ b/extras/Projucer/Source/ComponentEditor/jucer_BinaryResources.h @@ -36,9 +36,6 @@ class BinaryResources { public: //============================================================================== - BinaryResources(); - ~BinaryResources(); - BinaryResources& operator= (const BinaryResources& other); void loadFromCpp (const File& cppFileLocation, const String& cpp); @@ -57,8 +54,9 @@ public: void add (const String& name, const String& originalFileName, const MemoryBlock& data); void remove (const int index); bool reload (const int index); - String browseForResource (const String& title, const String& wildcard, - const File& fileToStartFrom, const String& resourceToReplace); + void browseForResource (const String& title, const String& wildcard, + const File& fileToStartFrom, const String& resourceToReplace, + std::function callback); String findUniqueName (const String& rootName) const; @@ -86,12 +84,13 @@ public: void fillInGeneratedCode (GeneratedCode& code) const; - private: //============================================================================== - JucerDocument* document; - OwnedArray resources; - BinaryResource* findResource (const String& name) const noexcept; void changed(); + + //============================================================================== + JucerDocument* document; + OwnedArray resources; + std::unique_ptr chooser; }; diff --git a/extras/Projucer/Source/ComponentEditor/jucer_ComponentLayout.h b/extras/Projucer/Source/ComponentEditor/jucer_ComponentLayout.h index bcce3fc1bd..224acfe4a9 100644 --- a/extras/Projucer/Source/ComponentEditor/jucer_ComponentLayout.h +++ b/extras/Projucer/Source/ComponentEditor/jucer_ComponentLayout.h @@ -122,7 +122,7 @@ public: private: JucerDocument* document; - OwnedArray components; + OwnedArray components; SelectedItemSet selected; int nextCompUID; diff --git a/extras/Projucer/Source/ComponentEditor/jucer_JucerDocument.cpp b/extras/Projucer/Source/ComponentEditor/jucer_JucerDocument.cpp index 3093d404a7..f6f98ea534 100644 --- a/extras/Projucer/Source/ComponentEditor/jucer_JucerDocument.cpp +++ b/extras/Projucer/Source/ComponentEditor/jucer_JucerDocument.cpp @@ -692,25 +692,52 @@ public: { } - bool save() override + void saveAsync (std::function callback) override { - return SourceCodeDocument::save() && saveHeader(); + WeakReference parent { this }; + SourceCodeDocument::saveAsync ([parent, callback] (bool saveResult) + { + if (parent == nullptr) + return; + + if (! saveResult) + { + callback (false); + return; + } + + parent->saveHeaderAsync ([parent, callback] (bool headerSaveResult) + { + if (parent != nullptr) + callback (headerSaveResult); + }); + }); } - bool saveHeader() + void saveHeaderAsync (std::function callback) { auto& odm = ProjucerApplication::getApp().openDocumentManager; if (auto* header = odm.openFile (nullptr, getFile().withFileExtension (".h"))) { - if (header->save()) + WeakReference parent { this }; + header->saveAsync ([parent, callback] (bool saveResult) { - odm.closeFile (getFile().withFileExtension(".h"), OpenDocumentManager::SaveIfNeeded::no); - return true; - } + if (parent == nullptr) + return; + + if (saveResult) + ProjucerApplication::getApp() + .openDocumentManager + .closeFileWithoutSaving (parent->getFile().withFileExtension (".h")); + + callback (saveResult); + }); + + return; } - return false; + callback (false); } std::unique_ptr createEditor() override @@ -733,6 +760,8 @@ public: bool canOpenFile (const File& f) override { return JucerDocument::isValidJucerCppFile (f); } Document* openFile (Project* p, const File& f) override { return new JucerComponentDocument (p, f); } }; + + JUCE_DECLARE_WEAK_REFERENCEABLE (JucerComponentDocument) }; OpenDocumentManager::DocumentType* createGUIDocumentType(); @@ -744,53 +773,65 @@ OpenDocumentManager::DocumentType* createGUIDocumentType() //============================================================================== struct NewGUIComponentWizard : public NewFileWizard::Type { - NewGUIComponentWizard() {} + NewGUIComponentWizard (Project& proj) + : project (proj) + {} String getName() override { return "GUI Component"; } - void createNewFile (Project& project, Project::Item parent) override + void createNewFile (Project& p, Project::Item parent) override { - auto newFile = askUserToChooseNewFile (String (defaultClassName) + ".h", "*.h;*.cpp", parent); + jassert (&p == &project); - if (newFile != File()) + askUserToChooseNewFile (String (defaultClassName) + ".h", "*.h;*.cpp", parent, [this, parent] (File newFile) mutable { - auto headerFile = newFile.withFileExtension (".h"); - auto cppFile = newFile.withFileExtension (".cpp"); + if (newFile != File()) + { + auto headerFile = newFile.withFileExtension (".h"); + auto cppFile = newFile.withFileExtension (".cpp"); - headerFile.replaceWithText (String()); - cppFile.replaceWithText (String()); + headerFile.replaceWithText (String()); + cppFile.replaceWithText (String()); - auto& odm = ProjucerApplication::getApp().openDocumentManager; + auto& odm = ProjucerApplication::getApp().openDocumentManager; - if (auto* cpp = dynamic_cast (odm.openFile (&project, cppFile))) - { - if (auto* header = dynamic_cast (odm.openFile (&project, headerFile))) + if (auto* cpp = dynamic_cast (odm.openFile (&project, cppFile))) { - std::unique_ptr jucerDoc (new ComponentDocument (cpp)); - - if (jucerDoc != nullptr) + if (auto* header = dynamic_cast (odm.openFile (&project, headerFile))) { - jucerDoc->setClassName (newFile.getFileNameWithoutExtension()); - - jucerDoc->flushChangesToDocuments (&project, true); - jucerDoc.reset(); - - cpp->save(); - header->save(); - odm.closeDocument (cpp, OpenDocumentManager::SaveIfNeeded::yes); - odm.closeDocument (header, OpenDocumentManager::SaveIfNeeded::yes); - - parent.addFileRetainingSortOrder (headerFile, true); - parent.addFileRetainingSortOrder (cppFile, true); + std::unique_ptr jucerDoc (new ComponentDocument (cpp)); + + if (jucerDoc != nullptr) + { + jucerDoc->setClassName (newFile.getFileNameWithoutExtension()); + + jucerDoc->flushChangesToDocuments (&project, true); + jucerDoc.reset(); + + for (auto* doc : { cpp, header }) + { + doc->saveAsync ([doc] (bool) + { + ProjucerApplication::getApp() + .openDocumentManager + .closeDocumentAsync (doc, OpenDocumentManager::SaveIfNeeded::yes, nullptr); + }); + } + + parent.addFileRetainingSortOrder (headerFile, true); + parent.addFileRetainingSortOrder (cppFile, true); + } } } } - } + }); } + + Project& project; }; -NewFileWizard::Type* createGUIComponentWizard(); -NewFileWizard::Type* createGUIComponentWizard() +NewFileWizard::Type* createGUIComponentWizard (Project&); +NewFileWizard::Type* createGUIComponentWizard (Project& p) { - return new NewGUIComponentWizard(); + return new NewGUIComponentWizard (p); } diff --git a/extras/Projucer/Source/ComponentEditor/jucer_PaintRoutine.h b/extras/Projucer/Source/ComponentEditor/jucer_PaintRoutine.h index 335d520f7c..a04d08bd7f 100644 --- a/extras/Projucer/Source/ComponentEditor/jucer_PaintRoutine.h +++ b/extras/Projucer/Source/ComponentEditor/jucer_PaintRoutine.h @@ -109,7 +109,7 @@ public: //============================================================================== private: - OwnedArray elements; + OwnedArray elements; SelectedItemSet selectedElements; SelectedItemSet selectedPoints; JucerDocument* document; diff --git a/extras/Projucer/Source/Project/Modules/jucer_Modules.cpp b/extras/Projucer/Source/Project/Modules/jucer_Modules.cpp index 69e8a31bcb..4b2b9ae239 100644 --- a/extras/Projucer/Source/Project/Modules/jucer_Modules.cpp +++ b/extras/Projucer/Source/Project/Modules/jucer_Modules.cpp @@ -664,15 +664,16 @@ void EnabledModulesList::addModuleInteractive (const String& moduleID) void EnabledModulesList::addModuleFromUserSelectedFile() { - auto lastLocation = getDefaultModulesFolder(); + chooser = std::make_unique ("Select a module to add...", getDefaultModulesFolder(), ""); + auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories; - FileChooser fc ("Select a module to add...", lastLocation, {}); - - if (fc.browseForDirectory()) + chooser->launchAsync (flags, [this] (const FileChooser& fc) { - lastLocation = fc.getResult(); - addModuleOfferingToCopy (lastLocation, true); - } + if (fc.getResult() == File{}) + return; + + addModuleOfferingToCopy (fc.getResult(), true); + }); } void EnabledModulesList::addModuleOfferingToCopy (const File& f, bool isFromUserSpecifiedFolder) diff --git a/extras/Projucer/Source/Project/Modules/jucer_Modules.h b/extras/Projucer/Source/Project/Modules/jucer_Modules.h index c75272bc3e..e82761b6d2 100644 --- a/extras/Projucer/Source/Project/Modules/jucer_Modules.h +++ b/extras/Projucer/Source/Project/Modules/jucer_Modules.h @@ -142,5 +142,7 @@ private: CriticalSection stateLock; ValueTree state; + std::unique_ptr chooser; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EnabledModulesList) }; diff --git a/extras/Projucer/Source/Project/UI/Sidebar/jucer_ExporterTreeItems.h b/extras/Projucer/Source/Project/UI/Sidebar/jucer_ExporterTreeItems.h index 53e046b586..267befc1b1 100644 --- a/extras/Projucer/Source/Project/UI/Sidebar/jucer_ExporterTreeItems.h +++ b/extras/Projucer/Source/Project/UI/Sidebar/jucer_ExporterTreeItems.h @@ -78,13 +78,25 @@ public: void deleteItem() override { - if (AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, "Delete Exporter", - "Are you sure you want to delete this export target?")) + WeakReference safeThis { this }; + AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, + "Delete Exporter", + "Are you sure you want to delete this export target?", + "", + "", + nullptr, + ModalCallbackFunction::create ([safeThis] (int result) { - closeSettingsPage(); - ValueTree parent (exporter->settings.getParent()); - parent.removeChild (exporter->settings, project.getUndoManagerFor (parent)); - } + if (safeThis == nullptr) + return; + + if (result == 0) + return; + + safeThis->closeSettingsPage(); + auto parent = safeThis->exporter->settings.getParent(); + parent.removeChild (safeThis->exporter->settings, safeThis->project.getUndoManagerFor (parent)); + })); } void addSubItems() override @@ -117,7 +129,7 @@ public: if (resultCode == 1) exporter->addNewConfiguration (false); else if (resultCode == 2) - project.saveProject (exporter.get()); + project.saveProject (Async::yes, exporter.get(), nullptr); else if (resultCode == 3) deleteAllSelectedItems(); } @@ -200,6 +212,7 @@ private: }; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExporterItem) + JUCE_DECLARE_WEAK_REFERENCEABLE (ExporterItem) }; @@ -231,12 +244,24 @@ public: void deleteItem() override { - if (AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, "Delete Configuration", - "Are you sure you want to delete this configuration?")) + WeakReference parent { this }; + AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, + "Delete Configuration", + "Are you sure you want to delete this configuration?", + "", + "", + nullptr, + ModalCallbackFunction::create ([parent] (int result) { - closeSettingsPage(); - config->removeFromExporter(); - } + if (parent == nullptr) + return; + + if (result == 0) + return; + + parent->closeSettingsPage(); + parent->config->removeFromExporter(); + })); } void showPopupMenu (Point p) override @@ -297,6 +322,8 @@ private: }; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConfigItem) + JUCE_DECLARE_WEAK_REFERENCEABLE (ConfigItem) + }; //============================================================================== diff --git a/extras/Projucer/Source/Project/UI/Sidebar/jucer_FileTreeItems.h b/extras/Projucer/Source/Project/UI/Sidebar/jucer_FileTreeItems.h index cb4a0dac64..19c7bccc1f 100644 --- a/extras/Projucer/Source/Project/UI/Sidebar/jucer_FileTreeItems.h +++ b/extras/Projucer/Source/Project/UI/Sidebar/jucer_FileTreeItems.h @@ -84,40 +84,26 @@ public: } } - if (filesToTrash.size() > 0) - { - String fileList; - auto maxFilesToList = 10; - for (auto i = jmin (maxFilesToList, filesToTrash.size()); --i >= 0;) - fileList << filesToTrash.getUnchecked(i).getFullPathName() << "\n"; - - if (filesToTrash.size() > maxFilesToList) - fileList << "\n...plus " << (filesToTrash.size() - maxFilesToList) << " more files..."; - - auto r = AlertWindow::showYesNoCancelBox (AlertWindow::NoIcon, "Delete Project Items", - "As well as removing the selected item(s) from the project, do you also want to move their files to the trash:\n\n" - + fileList, - "Just remove references", - "Also move files to Trash", - "Cancel", - tree->getTopLevelComponent()); + WeakReference treeRootItem { dynamic_cast (tree->getRootItem()) }; - if (r == 0) - return; - - if (r != 2) - filesToTrash.clear(); + if (treeRootItem == nullptr) + { + jassertfalse; + return; } - if (auto* treeRootItem = dynamic_cast (tree->getRootItem())) + auto doDelete = [treeRootItem, itemsToRemove] (const Array& fsToTrash) { + if (treeRootItem == nullptr) + return; + auto& om = ProjucerApplication::getApp().openDocumentManager; - for (auto i = filesToTrash.size(); --i >= 0;) + for (auto i = fsToTrash.size(); --i >= 0;) { - auto f = filesToTrash.getUnchecked(i); + auto f = fsToTrash.getUnchecked(i); - om.closeFile (f, OpenDocumentManager::SaveIfNeeded::no); + om.closeFileWithoutSaving (f); if (! f.moveToTrash()) { @@ -136,15 +122,48 @@ public: pcc->hideEditor(); } - om.closeFile (itemToRemove->getFile(), OpenDocumentManager::SaveIfNeeded::no); + om.closeFileWithoutSaving (itemToRemove->getFile()); itemToRemove->deleteItem(); } } - } - else + }; + + if (! filesToTrash.isEmpty()) { - jassertfalse; + String fileList; + auto maxFilesToList = 10; + for (auto i = jmin (maxFilesToList, filesToTrash.size()); --i >= 0;) + fileList << filesToTrash.getUnchecked(i).getFullPathName() << "\n"; + + if (filesToTrash.size() > maxFilesToList) + fileList << "\n...plus " << (filesToTrash.size() - maxFilesToList) << " more files..."; + + AlertWindow::showYesNoCancelBox (AlertWindow::NoIcon, + "Delete Project Items", + "As well as removing the selected item(s) from the project, do you also want to move their files to the trash:\n\n" + + fileList, + "Just remove references", + "Also move files to Trash", + "Cancel", + tree->getTopLevelComponent(), + ModalCallbackFunction::create ([treeRootItem, filesToTrash, doDelete] (int r) mutable + { + if (treeRootItem == nullptr) + return; + + if (r == 0) + return; + + if (r != 2) + filesToTrash.clear(); + + doDelete (filesToTrash); + })); + + return; } + + doDelete (filesToTrash); } virtual void revealInFinder() const @@ -155,17 +174,24 @@ public: virtual void browseToAddExistingFiles() { auto location = item.isGroup() ? item.determineGroupFolder() : getFile(); - FileChooser fc ("Add Files to Jucer Project", location, {}); + chooser = std::make_unique ("Add Files to Jucer Project", location, ""); + auto flags = FileBrowserComponent::openMode + | FileBrowserComponent::canSelectFiles + | FileBrowserComponent::canSelectDirectories + | FileBrowserComponent::canSelectMultipleItems; - if (fc.browseForMultipleFilesOrDirectories()) + chooser->launchAsync (flags, [this] (const FileChooser& fc) { + if (fc.getResults().isEmpty()) + return; + StringArray files; for (int i = 0; i < fc.getResults().size(); ++i) files.add (fc.getResults().getReference(i).getFullPathName()); addFilesRetainingSortOrder (files); - } + }); } virtual void checkFileStatus() // (recursive) @@ -192,7 +218,7 @@ public: p->addFilesRetainingSortOrder (files); } - virtual void moveSelectedItemsTo (OwnedArray &, int /*insertIndex*/) + virtual void moveSelectedItemsTo (OwnedArray&, int /*insertIndex*/) { jassertfalse; } @@ -269,7 +295,7 @@ public: void filesDropped (const StringArray& files, int insertIndex) override { if (files.size() == 1 && File (files[0]).hasFileExtension (Project::projectFileExtension)) - ProjucerApplication::getApp().openFile (files[0]); + ProjucerApplication::getApp().openFile (files[0], [] (bool) {}); else addFilesAtIndex (files, insertIndex); } @@ -444,6 +470,11 @@ protected: return -1; } + +private: + std::unique_ptr chooser; + + JUCE_DECLARE_WEAK_REFERENCEABLE (FileTreeItemBase) }; //============================================================================== @@ -456,7 +487,7 @@ public: } bool acceptsFileDrop (const StringArray&) const override { return false; } - bool acceptsDragItems (const OwnedArray &) override { return false; } + bool acceptsDragItems (const OwnedArray&) override { return false; } String getDisplayName() const override { @@ -490,8 +521,9 @@ public: { if (newName != File::createLegalFileName (newName)) { - AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename", - "That filename contained some illegal characters!"); + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + "File Rename", + "That filename contained some illegal characters!"); triggerAsyncRename (item); return; } @@ -506,30 +538,42 @@ public: if (correspondingItem.isValid()) { - if (AlertWindow::showOkCancelBox (AlertWindow::NoIcon, "File Rename", - "Do you also want to rename the corresponding file \"" + correspondingFile.getFileName() - + "\" to match?")) + WeakReference parent { this }; + AlertWindow::showOkCancelBox (AlertWindow::NoIcon, + "File Rename", + "Do you also want to rename the corresponding file \"" + correspondingFile.getFileName() + "\" to match?", + {}, + {}, + nullptr, + ModalCallbackFunction::create ([parent, oldFile, newFile, correspondingFile, correspondingItem] (int result) mutable { - if (! item.renameFile (newFile)) + if (parent == nullptr || result == 0) + return; + + if (! parent->item.renameFile (newFile)) { - AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename", - "Failed to rename \"" + oldFile.getFullPathName() + "\"!\n\nCheck your file permissions!"); + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + "File Rename", + "Failed to rename \"" + oldFile.getFullPathName() + "\"!\n\nCheck your file permissions!"); return; } if (! correspondingItem.renameFile (newFile.withFileExtension (correspondingFile.getFileExtension()))) { - AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename", - "Failed to rename \"" + correspondingFile.getFullPathName() + "\"!\n\nCheck your file permissions!"); + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + "File Rename", + "Failed to rename \"" + correspondingFile.getFullPathName() + "\"!\n\nCheck your file permissions!"); } - } + + })); } } if (! item.renameFile (newFile)) { - AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename", - "Failed to rename the file!\n\nCheck your file permissions!"); + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + "File Rename", + "Failed to rename the file!\n\nCheck your file permissions!"); } } @@ -600,6 +644,8 @@ public: break; } } + + JUCE_DECLARE_WEAK_REFERENCEABLE (SourceFileItem) }; //============================================================================== @@ -799,7 +845,7 @@ public: m.addItem (1002, "Add Existing Files..."); m.addSeparator(); - NewFileWizard().addWizardsToMenu (m); + wizard.addWizardsToMenu (m); } void processCreateFileMenuItem (int menuID) @@ -811,7 +857,7 @@ public: default: jassert (getProject() != nullptr); - NewFileWizard().runWizardFromMenu (menuID, *getProject(), item); + wizard.runWizardFromMenu (menuID, *getProject(), item); break; } } @@ -832,4 +878,5 @@ public: } String searchFilter; + NewFileWizard wizard; }; diff --git a/extras/Projucer/Source/Project/UI/Sidebar/jucer_ModuleTreeItems.h b/extras/Projucer/Source/Project/UI/Sidebar/jucer_ModuleTreeItems.h index f2aec1a3b3..7a4a87d599 100644 --- a/extras/Projucer/Source/Project/UI/Sidebar/jucer_ModuleTreeItems.h +++ b/extras/Projucer/Source/Project/UI/Sidebar/jucer_ModuleTreeItems.h @@ -270,7 +270,7 @@ private: Array exporterModulePathValues, globalPathValues; Value useGlobalPathValue; - OwnedArray configFlags; + OwnedArray configFlags; PropertyGroupComponent group; Project& project; diff --git a/extras/Projucer/Source/Project/UI/jucer_HeaderComponent.cpp b/extras/Projucer/Source/Project/UI/jucer_HeaderComponent.cpp index 729c7b8992..f036b0d4a2 100644 --- a/extras/Projucer/Source/Project/UI/jucer_HeaderComponent.cpp +++ b/extras/Projucer/Source/Project/UI/jucer_HeaderComponent.cpp @@ -248,7 +248,7 @@ void HeaderComponent::initialiseButtons() else { if (auto exporter = getSelectedExporter()) - project->openProjectInIDE (*exporter, true); + project->openProjectInIDE (*exporter, true, nullptr); } } }; diff --git a/extras/Projucer/Source/Project/UI/jucer_ProjectContentComponent.cpp b/extras/Projucer/Source/Project/UI/jucer_ProjectContentComponent.cpp index aba3b37a79..0d52d6b669 100644 --- a/extras/Projucer/Source/Project/UI/jucer_ProjectContentComponent.cpp +++ b/extras/Projucer/Source/Project/UI/jucer_ProjectContentComponent.cpp @@ -28,7 +28,12 @@ #include "Sidebar/jucer_Sidebar.h" -NewFileWizard::Type* createGUIComponentWizard(); +struct WizardHolder +{ + std::unique_ptr wizard; +}; + +NewFileWizard::Type* createGUIComponentWizard (Project&); //============================================================================== ProjectContentComponent::ProjectContentComponent() @@ -305,7 +310,7 @@ void ProjectContentComponent::closeDocument() if (currentDocument != nullptr) { ProjucerApplication::getApp().openDocumentManager - .closeDocument (currentDocument, OpenDocumentManager::SaveIfNeeded::yes); + .closeDocumentAsync (currentDocument, OpenDocumentManager::SaveIfNeeded::yes, nullptr); return; } @@ -315,35 +320,49 @@ void ProjectContentComponent::closeDocument() static void showSaveWarning (OpenDocumentManager::Document* currentDocument) { - AlertWindow::showMessageBox (AlertWindow::WarningIcon, - TRANS("Save failed!"), - TRANS("Couldn't save the file:") - + "\n" + currentDocument->getFile().getFullPathName()); + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + TRANS("Save failed!"), + TRANS("Couldn't save the file:") + + "\n" + currentDocument->getFile().getFullPathName()); } -void ProjectContentComponent::saveDocument() +void ProjectContentComponent::saveDocumentAsync() { if (currentDocument != nullptr) { - if (! currentDocument->save()) - showSaveWarning (currentDocument); + SafePointer parent { this }; + currentDocument->saveAsync ([parent] (bool savedSuccessfully) + { + if (parent == nullptr) + return; + + if (! savedSuccessfully) + showSaveWarning (parent->currentDocument); - refreshProjectTreeFileStatuses(); + parent->refreshProjectTreeFileStatuses(); + }); } else { - saveProject(); + saveProjectAsync(); } } -void ProjectContentComponent::saveAs() +void ProjectContentComponent::saveAsAsync() { if (currentDocument != nullptr) { - if (! currentDocument->saveAs()) - showSaveWarning (currentDocument); + SafePointer parent { this }; + currentDocument->saveAsAsync ([parent] (bool savedSuccessfully) + { + if (parent == nullptr) + return; + + if (! savedSuccessfully) + showSaveWarning (parent->currentDocument); - refreshProjectTreeFileStatuses(); + parent->refreshProjectTreeFileStatuses(); + }); } } @@ -381,18 +400,16 @@ bool ProjectContentComponent::goToCounterpart() return false; } -bool ProjectContentComponent::saveProject() +void ProjectContentComponent::saveProjectAsync() { if (project != nullptr) - return (project->save (true, true) == FileBasedDocument::savedOk); - - return false; + project->saveAsync (true, true, nullptr); } void ProjectContentComponent::closeProject() { if (auto* mw = findParentComponentOfClass()) - mw->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes); + mw->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes, nullptr); } void ProjectContentComponent::showProjectSettings() @@ -481,7 +498,7 @@ void ProjectContentComponent::openInSelectedIDE (bool saveFirst) { if (project != nullptr) if (auto selectedExporter = headerComponent.getSelectedExporter()) - project->openProjectInIDE (*selectedExporter, saveFirst); + project->openProjectInIDE (*selectedExporter, saveFirst, nullptr); } void ProjectContentComponent::showNewExporterMenu() @@ -787,7 +804,7 @@ void ProjectContentComponent::getCommandInfo (const CommandID commandID, Applica bool ProjectContentComponent::perform (const InvocationInfo& info) { // don't allow the project to be saved again if it's currently saving - if (isSaveCommand (info.commandID) && (project != nullptr && project->isCurrentlySaving())) + if (isSaveCommand (info.commandID) && project != nullptr && project->isCurrentlySaving()) return false; switch (info.commandID) @@ -818,14 +835,14 @@ bool ProjectContentComponent::perform (const InvocationInfo& info) switch (info.commandID) { - case CommandIDs::saveProject: saveProject(); break; - case CommandIDs::closeProject: closeProject(); break; - case CommandIDs::saveDocument: saveDocument(); break; - case CommandIDs::saveDocumentAs: saveAs(); break; - case CommandIDs::closeDocument: closeDocument(); break; - case CommandIDs::goToPreviousDoc: goToPreviousFile(); break; - case CommandIDs::goToNextDoc: goToNextFile(); break; - case CommandIDs::goToCounterpart: goToCounterpart(); break; + case CommandIDs::saveProject: saveProjectAsync(); break; + case CommandIDs::closeProject: closeProject(); break; + case CommandIDs::saveDocument: saveDocumentAsync(); break; + case CommandIDs::saveDocumentAs: saveAsAsync(); break; + case CommandIDs::closeDocument: closeDocument(); break; + case CommandIDs::goToPreviousDoc: goToPreviousFile(); break; + case CommandIDs::goToNextDoc: goToNextFile(); break; + case CommandIDs::goToCounterpart: goToCounterpart(); break; case CommandIDs::showProjectSettings: showProjectSettings(); break; case CommandIDs::showFileExplorerPanel: showFilesPanel(); break; @@ -866,8 +883,9 @@ void ProjectContentComponent::addNewGUIFile() { if (project != nullptr) { - std::unique_ptr wizard (createGUIComponentWizard()); - wizard->createNewFile (*project, project->getMainGroup()); + wizardHolder = std::make_unique(); + wizardHolder->wizard.reset (createGUIComponentWizard (*project)); + wizardHolder->wizard->createNewFile (*project, project->getMainGroup()); } } diff --git a/extras/Projucer/Source/Project/UI/jucer_ProjectContentComponent.h b/extras/Projucer/Source/Project/UI/jucer_ProjectContentComponent.h index 4daec61c99..3bcc636893 100644 --- a/extras/Projucer/Source/Project/UI/jucer_ProjectContentComponent.h +++ b/extras/Projucer/Source/Project/UI/jucer_ProjectContentComponent.h @@ -31,6 +31,7 @@ #include "jucer_ContentViewComponent.h" class Sidebar; +struct WizardHolder; //============================================================================== class ProjectContentComponent : public Component, @@ -57,8 +58,8 @@ public: void hideDocument (OpenDocumentManager::Document*); OpenDocumentManager::Document* getCurrentDocument() const { return currentDocument; } void closeDocument(); - void saveDocument(); - void saveAs(); + void saveDocumentAsync(); + void saveAsAsync(); void hideEditor(); void setScrollableEditorComponent (std::unique_ptr component); @@ -72,7 +73,7 @@ public: bool canGoToCounterpart() const; bool goToCounterpart(); - bool saveProject(); + void saveProjectAsync(); void closeProject(); void openInSelectedIDE (bool saveFirst); void showNewExporterMenu(); @@ -145,6 +146,8 @@ private: bool isForeground = false; int lastViewedTab = 0; + std::unique_ptr wizardHolder; + //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectContentComponent) }; diff --git a/extras/Projucer/Source/Project/jucer_Project.cpp b/extras/Projucer/Source/Project/jucer_Project.cpp index df0bcc9907..17d7931f37 100644 --- a/extras/Projucer/Source/Project/jucer_Project.cpp +++ b/extras/Projucer/Source/Project/jucer_Project.cpp @@ -66,12 +66,22 @@ void Project::ProjectFileModificationPoller::reloadProjectFromDisk() { if (auto* mw = ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (projectFile)) { - mw->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::no); - mw->openFile (projectFile); + Component::SafePointer parent { mw }; + mw->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::no, [parent, oldTemporaryDirectory, projectFile] (bool) + { + if (parent == nullptr) + return; - if (oldTemporaryDirectory != File()) - if (auto* newProject = mw->getProject()) - newProject->setTemporaryDirectory (oldTemporaryDirectory); + parent->openFile (projectFile, [parent, oldTemporaryDirectory] (bool openedSuccessfully) + { + if (parent == nullptr) + return; + + if (openedSuccessfully && oldTemporaryDirectory != File()) + if (auto* newProject = parent->getProject()) + newProject->setTemporaryDirectory (oldTemporaryDirectory); + }); + }); } }); } @@ -79,7 +89,7 @@ void Project::ProjectFileModificationPoller::reloadProjectFromDisk() void Project::ProjectFileModificationPoller::resaveProject() { reset(); - project.saveProject(); + project.saveProject (Async::yes, nullptr, nullptr); } //============================================================================== @@ -122,7 +132,7 @@ Project::~Project() auto& app = ProjucerApplication::getApp(); - app.openDocumentManager.closeAllDocumentsUsingProject (*this, OpenDocumentManager::SaveIfNeeded::no); + app.openDocumentManager.closeAllDocumentsUsingProjectWithoutSaving (*this); if (! app.isRunningCommandLine) app.getLicenseController().removeListener (this); @@ -397,9 +407,9 @@ void Project::removeDefunctExporters() if (ProjucerApplication::getApp().isRunningCommandLine) std::cout << "WARNING! The " + oldExporters[key] + " Exporter is deprecated. The exporter will be removed from this project." << std::endl; else - AlertWindow::showMessageBox (AlertWindow::WarningIcon, - TRANS (oldExporters[key]), - TRANS ("The " + oldExporters[key] + " Exporter is deprecated. The exporter will be removed from this project.")); + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + TRANS (oldExporters[key]), + TRANS ("The " + oldExporters[key] + " Exporter is deprecated. The exporter will be removed from this project.")); exporters.removeChild (oldExporter, nullptr); } @@ -664,40 +674,73 @@ Result Project::saveDocument (const File& file) jassert (file == getFile()); ignoreUnused (file); - return saveProject(); + auto sharedResult = Result::ok(); + + saveProject (Async::no, nullptr, [&sharedResult] (Result actualResult) + { + sharedResult = actualResult; + }); + + return sharedResult; +} + +void Project::saveDocumentAsync (const File& file, std::function afterSave) +{ + jassert (file == getFile()); + ignoreUnused (file); + + saveProject (Async::yes, nullptr, std::move (afterSave)); } -Result Project::saveProject (ProjectExporter* exporterToSave) +void Project::saveProject (Async async, + ProjectExporter* exporterToSave, + std::function onCompletion) { if (isSaveAndExportDisabled()) - return Result::fail ("Save and export is disabled."); + { + onCompletion (Result::fail ("Save and export is disabled.")); + return; + } - if (isSaving) - return Result::ok(); + if (saver != nullptr) + { + onCompletion (Result::ok()); + return; + } if (isTemporaryProject()) { saveAndMoveTemporaryProject (false); - return Result::ok(); + onCompletion (Result::ok()); + return; } updateProjectSettings(); if (! ProjucerApplication::getApp().isRunningCommandLine) { - ProjucerApplication::getApp().openDocumentManager.saveAll(); + ProjucerApplication::getApp().openDocumentManager.saveAllSyncWithoutAsking(); if (! isTemporaryProject()) registerRecentFile (getFile()); } - const ScopedValueSetter vs (isSaving, true, false); + WeakReference ref (this); + + saver = std::make_unique (*this); + saver->save (async, exporterToSave, [ref, onCompletion] (Result result) + { + if (ref == nullptr) + return; - ProjectSaver saver (*this); - return saver.save (exporterToSave); + ref->saver = nullptr; + + if (onCompletion != nullptr) + onCompletion (result); + }); } -Result Project::openProjectInIDE (ProjectExporter& exporterToOpen, bool saveFirst) +void Project::openProjectInIDE (ProjectExporter& exporterToOpen, bool saveFirst, std::function onCompletion) { for (ExporterIterator exporter (*this); exporter.next();) { @@ -706,33 +749,57 @@ Result Project::openProjectInIDE (ProjectExporter& exporterToOpen, bool saveFirs if (isTemporaryProject()) { saveAndMoveTemporaryProject (true); - return Result::ok(); + + if (onCompletion != nullptr) + onCompletion (Result::ok()); + + return; } if (saveFirst) { - auto result = saveProject(); + struct Callback + { + void operator() (Result saveResult) noexcept + { + if (! saveResult.wasOk()) + { + if (onCompletion != nullptr) + onCompletion (saveResult); + + return; + } + + // Workaround for a bug where Xcode thinks the project is invalid if opened immediately + // after writing + auto exporterCopy = exporter; + Timer::callAfterDelay (exporter->isXcode() ? 1000 : 0, [exporterCopy] + { + exporterCopy->launchProject(); + }); + } - if (! result.wasOk()) - return result; - } + std::shared_ptr exporter; + std::function onCompletion; + }; - // Workaround for a bug where Xcode thinks the project is invalid if opened immediately - // after writing - if (saveFirst && exporter->isXcode()) - Thread::sleep (1000); + saveProject (Async::yes, nullptr, Callback { std::move (exporter.exporter), onCompletion }); + return; + } exporter->launchProject(); + break; } } - return Result::ok(); + if (onCompletion != nullptr) + onCompletion (Result::ok()); } Result Project::saveResourcesOnly() { - ProjectSaver saver (*this); - return saver.saveResourcesOnly(); + saver = std::make_unique (*this); + return saver->saveResourcesOnly(); } bool Project::hasIncompatibleLicenseTypeAndSplashScreenSetting() const @@ -988,37 +1055,41 @@ void Project::setTemporaryDirectory (const File& dir) noexcept void Project::saveAndMoveTemporaryProject (bool openInIDE) { - FileChooser fc ("Save Project"); - fc.browseForDirectory(); + chooser = std::make_unique ("Save Project"); + auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories; - auto newParentDirectory = fc.getResult(); - - if (! newParentDirectory.exists()) - return; - - auto newDirectory = newParentDirectory.getChildFile (tempDirectory.getFileName()); - auto oldJucerFileName = getFile().getFileName(); - - ProjectSaver saver (*this); - saver.save(); + chooser->launchAsync (flags, [this, openInIDE] (const FileChooser& fc) + { + auto newParentDirectory = fc.getResult(); - tempDirectory.copyDirectoryTo (newDirectory); - tempDirectory.deleteRecursively(); - tempDirectory = File(); + if (! newParentDirectory.exists()) + return; - // reload project from new location - if (auto* window = ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (getFile())) - { - Component::SafePointer safeWindow (window); + auto newDirectory = newParentDirectory.getChildFile (tempDirectory.getFileName()); + auto oldJucerFileName = getFile().getFileName(); - MessageManager::callAsync ([safeWindow, newDirectory, oldJucerFileName, openInIDE]() mutable + saver = std::make_unique (*this); + saver->save (Async::yes, nullptr, [this, newDirectory, oldJucerFileName, openInIDE] (Result) { - if (safeWindow != nullptr) - safeWindow->moveProject (newDirectory.getChildFile (oldJucerFileName), - openInIDE ? MainWindow::OpenInIDE::yes - : MainWindow::OpenInIDE::no); + tempDirectory.copyDirectoryTo (newDirectory); + tempDirectory.deleteRecursively(); + tempDirectory = File(); + + // reload project from new location + if (auto* window = ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (getFile())) + { + Component::SafePointer safeWindow (window); + + MessageManager::callAsync ([safeWindow, newDirectory, oldJucerFileName, openInIDE]() mutable + { + if (safeWindow != nullptr) + safeWindow->moveProject (newDirectory.getChildFile (oldJucerFileName), + openInIDE ? MainWindow::OpenInIDE::yes + : MainWindow::OpenInIDE::no); + }); + } }); - } + }); } //============================================================================== @@ -2620,7 +2691,6 @@ StringPairArray Project::getAudioPluginFlags() const //============================================================================== Project::ExporterIterator::ExporterIterator (Project& p) : index (-1), project (p) {} -Project::ExporterIterator::~ExporterIterator() {} bool Project::ExporterIterator::next() { diff --git a/extras/Projucer/Source/Project/jucer_Project.h b/extras/Projucer/Source/Project/jucer_Project.h index 0221f89831..11ae32183d 100644 --- a/extras/Projucer/Source/Project/jucer_Project.h +++ b/extras/Projucer/Source/Project/jucer_Project.h @@ -31,6 +31,7 @@ class ProjectExporter; class LibraryModule; class EnabledModulesList; +class ProjectSaver; namespace ProjectMessages { @@ -109,6 +110,8 @@ namespace ProjectMessages using MessageAction = std::pair>; } +enum class Async { no, yes }; + //============================================================================== class Project : public FileBasedDocument, private ValueTree::Listener, @@ -125,10 +128,11 @@ public: String getDocumentTitle() override; Result loadDocument (const File& file) override; Result saveDocument (const File& file) override; + void saveDocumentAsync (const File& file, std::function callback) override; - Result saveProject (ProjectExporter* exporterToSave = nullptr); + void saveProject (Async, ProjectExporter* exporterToSave, std::function onCompletion); Result saveResourcesOnly(); - Result openProjectInIDE (ProjectExporter& exporterToOpen, bool saveFirst); + void openProjectInIDE (ProjectExporter& exporterToOpen, bool saveFirst, std::function onCompletion); File getLastDocumentOpened() override; void setLastDocumentOpened (const File& file) override; @@ -433,7 +437,6 @@ public: struct ExporterIterator { ExporterIterator (Project& project); - ~ExporterIterator(); bool next(); @@ -486,7 +489,7 @@ public: String getUniqueTargetFolderSuffixForExporter (const Identifier& exporterIdentifier, const String& baseTargetFolder); //============================================================================== - bool isCurrentlySaving() const noexcept { return isSaving; } + bool isCurrentlySaving() const noexcept { return saver != nullptr; } bool isTemporaryProject() const noexcept { return tempDirectory != File(); } File getTemporaryDirectory() const noexcept { return tempDirectory; } @@ -572,7 +575,6 @@ private: //============================================================================== friend class Item; - bool isSaving = false; StringPairArray parsedPreprocessorDefs; //============================================================================== @@ -612,12 +614,21 @@ private: void updateCLionWarning (bool showWarning); void updateModuleNotFoundWarning (bool showWarning); + void openProjectInIDEImpl (ExporterIterator exporter, + String exporterToOpen, + bool saveFirst, + std::function onCompletion); + ValueTree projectMessages { ProjectMessages::Ids::projectMessages, {}, { { ProjectMessages::Ids::notification, {} }, { ProjectMessages::Ids::warning, {} } } }; std::map> messageActions; ProjectFileModificationPoller fileModificationPoller { *this }; + std::unique_ptr chooser; + std::unique_ptr saver; + //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Project) + JUCE_DECLARE_WEAK_REFERENCEABLE (Project) }; diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.cpp b/extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.cpp index bfcf68196a..a779a1bdae 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.cpp +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.cpp @@ -42,17 +42,32 @@ ProjectSaver::ProjectSaver (Project& p) generatedFilesGroup.setID (generatedGroupID); } -Result ProjectSaver::save (ProjectExporter* exporterToSave) +void ProjectSaver::save (Async async, ProjectExporter* exporterToSave, std::function onCompletion) { - if (! ProjucerApplication::getApp().isRunningCommandLine) + if (async == Async::yes) + saveProjectAsync (exporterToSave, std::move (onCompletion)); + else + onCompletion (saveProject (exporterToSave)); +} + +void ProjectSaver::saveProjectAsync (ProjectExporter* exporterToSave, std::function onCompletion) +{ + jassert (saveThread == nullptr); + + WeakReference ref (this); + saveThread = std::make_unique (*this, exporterToSave, [ref, onCompletion] (Result result) { - SaveThreadWithProgressWindow thread (*this, exporterToSave); - thread.runThread(); + if (ref == nullptr) + return; - return thread.result; - } + // Clean up old save thread in case onCompletion wants to start a new save thread + ref->saveThread->waitForThreadToExit (-1); + ref->saveThread = nullptr; - return saveProject (exporterToSave); + if (onCompletion != nullptr) + onCompletion (result); + }); + saveThread->launchThread(); } Result ProjectSaver::saveResourcesOnly() diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.h index 3c8aec375a..22033a92ab 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.h @@ -36,7 +36,7 @@ class ProjectSaver public: ProjectSaver (Project& projectToSave); - Result save (ProjectExporter* exporterToSave = nullptr); + void save (Async async, ProjectExporter* exporterToSave, std::function onCompletion); Result saveResourcesOnly(); void saveBasicProjectItems (const OwnedArray& modules, const String& appConfigUserContent); @@ -52,21 +52,30 @@ private: struct SaveThreadWithProgressWindow : public ThreadWithProgressWindow { public: - SaveThreadWithProgressWindow (ProjectSaver& ps, ProjectExporter* exporterToSave) + SaveThreadWithProgressWindow (ProjectSaver& ps, + ProjectExporter* exporterToSave, + std::function onCompletionIn) : ThreadWithProgressWindow ("Saving...", true, false), saver (ps), - specifiedExporterToSave (exporterToSave) - {} + specifiedExporterToSave (exporterToSave), + onCompletion (std::move (onCompletionIn)) + { + jassert (onCompletion != nullptr); + } void run() override { setProgress (-1); - result = saver.saveProject (specifiedExporterToSave); + const auto result = saver.saveProject (specifiedExporterToSave); + const auto callback = onCompletion; + + MessageManager::callAsync ([callback, result] { callback (result); }); } + private: ProjectSaver& saver; - Result result = Result::ok(); ProjectExporter* specifiedExporterToSave; + std::function onCompletion; JUCE_DECLARE_NON_COPYABLE (SaveThreadWithProgressWindow) }; @@ -86,6 +95,7 @@ private: OwnedArray getModules(); Result saveProject (ProjectExporter* specifiedExporterToSave); + void saveProjectAsync (ProjectExporter* exporterToSave, std::function onCompletion); template void writeOrRemoveGeneratedFile (const String& name, WriterCallback&& writerCallback); @@ -118,8 +128,11 @@ private: CriticalSection errorLock; StringArray errors; + std::unique_ptr saveThread; + bool hasBinaryData = false; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSaver) + JUCE_DECLARE_WEAK_REFERENCEABLE (ProjectSaver) }; diff --git a/extras/Projucer/Source/Utility/Helpers/jucer_NewFileWizard.cpp b/extras/Projucer/Source/Utility/Helpers/jucer_NewFileWizard.cpp index 53566ff168..5a9920a5ee 100644 --- a/extras/Projucer/Source/Utility/Helpers/jucer_NewFileWizard.cpp +++ b/extras/Projucer/Source/Utility/Helpers/jucer_NewFileWizard.cpp @@ -60,16 +60,15 @@ namespace class NewCppFileWizard : public NewFileWizard::Type { public: - NewCppFileWizard() {} - String getName() override { return "CPP File"; } void createNewFile (Project&, Project::Item parent) override { - const File newFile (askUserToChooseNewFile ("SourceCode.cpp", "*.cpp", parent)); - - if (newFile != File()) - create (parent, newFile, "jucer_NewCppFileTemplate_cpp"); + askUserToChooseNewFile ("SourceCode.cpp", "*.cpp", parent, [parent] (File newFile) + { + if (newFile != File()) + create (parent, newFile, "jucer_NewCppFileTemplate_cpp"); + }); } static bool create (Project::Item parent, const File& newFile, const char* templateName) @@ -89,16 +88,15 @@ public: class NewHeaderFileWizard : public NewFileWizard::Type { public: - NewHeaderFileWizard() {} - String getName() override { return "Header File"; } void createNewFile (Project&, Project::Item parent) override { - const File newFile (askUserToChooseNewFile ("SourceCode.h", "*.h", parent)); - - if (newFile != File()) - create (parent, newFile, "jucer_NewCppFileTemplate_h"); + askUserToChooseNewFile ("SourceCode.h", "*.h", parent, [parent] (File newFile) + { + if (newFile != File()) + create (parent, newFile, "jucer_NewCppFileTemplate_h"); + }); } static bool create (Project::Item parent, const File& newFile, const char* templateName) @@ -118,19 +116,15 @@ public: class NewCppAndHeaderFileWizard : public NewFileWizard::Type { public: - NewCppAndHeaderFileWizard() {} - String getName() override { return "CPP & Header File"; } void createNewFile (Project&, Project::Item parent) override { - const File newFile (askUserToChooseNewFile ("SourceCode.h", "*.h;*.cpp", parent)); - - if (newFile != File()) + askUserToChooseNewFile ("SourceCode.h", "*.h;*.cpp", parent, [parent] (File newFile) { if (NewCppFileWizard::create (parent, newFile.withFileExtension ("h"), "jucer_NewCppFileTemplate_h")) NewCppFileWizard::create (parent, newFile.withFileExtension ("cpp"), "jucer_NewCppFileTemplate_cpp"); - } + }); } }; @@ -138,37 +132,11 @@ public: class NewComponentFileWizard : public NewFileWizard::Type { public: - NewComponentFileWizard() {} - String getName() override { return "Component class (split between a CPP & header)"; } void createNewFile (Project&, Project::Item parent) override { - for (;;) - { - AlertWindow aw (TRANS ("Create new Component class"), - TRANS ("Please enter the name for the new class"), - AlertWindow::NoIcon, nullptr); - - aw.addTextEditor (getClassNameFieldName(), String(), String(), false); - aw.addButton (TRANS ("Create Files"), 1, KeyPress (KeyPress::returnKey)); - aw.addButton (TRANS ("Cancel"), 0, KeyPress (KeyPress::escapeKey)); - - if (aw.runModalLoop() == 0) - break; - - const String className (aw.getTextEditorContents (getClassNameFieldName()).trim()); - - if (className == build_tools::makeValidIdentifier (className, false, true, false)) - { - const File newFile (askUserToChooseNewFile (className + ".h", "*.h;*.cpp", parent)); - - if (newFile != File()) - createFiles (parent, className, newFile); - - break; - } - } + createNewFileInternal (parent); } static bool create (const String& className, Project::Item parent, @@ -198,14 +166,61 @@ private: } static String getClassNameFieldName() { return "Class Name"; } + + void createNewFileInternal (Project::Item parent) + { + asyncAlertWindow = std::make_unique (TRANS ("Create new Component class"), + TRANS ("Please enter the name for the new class"), + AlertWindow::NoIcon, nullptr); + + asyncAlertWindow->addTextEditor (getClassNameFieldName(), String(), String(), false); + asyncAlertWindow->addButton (TRANS ("Create Files"), 1, KeyPress (KeyPress::returnKey)); + asyncAlertWindow->addButton (TRANS ("Cancel"), 0, KeyPress (KeyPress::escapeKey)); + + WeakReference safeThis { this }; + asyncAlertWindow->enterModalState (true, ModalCallbackFunction::create ([safeThis, parent] (int result) + { + if (safeThis == nullptr) + return; + + auto& aw = *(safeThis->asyncAlertWindow); + + aw.exitModalState (result); + aw.setVisible (false); + + if (result == 0) + return; + + const String className (aw.getTextEditorContents (getClassNameFieldName()).trim()); + + if (className == build_tools::makeValidIdentifier (className, false, true, false)) + { + safeThis->askUserToChooseNewFile (className + ".h", "*.h;*.cpp", parent, [safeThis, parent, className] (File newFile) + { + if (safeThis == nullptr) + return; + + if (newFile != File()) + safeThis->createFiles (parent, className, newFile); + }); + + return; + } + + safeThis->createNewFileInternal (parent); + + }), false); + } + + std::unique_ptr asyncAlertWindow; + + JUCE_DECLARE_WEAK_REFERENCEABLE (NewComponentFileWizard) }; //============================================================================== class NewSingleFileComponentFileWizard : public NewComponentFileWizard { public: - NewSingleFileComponentFileWizard() {} - String getName() override { return "Component class (in a single source file)"; } void createFiles (Project::Item parent, const String& className, const File& newFile) override @@ -218,24 +233,28 @@ public: //============================================================================== void NewFileWizard::Type::showFailedToWriteMessage (const File& file) { - AlertWindow::showMessageBox (AlertWindow::WarningIcon, - "Failed to Create File!", - "Couldn't write to the file: " + file.getFullPathName()); + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, + "Failed to Create File!", + "Couldn't write to the file: " + file.getFullPathName()); } -File NewFileWizard::Type::askUserToChooseNewFile (const String& suggestedFilename, const String& wildcard, - const Project::Item& projectGroupToAddTo) +void NewFileWizard::Type::askUserToChooseNewFile (const String& suggestedFilename, const String& wildcard, + const Project::Item& projectGroupToAddTo, + std::function callback) { - FileChooser fc ("Select File to Create", - projectGroupToAddTo.determineGroupFolder() - .getChildFile (suggestedFilename) - .getNonexistentSibling(), - wildcard); - - if (fc.browseForFileToSave (true)) - return fc.getResult(); - - return {}; + chooser = std::make_unique ("Select File to Create", + projectGroupToAddTo.determineGroupFolder() + .getChildFile (suggestedFilename) + .getNonexistentSibling(), + wildcard); + auto flags = FileBrowserComponent::saveMode + | FileBrowserComponent::canSelectFiles + | FileBrowserComponent::warnAboutOverwriting; + + chooser->launchAsync (flags, [callback] (const FileChooser& fc) + { + callback (fc.getResult()); + }); } //============================================================================== diff --git a/extras/Projucer/Source/Utility/Helpers/jucer_NewFileWizard.h b/extras/Projucer/Source/Utility/Helpers/jucer_NewFileWizard.h index ac9836d926..5fb11d71bc 100644 --- a/extras/Projucer/Source/Utility/Helpers/jucer_NewFileWizard.h +++ b/extras/Projucer/Source/Utility/Helpers/jucer_NewFileWizard.h @@ -48,10 +48,14 @@ public: protected: //============================================================================== - File askUserToChooseNewFile (const String& suggestedFilename, const String& wildcard, - const Project::Item& projectGroupToAddTo); + void askUserToChooseNewFile (const String& suggestedFilename, const String& wildcard, + const Project::Item& projectGroupToAddTo, + std::function callback); static void showFailedToWriteMessage (const File& file); + + private: + std::unique_ptr chooser; }; //============================================================================== diff --git a/extras/Projucer/Source/Utility/UI/PropertyComponents/jucer_FilePathPropertyComponent.h b/extras/Projucer/Source/Utility/UI/PropertyComponents/jucer_FilePathPropertyComponent.h index 8a0b24ebe2..b5fcf93c25 100644 --- a/extras/Projucer/Source/Utility/UI/PropertyComponents/jucer_FilePathPropertyComponent.h +++ b/extras/Projucer/Source/Utility/UI/PropertyComponents/jucer_FilePathPropertyComponent.h @@ -134,17 +134,29 @@ private: if (isDirectory) { - FileChooser chooser ("Select directory", currentFile); + chooser = std::make_unique ("Select directory", currentFile); + auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories; - if (chooser.browseForDirectory()) - setTo (chooser.getResult()); + chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc) + { + if (fc.getResult() == File{}) + return; + + setTo (fc.getResult()); + }); } else { - FileChooser chooser ("Select file", currentFile, wildcards); + chooser = std::make_unique ("Select file", currentFile, wildcards); + auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles; - if (chooser.browseForFileToOpen()) - setTo (chooser.getResult()); + chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc) + { + if (fc.getResult() == File{}) + return; + + setTo (fc.getResult()); + }); } } @@ -189,6 +201,8 @@ private: String wildcards; File root; + std::unique_ptr chooser; + //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilePathPropertyComponent) }; diff --git a/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h b/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h index 4e4c261c25..689b55c628 100644 --- a/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h +++ b/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h @@ -191,11 +191,18 @@ public: /** Pops up a dialog letting the user save the processor's state to a file. */ void askUserToSaveState (const String& fileSuffix = String()) { - #if JUCE_MODAL_LOOPS_PERMITTED - FileChooser fc (TRANS("Save current state"), getLastFile(), getFilePatterns (fileSuffix)); - - if (fc.browseForFileToSave (true)) + stateFileChooser = std::make_unique (TRANS("Save current state"), + getLastFile(), + getFilePatterns (fileSuffix)); + auto flags = FileBrowserComponent::saveMode + | FileBrowserComponent::canSelectFiles + | FileBrowserComponent::warnAboutOverwriting; + + stateFileChooser->launchAsync (flags, [this] (const FileChooser& fc) { + if (fc.getResult() == File{}) + return; + setLastFile (fc); MemoryBlock data; @@ -205,20 +212,23 @@ public: AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, TRANS("Error whilst saving"), TRANS("Couldn't write to the specified file!")); - } - #else - ignoreUnused (fileSuffix); - #endif + }); } /** Pops up a dialog letting the user re-load the processor's state from a file. */ void askUserToLoadState (const String& fileSuffix = String()) { - #if JUCE_MODAL_LOOPS_PERMITTED - FileChooser fc (TRANS("Load a saved state"), getLastFile(), getFilePatterns (fileSuffix)); + stateFileChooser = std::make_unique (TRANS("Load a saved state"), + getLastFile(), + getFilePatterns (fileSuffix)); + auto flags = FileBrowserComponent::openMode + | FileBrowserComponent::canSelectFiles; - if (fc.browseForFileToOpen()) + stateFileChooser->launchAsync (flags, [this] (const FileChooser& fc) { + if (fc.getResult() == File{}) + return; + setLastFile (fc); MemoryBlock data; @@ -229,10 +239,7 @@ public: AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, TRANS("Error whilst loading"), TRANS("Couldn't read from the specified file!")); - } - #else - ignoreUnused (fileSuffix); - #endif + }); } //============================================================================== @@ -407,6 +414,8 @@ public: std::unique_ptr options; Array lastMidiDevices; + std::unique_ptr stateFileChooser; + private: /* This class can be used to ensure that audio callbacks use buffers with a predictable maximum size. diff --git a/modules/juce_core/system/juce_PlatformDefs.h b/modules/juce_core/system/juce_PlatformDefs.h index eab4a30036..0639d25440 100644 --- a/modules/juce_core/system/juce_PlatformDefs.h +++ b/modules/juce_core/system/juce_PlatformDefs.h @@ -335,7 +335,7 @@ namespace juce #elif ! defined (JUCE_MODAL_LOOPS_PERMITTED) /** Some operating environments don't provide a modal loop mechanism, so this flag can be used to disable any functions that try to run a modal loop. */ - #define JUCE_MODAL_LOOPS_PERMITTED 1 + #define JUCE_MODAL_LOOPS_PERMITTED 0 #endif //============================================================================== diff --git a/modules/juce_events/messages/juce_MessageManager.h b/modules/juce_events/messages/juce_MessageManager.h index 79f7ad730f..a2f63d244c 100644 --- a/modules/juce_events/messages/juce_MessageManager.h +++ b/modules/juce_events/messages/juce_MessageManager.h @@ -79,7 +79,7 @@ public: */ bool hasStopMessageBeenSent() const noexcept { return quitMessagePosted.get() != 0; } - #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN + #if JUCE_MODAL_LOOPS_PERMITTED /** Synchronously dispatches messages until a given time has elapsed. Returns false if a quit message has been posted by a call to stopDispatchLoop(), diff --git a/modules/juce_gui_basics/components/juce_Component.h b/modules/juce_gui_basics/components/juce_Component.h index b3f2a1570e..20b8010c9e 100644 --- a/modules/juce_gui_basics/components/juce_Component.h +++ b/modules/juce_gui_basics/components/juce_Component.h @@ -2020,7 +2020,7 @@ public: virtual void handleCommandMessage (int commandId); //============================================================================== - #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN + #if JUCE_MODAL_LOOPS_PERMITTED /** Runs a component modally, waiting until the loop terminates. This method first makes the component visible, brings it to the front and diff --git a/modules/juce_gui_basics/components/juce_ModalComponentManager.h b/modules/juce_gui_basics/components/juce_ModalComponentManager.h index 029ac225ad..deee072327 100644 --- a/modules/juce_gui_basics/components/juce_ModalComponentManager.h +++ b/modules/juce_gui_basics/components/juce_ModalComponentManager.h @@ -119,7 +119,7 @@ public: */ bool cancelAllModalComponents(); - #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN + #if JUCE_MODAL_LOOPS_PERMITTED /** Runs the event loop until the currently topmost modal component is dismissed, and returns the exit code for that component. */ @@ -164,6 +164,7 @@ class JUCE_API ModalCallbackFunction public: /** This is a utility function to create a ModalComponentManager::Callback that will call a lambda function. + The lambda that you supply must take an integer parameter, which is the result code that was returned when the modal component was dismissed. diff --git a/modules/juce_gui_basics/filebrowser/juce_FileChooser.h b/modules/juce_gui_basics/filebrowser/juce_FileChooser.h index df6197b510..3fa1c87f8c 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileChooser.h +++ b/modules/juce_gui_basics/filebrowser/juce_FileChooser.h @@ -30,22 +30,18 @@ namespace juce /** Creates a dialog box to choose a file or directory to load or save. - To use a FileChooser: - - create one (as a local stack variable is the neatest way) - - call one of its browseFor.. methods - - if this returns true, the user has selected a file, so you can retrieve it - with the getResult() method. - e.g. @code void loadMooseFile() { - FileChooser myChooser ("Please select the moose you want to load...", - File::getSpecialLocation (File::userHomeDirectory), - "*.moose"); + myChooser = std::make_unique ("Please select the moose you want to load...", + File::getSpecialLocation (File::userHomeDirectory), + "*.moose"); + + auto folderChooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories; - if (myChooser.browseForFileToOpen()) + myChooser->launchAsync (folderChooserFlags, [this] (const FileChooser& chooser) { - File mooseFile (myChooser.getResult()); + File mooseFile (chooser.getResult()); loadMoose (mooseFile); } @@ -125,7 +121,7 @@ public: ~FileChooser(); //============================================================================== - #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN + #if JUCE_MODAL_LOOPS_PERMITTED /** Shows a dialog box to choose a file to open. This will display the dialog box modally, using an "open file" mode, so that @@ -181,7 +177,6 @@ public: browseForFileToOpen() for more info about the behaviour of this method. */ bool browseForMultipleFilesOrDirectories (FilePreviewComponent* previewComponent = nullptr); - #endif //============================================================================== /** Runs a dialog box for the given set of option flags. @@ -193,6 +188,7 @@ public: @see FileBrowserComponent::FileChooserFlags */ bool showDialog (int flags, FilePreviewComponent* previewComponent); +#endif /** Use this method to launch the file browser window asynchronously. @@ -200,12 +196,9 @@ public: structure and will launch it modally, returning immediately. You must specify a callback which is called when the file browser is - canceled or a file is selected. To abort the file selection, simply + cancelled or a file is selected. To abort the file selection, simply delete the FileChooser object. - You can use the ModalCallbackFunction::create method to wrap a lambda - into a modal Callback object. - You must ensure that the lifetime of the callback object is longer than the lifetime of the file-chooser. */ diff --git a/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h b/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h index 160978e4ca..f75958df00 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h +++ b/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h @@ -33,29 +33,34 @@ namespace juce This is a Juce-based file dialog box; to use a native file chooser, see the FileChooser class. - To use one of these, create it and call its show() method. e.g. - @code { - WildcardFileFilter wildcardFilter ("*.foo", String(), "Foo files"); + wildcardFilter = std::make_unique ("*.foo", String(), "Foo files"); - FileBrowserComponent browser (FileBrowserComponent::canSelectFiles, - File(), - &wildcardFilter, - nullptr); + browser = std::make_unique (FileBrowserComponent::canSelectFiles, + File(), + wildcardFilter.get(), + nullptr); - FileChooserDialogBox dialogBox ("Open some kind of file", - "Please choose some kind of file that you want to open...", - browser, - false, - Colours::lightgrey); + dialogBox = std::make_unique ("Open some kind of file", + "Please choose some kind of file that you want to open...", + *browser, + false, + Colours::lightgrey); - if (dialogBox.show()) + auto onFileSelected = [this] (int r) { - File selectedFile = browser.getSelectedFile (0); + modalStateFinished (r); + + auto selectedFile = browser->getSelectedFile (0); + + ...etc... + }; - ...etc.. - } + dialogBox->centreWithDefaultSize (nullptr); + dialogBox->enterModalState (true, + ModalCallbackFunction::create (onFileSelected), + true); } @endcode @@ -99,7 +104,7 @@ public: ~FileChooserDialogBox() override; //============================================================================== - #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN + #if JUCE_MODAL_LOOPS_PERMITTED /** Displays and runs the dialog box modally. This will show the box with the specified size, returning true if the user diff --git a/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp b/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp index 70ef0c426d..74677f403b 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp +++ b/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp @@ -149,18 +149,18 @@ void FileSearchPathListComponent::deleteKeyPressed (int row) void FileSearchPathListComponent::returnKeyPressed (int row) { - #if JUCE_MODAL_LOOPS_PERMITTED - FileChooser chooser (TRANS("Change folder..."), path[row], "*"); + chooser = std::make_unique (TRANS("Change folder..."), path[row], "*"); + auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories; - if (chooser.browseForDirectory()) + chooser->launchAsync (chooserFlags, [this, row] (const FileChooser& fc) { + if (fc.getResult() == File{}) + return; + path.remove (row); - path.add (chooser.getResult(), row); + path.add (fc.getResult(), row); changed(); - } - #else - ignoreUnused (row); - #endif + }); } void FileSearchPathListComponent::listBoxItemDoubleClicked (int row, const MouseEvent&) @@ -226,16 +226,17 @@ void FileSearchPathListComponent::addPath() if (start == File()) start = File::getCurrentWorkingDirectory(); - #if JUCE_MODAL_LOOPS_PERMITTED - FileChooser chooser (TRANS("Add a folder..."), start, "*"); + chooser = std::make_unique (TRANS("Add a folder..."), start, "*"); + auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories; - if (chooser.browseForDirectory()) - path.add (chooser.getResult(), listBox.getSelectedRow()); + chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc) + { + if (fc.getResult() == File{}) + return; - changed(); - #else - jassertfalse; // needs rewriting to deal with non-modal environments - #endif + path.add (fc.getResult(), listBox.getSelectedRow()); + changed(); + }); } void FileSearchPathListComponent::deleteSelected() diff --git a/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.h b/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.h index 26f21c07b5..a1a52fa16f 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.h +++ b/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.h @@ -100,6 +100,7 @@ private: //============================================================================== FileSearchPath path; File defaultBrowseTarget; + std::unique_ptr chooser; ListBox listBox; TextButton addButton, removeButton, changeButton; diff --git a/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp b/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp index 8912eb92ad..a4753da253 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp +++ b/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp @@ -114,22 +114,22 @@ File FilenameComponent::getLocationToBrowse() void FilenameComponent::showChooser() { - #if JUCE_MODAL_LOOPS_PERMITTED - FileChooser fc (isDir ? TRANS ("Choose a new directory") - : TRANS ("Choose a new file"), - getLocationToBrowse(), - wildcard); - - if (isDir ? fc.browseForDirectory() - : (isSaving ? fc.browseForFileToSave (false) - : fc.browseForFileToOpen())) + chooser = std::make_unique (isDir ? TRANS ("Choose a new directory") + : TRANS ("Choose a new file"), + getLocationToBrowse(), + wildcard); + + auto chooserFlags = isDir ? FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories + : FileBrowserComponent::canSelectFiles | (isSaving ? FileBrowserComponent::saveMode + : FileBrowserComponent::openMode); + + chooser->launchAsync (chooserFlags, [this] (const FileChooser&) { - setCurrentFile (fc.getResult(), true); - } - #else - ignoreUnused (isSaving); - jassertfalse; // needs rewriting to deal with non-modal environments - #endif + if (chooser->getResult() == File{}) + return; + + setCurrentFile (chooser->getResult(), true); + }); } bool FilenameComponent::isInterestedInFileDrag (const StringArray&) diff --git a/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.h b/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.h index 8faefa68cf..e5fb639133 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.h +++ b/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.h @@ -73,21 +73,21 @@ public: //============================================================================== /** Creates a FilenameComponent. - @param name the name for this component. - @param currentFile the file to initially show in the box - @param canEditFilename if true, the user can manually edit the filename; if false, - they can only change it by browsing for a new file - @param isDirectory if true, the file will be treated as a directory, and - an appropriate directory browser used - @param isForSaving if true, the file browser will allow non-existent files to - be picked, as the file is assumed to be used for saving rather - than loading - @param fileBrowserWildcard a wildcard pattern to use in the file browser - e.g. "*.txt;*.foo". - If an empty string is passed in, then the pattern is assumed to be "*" - @param enforcedSuffix if this is non-empty, it is treated as a suffix that will be added - to any filenames that are entered or chosen + @param name the name for this component. + @param currentFile the file to initially show in the box + @param canEditFilename if true, the user can manually edit the filename; if false, + they can only change it by browsing for a new file + @param isDirectory if true, the file will be treated as a directory, and + an appropriate directory browser used + @param isForSaving if true, the file browser will allow non-existent files to + be picked, as the file is assumed to be used for saving rather + than loading + @param fileBrowserWildcard a wildcard pattern to use in the file browser - e.g. "*.txt;*.foo". + If an empty string is passed in, then the pattern is assumed to be "*" + @param enforcedSuffix if this is non-empty, it is treated as a suffix that will be added + to any filenames that are entered or chosen @param textWhenNothingSelected the message to display in the box before any filename is entered. (This - will only appear if the initial file isn't valid) + will only appear if the initial file isn't valid) */ FilenameComponent (const String& name, const File& currentFile, @@ -216,6 +216,10 @@ public: private: //============================================================================== + void handleAsyncUpdate() override; + + void showChooser(); + ComboBox filenameBox; String lastFilename; std::unique_ptr