Browse Source

Set the default value of JUCE_MODAL_LOOPS_PERMITTED to 0

See BREAKING-CHANGES.txt for more details.
v6.1.6
Tom Poole 3 years ago
parent
commit
fe4ba9071b
79 changed files with 3391 additions and 1300 deletions
  1. +25
    -0
      BREAKING-CHANGES.txt
  2. +6
    -10
      examples/Assets/DSPDemos_Common.h
  3. +6
    -7
      examples/Audio/AudioPlaybackDemo.h
  4. +6
    -10
      examples/DemoRunner/Builds/Android/app/src/main/assets/DSPDemos_Common.h
  5. +61
    -33
      examples/GUI/DialogsDemo.h
  6. +29
    -23
      examples/GUI/MDIDemo.h
  7. +10
    -10
      examples/GUI/OpenGLDemo.h
  8. +6
    -10
      extras/AudioPluginHost/Builds/Android/app/src/main/assets/DSPDemos_Common.h
  9. +85
    -21
      extras/AudioPluginHost/Source/UI/MainHostWindow.cpp
  10. +23
    -13
      extras/Projucer/Source/Application/StartPage/jucer_ContentComponents.h
  11. +64
    -36
      extras/Projucer/Source/Application/StartPage/jucer_NewProjectWizard.cpp
  12. +4
    -3
      extras/Projucer/Source/Application/StartPage/jucer_NewProjectWizard.h
  13. +29
    -16
      extras/Projucer/Source/Application/Windows/jucer_EditorColourSchemeWindowComponent.h
  14. +15
    -6
      extras/Projucer/Source/Application/Windows/jucer_PIPCreatorWindowComponent.h
  15. +33
    -22
      extras/Projucer/Source/Application/Windows/jucer_TranslationToolWindowComponent.h
  16. +56
    -31
      extras/Projucer/Source/Application/jucer_Application.cpp
  17. +6
    -3
      extras/Projucer/Source/Application/jucer_Application.h
  18. +39
    -18
      extras/Projucer/Source/Application/jucer_AutoUpdater.cpp
  19. +3
    -0
      extras/Projucer/Source/Application/jucer_AutoUpdater.h
  20. +10
    -5
      extras/Projucer/Source/Application/jucer_CommandLine.cpp
  21. +264
    -77
      extras/Projucer/Source/Application/jucer_MainWindow.cpp
  22. +6
    -5
      extras/Projucer/Source/Application/jucer_MainWindow.h
  23. +160
    -48
      extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.cpp
  24. +24
    -9
      extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.h
  25. +46
    -17
      extras/Projucer/Source/CodeEditor/jucer_SourceCodeEditor.cpp
  26. +8
    -2
      extras/Projucer/Source/CodeEditor/jucer_SourceCodeEditor.h
  27. +1
    -1
      extras/Projucer/Source/ComponentEditor/Components/jucer_ComponentTypeHandler.h
  28. +13
    -11
      extras/Projucer/Source/ComponentEditor/Components/jucer_TabbedComponentHandler.h
  29. +10
    -8
      extras/Projucer/Source/ComponentEditor/PaintElements/jucer_ImageResourceProperty.h
  30. +1
    -2
      extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElement.cpp
  31. +1
    -1
      extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElement.h
  32. +1
    -1
      extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElementPath.h
  33. +125
    -110
      extras/Projucer/Source/ComponentEditor/Properties/jucer_PositionPropertyBase.h
  34. +2
    -2
      extras/Projucer/Source/ComponentEditor/UI/jucer_ComponentLayoutEditor.cpp
  35. +1
    -1
      extras/Projucer/Source/ComponentEditor/UI/jucer_PaintRoutineEditor.cpp
  36. +9
    -10
      extras/Projucer/Source/ComponentEditor/UI/jucer_ResourceEditorPanel.cpp
  37. +16
    -21
      extras/Projucer/Source/ComponentEditor/jucer_BinaryResources.cpp
  38. +8
    -9
      extras/Projucer/Source/ComponentEditor/jucer_BinaryResources.h
  39. +1
    -1
      extras/Projucer/Source/ComponentEditor/jucer_ComponentLayout.h
  40. +80
    -39
      extras/Projucer/Source/ComponentEditor/jucer_JucerDocument.cpp
  41. +1
    -1
      extras/Projucer/Source/ComponentEditor/jucer_PaintRoutine.h
  42. +8
    -7
      extras/Projucer/Source/Project/Modules/jucer_Modules.cpp
  43. +2
    -0
      extras/Projucer/Source/Project/Modules/jucer_Modules.h
  44. +39
    -12
      extras/Projucer/Source/Project/UI/Sidebar/jucer_ExporterTreeItems.h
  45. +98
    -51
      extras/Projucer/Source/Project/UI/Sidebar/jucer_FileTreeItems.h
  46. +1
    -1
      extras/Projucer/Source/Project/UI/Sidebar/jucer_ModuleTreeItems.h
  47. +1
    -1
      extras/Projucer/Source/Project/UI/jucer_HeaderComponent.cpp
  48. +50
    -32
      extras/Projucer/Source/Project/UI/jucer_ProjectContentComponent.cpp
  49. +6
    -3
      extras/Projucer/Source/Project/UI/jucer_ProjectContentComponent.h
  50. +129
    -59
      extras/Projucer/Source/Project/jucer_Project.cpp
  51. +16
    -5
      extras/Projucer/Source/Project/jucer_Project.h
  52. +22
    -7
      extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.cpp
  53. +19
    -6
      extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.h
  54. +81
    -62
      extras/Projucer/Source/Utility/Helpers/jucer_NewFileWizard.cpp
  55. +6
    -2
      extras/Projucer/Source/Utility/Helpers/jucer_NewFileWizard.h
  56. +20
    -6
      extras/Projucer/Source/Utility/UI/PropertyComponents/jucer_FilePathPropertyComponent.h
  57. +24
    -15
      modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h
  58. +1
    -1
      modules/juce_core/system/juce_PlatformDefs.h
  59. +1
    -1
      modules/juce_events/messages/juce_MessageManager.h
  60. +1
    -1
      modules/juce_gui_basics/components/juce_Component.h
  61. +2
    -1
      modules/juce_gui_basics/components/juce_ModalComponentManager.h
  62. +10
    -17
      modules/juce_gui_basics/filebrowser/juce_FileChooser.h
  63. +22
    -17
      modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h
  64. +17
    -16
      modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp
  65. +1
    -0
      modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.h
  66. +15
    -15
      modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp
  67. +19
    -17
      modules/juce_gui_basics/filebrowser/juce_FilenameComponent.h
  68. +0
    -1
      modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.h
  69. +167
    -63
      modules/juce_gui_basics/layout/juce_MultiDocumentPanel.cpp
  70. +75
    -6
      modules/juce_gui_basics/layout/juce_MultiDocumentPanel.h
  71. +1
    -1
      modules/juce_gui_basics/menus/juce_PopupMenu.h
  72. +2
    -2
      modules/juce_gui_basics/windows/juce_AlertWindow.h
  73. +2
    -2
      modules/juce_gui_basics/windows/juce_DialogWindow.h
  74. +1
    -1
      modules/juce_gui_basics/windows/juce_NativeMessageBox.h
  75. +1
    -1
      modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.h
  76. +1023
    -168
      modules/juce_gui_extra/documents/juce_FileBasedDocument.cpp
  77. +143
    -11
      modules/juce_gui_extra/documents/juce_FileBasedDocument.h
  78. +1
    -1
      modules/juce_gui_extra/misc/juce_PushNotifications.h
  79. +70
    -35
      modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm

+ 25
- 0
BREAKING-CHANGES.txt View File

@@ -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


+ 6
- 10
examples/Assets/DSPDemos_Common.h View File

@@ -590,14 +590,13 @@ private:
if (fileChooser != nullptr)
return;
SafePointer<AudioPlayerHeader> safeThis (this);
if (! RuntimePermissions::isGranted (RuntimePermissions::readExternalStorage))
{
SafePointer<AudioPlayerHeader> 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);
}


+ 6
- 7
examples/Audio/AudioPlaybackDemo.h View File

@@ -498,14 +498,13 @@ private:
{
if (btn == &chooseFileButton && fileChooser.get() == nullptr)
{
SafePointer<AudioPlaybackDemo> safeThis (this);
if (! RuntimePermissions::isGranted (RuntimePermissions::readExternalStorage))
{
SafePointer<AudioPlaybackDemo> 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


+ 6
- 10
examples/DemoRunner/Builds/Android/app/src/main/assets/DSPDemos_Common.h View File

@@ -590,14 +590,13 @@ private:
if (fileChooser != nullptr)
return;
SafePointer<AudioPlayerHeader> safeThis (this);
if (! RuntimePermissions::isGranted (RuntimePermissions::readExternalStorage))
{
SafePointer<AudioPlayerHeader> 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);
}


+ 61
- 33
examples/GUI/DialogsDemo.h View File

@@ -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<TextButton> 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> ("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<FileChooser> fc;
std::unique_ptr<AlertWindow> asyncAlertWindow;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DialogsDemo)
};

+ 29
- 23
examples/GUI/MDIDemo.h View File

@@ -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<void (bool)> callback) override
{
closeAllDocuments (true);
}
bool tryToCloseDocument (Component* component) override
{
#if JUCE_MODAL_LOOPS_PERMITTED
if (auto* note = dynamic_cast<Note*> (component))
return note->saveIfNeededAndUserAgrees() != FileBasedDocument::failedToWriteToFile;
#else
ignoreUnused (component);
#endif
return true;
{
SafePointer<DemoMultiDocumentPanel> 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)
};

+ 10
- 10
examples/GUI/OpenGLDemo.h View File

@@ -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<FileChooser> ("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<OpenGLUtils::DemoTexture> textures;
std::unique_ptr<FileChooser> textureFileChooser;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DemoControlsOverlay)
};


+ 6
- 10
extras/AudioPluginHost/Builds/Android/app/src/main/assets/DSPDemos_Common.h View File

@@ -590,14 +590,13 @@ private:
if (fileChooser != nullptr)
return;
SafePointer<AudioPlayerHeader> safeThis (this);
if (! RuntimePermissions::isGranted (RuntimePermissions::readExternalStorage))
{
SafePointer<AudioPlayerHeader> 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);
}


+ 85
- 21
extras/AudioPluginHost/Source/UI/MainHostWindow.cpp View File

@@ -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<MainHostWindow> 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<MainHostWindow> 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<MainHostWindow> 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<MainHostWindow> 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<MainHostWindow> parent;
g->saveIfNeededAndUserAgreesAsync ([parent, g, firstFile] (FileBasedDocument::SaveResult r)
{
if (parent == nullptr)
return;
if (r == FileBasedDocument::savedOk)
g->loadFrom (firstFile, true);
});
}
}
else
#endif


+ 23
- 13
extras/Projucer/Source/Application/StartPage/jucer_ContentComponents.h View File

@@ -99,25 +99,34 @@ public:
{
createProjectButton.onClick = [this]
{
FileChooser fc ("Save Project", NewProjectWizard::getLastWizardFolder());
chooser = std::make_unique<FileChooser> ("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<TemplateComponent> 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> 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<FileChooser> chooser;
std::function<void (std::unique_ptr<Project>)> projectCreatedCallback;
ItemHeader header;


+ 64
- 36
extras/Projucer/Source/Application/StartPage/jucer_NewProjectWizard.cpp View File

@@ -220,63 +220,91 @@ File NewProjectWizard::getLastWizardFolder()
return lastFolderFallback;
}
std::unique_ptr<Project> 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 <typename Callback>
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<Project> (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<void (std::unique_ptr<Project>)> callback)
{
prepareDirectory (targetFolder, [=]
{
auto project = std::make_unique<Project> (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::unique_ptr<Project>> (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);
});
}

+ 4
- 3
extras/Projucer/Source/Application/StartPage/jucer_NewProjectWizard.h View File

@@ -32,7 +32,8 @@ namespace NewProjectWizard
{
File getLastWizardFolder();
std::unique_ptr<Project> 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<void (std::unique_ptr<Project>)> callback);
}

+ 29
- 16
extras/Projucer/Source/Application/Windows/jucer_EditorColourSchemeWindowComponent.h View File

@@ -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<FileChooser> ("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<FileChooser> ("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<FileChooser> chooser;
JUCE_DECLARE_NON_COPYABLE (EditorPanel)
};


+ 15
- 6
extras/Projucer/Source/Application/Windows/jucer_PIPCreatorWindowComponent.h View File

@@ -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<FileChooser> ("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<FileChooser> chooser;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PIPCreatorWindowComponent)
};

+ 33
- 22
extras/Projucer/Source/Application/Windows/jucer_TranslationToolWindowComponent.h View File

@@ -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<FileChooser> ("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<FileChooser> ("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<FileChooser> chooser;
};

+ 56
- 31
extras/Projucer/Source/Application/jucer_Application.cpp View File

@@ -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<ProjucerApplication> 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<FileChooser> ("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<void (bool)> 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<void (bool)> callback)
{
return mainWindowList.askAllWindowsToClose();
mainWindowList.askAllWindowsToClose (std::move (callback));
}
void ProjucerApplication::closeAllMainWindowsAndQuitIfNeeded()
{
if (closeAllMainWindows())
WeakReference<ProjucerApplication> 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()


+ 6
- 3
extras/Projucer/Source/Application/jucer_Application.h View File

@@ -66,7 +66,7 @@ public:
bool isGUIEditorEnabled() const;
//==============================================================================
bool openFile (const File&);
void openFile (const File&, std::function<void (bool)>);
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 (bool)>);
void closeAllMainWindowsAndQuitIfNeeded();
void clearRecentFiles();
@@ -216,6 +216,9 @@ private:
int selectedColourSchemeIndex = 0, selectedEditorColourSchemeIndex = 0;
std::unique_ptr<FileChooser> chooser;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjucerApplication)
JUCE_DECLARE_WEAK_REFERENCEABLE (ProjucerApplication)
};

+ 39
- 18
extras/Projucer/Source/Application/jucer_AutoUpdater.cpp View File

@@ -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<FileChooser> ("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<LatestVersionCheckerAndUpdater> 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,


+ 3
- 0
extras/Projucer/Source/Application/jucer_AutoUpdater.h View File

@@ -56,4 +56,7 @@ private:
std::unique_ptr<DownloadAndInstallThread> installer;
std::unique_ptr<Component> dialogWindow;
std::unique_ptr<FileChooser> chooser;
JUCE_DECLARE_WEAK_REFERENCEABLE (LatestVersionCheckerAndUpdater)
};

+ 10
- 5
extras/Projucer/Source/Application/jucer_CommandLine.cpp View File

@@ -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);
}
}


+ 264
- 77
extras/Projucer/Source/Application/jucer_MainWindow.cpp View File

@@ -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<void (bool)> 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<MainWindow> 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<MainWindow> 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<Project> 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<void (bool)> callback)
{
if (file.hasFileExtension (Project::projectFileExtension))
{
auto newDoc = std::make_unique<Project> (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<MainWindow> parent { this };
auto sharedDoc = std::make_shared<std::unique_ptr<Project>> (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<MainWindow> 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<void (bool)> callback)
{
if (! generator.hasValidPIP())
return false;
auto generator = std::make_shared<PIPGenerator> (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<MainWindow> 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<MainWindow> 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<Project>&& 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<MainWindowList> parent, std::function<void (bool)> 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<void (bool)> 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<MainWindowList> 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<void (bool)> 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<MainWindowList> 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()


+ 6
- 5
extras/Projucer/Source/Application/jucer_MainWindow.h View File

@@ -53,7 +53,7 @@ public:
//==============================================================================
bool canOpenFile (const File& file) const;
bool openFile (const File& file);
void openFile (const File& file, std::function<void (bool)> callback);
void setProject (std::unique_ptr<Project> 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<void (bool)> 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<void (bool)> callback);
void setupTemporaryPIPProject (PIPGenerator&);
void initialiseProjectWindow();
@@ -112,14 +112,14 @@ public:
MainWindowList();
void forceCloseAllWindows();
bool askAllWindowsToClose();
void askAllWindowsToClose (std::function<void (bool)> 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<void (bool)> 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)
};

+ 160
- 48
extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.cpp View File

@@ -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<void (bool)>) override {}
void saveAsAsync (std::function<void (bool)>) 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<void (FileBasedDocument::SaveResult)> 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<OpenDocumentManager> 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<void (bool)> callback)
{
return closeDocument (documents.indexOf (document), saveIfNeeded);
if (! documents.contains (doc))
{
if (callback != nullptr)
callback (true);
return;
}
if (saveIfNeeded == SaveIfNeeded::yes)
{
WeakReference<OpenDocumentManager> 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<OpenDocumentManager> parent,
OpenDocumentManager::SaveIfNeeded askUserToSave,
std::function<void (bool)> 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<void (bool)> callback)
{
closeLastAsyncRecusrsive (this, askUserToSave, std::move (callback));
}
void OpenDocumentManager::closeLastDocumentUsingProjectRecursive (WeakReference<OpenDocumentManager> parent,
Project* project,
SaveIfNeeded askUserToSave,
std::function<void (bool)> 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<void (bool)> callback)
{
WeakReference<OpenDocumentManager> 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()


+ 24
- 9
extras/Projucer/Source/CodeEditor/jucer_OpenDocumentManager.h View File

@@ -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<void (bool)>) = 0;
virtual void saveAsAsync (std::function<void (bool)>) = 0;
virtual bool hasFileBeenModifiedExternally() = 0;
virtual void reloadFromFile() = 0;
virtual std::unique_ptr<Component> 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<void (bool)> callback);
bool closeDocumentWithoutSaving (Document* document);
void closeAllAsync (SaveIfNeeded askUserToSave, std::function<void (bool)> callback);
void closeAllDocumentsUsingProjectAsync (Project& project, SaveIfNeeded askUserToSave, std::function<void (bool)> 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 (FileBasedDocument::SaveResult)>);
void reloadModifiedFiles();
void fileHasBeenRenamed (const File& oldFile, const File& newFile);
@@ -112,11 +119,19 @@ public:
private:
//==============================================================================
void closeLastDocumentUsingProjectRecursive (WeakReference<OpenDocumentManager>,
Project*,
SaveIfNeeded,
std::function<void (bool)>);
//==============================================================================
OwnedArray<DocumentType> types;
OwnedArray<Document> documents;
Array<DocumentCloseListener*> listeners;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenDocumentManager)
JUCE_DECLARE_WEAK_REFERENCEABLE (OpenDocumentManager)
};
//==============================================================================


+ 46
- 17
extras/Projucer/Source/CodeEditor/jucer_SourceCodeEditor.cpp View File

@@ -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<void (bool)> callback)
{
FileChooser fc (TRANS("Save As..."), getFile(), "*");
callback (saveSyncWithoutAsking());
}
if (! fc.browseForFileToSave (true))
return true;
void SourceCodeDocument::saveAsAsync (std::function<void (bool)> callback)
{
chooser = std::make_unique<FileChooser> (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<AlertWindow> (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<CppCodeEditorComponent> 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();
}));
}

+ 8
- 2
extras/Projucer/Source/CodeEditor/jucer_SourceCodeEditor.h View File

@@ -80,8 +80,9 @@ public:
}
void reloadFromFile() override;
bool save() override;
bool saveAs() override;
bool saveSyncWithoutAsking() override;
void saveAsync (std::function<void (bool)>) override;
void saveAsAsync (std::function<void (bool)>) override;
std::unique_ptr<Component> createEditor() override;
std::unique_ptr<Component> createViewer() override { return createEditor(); }
@@ -132,6 +133,9 @@ protected:
std::unique_ptr<CodeEditorComponent::State> lastState;
void reloadInternal();
private:
std::unique_ptr<FileChooser> chooser;
};
class GenericCodeEditorComponent;
@@ -235,5 +239,7 @@ public:
private:
void insertComponentClass();
std::unique_ptr<AlertWindow> asyncAlertWindow;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CppCodeEditorComponent)
};

+ 1
- 1
extras/Projucer/Source/ComponentEditor/Components/jucer_ComponentTypeHandler.h View File

@@ -140,7 +140,7 @@ protected:
String colourIdCode, colourName, xmlTagName;
};
OwnedArray <ComponentColourInfo> colours;
OwnedArray<ComponentColourInfo> colours;
private:
JUCE_DECLARE_NON_COPYABLE (ComponentTypeHandler)


+ 13
- 11
extras/Projucer/Source/ComponentEditor/Components/jucer_TabbedComponentHandler.h View File

@@ -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


+ 10
- 8
extras/Projucer/Source/ComponentEditor/PaintElements/jucer_ImageResourceProperty.h View File

@@ -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
{


+ 1
- 2
extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElement.cpp View File

@@ -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 ({});
}

+ 1
- 1
extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElement.h View File

@@ -124,7 +124,7 @@ protected:
void siblingComponentsChanged();
OwnedArray <ElementSiblingComponent> siblingComponents;
OwnedArray<ElementSiblingComponent> siblingComponents;
void updateSiblingComps();


+ 1
- 1
extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElementPath.h View File

@@ -127,7 +127,7 @@ public:
private:
friend class PathPoint;
friend class PathPointComponent;
OwnedArray <PathPoint> points;
OwnedArray<PathPoint> points;
bool nonZeroWinding;
mutable Path path;
mutable Rectangle<int> lastPathBounds;


+ 125
- 110
extras/Projucer/Source/ComponentEditor/Properties/jucer_PositionPropertyBase.h View File

@@ -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<PositionPropertyBase> 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<void (bool)> callback)
{
RelativePositionedRectangle rpr (getPosition());
PositionedRectangle p (rpr.rect);
@@ -255,127 +262,135 @@ public:
m.addSubMenu ("Relative to", compLayout->getRelativeTargetMenu (component, (int) dimension));
}
WeakReference<Component> ref (this);
const int menuResult = m.showAt (&button);
if (menuResult == 0 || ref == nullptr)
return false;
SafePointer<PositionPropertyBase> 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<int>
{
if (ref->component->findParentComponentOfClass<ComponentLayoutEditor>() != nullptr)
return { ref->component->getParentWidth(), ref->component->getParentHeight() };
Rectangle<int> parentArea;
if (auto pre = dynamic_cast<PaintRoutineEditor*> (ref->component->getParentComponent()))
return pre->getComponentArea();
if (component->findParentComponentOfClass<ComponentLayoutEditor>() != nullptr)
parentArea.setSize (component->getParentWidth(), component->getParentHeight());
else if (auto pre = dynamic_cast<PaintRoutineEditor*> (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<int> (x, y, xw, yh));
xyRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
Rectangle<int> (x, y, xw, yh));
whRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
Rectangle<int> (x, y, w, h));
whRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
Rectangle<int> (x, y, w, h));
p.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
Rectangle<int> (x, y, xw, yh));
p.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
Rectangle<int> (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()


+ 2
- 2
extras/Projucer/Source/ComponentEditor/UI/jucer_ComponentLayoutEditor.cpp View File

@@ -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 <Project::Item> selectedNodes;
OwnedArray<Project::Item> selectedNodes;
ProjectContentComponent::getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
StringArray filenames;


+ 1
- 1
extras/Projucer/Source/ComponentEditor/UI/jucer_PaintRoutineEditor.cpp View File

@@ -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
{


+ 9
- 10
extras/Projucer/Source/ComponentEditor/UI/jucer_ResourceEditorPanel.cpp View File

@@ -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 (", "));
}

+ 16
- 21
extras/Projucer/Source/ComponentEditor/jucer_BinaryResources.cpp View File

@@ -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<void (String)> callback)
{
FileChooser fc (title, fileToStartFrom, wildcard);
chooser = std::make_unique<FileChooser> (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


+ 8
- 9
extras/Projucer/Source/ComponentEditor/jucer_BinaryResources.h View File

@@ -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<void (String)> callback);
String findUniqueName (const String& rootName) const;
@@ -86,12 +84,13 @@ public:
void fillInGeneratedCode (GeneratedCode& code) const;
private:
//==============================================================================
JucerDocument* document;
OwnedArray <BinaryResource> resources;
BinaryResource* findResource (const String& name) const noexcept;
void changed();
//==============================================================================
JucerDocument* document;
OwnedArray<BinaryResource> resources;
std::unique_ptr<FileChooser> chooser;
};

+ 1
- 1
extras/Projucer/Source/ComponentEditor/jucer_ComponentLayout.h View File

@@ -122,7 +122,7 @@ public:
private:
JucerDocument* document;
OwnedArray <Component> components;
OwnedArray<Component> components;
SelectedItemSet <Component*> selected;
int nextCompUID;


+ 80
- 39
extras/Projucer/Source/ComponentEditor/jucer_JucerDocument.cpp View File

@@ -692,25 +692,52 @@ public:
{
}
bool save() override
void saveAsync (std::function<void (bool)> callback) override
{
return SourceCodeDocument::save() && saveHeader();
WeakReference<JucerComponentDocument> 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<void (bool)> callback)
{
auto& odm = ProjucerApplication::getApp().openDocumentManager;
if (auto* header = odm.openFile (nullptr, getFile().withFileExtension (".h")))
{
if (header->save())
WeakReference<JucerComponentDocument> 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<Component> 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<SourceCodeDocument*> (odm.openFile (&project, cppFile)))
{
if (auto* header = dynamic_cast<SourceCodeDocument*> (odm.openFile (&project, headerFile)))
if (auto* cpp = dynamic_cast<SourceCodeDocument*> (odm.openFile (&project, cppFile)))
{
std::unique_ptr<JucerDocument> jucerDoc (new ComponentDocument (cpp));
if (jucerDoc != nullptr)
if (auto* header = dynamic_cast<SourceCodeDocument*> (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<JucerDocument> 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);
}

+ 1
- 1
extras/Projucer/Source/ComponentEditor/jucer_PaintRoutine.h View File

@@ -109,7 +109,7 @@ public:
//==============================================================================
private:
OwnedArray <PaintElement> elements;
OwnedArray<PaintElement> elements;
SelectedItemSet <PaintElement*> selectedElements;
SelectedItemSet <PathPoint*> selectedPoints;
JucerDocument* document;


+ 8
- 7
extras/Projucer/Source/Project/Modules/jucer_Modules.cpp View File

@@ -664,15 +664,16 @@ void EnabledModulesList::addModuleInteractive (const String& moduleID)
void EnabledModulesList::addModuleFromUserSelectedFile()
{
auto lastLocation = getDefaultModulesFolder();
chooser = std::make_unique<FileChooser> ("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)


+ 2
- 0
extras/Projucer/Source/Project/Modules/jucer_Modules.h View File

@@ -142,5 +142,7 @@ private:
CriticalSection stateLock;
ValueTree state;
std::unique_ptr<FileChooser> chooser;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EnabledModulesList)
};

+ 39
- 12
extras/Projucer/Source/Project/UI/Sidebar/jucer_ExporterTreeItems.h View File

@@ -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<ExporterItem> 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<ConfigItem> 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<int> p) override
@@ -297,6 +322,8 @@ private:
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConfigItem)
JUCE_DECLARE_WEAK_REFERENCEABLE (ConfigItem)
};
//==============================================================================


+ 98
- 51
extras/Projucer/Source/Project/UI/Sidebar/jucer_FileTreeItems.h View File

@@ -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<FileTreeItemBase> treeRootItem { dynamic_cast<FileTreeItemBase*> (tree->getRootItem()) };
if (r == 0)
return;
if (r != 2)
filesToTrash.clear();
if (treeRootItem == nullptr)
{
jassertfalse;
return;
}
if (auto* treeRootItem = dynamic_cast<FileTreeItemBase*> (tree->getRootItem()))
auto doDelete = [treeRootItem, itemsToRemove] (const Array<File>& 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<FileChooser> ("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 <Project::Item>&, int /*insertIndex*/)
virtual void moveSelectedItemsTo (OwnedArray<Project::Item>&, 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<FileChooser> chooser;
JUCE_DECLARE_WEAK_REFERENCEABLE (FileTreeItemBase)
};
//==============================================================================
@@ -456,7 +487,7 @@ public:
}
bool acceptsFileDrop (const StringArray&) const override { return false; }
bool acceptsDragItems (const OwnedArray <Project::Item>&) override { return false; }
bool acceptsDragItems (const OwnedArray<Project::Item>&) 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<SourceFileItem> 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;
};

+ 1
- 1
extras/Projucer/Source/Project/UI/Sidebar/jucer_ModuleTreeItems.h View File

@@ -270,7 +270,7 @@ private:
Array<Value> exporterModulePathValues, globalPathValues;
Value useGlobalPathValue;
OwnedArray <Project::ConfigFlag> configFlags;
OwnedArray<Project::ConfigFlag> configFlags;
PropertyGroupComponent group;
Project& project;


+ 1
- 1
extras/Projucer/Source/Project/UI/jucer_HeaderComponent.cpp View File

@@ -248,7 +248,7 @@ void HeaderComponent::initialiseButtons()
else
{
if (auto exporter = getSelectedExporter())
project->openProjectInIDE (*exporter, true);
project->openProjectInIDE (*exporter, true, nullptr);
}
}
};


+ 50
- 32
extras/Projucer/Source/Project/UI/jucer_ProjectContentComponent.cpp View File

@@ -28,7 +28,12 @@
#include "Sidebar/jucer_Sidebar.h"
NewFileWizard::Type* createGUIComponentWizard();
struct WizardHolder
{
std::unique_ptr<NewFileWizard::Type> 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<ProjectContentComponent> 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<ProjectContentComponent> 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<MainWindow>())
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<NewFileWizard::Type> wizard (createGUIComponentWizard());
wizard->createNewFile (*project, project->getMainGroup());
wizardHolder = std::make_unique<WizardHolder>();
wizardHolder->wizard.reset (createGUIComponentWizard (*project));
wizardHolder->wizard->createNewFile (*project, project->getMainGroup());
}
}


+ 6
- 3
extras/Projucer/Source/Project/UI/jucer_ProjectContentComponent.h View File

@@ -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> 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> wizardHolder;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectContentComponent)
};

+ 129
- 59
extras/Projucer/Source/Project/jucer_Project.cpp View File

@@ -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<MainWindow> 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<void (Result)> 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<void (Result)> 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<bool> vs (isSaving, true, false);
WeakReference<Project> ref (this);
saver = std::make_unique<ProjectSaver> (*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<void (Result)> 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<ProjectExporter> exporter;
std::function<void (Result)> 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<ProjectSaver> (*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<FileChooser> ("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<MainWindow> safeWindow (window);
auto newDirectory = newParentDirectory.getChildFile (tempDirectory.getFileName());
auto oldJucerFileName = getFile().getFileName();
MessageManager::callAsync ([safeWindow, newDirectory, oldJucerFileName, openInIDE]() mutable
saver = std::make_unique<ProjectSaver> (*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<MainWindow> 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()
{


+ 16
- 5
extras/Projucer/Source/Project/jucer_Project.h View File

@@ -31,6 +31,7 @@
class ProjectExporter;
class LibraryModule;
class EnabledModulesList;
class ProjectSaver;
namespace ProjectMessages
{
@@ -109,6 +110,8 @@ namespace ProjectMessages
using MessageAction = std::pair<String, std::function<void()>>;
}
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<void (Result)> callback) override;
Result saveProject (ProjectExporter* exporterToSave = nullptr);
void saveProject (Async, ProjectExporter* exporterToSave, std::function<void (Result)> onCompletion);
Result saveResourcesOnly();
Result openProjectInIDE (ProjectExporter& exporterToOpen, bool saveFirst);
void openProjectInIDE (ProjectExporter& exporterToOpen, bool saveFirst, std::function<void (Result)> 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<void (Result)> onCompletion);
ValueTree projectMessages { ProjectMessages::Ids::projectMessages, {},
{ { ProjectMessages::Ids::notification, {} }, { ProjectMessages::Ids::warning, {} } } };
std::map<Identifier, std::vector<ProjectMessages::MessageAction>> messageActions;
ProjectFileModificationPoller fileModificationPoller { *this };
std::unique_ptr<FileChooser> chooser;
std::unique_ptr<ProjectSaver> saver;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Project)
JUCE_DECLARE_WEAK_REFERENCEABLE (Project)
};

+ 22
- 7
extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.cpp View File

@@ -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<void (Result)> 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<void (Result)> onCompletion)
{
jassert (saveThread == nullptr);
WeakReference<ProjectSaver> ref (this);
saveThread = std::make_unique<SaveThreadWithProgressWindow> (*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()


+ 19
- 6
extras/Projucer/Source/ProjectSaving/jucer_ProjectSaver.h View File

@@ -36,7 +36,7 @@ class ProjectSaver
public:
ProjectSaver (Project& projectToSave);
Result save (ProjectExporter* exporterToSave = nullptr);
void save (Async async, ProjectExporter* exporterToSave, std::function<void (Result)> onCompletion);
Result saveResourcesOnly();
void saveBasicProjectItems (const OwnedArray<LibraryModule>& 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<void (Result)> 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<void (Result)> onCompletion;
JUCE_DECLARE_NON_COPYABLE (SaveThreadWithProgressWindow)
};
@@ -86,6 +95,7 @@ private:
OwnedArray<LibraryModule> getModules();
Result saveProject (ProjectExporter* specifiedExporterToSave);
void saveProjectAsync (ProjectExporter* exporterToSave, std::function<void (Result)> onCompletion);
template <typename WriterCallback>
void writeOrRemoveGeneratedFile (const String& name, WriterCallback&& writerCallback);
@@ -118,8 +128,11 @@ private:
CriticalSection errorLock;
StringArray errors;
std::unique_ptr<SaveThreadWithProgressWindow> saveThread;
bool hasBinaryData = false;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSaver)
JUCE_DECLARE_WEAK_REFERENCEABLE (ProjectSaver)
};

+ 81
- 62
extras/Projucer/Source/Utility/Helpers/jucer_NewFileWizard.cpp View File

@@ -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<AlertWindow> (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<NewComponentFileWizard> 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<AlertWindow> 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<void (File)> 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<FileChooser> ("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());
});
}
//==============================================================================


+ 6
- 2
extras/Projucer/Source/Utility/Helpers/jucer_NewFileWizard.h View File

@@ -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<void (File)> callback);
static void showFailedToWriteMessage (const File& file);
private:
std::unique_ptr<FileChooser> chooser;
};
//==============================================================================


+ 20
- 6
extras/Projucer/Source/Utility/UI/PropertyComponents/jucer_FilePathPropertyComponent.h View File

@@ -134,17 +134,29 @@ private:
if (isDirectory)
{
FileChooser chooser ("Select directory", currentFile);
chooser = std::make_unique<FileChooser> ("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<FileChooser> ("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<FileChooser> chooser;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilePathPropertyComponent)
};


+ 24
- 15
modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h View File

@@ -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<FileChooser> (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<FileChooser> (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<AudioDeviceManager::AudioDeviceSetup> options;
Array<MidiDeviceInfo> lastMidiDevices;
std::unique_ptr<FileChooser> stateFileChooser;
private:
/* This class can be used to ensure that audio callbacks use buffers with a
predictable maximum size.


+ 1
- 1
modules/juce_core/system/juce_PlatformDefs.h View File

@@ -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
//==============================================================================


+ 1
- 1
modules/juce_events/messages/juce_MessageManager.h View File

@@ -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(),


+ 1
- 1
modules/juce_gui_basics/components/juce_Component.h View File

@@ -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


+ 2
- 1
modules/juce_gui_basics/components/juce_ModalComponentManager.h View File

@@ -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.


+ 10
- 17
modules/juce_gui_basics/filebrowser/juce_FileChooser.h View File

@@ -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<FileChooser> ("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.
*/


+ 22
- 17
modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h View File

@@ -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<WildcardFileFilter> ("*.foo", String(), "Foo files");
FileBrowserComponent browser (FileBrowserComponent::canSelectFiles,
File(),
&wildcardFilter,
nullptr);
browser = std::make_unique<FileBrowserComponent> (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<FileChooserDialogBox> ("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


+ 17
- 16
modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp View File

@@ -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<FileChooser> (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<FileChooser> (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()


+ 1
- 0
modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.h View File

@@ -100,6 +100,7 @@ private:
//==============================================================================
FileSearchPath path;
File defaultBrowseTarget;
std::unique_ptr<FileChooser> chooser;
ListBox listBox;
TextButton addButton, removeButton, changeButton;


+ 15
- 15
modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp View File

@@ -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<FileChooser> (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&)


+ 19
- 17
modules/juce_gui_basics/filebrowser/juce_FilenameComponent.h View File

@@ -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<Button> browseButton;
@@ -224,9 +228,7 @@ private:
String wildcard, enforcedSuffix, browseButtonText;
ListenerList <FilenameComponentListener> listeners;
File defaultBrowseFile;
void showChooser();
void handleAsyncUpdate() override;
std::unique_ptr<FileChooser> chooser;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilenameComponent)
};


+ 0
- 1
modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.h View File

@@ -45,7 +45,6 @@ public:
/** Destructor. */
~ImagePreviewComponent() override;
//==============================================================================
/** @internal */
void selectedFileChanged (const File& newSelectedFile) override;


+ 167
- 63
modules/juce_gui_basics/layout/juce_MultiDocumentPanel.cpp View File

@@ -48,7 +48,7 @@ void MultiDocumentPanelWindow::maximiseButtonPressed()
void MultiDocumentPanelWindow::closeButtonPressed()
{
if (auto* owner = getOwner())
owner->closeDocument (getContentComponent(), true);
owner->closeDocumentAsync (getContentComponent(), true, nullptr);
else
jassertfalse; // these windows are only designed to be used inside a MultiDocumentPanel!
}
@@ -95,10 +95,7 @@ MultiDocumentPanel::MultiDocumentPanel()
setOpaque (true);
}
MultiDocumentPanel::~MultiDocumentPanel()
{
closeAllDocuments (false);
}
MultiDocumentPanel::~MultiDocumentPanel() = default;
//==============================================================================
namespace MultiDocHelpers
@@ -109,6 +106,7 @@ namespace MultiDocHelpers
}
}
#if JUCE_MODAL_LOOPS_PERMITTED
bool MultiDocumentPanel::closeAllDocuments (const bool checkItsOkToCloseFirst)
{
while (! components.isEmpty())
@@ -117,6 +115,52 @@ bool MultiDocumentPanel::closeAllDocuments (const bool checkItsOkToCloseFirst)
return true;
}
#endif
void MultiDocumentPanel::closeLastDocumentRecursive (SafePointer<MultiDocumentPanel> parent,
bool checkItsOkToCloseFirst,
std::function<void (bool)> callback)
{
if (parent->components.isEmpty())
{
if (callback != nullptr)
callback (true);
return;
}
parent->closeDocumentAsync (parent->components.getLast(),
checkItsOkToCloseFirst,
[parent, checkItsOkToCloseFirst, callback] (bool closeResult)
{
if (parent == nullptr)
return;
if (! closeResult)
{
if (callback != nullptr)
callback (false);
return;
}
parent->closeLastDocumentRecursive (parent, checkItsOkToCloseFirst, std::move (callback));
});
}
void MultiDocumentPanel::closeAllDocumentsAsync (bool checkItsOkToCloseFirst, std::function<void (bool)> callback)
{
closeLastDocumentRecursive (this, checkItsOkToCloseFirst, std::move (callback));
}
#if JUCE_MODAL_LOOPS_PERMITTED
bool MultiDocumentPanel::tryToCloseDocument (Component*)
{
// If you hit this assertion then you need to implement this method in a subclass.
jassertfalse;
return false;
}
#endif
MultiDocumentPanelWindow* MultiDocumentPanel::createNewDocumentWindow()
{
@@ -217,93 +261,105 @@ bool MultiDocumentPanel::addDocument (Component* const component,
return true;
}
bool MultiDocumentPanel::closeDocument (Component* component,
const bool checkItsOkToCloseFirst)
void MultiDocumentPanel::closeDocumentInternal (Component* component)
{
// Intellisense warns about component being uninitialised.
// I'm not sure how a function argument could be uninitialised.
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6001)
if (component == nullptr)
return true;
component->removeComponentListener (this);
if (components.contains (component))
{
if (checkItsOkToCloseFirst && ! tryToCloseDocument (component))
return false;
const bool shouldDelete = MultiDocHelpers::shouldDeleteComp (component);
component->getProperties().remove ("mdiDocumentDelete_");
component->getProperties().remove ("mdiDocumentBkg_");
component->removeComponentListener (this);
const bool shouldDelete = MultiDocHelpers::shouldDeleteComp (component);
component->getProperties().remove ("mdiDocumentDelete_");
component->getProperties().remove ("mdiDocumentBkg_");
if (mode == FloatingWindows)
if (mode == FloatingWindows)
{
for (auto* child : getChildren())
{
for (auto* child : getChildren())
if (auto* dw = dynamic_cast<MultiDocumentPanelWindow*> (child))
{
if (auto* dw = dynamic_cast<MultiDocumentPanelWindow*> (child))
if (dw->getContentComponent() == component)
{
if (dw->getContentComponent() == component)
{
std::unique_ptr<MultiDocumentPanelWindow> (dw)->clearContentComponent();
break;
}
std::unique_ptr<MultiDocumentPanelWindow> (dw)->clearContentComponent();
break;
}
}
}
if (shouldDelete)
delete component;
if (shouldDelete)
delete component;
components.removeFirstMatchingValue (component);
components.removeFirstMatchingValue (component);
if (isFullscreenWhenOneDocument() && components.size() == 1)
if (isFullscreenWhenOneDocument() && components.size() == 1)
{
for (int i = getNumChildComponents(); --i >= 0;)
{
for (int i = getNumChildComponents(); --i >= 0;)
{
std::unique_ptr<MultiDocumentPanelWindow> dw (dynamic_cast<MultiDocumentPanelWindow*> (getChildComponent (i)));
if (dw != nullptr)
dw->clearContentComponent();
}
std::unique_ptr<MultiDocumentPanelWindow> dw (dynamic_cast<MultiDocumentPanelWindow*> (getChildComponent (i)));
addAndMakeVisible (components.getFirst());
if (dw != nullptr)
dw->clearContentComponent();
}
addAndMakeVisible (components.getFirst());
}
}
else
{
jassert (components.indexOf (component) >= 0);
if (tabComponent != nullptr)
{
for (int i = tabComponent->getNumTabs(); --i >= 0;)
if (tabComponent->getTabContentComponent (i) == component)
tabComponent->removeTab (i);
}
else
{
jassert (components.indexOf (component) >= 0);
removeChildComponent (component);
}
if (tabComponent != nullptr)
{
for (int i = tabComponent->getNumTabs(); --i >= 0;)
if (tabComponent->getTabContentComponent (i) == component)
tabComponent->removeTab (i);
}
else
{
removeChildComponent (component);
}
if (shouldDelete)
delete component;
if (shouldDelete)
delete component;
if (tabComponent != nullptr && tabComponent->getNumTabs() <= numDocsBeforeTabsUsed)
tabComponent.reset();
if (tabComponent != nullptr && tabComponent->getNumTabs() <= numDocsBeforeTabsUsed)
tabComponent.reset();
components.removeFirstMatchingValue (component);
components.removeFirstMatchingValue (component);
if (components.size() > 0 && tabComponent == nullptr)
addAndMakeVisible (components.getFirst());
}
if (components.size() > 0 && tabComponent == nullptr)
addAndMakeVisible (components.getFirst());
}
resized();
resized();
// This ensures that the active tab is painted properly when a tab is closed!
if (auto* activeComponent = getActiveDocument())
setActiveDocument (activeComponent);
// This ensures that the active tab is painted properly when a tab is closed!
if (auto* activeComponent = getActiveDocument())
setActiveDocument (activeComponent);
activeDocumentChanged();
activeDocumentChanged();
JUCE_END_IGNORE_WARNINGS_MSVC
}
#if JUCE_MODAL_LOOPS_PERMITTED
bool MultiDocumentPanel::closeDocument (Component* component,
const bool checkItsOkToCloseFirst)
{
// Intellisense warns about component being uninitialised.
// I'm not sure how a function argument could be uninitialised.
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6001)
if (component == nullptr)
return true;
if (components.contains (component))
{
if (checkItsOkToCloseFirst && ! tryToCloseDocument (component))
return false;
closeDocumentInternal (component);
}
else
{
@@ -314,6 +370,54 @@ bool MultiDocumentPanel::closeDocument (Component* component,
JUCE_END_IGNORE_WARNINGS_MSVC
}
#endif
void MultiDocumentPanel::closeDocumentAsync (Component* component,
const bool checkItsOkToCloseFirst,
std::function<void (bool)> callback)
{
// Intellisense warns about component being uninitialised.
// I'm not sure how a function argument could be uninitialised.
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6001)
if (component == nullptr)
{
if (callback != nullptr)
callback (true);
return;
}
if (components.contains (component))
{
if (checkItsOkToCloseFirst)
{
SafePointer<MultiDocumentPanel> parent { this };
tryToCloseDocumentAsync (component, [parent, component, callback] (bool closedSuccessfully)
{
if (parent == nullptr)
return;
if (closedSuccessfully)
parent->closeDocumentInternal (component);
if (callback != nullptr)
callback (closedSuccessfully);
});
return;
}
}
else
{
jassertfalse;
}
if (callback != nullptr)
callback (true);
JUCE_END_IGNORE_WARNINGS_MSVC
}
int MultiDocumentPanel::getNumDocuments() const noexcept
{


+ 75
- 6
modules/juce_gui_basics/layout/juce_MultiDocumentPanel.h View File

@@ -108,6 +108,7 @@ public:
~MultiDocumentPanel() override;
//==============================================================================
#if JUCE_MODAL_LOOPS_PERMITTED
/** Tries to close all the documents.
If checkItsOkToCloseFirst is true, then the tryToCloseDocument() method will
@@ -120,6 +121,22 @@ public:
@see closeDocument
*/
bool closeAllDocuments (bool checkItsOkToCloseFirst);
#endif
/** Tries to close all the documents.
If checkItsOkToCloseFirst is true, then the tryToCloseDocumentAsync() method will
be called for each open document, and any of these calls fails, this method
will stop and provide an argument of false to the callback, leaving some documents
still open.
If checkItsOkToCloseFirst is false, then all documents will be closed
unconditionally.
@see closeDocument
*/
void closeAllDocumentsAsync (bool checkItsOkToCloseFirst,
std::function<void (bool)> callback);
/** Adds a document component to the panel.
@@ -142,6 +159,7 @@ public:
Colour backgroundColour,
bool deleteWhenRemoved);
#if JUCE_MODAL_LOOPS_PERMITTED
/** Closes one of the documents.
If checkItsOkToCloseFirst is true, then the tryToCloseDocument() method will
@@ -158,6 +176,25 @@ public:
*/
bool closeDocument (Component* component,
bool checkItsOkToCloseFirst);
#endif
/** Closes one of the documents.
If checkItsOkToCloseFirst is true, then the tryToCloseDocumentAsync() method will
be called, and if it fails, this method will call the callback with a false
argument without closing the document.
If checkItsOkToCloseFirst is false, then the documents will be closed
unconditionally.
The component will be deleted if the deleteWhenRemoved parameter was set to
true when it was added with addDocument.
@see addDocument, closeAllDocuments
*/
void closeDocumentAsync (Component* component,
bool checkItsOkToCloseFirst,
std::function<void (bool)> callback);
/** Returns the number of open document windows.
@@ -248,6 +285,7 @@ public:
TabbedComponent* getCurrentTabbedComponent() const noexcept { return tabComponent.get(); }
//==============================================================================
#if JUCE_MODAL_LOOPS_PERMITTED
/** A subclass must override this to say whether its currently ok for a document
to be closed.
@@ -269,7 +307,32 @@ public:
@see closeDocument, FileBasedDocument::saveIfNeededAndUserAgrees()
*/
virtual bool tryToCloseDocument (Component* component) = 0;
virtual bool tryToCloseDocument (Component* component);
#endif
/** A subclass must override this to say whether its currently ok for a document
to be closed.
This method is called by closeDocumentAsync() and closeAllDocumentsAsync()
to indicate that a document should be saved if possible, ready for it to be closed.
If the callback is called with a true argument, then it means the document is ok
and can be closed.
If the callback is called with a false argument, then it means that the
closeDocumentAsync() method should stop and not close.
Normally, you'd use this method to ask the user if they want to save any changes,
then return true if the save operation went ok. If the user cancelled the save
operation you could give a value of false to the callback to abort the close operation.
If your component is based on the FileBasedDocument class, then you'd probably want
to call FileBasedDocument::saveIfNeededAndUserAgreesAsync() and call the calback with
true if this returned FileBasedDocument::savedOk.
@see closeDocumentAsync, FileBasedDocument::saveIfNeededAndUserAgreesAsync()
*/
virtual void tryToCloseDocumentAsync (Component* component, std::function<void (bool)> callback) = 0;
/** Creates a new window to be used for a document.
@@ -288,12 +351,12 @@ public:
private:
//==============================================================================
LayoutMode mode = MaximisedWindowsWithTabs;
Array<Component*> components;
std::unique_ptr<TabbedComponent> tabComponent;
Colour backgroundColour { Colours::lightblue };
int maximumNumDocuments = 0, numDocsBeforeTabsUsed = 0;
void closeDocumentInternal (Component*);
static void closeLastDocumentRecursive (SafePointer<MultiDocumentPanel>,
bool,
std::function<void (bool)>);
//==============================================================================
struct TabbedComponentInternal;
friend class MultiDocumentPanelWindow;
@@ -301,6 +364,12 @@ private:
void updateOrder();
void addWindow (Component*);
LayoutMode mode = MaximisedWindowsWithTabs;
Array<Component*> components;
std::unique_ptr<TabbedComponent> tabComponent;
Colour backgroundColour { Colours::lightblue };
int maximumNumDocuments = 0, numDocsBeforeTabsUsed = 0;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiDocumentPanel)
};


+ 1
- 1
modules/juce_gui_basics/menus/juce_PopupMenu.h View File

@@ -497,7 +497,7 @@ public:
};
//==============================================================================
#if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN
#if JUCE_MODAL_LOOPS_PERMITTED
/** Displays the menu and waits for the user to pick something.
This will display the menu modally, and return the ID of the item that the


+ 2
- 2
modules/juce_gui_basics/windows/juce_AlertWindow.h View File

@@ -228,7 +228,7 @@ public:
//==============================================================================
// easy-to-use message box functions:
#if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN
#if JUCE_MODAL_LOOPS_PERMITTED
/** Shows a dialog box that just has a message and a single button to get rid of it.
The box is shown modally, and the method will block until the user has clicked the
@@ -394,7 +394,7 @@ public:
it'll show a box with just an ok button
@returns true if the ok button was pressed, false if they pressed cancel.
*/
#if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN
#if JUCE_MODAL_LOOPS_PERMITTED
static bool JUCE_CALLTYPE showNativeDialogBox (const String& title,
const String& bodyText,
bool isOkCancel);


+ 2
- 2
modules/juce_gui_basics/windows/juce_DialogWindow.h View File

@@ -142,7 +142,7 @@ public:
*/
DialogWindow* create();
#if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN
#if JUCE_MODAL_LOOPS_PERMITTED
/** Launches and runs the dialog modally, returning the status code that was
used to terminate the modal loop.
@@ -201,7 +201,7 @@ public:
bool shouldBeResizable = false,
bool useBottomRightCornerResizer = false);
#if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN
#if JUCE_MODAL_LOOPS_PERMITTED
/** Easy way of quickly showing a dialog box containing a given component.
Note: This method has been superseded by the DialogWindow::LaunchOptions structure,


+ 1
- 1
modules/juce_gui_basics/windows/juce_NativeMessageBox.h View File

@@ -47,7 +47,7 @@ public:
alert window should be associated with. Depending on the look
and feel, this might be used for positioning of the alert window.
*/
#if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN
#if JUCE_MODAL_LOOPS_PERMITTED
static void JUCE_CALLTYPE showMessageBox (AlertWindow::AlertIconType iconType,
const String& title,
const String& message,


+ 1
- 1
modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.h View File

@@ -113,7 +113,7 @@ public:
~ThreadWithProgressWindow() override;
//==============================================================================
#if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN
#if JUCE_MODAL_LOOPS_PERMITTED
/** Starts the thread and waits for it to finish.
This will start the thread, make the dialog box appear, and wait until either


+ 1023
- 168
modules/juce_gui_extra/documents/juce_FileBasedDocument.cpp
File diff suppressed because it is too large
View File


+ 143
- 11
modules/juce_gui_extra/documents/juce_FileBasedDocument.h View File

@@ -72,7 +72,7 @@ public:
@see setChangedFlag, changed
*/
bool hasChangedSinceSaved() const { return changedSinceSave; }
bool hasChangedSinceSaved() const;
/** Called to indicate that the document has changed and needs saving.
@@ -98,7 +98,7 @@ public:
//==============================================================================
/** Tries to open a file.
If the file opens correctly, the document's file (see the getFile() method) is set
If the file opens correctly the document's file (see the getFile() method) is set
to this new one; if it fails, the document's file is left unchanged, and optionally
a message box is shown telling the user there was an error.
@@ -110,6 +110,22 @@ public:
bool showMessageOnFailure,
bool showWaitCursor = true);
/** Tries to open a file.
The callback is called with the result indicating whether the new file loaded
successfully, or the error message if it failed.
If the file opens correctly the document's file (see the getFile() method) is set
to this new one; if it fails, the document's file is left unchanged, and optionally
a message box is shown telling the user there was an error.
@see loadDocumentAsync, loadFromUserSpecifiedFileAsync
*/
void loadFromAsync (const File& fileToLoadFrom,
bool showMessageOnFailure,
std::function<void (Result)> callback);
#if JUCE_MODAL_LOOPS_PERMITTED
/** Asks the user for a file and tries to load it.
This will pop up a dialog box using the title, file extension and
@@ -122,6 +138,19 @@ public:
@see loadFrom
*/
Result loadFromUserSpecifiedFile (bool showMessageOnFailure);
#endif
/** Asks the user for a file and tries to load it.
This will pop up a dialog box using the title, file extension and
wildcard specified in the document's constructor, and asks the user
for a file. If they pick one, the loadFrom() method is used to
try to load it, optionally showing a message if it fails. The result
of the operation is provided in the callback function.
@see loadFrom
*/
void loadFromUserSpecifiedFileAsync (bool showMessageOnFailure, std::function<void (Result)> callback);
//==============================================================================
/** A set of possible outcomes of one of the save() methods
@@ -133,6 +162,7 @@ public:
failedToWriteToFile /**< indicates that it tried to write to a file but this failed. */
};
#if JUCE_MODAL_LOOPS_PERMITTED
/** Tries to save the document to the last file it was saved or loaded from.
This will always try to write to the file, even if the document isn't flagged as
@@ -147,7 +177,26 @@ public:
*/
SaveResult save (bool askUserForFileIfNotSpecified,
bool showMessageOnFailure);
#endif
/** Tries to save the document to the last file it was saved or loaded from.
This will always try to write to the file, even if the document isn't flagged as
having changed.
@param askUserForFileIfNotSpecified if there's no file currently specified and this is
true, it will prompt the user to pick a file, as if
saveAsInteractive() was called.
@param showMessageOnFailure if true it will show a warning message when if the
save operation fails
@param callback called after the save operation with the result
@see saveIfNeededAndUserAgrees, saveAs, saveAsInteractive
*/
void saveAsync (bool askUserForFileIfNotSpecified,
bool showMessageOnFailure,
std::function<void (SaveResult)> callback);
#if JUCE_MODAL_LOOPS_PERMITTED
/** If the file needs saving, it'll ask the user if that's what they want to do, and save
it if they say yes.
@@ -169,7 +218,31 @@ public:
@see save, saveAs, saveAsInteractive
*/
SaveResult saveIfNeededAndUserAgrees();
#endif
/** If the file needs saving, it'll ask the user if that's what they want to do, and save
it if they say yes.
If you've got a document open and want to close it (e.g. to quit the app), this is the
method to call.
If the document doesn't need saving the callback will be called with the value savedOk
so you can go ahead and delete the document.
If it does need saving it'll prompt the user, and if they say "discard changes" the
callback will be called with savedOk, so again, you can safely delete the document.
If the user clicks "cancel", the callback will be aclled with userCancelledSave, so
you can abort the close-document operation.
And if they click "save changes", it'll try to save and the callback will be called
with either savedOk, or failedToWriteToFile if there was a problem.
@see saveAsync, saveAsAsync, saveAsInteractiveAsync
*/
void saveIfNeededAndUserAgreesAsync (std::function<void (SaveResult)> callback);
#if JUCE_MODAL_LOOPS_PERMITTED
/** Tries to save the document to a specified file.
If this succeeds, it'll also change the document's internal file (as returned by
@@ -192,7 +265,32 @@ public:
bool askUserForFileIfNotSpecified,
bool showMessageOnFailure,
bool showWaitCursor = true);
#endif
/** Tries to save the document to a specified file.
If this succeeds, it'll also change the document's internal file (as returned by
the getFile() method). If it fails, the file will be left unchanged.
@param newFile the file to try to write to
@param warnAboutOverwritingExistingFiles if true and the file exists, it'll ask the user
first if they want to overwrite it
@param askUserForFileIfNotSpecified if the file is non-existent and this is true, it'll
use the saveAsInteractive() method to ask the user
for a filename
@param showMessageOnFailure if true and the write operation fails, it'll show
a message box to warn the user
@param callback called with the result of the save operation
@see saveIfNeededAndUserAgreesAsync, saveAsync, saveAsInteractiveAsync
*/
void saveAsAsync (const File& newFile,
bool warnAboutOverwritingExistingFiles,
bool askUserForFileIfNotSpecified,
bool showMessageOnFailure,
std::function<void (SaveResult)> callback);
#if JUCE_MODAL_LOOPS_PERMITTED
/** Prompts the user for a filename and tries to save to it.
This will pop up a dialog box using the title, file extension and
@@ -201,10 +299,26 @@ public:
to this file.
@param warnAboutOverwritingExistingFiles if true and the file exists, it'll ask
the user first if they want to overwrite it
the user first if they want to overwrite it
@see saveIfNeededAndUserAgrees, save, saveAs
*/
SaveResult saveAsInteractive (bool warnAboutOverwritingExistingFiles);
#endif
/** Prompts the user for a filename and tries to save to it.
This will pop up a dialog box using the title, file extension and
wildcard specified in the document's constructor, and asks the user
for a file. If they pick one, the saveAs() method is used to try to save
to this file.
@param warnAboutOverwritingExistingFiles if true and the file exists, it'll ask
the user first if they want to overwrite it
@param callback called with the result of the save operation
@see saveIfNeededAndUserAgreesAsync, saveAsync, saveAsAsync
*/
void saveAsInteractiveAsync (bool warnAboutOverwritingExistingFiles,
std::function<void (SaveResult)> callback);
//==============================================================================
/** Returns the file that this document was last successfully saved or loaded from.
@@ -214,7 +328,7 @@ public:
It is changed when one of the load or save methods is used, or when setFile()
is used to explicitly set it.
*/
const File& getFile() const { return documentFile; }
const File& getFile() const;
/** Sets the file that this document thinks it was loaded from.
@@ -224,7 +338,6 @@ public:
*/
void setFile (const File& newFile);
protected:
//==============================================================================
/** Overload this to return the title of the document.
@@ -239,11 +352,33 @@ protected:
*/
virtual Result loadDocument (const File& file) = 0;
/** This method should try to load your document from the given file, then
call the provided callback on the message thread, passing the result of the load.
By default, this will synchronously call through to loadDocument.
For longer-running load operations, you may wish to override this function to
run the load on a background thread, and then to call the callback later on the
message thread to signal that the load has completed.
*/
virtual void loadDocumentAsync (const File& file, std::function<void (Result)> callback);
/** This method should try to write your document to the given file.
@returns a Result object to indicate the whether there was an error.
*/
virtual Result saveDocument (const File& file) = 0;
/** This method should try to write your document to the given file, then
call the provided callback on the message thread, passing the result of the write.
By default, this will synchronously call through to saveDocument.
For longer-running save operations, you may wish to override this function to
run the save on a background thread, and then to call the callback later on the
message thread to signal that the save has completed.
*/
virtual void saveDocumentAsync (const File& file, std::function<void (Result)> callback);
/** This is used for dialog boxes to make them open at the last folder you
were using.
@@ -277,21 +412,18 @@ protected:
*/
virtual void setLastDocumentOpened (const File& file) = 0;
#if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN
/** This is called by saveAsInteractive() to allow you to optionally customise the
/** This is called by saveAsInteractiveAsync() to allow you to optionally customise the
filename that the user is presented with in the save dialog.
The defaultFile parameter is an initial suggestion based on what the class knows
about the current document - you can return a variation on this file with a different
extension, etc, or just return something completely different.
*/
virtual File getSuggestedSaveAsFile (const File& defaultFile);
#endif
private:
//==============================================================================
File documentFile;
bool changedSinceSave = false;
String fileExtension, fileWildcard, openFileDialogTitle, saveFileDialogTitle;
class Pimpl;
std::unique_ptr<Pimpl> pimpl;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileBasedDocument)
};


+ 1
- 1
modules/juce_gui_extra/misc/juce_PushNotifications.h View File

@@ -317,7 +317,7 @@ public:
GroupAlertBehaviour groupAlertBehaviour = alertAll;
int timeoutAfterMs = 0; /**< specifies a duration in milliseconds, after which the notification should be
cancelled, if it is not already canceled. Available from Android API 26 or above. */
cancelled, if it is not already cancelled. Available from Android API 26 or above. */
/**@}*/
};


+ 70
- 35
modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm View File

@@ -95,17 +95,11 @@ struct WebViewKeyEquivalentResponder : public WebViewBase
WebViewKeyEquivalentResponder()
: WebViewBase ("WebViewKeyEquivalentResponder_")
{
addIvar<WebViewKeyEquivalentResponder*> ("owner");
addMethod (@selector (performKeyEquivalent:), performKeyEquivalent, @encode (BOOL), "@:@");
registerClass();
}
private:
static WebViewKeyEquivalentResponder* getOwner (id self)
{
return getIvar<WebViewKeyEquivalentResponder*> (self, "owner");
}
static BOOL performKeyEquivalent (id self, SEL selector, NSEvent* event)
{
NSResponder* first = [[self window] firstResponder];
@@ -225,9 +219,42 @@ private:
static void runOpenPanel (id, SEL, WKWebView*, WKOpenPanelParameters* parameters, WKFrameInfo*,
void (^completionHandler)(NSArray<NSURL*>*))
{
#if JUCE_MODAL_LOOPS_PERMITTED
FileChooser chooser (TRANS("Select the file you want to upload..."),
File::getSpecialLocation (File::userHomeDirectory), "*");
using CompletionHandlerType = decltype (completionHandler);
class DeletedFileChooserWrapper : private DeletedAtShutdown
{
public:
DeletedFileChooserWrapper (std::unique_ptr<FileChooser> fc, CompletionHandlerType h)
: chooser (std::move (fc)), handler (h)
{
[handler retain];
}
~DeletedFileChooserWrapper()
{
callHandler (nullptr);
[handler release];
}
void callHandler (NSArray<NSURL*>* urls)
{
if (handlerCalled)
return;
handler (urls);
handlerCalled = true;
}
std::unique_ptr<FileChooser> chooser;
private:
CompletionHandlerType handler;
bool handlerCalled = false;
};
auto chooser = std::make_unique<FileChooser> (TRANS("Select the file you want to upload..."),
File::getSpecialLocation (File::userHomeDirectory), "*");
auto* wrapper = new DeletedFileChooserWrapper (std::move (chooser), completionHandler);
auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles
| ([parameters allowsMultipleSelection] ? FileBrowserComponent::canSelectMultipleItems : 0);
@@ -237,24 +264,17 @@ private:
flags |= FileBrowserComponent::canSelectDirectories;
#endif
if (chooser.showDialog (flags, nullptr))
wrapper->chooser->launchAsync (flags, [wrapper] (const FileChooser&)
{
auto results = chooser.getResults();
auto results = wrapper->chooser->getResults();
auto urls = [NSMutableArray arrayWithCapacity: (NSUInteger) results.size()];
for (auto& f : results)
[urls addObject: [NSURL fileURLWithPath: juceStringToNS (f.getFullPathName())]];
completionHandler (urls);
}
else
{
completionHandler (nil);
}
#else
ignoreUnused (parameters, completionHandler);
jassertfalse; // Can't use this without modal loops being enabled!
#endif
wrapper->callHandler (urls);
delete wrapper;
});
}
#endif
};
@@ -270,8 +290,6 @@ class WebBrowserComponent::Pimpl
public:
Pimpl (WebBrowserComponent* owner)
{
ignoreUnused (owner);
#if JUCE_MAC
static WebViewKeyEquivalentResponder webviewClass;
webView = (WKWebView*) webviewClass.createInstance();
@@ -421,20 +439,37 @@ private:
static void runOpenPanel (id, SEL, WebView*, id<WebOpenPanelResultListener> resultListener, BOOL allowMultipleFiles)
{
#if JUCE_MODAL_LOOPS_PERMITTED
FileChooser chooser (TRANS("Select the file you want to upload..."),
File::getSpecialLocation (File::userHomeDirectory), "*");
struct DeletedFileChooserWrapper : private DeletedAtShutdown
{
DeletedFileChooserWrapper (std::unique_ptr<FileChooser> fc, id<WebOpenPanelResultListener> rl)
: chooser (std::move (fc)), listener (rl)
{
[listener retain];
}
~DeletedFileChooserWrapper()
{
[listener release];
}
std::unique_ptr<FileChooser> chooser;
id<WebOpenPanelResultListener> listener;
};
auto chooser = std::make_unique<FileChooser> (TRANS("Select the file you want to upload..."),
File::getSpecialLocation (File::userHomeDirectory), "*");
auto* wrapper = new DeletedFileChooserWrapper (std::move (chooser), resultListener);
auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles
| (allowMultipleFiles ? FileBrowserComponent::canSelectMultipleItems : 0);
if (allowMultipleFiles ? chooser.browseForMultipleFilesToOpen()
: chooser.browseForFileToOpen())
wrapper->chooser->launchAsync (flags, [wrapper] (const FileChooser&)
{
for (auto& f : chooser.getResults())
[resultListener chooseFilename: juceStringToNS (f.getFullPathName())];
}
#else
ignoreUnused (resultListener, allowMultipleFiles);
jassertfalse; // Can't use this without modal loops being enabled!
#endif
for (auto& f : wrapper->chooser->getResults())
[wrapper->listener chooseFilename: juceStringToNS (f.getFullPathName())];
delete wrapper;
});
}
};


Loading…
Cancel
Save