mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Set the default value of JUCE_MODAL_LOOPS_PERMITTED to 0
See BREAKING-CHANGES.txt for more details.
This commit is contained in:
parent
f1768843fb
commit
fe4ba9071b
79 changed files with 3423 additions and 1332 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
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);
|
||||
|
||||
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);
|
||||
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));
|
||||
|
||||
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);
|
||||
|
||||
// this is the text they entered..
|
||||
auto text = w.getTextEditorContents ("text");
|
||||
}
|
||||
#endif
|
||||
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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
// Some plug-ins do not want [NSApp stop] to be called
|
||||
// before the plug-ins are not deallocated.
|
||||
graphHolder->releaseGraph();
|
||||
|
||||
JUCEApplication::quit();
|
||||
if (graphHolder != nullptr)
|
||||
{
|
||||
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
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
auto sharedProject = std::make_shared<std::unique_ptr<Project>> (std::move (project));
|
||||
(*sharedProject)->saveAsync (false, true, [sharedProject, failedFiles, callback] (FileBasedDocument::SaveResult r)
|
||||
{
|
||||
project->setChangedFlag (false);
|
||||
project->loadFrom (project->getFile(), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
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;
|
||||
|
||||
if (fc.browseForFileToSave (true))
|
||||
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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
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;
|
||||
|
||||
fc.browseForFileToSave (true);
|
||||
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,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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
if (fc.browseForFileToOpen())
|
||||
openFile (fc.getResult());
|
||||
chooser->launchAsync (flags, [this] (const FileChooser& fc)
|
||||
{
|
||||
const auto result = 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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
{
|
||||
setProject (nullptr);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (parent == nullptr)
|
||||
return;
|
||||
|
||||
return false;
|
||||
if (! closedSuccessfully)
|
||||
{
|
||||
if (callback != nullptr)
|
||||
callback (false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (currentProject != nullptr && openInIDE == OpenInIDE::yes)
|
||||
ProjucerApplication::getApp().getCommandManager().invokeDirectly (CommandIDs::openInIDE, false);
|
||||
parent->openFile (newProjectFileToOpen, [parent, openInIDE] (bool openedSuccessfully)
|
||||
{
|
||||
if (parent == nullptr)
|
||||
return;
|
||||
|
||||
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;
|
||||
|
||||
createProjectContentCompIfNeeded();
|
||||
getProjectContentComponent()->reloadLastOpenDocuments();
|
||||
if (saveResult)
|
||||
{
|
||||
parent->setProject (std::move (*sharedDoc.get()));
|
||||
parent->currentProject->setChangedFlag (false);
|
||||
|
||||
currentProject->updateDeprecatedProjectSettingsInteractively();
|
||||
parent->createProjectContentCompIfNeeded();
|
||||
parent->getProjectContentComponent()->reloadLastOpenDocuments();
|
||||
|
||||
return true;
|
||||
parent->currentProject->updateDeprecatedProjectSettingsInteractively();
|
||||
}
|
||||
|
||||
if (callback != nullptr)
|
||||
callback (saveResult);
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static void filesDroppedRecursive (Component::SafePointer<MainWindow> parent, StringArray filenames)
|
||||
{
|
||||
if (filenames.isEmpty())
|
||||
return;
|
||||
|
||||
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*/)
|
||||
{
|
||||
for (auto& filename : filenames)
|
||||
{
|
||||
const File f (filename);
|
||||
|
||||
if (canOpenFile (f) && openFile (f))
|
||||
break;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
closeWindow (w);
|
||||
return false;
|
||||
if (callback != nullptr)
|
||||
callback (openedSuccessfully);
|
||||
});
|
||||
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
bool OpenDocumentManager::closeAllDocumentsUsingProject (Project& project, SaveIfNeeded saveIfNeeded)
|
||||
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));
|
||||
}
|
||||
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
return writeCodeDocToFile (fc.getResult(), getCodeDocument());
|
||||
chooser->launchAsync (flags, [this, callback] (const FileChooser& fc)
|
||||
{
|
||||
if (fc.getResult() == File{})
|
||||
{
|
||||
callback (true);
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ protected:
|
|||
String colourIdCode, colourName, xmlTagName;
|
||||
};
|
||||
|
||||
OwnedArray <ComponentColourInfo> colours;
|
||||
OwnedArray<ComponentColourInfo> colours;
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE (ComponentTypeHandler)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 ({});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ protected:
|
|||
|
||||
void siblingComponentsChanged();
|
||||
|
||||
OwnedArray <ElementSiblingComponent> siblingComponents;
|
||||
OwnedArray<ElementSiblingComponent> siblingComponents;
|
||||
|
||||
void updateSiblingComps();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
SafePointer<PositionPropertyBase> ref (this);
|
||||
|
||||
const int menuResult = m.showAt (&button);
|
||||
|
||||
if (menuResult == 0 || ref == nullptr)
|
||||
return false;
|
||||
|
||||
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;
|
||||
if (menuResult == 0 || ref == nullptr)
|
||||
{
|
||||
callback (false);
|
||||
return;
|
||||
}
|
||||
|
||||
case 11:
|
||||
if (dimension == componentX)
|
||||
xMode = PositionedRectangle::absoluteFromParentBottomRight;
|
||||
else
|
||||
yMode = PositionedRectangle::absoluteFromParentBottomRight;
|
||||
break;
|
||||
switch (menuResult)
|
||||
{
|
||||
case 10:
|
||||
if (ref->dimension == componentX)
|
||||
xMode = PositionedRectangle::absoluteFromParentTopLeft;
|
||||
else
|
||||
yMode = PositionedRectangle::absoluteFromParentTopLeft;
|
||||
break;
|
||||
|
||||
case 12:
|
||||
if (dimension == componentX)
|
||||
xMode = PositionedRectangle::absoluteFromParentCentre;
|
||||
else
|
||||
yMode = PositionedRectangle::absoluteFromParentCentre;
|
||||
break;
|
||||
case 11:
|
||||
if (ref->dimension == componentX)
|
||||
xMode = PositionedRectangle::absoluteFromParentBottomRight;
|
||||
else
|
||||
yMode = PositionedRectangle::absoluteFromParentBottomRight;
|
||||
break;
|
||||
|
||||
case 13:
|
||||
if (dimension == componentX)
|
||||
xMode = PositionedRectangle::proportionOfParentSize;
|
||||
else
|
||||
yMode = PositionedRectangle::proportionOfParentSize;
|
||||
break;
|
||||
case 12:
|
||||
if (ref->dimension == componentX)
|
||||
xMode = PositionedRectangle::absoluteFromParentCentre;
|
||||
else
|
||||
yMode = PositionedRectangle::absoluteFromParentCentre;
|
||||
break;
|
||||
|
||||
case 14:
|
||||
if (dimension == componentX)
|
||||
xAnchor = PositionedRectangle::anchorAtLeftOrTop;
|
||||
else
|
||||
yAnchor = PositionedRectangle::anchorAtLeftOrTop;
|
||||
break;
|
||||
case 13:
|
||||
if (ref->dimension == componentX)
|
||||
xMode = PositionedRectangle::proportionOfParentSize;
|
||||
else
|
||||
yMode = PositionedRectangle::proportionOfParentSize;
|
||||
break;
|
||||
|
||||
case 15:
|
||||
if (dimension == componentX)
|
||||
xAnchor = PositionedRectangle::anchorAtCentre;
|
||||
else
|
||||
yAnchor = PositionedRectangle::anchorAtCentre;
|
||||
break;
|
||||
case 14:
|
||||
if (ref->dimension == componentX)
|
||||
xAnchor = PositionedRectangle::anchorAtLeftOrTop;
|
||||
else
|
||||
yAnchor = PositionedRectangle::anchorAtLeftOrTop;
|
||||
break;
|
||||
|
||||
case 16:
|
||||
if (dimension == componentX)
|
||||
xAnchor = PositionedRectangle::anchorAtRightOrBottom;
|
||||
else
|
||||
yAnchor = PositionedRectangle::anchorAtRightOrBottom;
|
||||
break;
|
||||
case 15:
|
||||
if (ref->dimension == componentX)
|
||||
xAnchor = PositionedRectangle::anchorAtCentre;
|
||||
else
|
||||
yAnchor = PositionedRectangle::anchorAtCentre;
|
||||
break;
|
||||
|
||||
case 20:
|
||||
if (dimension == componentWidth)
|
||||
sizeW = PositionedRectangle::absoluteSize;
|
||||
else
|
||||
sizeH = PositionedRectangle::absoluteSize;
|
||||
break;
|
||||
case 16:
|
||||
if (ref->dimension == componentX)
|
||||
xAnchor = PositionedRectangle::anchorAtRightOrBottom;
|
||||
else
|
||||
yAnchor = PositionedRectangle::anchorAtRightOrBottom;
|
||||
break;
|
||||
|
||||
case 21:
|
||||
if (dimension == componentWidth)
|
||||
sizeW = PositionedRectangle::proportionalSize;
|
||||
else
|
||||
sizeH = PositionedRectangle::proportionalSize;
|
||||
break;
|
||||
case 20:
|
||||
if (ref->dimension == componentWidth)
|
||||
sizeW = PositionedRectangle::absoluteSize;
|
||||
else
|
||||
sizeH = PositionedRectangle::absoluteSize;
|
||||
break;
|
||||
|
||||
case 22:
|
||||
if (dimension == componentWidth)
|
||||
sizeW = PositionedRectangle::parentSizeMinusAbsolute;
|
||||
else
|
||||
sizeH = PositionedRectangle::parentSizeMinusAbsolute;
|
||||
break;
|
||||
case 21:
|
||||
if (ref->dimension == componentWidth)
|
||||
sizeW = PositionedRectangle::proportionalSize;
|
||||
else
|
||||
sizeH = PositionedRectangle::proportionalSize;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (allowRelativeOptions && compLayout != nullptr)
|
||||
compLayout->processRelativeTargetMenuResult (component, (int) dimension, menuResult);
|
||||
break;
|
||||
}
|
||||
case 22:
|
||||
if (ref->dimension == componentWidth)
|
||||
sizeW = PositionedRectangle::parentSizeMinusAbsolute;
|
||||
else
|
||||
sizeH = PositionedRectangle::parentSizeMinusAbsolute;
|
||||
break;
|
||||
|
||||
Rectangle<int> parentArea;
|
||||
default:
|
||||
if (ref->allowRelativeOptions && compLayout != nullptr)
|
||||
compLayout->processRelativeTargetMenuResult (ref->component, (int) ref->dimension, menuResult);
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
const auto parentArea = [&]() -> Rectangle<int>
|
||||
{
|
||||
if (ref->component->findParentComponentOfClass<ComponentLayoutEditor>() != nullptr)
|
||||
return { ref->component->getParentWidth(), ref->component->getParentHeight() };
|
||||
|
||||
int x, xw, y, yh, w, h;
|
||||
rpr.getRelativeTargetBounds (parentArea, compLayout, x, xw, y, yh, w, h);
|
||||
if (auto pre = dynamic_cast<PaintRoutineEditor*> (ref->component->getParentComponent()))
|
||||
return pre->getComponentArea();
|
||||
|
||||
PositionedRectangle xyRect (p);
|
||||
PositionedRectangle whRect (p);
|
||||
jassertfalse;
|
||||
return {};
|
||||
}();
|
||||
|
||||
xyRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
|
||||
Rectangle<int> (x, y, xw, yh));
|
||||
int x, xw, y, yh, w, h;
|
||||
rpr.getRelativeTargetBounds (parentArea, compLayout, x, xw, y, yh, w, h);
|
||||
|
||||
whRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
|
||||
Rectangle<int> (x, y, w, h));
|
||||
PositionedRectangle xyRect (p);
|
||||
PositionedRectangle whRect (p);
|
||||
|
||||
p.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));
|
||||
|
||||
p.setX (xyRect.getX());
|
||||
p.setY (xyRect.getY());
|
||||
p.setWidth (whRect.getWidth());
|
||||
p.setHeight (whRect.getHeight());
|
||||
whRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
|
||||
Rectangle<int> (x, y, w, h));
|
||||
|
||||
if (p != rpr.rect)
|
||||
{
|
||||
rpr.rect = p;
|
||||
setPosition (rpr);
|
||||
}
|
||||
p.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
|
||||
Rectangle<int> (x, y, xw, yh));
|
||||
|
||||
return true;
|
||||
p.setX (xyRect.getX());
|
||||
p.setY (xyRect.getY());
|
||||
p.setWidth (whRect.getWidth());
|
||||
p.setHeight (whRect.getHeight());
|
||||
|
||||
if (p != rpr.rect)
|
||||
{
|
||||
rpr.rect = p;
|
||||
ref->setPosition (rpr);
|
||||
}
|
||||
|
||||
callback (true);
|
||||
});
|
||||
}
|
||||
|
||||
void resized()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 (", "));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ public:
|
|||
|
||||
private:
|
||||
JucerDocument* document;
|
||||
OwnedArray <Component> components;
|
||||
OwnedArray<Component> components;
|
||||
SelectedItemSet <Component*> selected;
|
||||
int nextCompUID;
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
headerFile.replaceWithText (String());
|
||||
cppFile.replaceWithText (String());
|
||||
|
||||
auto& odm = ProjucerApplication::getApp().openDocumentManager;
|
||||
|
||||
if (auto* cpp = dynamic_cast<SourceCodeDocument*> (odm.openFile (&project, cppFile)))
|
||||
if (newFile != File())
|
||||
{
|
||||
if (auto* header = dynamic_cast<SourceCodeDocument*> (odm.openFile (&project, headerFile)))
|
||||
auto headerFile = newFile.withFileExtension (".h");
|
||||
auto cppFile = newFile.withFileExtension (".cpp");
|
||||
|
||||
headerFile.replaceWithText (String());
|
||||
cppFile.replaceWithText (String());
|
||||
|
||||
auto& odm = ProjucerApplication::getApp().openDocumentManager;
|
||||
|
||||
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());
|
||||
std::unique_ptr<JucerDocument> jucerDoc (new ComponentDocument (cpp));
|
||||
|
||||
jucerDoc->flushChangesToDocuments (&project, true);
|
||||
jucerDoc.reset();
|
||||
if (jucerDoc != nullptr)
|
||||
{
|
||||
jucerDoc->setClassName (newFile.getFileNameWithoutExtension());
|
||||
|
||||
cpp->save();
|
||||
header->save();
|
||||
odm.closeDocument (cpp, OpenDocumentManager::SaveIfNeeded::yes);
|
||||
odm.closeDocument (header, OpenDocumentManager::SaveIfNeeded::yes);
|
||||
jucerDoc->flushChangesToDocuments (&project, true);
|
||||
jucerDoc.reset();
|
||||
|
||||
parent.addFileRetainingSortOrder (headerFile, true);
|
||||
parent.addFileRetainingSortOrder (cppFile, true);
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ public:
|
|||
|
||||
//==============================================================================
|
||||
private:
|
||||
OwnedArray <PaintElement> elements;
|
||||
OwnedArray<PaintElement> elements;
|
||||
SelectedItemSet <PaintElement*> selectedElements;
|
||||
SelectedItemSet <PathPoint*> selectedPoints;
|
||||
JucerDocument* document;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -142,5 +142,7 @@ private:
|
|||
CriticalSection stateLock;
|
||||
ValueTree state;
|
||||
|
||||
std::unique_ptr<FileChooser> chooser;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EnabledModulesList)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -84,40 +84,26 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
if (filesToTrash.size() > 0)
|
||||
WeakReference<FileTreeItemBase> treeRootItem { dynamic_cast<FileTreeItemBase*> (tree->getRootItem()) };
|
||||
|
||||
if (treeRootItem == nullptr)
|
||||
{
|
||||
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());
|
||||
|
||||
if (r == 0)
|
||||
return;
|
||||
|
||||
if (r != 2)
|
||||
filesToTrash.clear();
|
||||
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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@ private:
|
|||
Array<Value> exporterModulePathValues, globalPathValues;
|
||||
Value useGlobalPathValue;
|
||||
|
||||
OwnedArray <Project::ConfigFlag> configFlags;
|
||||
OwnedArray<Project::ConfigFlag> configFlags;
|
||||
|
||||
PropertyGroupComponent group;
|
||||
Project& project;
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ void HeaderComponent::initialiseButtons()
|
|||
else
|
||||
{
|
||||
if (auto exporter = getSelectedExporter())
|
||||
project->openProjectInIDE (*exporter, true);
|
||||
project->openProjectInIDE (*exporter, true, nullptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
refreshProjectTreeFileStatuses();
|
||||
if (! savedSuccessfully)
|
||||
showSaveWarning (parent->currentDocument);
|
||||
|
||||
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;
|
||||
|
||||
refreshProjectTreeFileStatuses();
|
||||
if (! savedSuccessfully)
|
||||
showSaveWarning (parent->currentDocument);
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
Result Project::saveProject (ProjectExporter* exporterToSave)
|
||||
void Project::saveDocumentAsync (const File& file, std::function<void (Result)> afterSave)
|
||||
{
|
||||
jassert (file == getFile());
|
||||
ignoreUnused (file);
|
||||
|
||||
saveProject (Async::yes, nullptr, std::move (afterSave));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
ProjectSaver saver (*this);
|
||||
return saver.save (exporterToSave);
|
||||
saver = std::make_unique<ProjectSaver> (*this);
|
||||
saver->save (async, exporterToSave, [ref, onCompletion] (Result result)
|
||||
{
|
||||
if (ref == nullptr)
|
||||
return;
|
||||
|
||||
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);
|
||||
|
||||
if (! result.wasOk())
|
||||
return result;
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<ProjectExporter> exporter;
|
||||
std::function<void (Result)> onCompletion;
|
||||
};
|
||||
|
||||
saveProject (Async::yes, nullptr, Callback { std::move (exporter.exporter), onCompletion });
|
||||
return;
|
||||
}
|
||||
|
||||
// Workaround for a bug where Xcode thinks the project is invalid if opened immediately
|
||||
// after writing
|
||||
if (saveFirst && exporter->isXcode())
|
||||
Thread::sleep (1000);
|
||||
|
||||
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();
|
||||
|
||||
tempDirectory.copyDirectoryTo (newDirectory);
|
||||
tempDirectory.deleteRecursively();
|
||||
tempDirectory = File();
|
||||
|
||||
// reload project from new location
|
||||
if (auto* window = ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (getFile()))
|
||||
chooser->launchAsync (flags, [this, openInIDE] (const FileChooser& fc)
|
||||
{
|
||||
Component::SafePointer<MainWindow> safeWindow (window);
|
||||
auto newParentDirectory = fc.getResult();
|
||||
|
||||
MessageManager::callAsync ([safeWindow, newDirectory, oldJucerFileName, openInIDE]() mutable
|
||||
if (! newParentDirectory.exists())
|
||||
return;
|
||||
|
||||
auto newDirectory = newParentDirectory.getChildFile (tempDirectory.getFileName());
|
||||
auto oldJucerFileName = getFile().getFileName();
|
||||
|
||||
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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
chooser = std::make_unique<FileChooser> ("Select File to Create",
|
||||
projectGroupToAddTo.determineGroupFolder()
|
||||
.getChildFile (suggestedFilename)
|
||||
.getNonexistentSibling(),
|
||||
wildcard);
|
||||
auto flags = FileBrowserComponent::saveMode
|
||||
| FileBrowserComponent::canSelectFiles
|
||||
| FileBrowserComponent::warnAboutOverwriting;
|
||||
|
||||
if (fc.browseForFileToSave (true))
|
||||
return fc.getResult();
|
||||
|
||||
return {};
|
||||
chooser->launchAsync (flags, [callback] (const FileChooser& fc)
|
||||
{
|
||||
callback (fc.getResult());
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
stateFileChooser = std::make_unique<FileChooser> (TRANS("Save current state"),
|
||||
getLastFile(),
|
||||
getFilePatterns (fileSuffix));
|
||||
auto flags = FileBrowserComponent::saveMode
|
||||
| FileBrowserComponent::canSelectFiles
|
||||
| FileBrowserComponent::warnAboutOverwriting;
|
||||
|
||||
if (fc.browseForFileToSave (true))
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
if (myChooser.browseForFileToOpen())
|
||||
auto folderChooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
|
||||
|
||||
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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
...etc..
|
||||
}
|
||||
auto selectedFile = browser->getSelectedFile (0);
|
||||
|
||||
...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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ private:
|
|||
//==============================================================================
|
||||
FileSearchPath path;
|
||||
File defaultBrowseTarget;
|
||||
std::unique_ptr<FileChooser> chooser;
|
||||
|
||||
ListBox listBox;
|
||||
TextButton addButton, removeButton, changeButton;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
chooser = std::make_unique<FileChooser> (isDir ? TRANS ("Choose a new directory")
|
||||
: TRANS ("Choose a new file"),
|
||||
getLocationToBrowse(),
|
||||
wildcard);
|
||||
|
||||
if (isDir ? fc.browseForDirectory()
|
||||
: (isSaving ? fc.browseForFileToSave (false)
|
||||
: fc.browseForFileToOpen()))
|
||||
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&)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ public:
|
|||
/** Destructor. */
|
||||
~ImagePreviewComponent() override;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void selectedFileChanged (const File& newSelectedFile) override;
|
||||
|
|
|
|||
|
|
@ -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,6 +261,89 @@ bool MultiDocumentPanel::addDocument (Component* const component,
|
|||
return true;
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
component->removeComponentListener (this);
|
||||
|
||||
const bool shouldDelete = MultiDocHelpers::shouldDeleteComp (component);
|
||||
component->getProperties().remove ("mdiDocumentDelete_");
|
||||
component->getProperties().remove ("mdiDocumentBkg_");
|
||||
|
||||
if (mode == FloatingWindows)
|
||||
{
|
||||
for (auto* child : getChildren())
|
||||
{
|
||||
if (auto* dw = dynamic_cast<MultiDocumentPanelWindow*> (child))
|
||||
{
|
||||
if (dw->getContentComponent() == component)
|
||||
{
|
||||
std::unique_ptr<MultiDocumentPanelWindow> (dw)->clearContentComponent();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldDelete)
|
||||
delete component;
|
||||
|
||||
components.removeFirstMatchingValue (component);
|
||||
|
||||
if (isFullscreenWhenOneDocument() && components.size() == 1)
|
||||
{
|
||||
for (int i = getNumChildComponents(); --i >= 0;)
|
||||
{
|
||||
std::unique_ptr<MultiDocumentPanelWindow> dw (dynamic_cast<MultiDocumentPanelWindow*> (getChildComponent (i)));
|
||||
|
||||
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
|
||||
{
|
||||
removeChildComponent (component);
|
||||
}
|
||||
|
||||
if (shouldDelete)
|
||||
delete component;
|
||||
|
||||
if (tabComponent != nullptr && tabComponent->getNumTabs() <= numDocsBeforeTabsUsed)
|
||||
tabComponent.reset();
|
||||
|
||||
components.removeFirstMatchingValue (component);
|
||||
|
||||
if (components.size() > 0 && tabComponent == nullptr)
|
||||
addAndMakeVisible (components.getFirst());
|
||||
}
|
||||
|
||||
resized();
|
||||
|
||||
// This ensures that the active tab is painted properly when a tab is closed!
|
||||
if (auto* activeComponent = getActiveDocument())
|
||||
setActiveDocument (activeComponent);
|
||||
|
||||
activeDocumentChanged();
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_MSVC
|
||||
}
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
bool MultiDocumentPanel::closeDocument (Component* component,
|
||||
const bool checkItsOkToCloseFirst)
|
||||
{
|
||||
|
|
@ -232,78 +359,7 @@ bool MultiDocumentPanel::closeDocument (Component* component,
|
|||
if (checkItsOkToCloseFirst && ! tryToCloseDocument (component))
|
||||
return false;
|
||||
|
||||
component->removeComponentListener (this);
|
||||
|
||||
const bool shouldDelete = MultiDocHelpers::shouldDeleteComp (component);
|
||||
component->getProperties().remove ("mdiDocumentDelete_");
|
||||
component->getProperties().remove ("mdiDocumentBkg_");
|
||||
|
||||
if (mode == FloatingWindows)
|
||||
{
|
||||
for (auto* child : getChildren())
|
||||
{
|
||||
if (auto* dw = dynamic_cast<MultiDocumentPanelWindow*> (child))
|
||||
{
|
||||
if (dw->getContentComponent() == component)
|
||||
{
|
||||
std::unique_ptr<MultiDocumentPanelWindow> (dw)->clearContentComponent();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldDelete)
|
||||
delete component;
|
||||
|
||||
components.removeFirstMatchingValue (component);
|
||||
|
||||
if (isFullscreenWhenOneDocument() && components.size() == 1)
|
||||
{
|
||||
for (int i = getNumChildComponents(); --i >= 0;)
|
||||
{
|
||||
std::unique_ptr<MultiDocumentPanelWindow> dw (dynamic_cast<MultiDocumentPanelWindow*> (getChildComponent (i)));
|
||||
|
||||
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
|
||||
{
|
||||
removeChildComponent (component);
|
||||
}
|
||||
|
||||
if (shouldDelete)
|
||||
delete component;
|
||||
|
||||
if (tabComponent != nullptr && tabComponent->getNumTabs() <= numDocsBeforeTabsUsed)
|
||||
tabComponent.reset();
|
||||
|
||||
components.removeFirstMatchingValue (component);
|
||||
|
||||
if (components.size() > 0 && tabComponent == nullptr)
|
||||
addAndMakeVisible (components.getFirst());
|
||||
}
|
||||
|
||||
resized();
|
||||
|
||||
// This ensures that the active tab is painted properly when a tab is closed!
|
||||
if (auto* activeComponent = getActiveDocument())
|
||||
setActiveDocument (activeComponent);
|
||||
|
||||
activeDocumentChanged();
|
||||
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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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,6 +265,45 @@ 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
|
||||
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
|
||||
@see saveIfNeededAndUserAgrees, save, saveAs
|
||||
*/
|
||||
SaveResult saveAsInteractive (bool warnAboutOverwritingExistingFiles);
|
||||
#endif
|
||||
|
||||
/** Prompts the user for a filename and tries to save to it.
|
||||
|
||||
|
|
@ -201,10 +313,12 @@ public:
|
|||
to this file.
|
||||
|
||||
@param warnAboutOverwritingExistingFiles if true and the file exists, it'll ask
|
||||
the user first if they want to overwrite it
|
||||
@see saveIfNeededAndUserAgrees, save, saveAs
|
||||
the user first if they want to overwrite it
|
||||
@param callback called with the result of the save operation
|
||||
@see saveIfNeededAndUserAgreesAsync, saveAsync, saveAsAsync
|
||||
*/
|
||||
SaveResult saveAsInteractive (bool warnAboutOverwritingExistingFiles);
|
||||
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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
/**@}*/
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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), "*");
|
||||
|
||||
if (allowMultipleFiles ? chooser.browseForMultipleFilesToOpen()
|
||||
: chooser.browseForFileToOpen())
|
||||
struct DeletedFileChooserWrapper : private DeletedAtShutdown
|
||||
{
|
||||
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
|
||||
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);
|
||||
|
||||
wrapper->chooser->launchAsync (flags, [wrapper] (const FileChooser&)
|
||||
{
|
||||
for (auto& f : wrapper->chooser->getResults())
|
||||
[wrapper->listener chooseFilename: juceStringToNS (f.getFullPathName())];
|
||||
|
||||
delete wrapper;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue