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
|
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
|
Change
|
||||||
------
|
------
|
||||||
The minimum supported C++ standard is now C++14 and the oldest supported
|
The minimum supported C++ standard is now C++14 and the oldest supported
|
||||||
|
|
|
||||||
|
|
@ -590,14 +590,13 @@ private:
|
||||||
if (fileChooser != nullptr)
|
if (fileChooser != nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SafePointer<AudioPlayerHeader> safeThis (this);
|
|
||||||
|
|
||||||
if (! RuntimePermissions::isGranted (RuntimePermissions::readExternalStorage))
|
if (! RuntimePermissions::isGranted (RuntimePermissions::readExternalStorage))
|
||||||
{
|
{
|
||||||
|
SafePointer<AudioPlayerHeader> safeThis (this);
|
||||||
RuntimePermissions::request (RuntimePermissions::readExternalStorage,
|
RuntimePermissions::request (RuntimePermissions::readExternalStorage,
|
||||||
[safeThis] (bool granted) mutable
|
[safeThis] (bool granted) mutable
|
||||||
{
|
{
|
||||||
if (granted)
|
if (safeThis != nullptr && granted)
|
||||||
safeThis->openFile();
|
safeThis->openFile();
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
@ -606,22 +605,19 @@ private:
|
||||||
fileChooser.reset (new FileChooser ("Select an audio file...", File(), "*.wav;*.mp3;*.aif"));
|
fileChooser.reset (new FileChooser ("Select an audio file...", File(), "*.wav;*.mp3;*.aif"));
|
||||||
|
|
||||||
fileChooser->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles,
|
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)
|
if (fc.getURLResults().size() > 0)
|
||||||
{
|
{
|
||||||
auto u = fc.getURLResult();
|
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);
|
NativeMessageBox::showOkCancelBox (AlertWindow::WarningIcon, "Error loading file", "Unable to load audio file", nullptr, nullptr);
|
||||||
else
|
else
|
||||||
safeThis->thumbnailComp.setCurrentURL (u);
|
thumbnailComp.setCurrentURL (u);
|
||||||
}
|
}
|
||||||
|
|
||||||
safeThis->fileChooser = nullptr;
|
fileChooser = nullptr;
|
||||||
}, nullptr);
|
}, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -498,14 +498,13 @@ private:
|
||||||
{
|
{
|
||||||
if (btn == &chooseFileButton && fileChooser.get() == nullptr)
|
if (btn == &chooseFileButton && fileChooser.get() == nullptr)
|
||||||
{
|
{
|
||||||
SafePointer<AudioPlaybackDemo> safeThis (this);
|
|
||||||
|
|
||||||
if (! RuntimePermissions::isGranted (RuntimePermissions::readExternalStorage))
|
if (! RuntimePermissions::isGranted (RuntimePermissions::readExternalStorage))
|
||||||
{
|
{
|
||||||
|
SafePointer<AudioPlaybackDemo> safeThis (this);
|
||||||
RuntimePermissions::request (RuntimePermissions::readExternalStorage,
|
RuntimePermissions::request (RuntimePermissions::readExternalStorage,
|
||||||
[safeThis] (bool granted) mutable
|
[safeThis] (bool granted) mutable
|
||||||
{
|
{
|
||||||
if (granted)
|
if (safeThis != nullptr && granted)
|
||||||
safeThis->buttonClicked (&safeThis->chooseFileButton);
|
safeThis->buttonClicked (&safeThis->chooseFileButton);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
@ -516,16 +515,16 @@ private:
|
||||||
fileChooser.reset (new FileChooser ("Select an audio file...", File(), "*.wav;*.mp3;*.aif"));
|
fileChooser.reset (new FileChooser ("Select an audio file...", File(), "*.wav;*.mp3;*.aif"));
|
||||||
|
|
||||||
fileChooser->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles,
|
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();
|
auto u = fc.getURLResult();
|
||||||
|
|
||||||
safeThis->showAudioResource (std::move (u));
|
showAudioResource (std::move (u));
|
||||||
}
|
}
|
||||||
|
|
||||||
safeThis->fileChooser = nullptr;
|
fileChooser = nullptr;
|
||||||
}, nullptr);
|
}, nullptr);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -590,14 +590,13 @@ private:
|
||||||
if (fileChooser != nullptr)
|
if (fileChooser != nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SafePointer<AudioPlayerHeader> safeThis (this);
|
|
||||||
|
|
||||||
if (! RuntimePermissions::isGranted (RuntimePermissions::readExternalStorage))
|
if (! RuntimePermissions::isGranted (RuntimePermissions::readExternalStorage))
|
||||||
{
|
{
|
||||||
|
SafePointer<AudioPlayerHeader> safeThis (this);
|
||||||
RuntimePermissions::request (RuntimePermissions::readExternalStorage,
|
RuntimePermissions::request (RuntimePermissions::readExternalStorage,
|
||||||
[safeThis] (bool granted) mutable
|
[safeThis] (bool granted) mutable
|
||||||
{
|
{
|
||||||
if (granted)
|
if (safeThis != nullptr && granted)
|
||||||
safeThis->openFile();
|
safeThis->openFile();
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
@ -606,22 +605,19 @@ private:
|
||||||
fileChooser.reset (new FileChooser ("Select an audio file...", File(), "*.wav;*.mp3;*.aif"));
|
fileChooser.reset (new FileChooser ("Select an audio file...", File(), "*.wav;*.mp3;*.aif"));
|
||||||
|
|
||||||
fileChooser->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles,
|
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)
|
if (fc.getURLResults().size() > 0)
|
||||||
{
|
{
|
||||||
auto u = fc.getURLResult();
|
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);
|
NativeMessageBox::showOkCancelBox (AlertWindow::WarningIcon, "Error loading file", "Unable to load audio file", nullptr, nullptr);
|
||||||
else
|
else
|
||||||
safeThis->thumbnailComp.setCurrentURL (u);
|
thumbnailComp.setCurrentURL (u);
|
||||||
}
|
}
|
||||||
|
|
||||||
safeThis->fileChooser = nullptr;
|
fileChooser = nullptr;
|
||||||
}, nullptr);
|
}, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -141,10 +141,21 @@ public:
|
||||||
nativeButton.setButtonText ("Use Native Windows");
|
nativeButton.setButtonText ("Use Native Windows");
|
||||||
nativeButton.onClick = [this] { getLookAndFeel().setUsingNativeAlertWindows (nativeButton.getToggleState()); };
|
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",
|
StringArray windowNames { "Plain Alert Window",
|
||||||
"OK Cancel Alert Window", "Alert Window With Extra Components", "CalloutBox", "Thread With Progress Window",
|
"Alert Window With Warning Icon",
|
||||||
"'Load' File Browser", "'Load' File Browser With Image Preview", "'Choose Directory' File Browser", "'Save' File Browser",
|
"Alert Window With Info Icon",
|
||||||
"Share Text", "Share Files", "Share Images" };
|
"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
|
// warn in case we add any windows
|
||||||
jassert (windowNames.size() == numDialogs);
|
jassert (windowNames.size() == numDialogs);
|
||||||
|
|
@ -207,11 +218,42 @@ private:
|
||||||
OwnedArray<TextButton> windowButtons;
|
OwnedArray<TextButton> windowButtons;
|
||||||
ToggleButton nativeButton;
|
ToggleButton nativeButton;
|
||||||
|
|
||||||
static void alertBoxResultChosen (int result, DialogsDemo*)
|
struct AlertBoxResultChosen
|
||||||
{
|
{
|
||||||
AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon, "Alert Box",
|
void operator() (int result) const noexcept
|
||||||
"Result code: " + String (result));
|
{
|
||||||
}
|
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)
|
void showWindow (Component& button, DialogType type)
|
||||||
{
|
{
|
||||||
|
|
@ -232,7 +274,7 @@ private:
|
||||||
AlertWindow::showOkCancelBox (AlertWindow::QuestionIcon, "This is an ok/cancel AlertWindow",
|
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.",
|
"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)
|
else if (type == calloutBoxWindow)
|
||||||
{
|
{
|
||||||
|
|
@ -247,31 +289,16 @@ private:
|
||||||
}
|
}
|
||||||
else if (type == extraComponentsAlertWindow)
|
else if (type == extraComponentsAlertWindow)
|
||||||
{
|
{
|
||||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
asyncAlertWindow = std::make_unique<AlertWindow> ("AlertWindow demo..",
|
||||||
// Modal loops are extremely dangerous. Do not copy the code below unless you are absolutely
|
"This AlertWindow has a couple of extra components added to show how to add drop-down lists and text entry boxes.",
|
||||||
// certain you are aware of all the many complicated things that can go catastrophically
|
AlertWindow::QuestionIcon);
|
||||||
// wrong. Read the documentation for Component::runModalLoop. If you find you are using code
|
|
||||||
// similar to this you should refactor things to remove it.
|
|
||||||
|
|
||||||
AlertWindow w ("AlertWindow demo..",
|
asyncAlertWindow->addTextEditor ("text", "enter some text here", "text field:");
|
||||||
"This AlertWindow has a couple of extra components added to show how to add drop-down lists and text entry boxes.",
|
asyncAlertWindow->addComboBox ("option", { "option 1", "option 2", "option 3", "option 4" }, "some options");
|
||||||
AlertWindow::QuestionIcon);
|
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:");
|
asyncAlertWindow->enterModalState (true, ModalCallbackFunction::create (AsyncAlertBoxResultChosen { *this }));
|
||||||
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
|
|
||||||
}
|
}
|
||||||
else if (type == progressWindow)
|
else if (type == progressWindow)
|
||||||
{
|
{
|
||||||
|
|
@ -461,6 +488,7 @@ private:
|
||||||
|
|
||||||
ImagePreviewComponent imagePreview;
|
ImagePreviewComponent imagePreview;
|
||||||
std::unique_ptr<FileChooser> fc;
|
std::unique_ptr<FileChooser> fc;
|
||||||
|
std::unique_ptr<AlertWindow> asyncAlertWindow;
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DialogsDemo)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DialogsDemo)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -110,14 +110,12 @@ public:
|
||||||
// not interested in this for now
|
// not interested in this for now
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
|
||||||
File getSuggestedSaveAsFile (const File&) override
|
File getSuggestedSaveAsFile (const File&) override
|
||||||
{
|
{
|
||||||
return File::getSpecialLocation (File::userDesktopDirectory)
|
return File::getSpecialLocation (File::userDesktopDirectory)
|
||||||
.getChildFile (getName())
|
.getChildFile (getName())
|
||||||
.withFileExtension ("jnote");
|
.withFileExtension ("jnote");
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Value textValueObject;
|
Value textValueObject;
|
||||||
|
|
@ -138,23 +136,19 @@ private:
|
||||||
class DemoMultiDocumentPanel : public MultiDocumentPanel
|
class DemoMultiDocumentPanel : public MultiDocumentPanel
|
||||||
{
|
{
|
||||||
public:
|
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))
|
if (auto* note = dynamic_cast<Note*> (component))
|
||||||
return note->saveIfNeededAndUserAgrees() != FileBasedDocument::failedToWriteToFile;
|
{
|
||||||
#else
|
SafePointer<DemoMultiDocumentPanel> parent { this };
|
||||||
ignoreUnused (component);
|
note->saveIfNeededAndUserAgreesAsync ([parent, callback] (FileBasedDocument::SaveResult result)
|
||||||
#endif
|
{
|
||||||
|
if (parent != nullptr)
|
||||||
return true;
|
callback (result == FileBasedDocument::savedOk);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -180,6 +174,16 @@ public:
|
||||||
addNoteButton.onClick = [this] { addNote ("Note " + String (multiDocumentPanel.getNumDocuments() + 1), "Hello World!"); };
|
addNoteButton.onClick = [this] { addNote ("Note " + String (multiDocumentPanel.getNumDocuments() + 1), "Hello World!"); };
|
||||||
addAndMakeVisible (addNoteButton);
|
addAndMakeVisible (addNoteButton);
|
||||||
|
|
||||||
|
closeApplicationButton.onClick = [this]
|
||||||
|
{
|
||||||
|
multiDocumentPanel.closeAllDocumentsAsync (true, [] (bool allSaved)
|
||||||
|
{
|
||||||
|
if (allSaved)
|
||||||
|
JUCEApplicationBase::quit();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
addAndMakeVisible (closeApplicationButton);
|
||||||
|
|
||||||
addAndMakeVisible (multiDocumentPanel);
|
addAndMakeVisible (multiDocumentPanel);
|
||||||
multiDocumentPanel.setBackgroundColour (Colours::transparentBlack);
|
multiDocumentPanel.setBackgroundColour (Colours::transparentBlack);
|
||||||
|
|
||||||
|
|
@ -200,8 +204,9 @@ public:
|
||||||
auto area = getLocalBounds();
|
auto area = getLocalBounds();
|
||||||
|
|
||||||
auto buttonArea = area.removeFromTop (28).reduced (2);
|
auto buttonArea = area.removeFromTop (28).reduced (2);
|
||||||
addNoteButton .setBounds (buttonArea.removeFromRight (150));
|
closeApplicationButton.setBounds (buttonArea.removeFromRight (150));
|
||||||
showInTabsButton.setBounds (buttonArea);
|
addNoteButton .setBounds (buttonArea.removeFromRight (150));
|
||||||
|
showInTabsButton .setBounds (buttonArea);
|
||||||
|
|
||||||
multiDocumentPanel.setBounds (area);
|
multiDocumentPanel.setBounds (area);
|
||||||
}
|
}
|
||||||
|
|
@ -235,11 +240,6 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ToggleButton showInTabsButton { "Show with tabs" };
|
|
||||||
TextButton addNoteButton { "Create a new note" };
|
|
||||||
|
|
||||||
DemoMultiDocumentPanel multiDocumentPanel;
|
|
||||||
|
|
||||||
void updateLayoutMode()
|
void updateLayoutMode()
|
||||||
{
|
{
|
||||||
multiDocumentPanel.setLayoutMode (showInTabsButton.getToggleState() ? MultiDocumentPanel::MaximisedWindowsWithTabs
|
multiDocumentPanel.setLayoutMode (showInTabsButton.getToggleState() ? MultiDocumentPanel::MaximisedWindowsWithTabs
|
||||||
|
|
@ -261,5 +261,11 @@ private:
|
||||||
createNotesForFiles (files);
|
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)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MDIDemo)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1103,25 +1103,25 @@ private:
|
||||||
|
|
||||||
void selectTexture (int itemID)
|
void selectTexture (int itemID)
|
||||||
{
|
{
|
||||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
|
||||||
if (itemID == 1000)
|
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");
|
textureFileChooser->launchAsync (chooserFlags, [this] (const FileChooser& fc)
|
||||||
|
|
||||||
if (fc.browseForFileToOpen())
|
|
||||||
{
|
{
|
||||||
lastLocation = fc.getResult();
|
if (fc.getResult() == File{})
|
||||||
|
return;
|
||||||
|
|
||||||
textures.add (new OpenGLUtils::TextureFromFile (fc.getResult()));
|
textures.add (new OpenGLUtils::TextureFromFile (fc.getResult()));
|
||||||
updateTexturesList();
|
updateTexturesList();
|
||||||
|
|
||||||
textureBox.setSelectedId (textures.size());
|
textureBox.setSelectedId (textures.size());
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
if (auto* t = textures[itemID - 1])
|
if (auto* t = textures[itemID - 1])
|
||||||
demo.setTexture (t);
|
demo.setTexture (t);
|
||||||
|
|
@ -1135,10 +1135,8 @@ private:
|
||||||
for (int i = 0; i < textures.size(); ++i)
|
for (int i = 0; i < textures.size(); ++i)
|
||||||
textureBox.addItem (textures.getUnchecked (i)->name, i + 1);
|
textureBox.addItem (textures.getUnchecked (i)->name, i + 1);
|
||||||
|
|
||||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
|
||||||
textureBox.addSeparator();
|
textureBox.addSeparator();
|
||||||
textureBox.addItem ("Load from a file...", 1000);
|
textureBox.addItem ("Load from a file...", 1000);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateShader()
|
void updateShader()
|
||||||
|
|
@ -1208,6 +1206,8 @@ private:
|
||||||
|
|
||||||
OwnedArray<OpenGLUtils::DemoTexture> textures;
|
OwnedArray<OpenGLUtils::DemoTexture> textures;
|
||||||
|
|
||||||
|
std::unique_ptr<FileChooser> textureFileChooser;
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DemoControlsOverlay)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DemoControlsOverlay)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -590,14 +590,13 @@ private:
|
||||||
if (fileChooser != nullptr)
|
if (fileChooser != nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SafePointer<AudioPlayerHeader> safeThis (this);
|
|
||||||
|
|
||||||
if (! RuntimePermissions::isGranted (RuntimePermissions::readExternalStorage))
|
if (! RuntimePermissions::isGranted (RuntimePermissions::readExternalStorage))
|
||||||
{
|
{
|
||||||
|
SafePointer<AudioPlayerHeader> safeThis (this);
|
||||||
RuntimePermissions::request (RuntimePermissions::readExternalStorage,
|
RuntimePermissions::request (RuntimePermissions::readExternalStorage,
|
||||||
[safeThis] (bool granted) mutable
|
[safeThis] (bool granted) mutable
|
||||||
{
|
{
|
||||||
if (granted)
|
if (safeThis != nullptr && granted)
|
||||||
safeThis->openFile();
|
safeThis->openFile();
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
@ -606,22 +605,19 @@ private:
|
||||||
fileChooser.reset (new FileChooser ("Select an audio file...", File(), "*.wav;*.mp3;*.aif"));
|
fileChooser.reset (new FileChooser ("Select an audio file...", File(), "*.wav;*.mp3;*.aif"));
|
||||||
|
|
||||||
fileChooser->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles,
|
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)
|
if (fc.getURLResults().size() > 0)
|
||||||
{
|
{
|
||||||
auto u = fc.getURLResult();
|
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);
|
NativeMessageBox::showOkCancelBox (AlertWindow::WarningIcon, "Error loading file", "Unable to load audio file", nullptr, nullptr);
|
||||||
else
|
else
|
||||||
safeThis->thumbnailComp.setCurrentURL (u);
|
thumbnailComp.setCurrentURL (u);
|
||||||
}
|
}
|
||||||
|
|
||||||
safeThis->fileChooser = nullptr;
|
fileChooser = nullptr;
|
||||||
}, 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
|
// to flush any GUI events that may have been in transit before the app forces them to
|
||||||
// be unloaded
|
// be unloaded
|
||||||
new AsyncQuitRetrier();
|
new AsyncQuitRetrier();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (ModalComponentManager::getInstance()->cancelAllModalComponents())
|
|
||||||
|
if (ModalComponentManager::getInstance()->cancelAllModalComponents())
|
||||||
{
|
{
|
||||||
new AsyncQuitRetrier();
|
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)
|
void MainHostWindow::changeListenerCallback (ChangeBroadcaster* changed)
|
||||||
|
|
@ -329,9 +351,20 @@ void MainHostWindow::menuItemSelected (int menuItemID, int /*topLevelMenuIndex*/
|
||||||
->getValue ("recentFilterGraphFiles"));
|
->getValue ("recentFilterGraphFiles"));
|
||||||
|
|
||||||
if (graphHolder != nullptr)
|
if (graphHolder != nullptr)
|
||||||
|
{
|
||||||
if (auto* graph = graphHolder->graph.get())
|
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
|
#endif
|
||||||
else if (menuItemID >= 200 && menuItemID < 210)
|
else if (menuItemID >= 200 && menuItemID < 210)
|
||||||
|
|
@ -492,23 +525,43 @@ bool MainHostWindow::perform (const InvocationInfo& info)
|
||||||
{
|
{
|
||||||
#if ! (JUCE_IOS || JUCE_ANDROID)
|
#if ! (JUCE_IOS || JUCE_ANDROID)
|
||||||
case CommandIDs::newFile:
|
case CommandIDs::newFile:
|
||||||
if (graphHolder != nullptr && graphHolder->graph != nullptr && graphHolder->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk)
|
if (graphHolder != nullptr && graphHolder->graph != nullptr)
|
||||||
graphHolder->graph->newDocument();
|
{
|
||||||
|
SafePointer<MainHostWindow> parent { this };
|
||||||
|
graphHolder->graph->saveIfNeededAndUserAgreesAsync ([parent] (FileBasedDocument::SaveResult r)
|
||||||
|
{
|
||||||
|
if (parent == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (r == FileBasedDocument::savedOk)
|
||||||
|
parent->graphHolder->graph->newDocument();
|
||||||
|
});
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CommandIDs::open:
|
case CommandIDs::open:
|
||||||
if (graphHolder != nullptr && graphHolder->graph != nullptr && graphHolder->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk)
|
if (graphHolder != nullptr && graphHolder->graph != nullptr)
|
||||||
graphHolder->graph->loadFromUserSpecifiedFile (true);
|
{
|
||||||
|
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;
|
break;
|
||||||
|
|
||||||
case CommandIDs::save:
|
case CommandIDs::save:
|
||||||
if (graphHolder != nullptr && graphHolder->graph != nullptr)
|
if (graphHolder != nullptr && graphHolder->graph != nullptr)
|
||||||
graphHolder->graph->save (true, true);
|
graphHolder->graph->saveAsync (true, true, nullptr);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CommandIDs::saveAs:
|
case CommandIDs::saveAs:
|
||||||
if (graphHolder != nullptr && graphHolder->graph != nullptr)
|
if (graphHolder != nullptr && graphHolder->graph != nullptr)
|
||||||
graphHolder->graph->saveAs (File(), true, true, true);
|
graphHolder->graph->saveAsAsync ({}, true, true, true, nullptr);
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -630,11 +683,22 @@ void MainHostWindow::filesDropped (const StringArray& files, int x, int y)
|
||||||
if (graphHolder != nullptr)
|
if (graphHolder != nullptr)
|
||||||
{
|
{
|
||||||
#if ! (JUCE_ANDROID || JUCE_IOS)
|
#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 (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
|
else
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -99,25 +99,34 @@ public:
|
||||||
{
|
{
|
||||||
createProjectButton.onClick = [this]
|
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();
|
auto dir = fc.getResult();
|
||||||
|
|
||||||
if (auto project = NewProjectWizard::createNewProject (projectTemplate,
|
if (dir == File{})
|
||||||
dir.getChildFile (projectNameValue.get().toString()),
|
return;
|
||||||
projectNameValue.get(),
|
|
||||||
modulesValue.get(),
|
SafePointer<TemplateComponent> safeThis { this };
|
||||||
exportersValue.get(),
|
NewProjectWizard::createNewProject (projectTemplate,
|
||||||
fileOptionsValue.get(),
|
dir.getChildFile (projectNameValue.get().toString()),
|
||||||
modulePathValue.getCurrentValue(),
|
projectNameValue.get(),
|
||||||
modulePathValue.getWrappedValueWithDefault().isUsingDefault()))
|
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;
|
getAppSettings().lastWizardFolder = dir;
|
||||||
}
|
});
|
||||||
}
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
addAndMakeVisible (createProjectButton);
|
addAndMakeVisible (createProjectButton);
|
||||||
|
|
@ -150,6 +159,7 @@ public:
|
||||||
private:
|
private:
|
||||||
NewProjectTemplates::ProjectTemplate projectTemplate;
|
NewProjectTemplates::ProjectTemplate projectTemplate;
|
||||||
|
|
||||||
|
std::unique_ptr<FileChooser> chooser;
|
||||||
std::function<void (std::unique_ptr<Project>)> projectCreatedCallback;
|
std::function<void (std::unique_ptr<Project>)> projectCreatedCallback;
|
||||||
|
|
||||||
ItemHeader header;
|
ItemHeader header;
|
||||||
|
|
|
||||||
|
|
@ -220,63 +220,91 @@ File NewProjectWizard::getLastWizardFolder()
|
||||||
return lastFolderFallback;
|
return lastFolderFallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Project> NewProjectWizard::createNewProject (const NewProjectTemplates::ProjectTemplate& projectTemplate,
|
static void displayFailedFilesMessage (const StringArray& failedFiles)
|
||||||
const File& targetFolder, const String& name, var modules, var exporters, var fileOptions,
|
{
|
||||||
const String& modulePath, bool useGlobalModulePath)
|
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;
|
StringArray failedFiles;
|
||||||
|
|
||||||
if (! targetFolder.exists())
|
if (! targetFolder.exists())
|
||||||
{
|
{
|
||||||
if (! targetFolder.createDirectory())
|
if (! targetFolder.createDirectory())
|
||||||
failedFiles.add (targetFolder.getFullPathName());
|
{
|
||||||
|
displayFailedFilesMessage ({ targetFolder.getFullPathName() });
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (FileHelpers::containsAnyNonHiddenFiles (targetFolder))
|
else if (FileHelpers::containsAnyNonHiddenFiles (targetFolder))
|
||||||
{
|
{
|
||||||
if (! AlertWindow::showOkCancelBox (AlertWindow::InfoIcon,
|
AlertWindow::showOkCancelBox (AlertWindow::InfoIcon,
|
||||||
TRANS("New JUCE Project"),
|
TRANS("New JUCE Project"),
|
||||||
TRANS("You chose the folder:\n\nXFLDRX\n\n").replace ("XFLDRX", targetFolder.getFullPathName())
|
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?")
|
+ TRANS("This folder isn't empty - are you sure you want to create the project there?")
|
||||||
+ "\n\n"
|
+ "\n\n"
|
||||||
+ TRANS("Any existing files with the same names may be overwritten by the new files.")))
|
+ TRANS("Any existing files with the same names may be overwritten by the new files."),
|
||||||
{
|
{},
|
||||||
return nullptr;
|
{},
|
||||||
}
|
nullptr,
|
||||||
|
ModalCallbackFunction::create ([callback] (int result)
|
||||||
|
{
|
||||||
|
if (result != 0)
|
||||||
|
callback();
|
||||||
|
}));
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto project = std::make_unique<Project> (targetFolder.getChildFile (File::createLegalFileName (name))
|
callback();
|
||||||
.withFileExtension (Project::projectFileExtension));
|
}
|
||||||
|
|
||||||
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);
|
doBasicProjectSetup (*project, projectTemplate, name);
|
||||||
|
|
||||||
|
StringArray failedFiles;
|
||||||
|
|
||||||
if (addFiles (*project, projectTemplate, name, fileOptions, failedFiles))
|
if (addFiles (*project, projectTemplate, name, fileOptions, failedFiles))
|
||||||
{
|
{
|
||||||
addExporters (*project, *exporters.getArray());
|
addExporters (*project, *exporters.getArray());
|
||||||
addModules (*project, *modules.getArray(), modulePath, useGlobalModulePath);
|
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);
|
auto uniqueProject = std::move (*sharedProject.get());
|
||||||
project->loadFrom (project->getFile(), true);
|
|
||||||
}
|
if (r == FileBasedDocument::savedOk)
|
||||||
else
|
{
|
||||||
{
|
uniqueProject->setChangedFlag (false);
|
||||||
failedFiles.add (project->getFile().getFullPathName());
|
uniqueProject->loadFrom (uniqueProject->getFile(), true);
|
||||||
}
|
callback (std::move (uniqueProject));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto failedFilesCopy = failedFiles;
|
||||||
|
failedFilesCopy.add (uniqueProject->getFile().getFullPathName());
|
||||||
|
displayFailedFilesMessage (failedFilesCopy);
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (! failedFiles.isEmpty())
|
displayFailedFilesMessage (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));
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return project;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,8 @@ namespace NewProjectWizard
|
||||||
{
|
{
|
||||||
File getLastWizardFolder();
|
File getLastWizardFolder();
|
||||||
|
|
||||||
std::unique_ptr<Project> createNewProject (const NewProjectTemplates::ProjectTemplate& projectTemplate,
|
void createNewProject (const NewProjectTemplates::ProjectTemplate& projectTemplate,
|
||||||
const File& targetFolder, const String& name, var modules, var exporters, var fileOptions,
|
const File& targetFolder, const String& name, var modules, var exporters, var fileOptions,
|
||||||
const String& modulePath, bool useGlobalModulePath);
|
const String& modulePath, bool useGlobalModulePath,
|
||||||
|
std::function<void (std::unique_ptr<Project>)> callback);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -184,40 +184,52 @@ private:
|
||||||
|
|
||||||
void saveScheme (bool isExit)
|
void saveScheme (bool isExit)
|
||||||
{
|
{
|
||||||
FileChooser fc ("Select a file in which to save this colour-scheme...",
|
chooser = std::make_unique<FileChooser> ("Select a file in which to save this colour-scheme...",
|
||||||
getAppSettings().appearance.getSchemesFolder()
|
getAppSettings().appearance.getSchemesFolder()
|
||||||
.getNonexistentChildFile ("Scheme", AppearanceSettings::getSchemeFileSuffix()),
|
.getNonexistentChildFile ("Scheme", AppearanceSettings::getSchemeFileSuffix()),
|
||||||
AppearanceSettings::getSchemeFileWildCard());
|
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()));
|
File file (fc.getResult().withFileExtension (AppearanceSettings::getSchemeFileSuffix()));
|
||||||
getAppSettings().appearance.writeToFile (file);
|
getAppSettings().appearance.writeToFile (file);
|
||||||
getAppSettings().appearance.refreshPresetSchemeList();
|
getAppSettings().appearance.refreshPresetSchemeList();
|
||||||
|
|
||||||
saveSchemeState();
|
saveSchemeState();
|
||||||
ProjucerApplication::getApp().selectEditorColourSchemeWithName (file.getFileNameWithoutExtension());
|
ProjucerApplication::getApp().selectEditorColourSchemeWithName (file.getFileNameWithoutExtension());
|
||||||
}
|
});
|
||||||
else if (isExit)
|
|
||||||
{
|
|
||||||
restorePreviousScheme();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadScheme()
|
void loadScheme()
|
||||||
{
|
{
|
||||||
FileChooser fc ("Please select a colour-scheme file to load...",
|
chooser = std::make_unique<FileChooser> ("Please select a colour-scheme file to load...",
|
||||||
getAppSettings().appearance.getSchemesFolder(),
|
getAppSettings().appearance.getSchemesFolder(),
|
||||||
AppearanceSettings::getSchemeFileWildCard());
|
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()))
|
if (getAppSettings().appearance.readFromFile (fc.getResult()))
|
||||||
{
|
{
|
||||||
rebuildProperties();
|
rebuildProperties();
|
||||||
saveSchemeState();
|
saveSchemeState();
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void lookAndFeelChanged() override
|
void lookAndFeelChanged() override
|
||||||
|
|
@ -264,6 +276,7 @@ private:
|
||||||
appearance.getColourValue (colourNames[i]).setValue (colourValues[i]);
|
appearance.getColourValue (colourNames[i]).setValue (colourValues[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<FileChooser> chooser;
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE (EditorPanel)
|
JUCE_DECLARE_NON_COPYABLE (EditorPanel)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,20 @@ public:
|
||||||
addAndMakeVisible (createButton);
|
addAndMakeVisible (createButton);
|
||||||
createButton.onClick = [this]
|
createButton.onClick = [this]
|
||||||
{
|
{
|
||||||
FileChooser fc ("Save PIP File",
|
chooser = std::make_unique<FileChooser> ("Save PIP File",
|
||||||
File::getSpecialLocation (File::SpecialLocationType::userDesktopDirectory)
|
File::getSpecialLocation (File::SpecialLocationType::userDesktopDirectory)
|
||||||
.getChildFile (nameValue.get().toString() + ".h"));
|
.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);
|
pipTree.addListener (this);
|
||||||
|
|
@ -333,6 +340,8 @@ private:
|
||||||
|
|
||||||
TextButton createButton { "Create PIP" };
|
TextButton createButton { "Create PIP" };
|
||||||
|
|
||||||
|
std::unique_ptr<FileChooser> chooser;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PIPCreatorWindowComponent)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PIPCreatorWindowComponent)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,9 @@ class TranslationToolComponent : public Component
|
||||||
public:
|
public:
|
||||||
TranslationToolComponent()
|
TranslationToolComponent()
|
||||||
: editorOriginal (documentOriginal, nullptr),
|
: editorOriginal (documentOriginal, nullptr),
|
||||||
editorPre (documentPre, nullptr),
|
editorPre (documentPre, nullptr),
|
||||||
editorPost (documentPost, nullptr),
|
editorPost (documentPost, nullptr),
|
||||||
editorResult (documentResult, nullptr)
|
editorResult (documentResult, nullptr)
|
||||||
{
|
{
|
||||||
instructionsLabel.setText (
|
instructionsLabel.setText (
|
||||||
"This utility converts translation files to/from a format that can be passed to automatic translation tools."
|
"This utility converts translation files to/from a format that can be passed to automatic translation tools."
|
||||||
|
|
@ -114,17 +114,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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()
|
void generate()
|
||||||
{
|
{
|
||||||
StringArray preStrings (TranslationHelpers::breakApart (documentPre.getAllContent()));
|
StringArray preStrings (TranslationHelpers::breakApart (documentPre.getAllContent()));
|
||||||
|
|
@ -154,28 +144,35 @@ private:
|
||||||
|
|
||||||
void scanFolder()
|
void scanFolder()
|
||||||
{
|
{
|
||||||
FileChooser fc ("Choose the root folder to search for the TRANS macros",
|
chooser = std::make_unique<FileChooser> ("Choose the root folder to search for the TRANS macros",
|
||||||
File(), "*");
|
File(), "*");
|
||||||
|
auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
|
||||||
|
|
||||||
if (fc.browseForDirectory())
|
chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc)
|
||||||
{
|
{
|
||||||
|
if (fc.getResult() == File{})
|
||||||
|
return;
|
||||||
|
|
||||||
StringArray strings;
|
StringArray strings;
|
||||||
TranslationHelpers::scanFolderForTranslations (strings, fc.getResult());
|
TranslationHelpers::scanFolderForTranslations (strings, fc.getResult());
|
||||||
setPreTranslationText (TranslationHelpers::mungeStrings(strings));
|
setPreTranslationText (TranslationHelpers::mungeStrings(strings));
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadFile()
|
void loadFile()
|
||||||
{
|
{
|
||||||
FileChooser fc ("Choose a translation file to load",
|
chooser = std::make_unique<FileChooser> ("Choose a translation file to load", File(), "*");
|
||||||
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);
|
const LocalisedStrings loadedStrings (fc.getResult(), false);
|
||||||
documentOriginal.replaceAllContent (fc.getResult().loadFileAsString().trim());
|
documentOriginal.replaceAllContent (fc.getResult().loadFileAsString().trim());
|
||||||
setPreTranslationText (TranslationHelpers::getPreTranslationText (loadedStrings));
|
setPreTranslationText (TranslationHelpers::getPreTranslationText (loadedStrings));
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void setPreTranslationText (const String& text)
|
void setPreTranslationText (const String& text)
|
||||||
|
|
@ -184,4 +181,18 @@ private:
|
||||||
editorPre.grabKeyboardFocus();
|
editorPre.grabKeyboardFocus();
|
||||||
editorPre.selectAll();
|
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
|
else
|
||||||
{
|
{
|
||||||
if (closeAllMainWindows())
|
closeAllMainWindows ([] (bool closedSuccessfully)
|
||||||
quit();
|
{
|
||||||
|
if (closedSuccessfully)
|
||||||
|
ProjucerApplication::quit();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -251,7 +254,7 @@ void ProjucerApplication::anotherInstanceStarted (const String& commandLine)
|
||||||
ArgumentList list ({}, commandLine);
|
ArgumentList list ({}, commandLine);
|
||||||
|
|
||||||
for (auto& arg : list.arguments)
|
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?
|
// example doesn't exist?
|
||||||
jassert (example != File());
|
jassert (example != File());
|
||||||
|
|
||||||
openFile (example);
|
openFile (example, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
@ -863,7 +866,7 @@ void ProjucerApplication::handleMainMenuCommand (int menuItemID)
|
||||||
if (menuItemID >= recentProjectsBaseID && menuItemID < (recentProjectsBaseID + 100))
|
if (menuItemID >= recentProjectsBaseID && menuItemID < (recentProjectsBaseID + 100))
|
||||||
{
|
{
|
||||||
// open a file from the "recent files" menu
|
// 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))
|
else if (menuItemID >= openWindowsBaseID && menuItemID < (openWindowsBaseID + 100))
|
||||||
{
|
{
|
||||||
|
|
@ -1095,23 +1098,33 @@ void ProjucerApplication::createNewProjectFromClipboard()
|
||||||
tempFile.create();
|
tempFile.create();
|
||||||
tempFile.appendText (SystemClipboard::getTextFromClipboard());
|
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))
|
if (! isPIPFile (tempFile))
|
||||||
{
|
{
|
||||||
errorString = "Clipboard does not contain a valid PIP.";
|
cleanup ("Clipboard does not contain a valid PIP.");
|
||||||
}
|
return;
|
||||||
else if (! openFile (tempFile))
|
|
||||||
{
|
|
||||||
errorString = "Couldn't create project from clipboard contents.";
|
|
||||||
mainWindowList.closeWindow (mainWindowList.windows.getLast());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorString.isNotEmpty())
|
WeakReference<ProjucerApplication> parent { this };
|
||||||
|
openFile (tempFile, [parent, cleanup] (bool openedSuccessfully)
|
||||||
{
|
{
|
||||||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "Error", errorString);
|
if (parent == nullptr)
|
||||||
tempFile.deleteFile();
|
return;
|
||||||
}
|
|
||||||
|
if (! openedSuccessfully)
|
||||||
|
{
|
||||||
|
cleanup ("Couldn't create project from clipboard contents.");
|
||||||
|
parent->mainWindowList.closeWindow (parent->mainWindowList.windows.getLast());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjucerApplication::createNewPIP()
|
void ProjucerApplication::createNewPIP()
|
||||||
|
|
@ -1121,45 +1134,57 @@ void ProjucerApplication::createNewPIP()
|
||||||
|
|
||||||
void ProjucerApplication::askUserToOpenFile()
|
void ProjucerApplication::askUserToOpenFile()
|
||||||
{
|
{
|
||||||
FileChooser fc ("Open File");
|
chooser = std::make_unique<FileChooser> ("Open File");
|
||||||
|
auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles;
|
||||||
|
|
||||||
if (fc.browseForFileToOpen())
|
chooser->launchAsync (flags, [this] (const FileChooser& fc)
|
||||||
openFile (fc.getResult());
|
{
|
||||||
|
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()
|
void ProjucerApplication::saveAllDocuments()
|
||||||
{
|
{
|
||||||
openDocumentManager.saveAll();
|
openDocumentManager.saveAllSyncWithoutAsking();
|
||||||
|
|
||||||
for (int i = 0; i < mainWindowList.windows.size(); ++i)
|
for (int i = 0; i < mainWindowList.windows.size(); ++i)
|
||||||
if (auto* pcc = mainWindowList.windows.getUnchecked(i)->getProjectContentComponent())
|
if (auto* pcc = mainWindowList.windows.getUnchecked(i)->getProjectContentComponent())
|
||||||
pcc->refreshProjectTreeFileStatuses();
|
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()
|
void ProjucerApplication::closeAllMainWindowsAndQuitIfNeeded()
|
||||||
{
|
{
|
||||||
if (closeAllMainWindows())
|
WeakReference<ProjucerApplication> parent;
|
||||||
|
closeAllMainWindows ([parent] (bool closedSuccessfully)
|
||||||
{
|
{
|
||||||
#if ! JUCE_MAC
|
#if JUCE_MAC
|
||||||
if (mainWindowList.windows.size() == 0)
|
ignoreUnused (parent, closedSuccessfully);
|
||||||
systemRequestedQuit();
|
#else
|
||||||
|
if (parent == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (closedSuccessfully && parent->mainWindowList.windows.size() == 0)
|
||||||
|
parent->systemRequestedQuit();
|
||||||
#endif
|
#endif
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjucerApplication::clearRecentFiles()
|
void ProjucerApplication::clearRecentFiles()
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ public:
|
||||||
bool isGUIEditorEnabled() const;
|
bool isGUIEditorEnabled() const;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
bool openFile (const File&);
|
void openFile (const File&, std::function<void (bool)>);
|
||||||
void showPathsWindow (bool highlightJUCEPath = false);
|
void showPathsWindow (bool highlightJUCEPath = false);
|
||||||
PropertiesFile::Options getPropertyFileOptionsFor (const String& filename, bool isProjectSettings);
|
PropertiesFile::Options getPropertyFileOptionsFor (const String& filename, bool isProjectSettings);
|
||||||
void selectEditorColourSchemeWithName (const String& schemeName);
|
void selectEditorColourSchemeWithName (const String& schemeName);
|
||||||
|
|
@ -119,8 +119,8 @@ private:
|
||||||
void createNewPIP();
|
void createNewPIP();
|
||||||
void askUserToOpenFile();
|
void askUserToOpenFile();
|
||||||
void saveAllDocuments();
|
void saveAllDocuments();
|
||||||
bool closeAllDocuments (OpenDocumentManager::SaveIfNeeded askUserToSave);
|
void closeAllDocuments (OpenDocumentManager::SaveIfNeeded askUserToSave);
|
||||||
bool closeAllMainWindows();
|
void closeAllMainWindows (std::function<void (bool)>);
|
||||||
void closeAllMainWindowsAndQuitIfNeeded();
|
void closeAllMainWindowsAndQuitIfNeeded();
|
||||||
void clearRecentFiles();
|
void clearRecentFiles();
|
||||||
|
|
||||||
|
|
@ -216,6 +216,9 @@ private:
|
||||||
|
|
||||||
int selectedColourSchemeIndex = 0, selectedEditorColourSchemeIndex = 0;
|
int selectedColourSchemeIndex = 0, selectedEditorColourSchemeIndex = 0;
|
||||||
|
|
||||||
|
std::unique_ptr<FileChooser> chooser;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjucerApplication)
|
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)
|
void LatestVersionCheckerAndUpdater::askUserForLocationToDownload (const VersionInfo::Asset& asset)
|
||||||
{
|
{
|
||||||
FileChooser chooser ("Please select the location into which you would like to install the new version",
|
chooser = std::make_unique<FileChooser> ("Please select the location into which you would like to install the new version",
|
||||||
{ getAppSettings().getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get() });
|
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
|
// By default we will install into 'targetFolder/JUCE', but we should install into
|
||||||
// 'targetFolder' if that is an existing JUCE directory.
|
// 'targetFolder' if that is an existing JUCE directory.
|
||||||
|
|
@ -259,6 +264,15 @@ void LatestVersionCheckerAndUpdater::askUserForLocationToDownload (const Version
|
||||||
|
|
||||||
auto targetFolderPath = targetFolder.getFullPathName();
|
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 (willOverwriteJuceFolder)
|
||||||
{
|
{
|
||||||
if (targetFolder.getChildFile (".git").isDirectory())
|
if (targetFolder.getChildFile (".git").isDirectory())
|
||||||
|
|
@ -269,25 +283,32 @@ void LatestVersionCheckerAndUpdater::askUserForLocationToDownload (const Version
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, "Overwrite Existing JUCE Folder?",
|
AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
|
||||||
"Do you want to replace the folder\n\n" + targetFolderPath + "\n\nwith the latest version from juce.com?\n\n"
|
"Overwrite Existing JUCE Folder?",
|
||||||
"This will move the existing folder to " + targetFolderPath + "_old.\n\n"
|
"Do you want to replace the folder\n\n" + targetFolderPath + "\n\nwith the latest version from juce.com?\n\n"
|
||||||
"Replacing the folder that contains the currently running Projucer executable may not work on Windows."))
|
"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;
|
{},
|
||||||
}
|
{},
|
||||||
|
nullptr,
|
||||||
|
callback);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (targetFolder.exists())
|
|
||||||
|
if (targetFolder.exists())
|
||||||
{
|
{
|
||||||
if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, "Existing File Or Directory",
|
AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
|
||||||
"Do you want to move\n\n" + targetFolderPath + "\n\nto\n\n" + targetFolderPath + "_old?"))
|
"Existing File Or Directory",
|
||||||
{
|
"Do you want to move\n\n" + targetFolderPath + "\n\nto\n\n" + targetFolderPath + "_old?",
|
||||||
return;
|
{},
|
||||||
}
|
{},
|
||||||
|
nullptr,
|
||||||
|
callback);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadAndInstall (asset, targetFolder);
|
downloadAndInstall (asset, targetFolder);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void LatestVersionCheckerAndUpdater::askUserAboutNewVersion (const String& newVersionString,
|
void LatestVersionCheckerAndUpdater::askUserAboutNewVersion (const String& newVersionString,
|
||||||
|
|
|
||||||
|
|
@ -56,4 +56,7 @@ private:
|
||||||
|
|
||||||
std::unique_ptr<DownloadAndInstallThread> installer;
|
std::unique_ptr<DownloadAndInstallThread> installer;
|
||||||
std::unique_ptr<Component> dialogWindow;
|
std::unique_ptr<Component> dialogWindow;
|
||||||
|
std::unique_ptr<FileChooser> chooser;
|
||||||
|
|
||||||
|
JUCE_DECLARE_WEAK_REFERENCEABLE (LatestVersionCheckerAndUpdater)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -100,13 +100,18 @@ namespace
|
||||||
if (fixMissingDependencies)
|
if (fixMissingDependencies)
|
||||||
tryToFixMissingModuleDependencies();
|
tryToFixMissingModuleDependencies();
|
||||||
|
|
||||||
auto error = justSaveResources ? project->saveResourcesOnly()
|
const auto onCompletion = [this] (Result result)
|
||||||
: project->saveProject();
|
{
|
||||||
|
project.reset();
|
||||||
|
|
||||||
project.reset();
|
if (result.failed())
|
||||||
|
ConsoleApplication::fail ("Error when saving: " + result.getErrorMessage());
|
||||||
|
};
|
||||||
|
|
||||||
if (error.failed())
|
if (justSaveResources)
|
||||||
ConsoleApplication::fail ("Error when saving: " + error.getErrorMessage());
|
onCompletion (project->saveResourcesOnly());
|
||||||
|
else
|
||||||
|
project->saveProject (Async::no, nullptr, onCompletion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -229,10 +229,15 @@ void MainWindow::closeButtonPressed()
|
||||||
ProjucerApplication::getApp().mainWindowList.closeWindow (this);
|
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)
|
if (currentProject == nullptr)
|
||||||
return true;
|
{
|
||||||
|
if (callback != nullptr)
|
||||||
|
callback (true);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
currentProject->getStoredProperties().setValue (getProjectWindowPosName(), getWindowStateAsString());
|
currentProject->getStoredProperties().setValue (getProjectWindowPosName(), getWindowStateAsString());
|
||||||
|
|
||||||
|
|
@ -242,27 +247,65 @@ bool MainWindow::closeCurrentProject (OpenDocumentManager::SaveIfNeeded askUserT
|
||||||
pcc->hideEditor();
|
pcc->hideEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ProjucerApplication::getApp().openDocumentManager
|
SafePointer<MainWindow> parent { this };
|
||||||
.closeAllDocumentsUsingProject (*currentProject, askUserToSave))
|
ProjucerApplication::getApp().openDocumentManager
|
||||||
|
.closeAllDocumentsUsingProjectAsync (*currentProject, askUserToSave, [parent, askUserToSave, callback] (bool closedSuccessfully)
|
||||||
{
|
{
|
||||||
if (askUserToSave == OpenDocumentManager::SaveIfNeeded::no
|
if (parent == nullptr)
|
||||||
|| (currentProject->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk))
|
return;
|
||||||
{
|
|
||||||
setProject (nullptr);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
void MainWindow::moveProject (File newProjectFileToOpen, OpenInIDE openInIDE)
|
||||||
{
|
{
|
||||||
closeCurrentProject (OpenDocumentManager::SaveIfNeeded::no);
|
SafePointer<MainWindow> parent { this };
|
||||||
openFile (newProjectFileToOpen);
|
closeCurrentProject (OpenDocumentManager::SaveIfNeeded::no, [parent, newProjectFileToOpen, openInIDE] (bool)
|
||||||
|
{
|
||||||
|
if (parent == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
if (currentProject != nullptr && openInIDE == OpenInIDE::yes)
|
parent->openFile (newProjectFileToOpen, [parent, openInIDE] (bool openedSuccessfully)
|
||||||
ProjucerApplication::getApp().getCommandManager().invokeDirectly (CommandIDs::openInIDE, false);
|
{
|
||||||
|
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)
|
void MainWindow::setProject (std::unique_ptr<Project> newProject)
|
||||||
|
|
@ -308,44 +351,102 @@ bool MainWindow::canOpenFile (const File& file) const
|
||||||
|| ProjucerApplication::getApp().openDocumentManager.canOpenFile (file));
|
|| 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))
|
if (file.hasFileExtension (Project::projectFileExtension))
|
||||||
{
|
{
|
||||||
auto newDoc = std::make_unique<Project> (file);
|
auto newDoc = std::make_unique<Project> (file);
|
||||||
auto result = newDoc->loadFrom (file, true);
|
auto result = newDoc->loadFrom (file, true);
|
||||||
|
|
||||||
if (result.wasOk() && closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes))
|
if (result.wasOk())
|
||||||
{
|
{
|
||||||
setProject (std::move (newDoc));
|
SafePointer<MainWindow> parent { this };
|
||||||
currentProject->setChangedFlag (false);
|
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();
|
if (saveResult)
|
||||||
getProjectContentComponent()->reloadLastOpenDocuments();
|
{
|
||||||
|
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 }))
|
SafePointer<MainWindow> parent { this };
|
||||||
return true;
|
auto createCompAndShowEditor = [parent, file, callback]
|
||||||
|
{
|
||||||
|
if (parent != nullptr)
|
||||||
|
{
|
||||||
|
parent->createProjectContentCompIfNeeded();
|
||||||
|
|
||||||
createProjectContentCompIfNeeded();
|
if (callback != nullptr)
|
||||||
return getProjectContentComponent()->showEditorForFile (file, true);
|
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())
|
auto generator = std::make_shared<PIPGenerator> (pipFile);
|
||||||
return false;
|
|
||||||
|
|
||||||
auto generatorResult = generator.createJucerFile();
|
if (! generator->hasValidPIP())
|
||||||
|
{
|
||||||
|
if (callback != nullptr)
|
||||||
|
callback (false);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto generatorResult = generator->createJucerFile();
|
||||||
|
|
||||||
if (generatorResult != Result::ok())
|
if (generatorResult != Result::ok())
|
||||||
{
|
{
|
||||||
|
|
@ -353,29 +454,47 @@ bool MainWindow::openPIP (PIPGenerator generator)
|
||||||
"PIP Error.",
|
"PIP Error.",
|
||||||
generatorResult.getErrorMessage());
|
generatorResult.getErrorMessage());
|
||||||
|
|
||||||
return false;
|
if (callback != nullptr)
|
||||||
|
callback (false);
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! generator.createMainCpp())
|
if (! generator->createMainCpp())
|
||||||
{
|
{
|
||||||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||||
"PIP Error.",
|
"PIP Error.",
|
||||||
"Failed to create Main.cpp.");
|
"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,
|
if (parent == nullptr)
|
||||||
"PIP Error.",
|
return;
|
||||||
"Failed to open .jucer file.");
|
|
||||||
|
|
||||||
return false;
|
if (! openedSuccessfully)
|
||||||
}
|
{
|
||||||
|
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||||
|
"PIP Error.",
|
||||||
|
"Failed to open .jucer file.");
|
||||||
|
|
||||||
setupTemporaryPIPProject (generator);
|
if (callback != nullptr)
|
||||||
return true;
|
callback (false);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent->setupTemporaryPIPProject (*generator);
|
||||||
|
|
||||||
|
if (callback != nullptr)
|
||||||
|
callback (true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::setupTemporaryPIPProject (PIPGenerator& generator)
|
void MainWindow::setupTemporaryPIPProject (PIPGenerator& generator)
|
||||||
|
|
@ -408,15 +527,32 @@ bool MainWindow::isInterestedInFileDrag (const StringArray& filenames)
|
||||||
return false;
|
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*/)
|
void MainWindow::filesDropped (const StringArray& filenames, int /*mouseX*/, int /*mouseY*/)
|
||||||
{
|
{
|
||||||
for (auto& filename : filenames)
|
filesDroppedRecursive (this, filenames);
|
||||||
{
|
|
||||||
const File f (filename);
|
|
||||||
|
|
||||||
if (canOpenFile (f) && openFile (f))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails,
|
bool MainWindow::shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails,
|
||||||
|
|
@ -472,7 +608,7 @@ void MainWindow::showStartPage()
|
||||||
jassert (currentProject == nullptr);
|
jassert (currentProject == nullptr);
|
||||||
|
|
||||||
setContentOwned (new StartPageComponent ([this] (std::unique_ptr<Project>&& newProject) { setProject (std::move (newProject)); },
|
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);
|
true);
|
||||||
|
|
||||||
setResizable (false, false);
|
setResizable (false, false);
|
||||||
|
|
@ -580,19 +716,38 @@ void MainWindowList::forceCloseAllWindows()
|
||||||
windows.clear();
|
windows.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindowList::askAllWindowsToClose()
|
static void askAllWindowsToCloseRecursive (WeakReference<MainWindowList> parent, std::function<void (bool)> callback)
|
||||||
{
|
{
|
||||||
saveCurrentlyOpenProjectList();
|
if (parent->windows.size() == 0)
|
||||||
|
|
||||||
while (windows.size() > 0)
|
|
||||||
{
|
{
|
||||||
if (! windows[0]->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes))
|
if (callback != nullptr)
|
||||||
return false;
|
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()
|
void MainWindowList::createWindowIfNoneAreOpen()
|
||||||
|
|
@ -613,11 +768,18 @@ void MainWindowList::closeWindow (MainWindow* w)
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
if (w->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes))
|
WeakReference<MainWindowList> parent { this };
|
||||||
|
w->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes, [parent, w] (bool closedSuccessfully)
|
||||||
{
|
{
|
||||||
windows.removeObject (w);
|
if (parent == nullptr)
|
||||||
saveCurrentlyOpenProjectList();
|
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);
|
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())
|
if (! file.exists())
|
||||||
return false;
|
{
|
||||||
|
if (callback != nullptr)
|
||||||
|
callback (false);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto* w : windows)
|
for (auto* w : windows)
|
||||||
{
|
{
|
||||||
if (w->getProject() != nullptr && w->getProject()->getFile() == file)
|
if (w->getProject() != nullptr && w->getProject()->getFile() == file)
|
||||||
{
|
{
|
||||||
w->toFront (true);
|
w->toFront (true);
|
||||||
return true;
|
|
||||||
|
if (callback != nullptr)
|
||||||
|
callback (true);
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WeakReference<MainWindowList> parent { this };
|
||||||
|
|
||||||
if (file.hasFileExtension (Project::projectFileExtension)
|
if (file.hasFileExtension (Project::projectFileExtension)
|
||||||
|| isPIPFile (file))
|
|| isPIPFile (file))
|
||||||
{
|
{
|
||||||
|
|
@ -675,23 +848,37 @@ bool MainWindowList::openFile (const File& file, bool openInBackground)
|
||||||
auto* w = getOrCreateEmptyWindow();
|
auto* w = getOrCreateEmptyWindow();
|
||||||
jassert (w != nullptr);
|
jassert (w != nullptr);
|
||||||
|
|
||||||
if (w->openFile (file))
|
w->openFile (file, [parent, previousFrontWindow, w, openInBackground, callback] (bool openedSuccessfully)
|
||||||
{
|
{
|
||||||
w->makeVisible();
|
if (parent == nullptr)
|
||||||
w->setResizable (true, false);
|
return;
|
||||||
checkWindowBounds (*w);
|
|
||||||
|
|
||||||
if (openInBackground && previousFrontWindow != nullptr)
|
if (openedSuccessfully)
|
||||||
previousFrontWindow->toFront (true);
|
{
|
||||||
|
w->makeVisible();
|
||||||
|
w->setResizable (true, false);
|
||||||
|
parent->checkWindowBounds (*w);
|
||||||
|
|
||||||
return true;
|
if (openInBackground && previousFrontWindow != nullptr)
|
||||||
}
|
previousFrontWindow->toFront (true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parent->closeWindow (w);
|
||||||
|
}
|
||||||
|
|
||||||
closeWindow (w);
|
if (callback != nullptr)
|
||||||
return false;
|
callback (openedSuccessfully);
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getFrontmostWindow()->openFile (file);
|
getFrontmostWindow()->openFile (file, [parent, callback] (bool openedSuccessfully)
|
||||||
|
{
|
||||||
|
if (parent != nullptr && callback != nullptr)
|
||||||
|
callback (openedSuccessfully);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow* MainWindowList::createNewMainWindow()
|
MainWindow* MainWindowList::createNewMainWindow()
|
||||||
|
|
@ -841,7 +1028,7 @@ void MainWindowList::reopenLastProjects()
|
||||||
|
|
||||||
for (auto& p : getAppSettings().getLastProjects())
|
for (auto& p : getAppSettings().getLastProjects())
|
||||||
if (p.existsAsFile())
|
if (p.existsAsFile())
|
||||||
openFile (p, true);
|
openFile (p, nullptr, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindowList::sendLookAndFeelChange()
|
void MainWindowList::sendLookAndFeelChange()
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ public:
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
bool canOpenFile (const File& file) const;
|
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);
|
void setProject (std::unique_ptr<Project> newProject);
|
||||||
Project* getProject() const { return currentProject.get(); }
|
Project* getProject() const { return currentProject.get(); }
|
||||||
|
|
@ -61,7 +61,7 @@ public:
|
||||||
void makeVisible();
|
void makeVisible();
|
||||||
void restoreWindowPosition();
|
void restoreWindowPosition();
|
||||||
void updateTitleBarIcon();
|
void updateTitleBarIcon();
|
||||||
bool closeCurrentProject (OpenDocumentManager::SaveIfNeeded askToSave);
|
void closeCurrentProject (OpenDocumentManager::SaveIfNeeded askToSave, std::function<void (bool)> callback);
|
||||||
void moveProject (File newProjectFile, OpenInIDE openInIDE);
|
void moveProject (File newProjectFile, OpenInIDE openInIDE);
|
||||||
|
|
||||||
void showStartPage();
|
void showStartPage();
|
||||||
|
|
@ -91,7 +91,7 @@ private:
|
||||||
static const char* getProjectWindowPosName() { return "projectWindowPos"; }
|
static const char* getProjectWindowPosName() { return "projectWindowPos"; }
|
||||||
void createProjectContentCompIfNeeded();
|
void createProjectContentCompIfNeeded();
|
||||||
|
|
||||||
bool openPIP (PIPGenerator);
|
void openPIP (const File&, std::function<void (bool)> callback);
|
||||||
void setupTemporaryPIPProject (PIPGenerator&);
|
void setupTemporaryPIPProject (PIPGenerator&);
|
||||||
|
|
||||||
void initialiseProjectWindow();
|
void initialiseProjectWindow();
|
||||||
|
|
@ -112,14 +112,14 @@ public:
|
||||||
MainWindowList();
|
MainWindowList();
|
||||||
|
|
||||||
void forceCloseAllWindows();
|
void forceCloseAllWindows();
|
||||||
bool askAllWindowsToClose();
|
void askAllWindowsToClose (std::function<void (bool)> callback);
|
||||||
void closeWindow (MainWindow*);
|
void closeWindow (MainWindow*);
|
||||||
|
|
||||||
void goToSiblingWindow (MainWindow*, int delta);
|
void goToSiblingWindow (MainWindow*, int delta);
|
||||||
|
|
||||||
void createWindowIfNoneAreOpen();
|
void createWindowIfNoneAreOpen();
|
||||||
void openDocument (OpenDocumentManager::Document*, bool grabFocus);
|
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* createNewMainWindow();
|
||||||
MainWindow* getFrontmostWindow (bool createIfNotFound = true);
|
MainWindow* getFrontmostWindow (bool createIfNotFound = true);
|
||||||
|
|
@ -142,4 +142,5 @@ private:
|
||||||
bool isInReopenLastProjects = false;
|
bool isInReopenLastProjects = false;
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindowList)
|
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; }
|
bool refersToProject (Project& p) const override { return project == &p; }
|
||||||
Project* getProject() const override { return project; }
|
Project* getProject() const override { return project; }
|
||||||
bool needsSaving() const override { return false; }
|
bool needsSaving() const override { return false; }
|
||||||
bool save() override { return true; }
|
bool saveSyncWithoutAsking() override { return true; }
|
||||||
bool saveAs() override { return false; }
|
void saveAsync (std::function<void (bool)>) override {}
|
||||||
|
void saveAsAsync (std::function<void (bool)>) override {}
|
||||||
bool hasFileBeenModifiedExternally() override { return fileModificationTime != file.getLastModificationTime(); }
|
bool hasFileBeenModifiedExternally() override { return fileModificationTime != file.getLastModificationTime(); }
|
||||||
void reloadFromFile() override { fileModificationTime = file.getLastModificationTime(); }
|
void reloadFromFile() override { fileModificationTime = file.getLastModificationTime(); }
|
||||||
String getName() const override { return file.getFileName(); }
|
String getName() const override { return file.getFileName(); }
|
||||||
|
|
@ -164,86 +165,201 @@ OpenDocumentManager::Document* OpenDocumentManager::getOpenDocument (int index)
|
||||||
return documents.getUnchecked (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())
|
if (! doc->needsSaving())
|
||||||
return FileBasedDocument::savedOk;
|
{
|
||||||
|
if (callback != nullptr)
|
||||||
|
callback (FileBasedDocument::savedOk);
|
||||||
|
|
||||||
const int r = AlertWindow::showYesNoCancelBox (AlertWindow::QuestionIcon,
|
return;
|
||||||
TRANS("Closing document..."),
|
}
|
||||||
TRANS("Do you want to save the changes to \"")
|
|
||||||
+ doc->getName() + "\"?",
|
|
||||||
TRANS("Save"),
|
|
||||||
TRANS("Discard changes"),
|
|
||||||
TRANS("Cancel"));
|
|
||||||
|
|
||||||
if (r == 1) // save changes
|
WeakReference<OpenDocumentManager> parent { this };
|
||||||
return doc->save() ? FileBasedDocument::savedOk
|
AlertWindow::showYesNoCancelBox (AlertWindow::QuestionIcon,
|
||||||
: FileBasedDocument::failedToWriteToFile;
|
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
|
if (r == 1)
|
||||||
return FileBasedDocument::savedOk;
|
{
|
||||||
|
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::closeDocumentWithoutSaving (Document* doc)
|
||||||
bool OpenDocumentManager::closeDocument (int index, SaveIfNeeded saveIfNeeded)
|
|
||||||
{
|
{
|
||||||
if (Document* doc = documents [index])
|
if (documents.contains (doc))
|
||||||
{
|
{
|
||||||
if (saveIfNeeded == SaveIfNeeded::yes)
|
|
||||||
if (saveIfNeededAndUserAgrees (doc) != FileBasedDocument::savedOk)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
bool canClose = true;
|
bool canClose = true;
|
||||||
|
|
||||||
for (int i = listeners.size(); --i >= 0;)
|
for (int i = listeners.size(); --i >= 0;)
|
||||||
if (DocumentCloseListener* l = listeners[i])
|
if (auto* l = listeners[i])
|
||||||
if (! l->documentAboutToClose (doc))
|
if (! l->documentAboutToClose (doc))
|
||||||
canClose = false;
|
canClose = false;
|
||||||
|
|
||||||
if (! canClose)
|
if (! canClose)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
documents.remove (index);
|
documents.removeObject (doc);
|
||||||
ProjucerApplication::getCommandManager().commandStatusChanged();
|
ProjucerApplication::getCommandManager().commandStatusChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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;)
|
for (int i = documents.size(); --i >= 0;)
|
||||||
if (Document* d = documents[i])
|
if (auto* d = documents[i])
|
||||||
if (d->isForFile (f))
|
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;)
|
auto lastIndex = parent->getNumOpenDocuments() - 1;
|
||||||
if (! closeDocument (i, askUserToSave))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
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;)
|
for (int i = documents.size(); --i >= 0;)
|
||||||
if (Document* d = documents[i])
|
if (Document* d = documents[i])
|
||||||
if (d->refersToProject (project))
|
if (d->refersToProject (project))
|
||||||
if (! closeDocument (i, saveIfNeeded))
|
closeDocumentWithoutSaving (d);
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenDocumentManager::anyFilesNeedSaving() const
|
bool OpenDocumentManager::anyFilesNeedSaving() const
|
||||||
|
|
@ -255,17 +371,13 @@ bool OpenDocumentManager::anyFilesNeedSaving() const
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenDocumentManager::saveAll()
|
void OpenDocumentManager::saveAllSyncWithoutAsking()
|
||||||
{
|
{
|
||||||
for (int i = documents.size(); --i >= 0;)
|
for (int i = documents.size(); --i >= 0;)
|
||||||
{
|
{
|
||||||
if (! documents.getUnchecked (i)->save())
|
if (documents.getUnchecked (i)->saveSyncWithoutAsking())
|
||||||
return false;
|
ProjucerApplication::getCommandManager().commandStatusChanged();
|
||||||
|
|
||||||
ProjucerApplication::getCommandManager().commandStatusChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenDocumentManager::reloadModifiedFiles()
|
void OpenDocumentManager::reloadModifiedFiles()
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,9 @@ public:
|
||||||
virtual String getType() const = 0;
|
virtual String getType() const = 0;
|
||||||
virtual File getFile() const = 0;
|
virtual File getFile() const = 0;
|
||||||
virtual bool needsSaving() const = 0;
|
virtual bool needsSaving() const = 0;
|
||||||
virtual bool save() = 0;
|
virtual bool saveSyncWithoutAsking() = 0;
|
||||||
virtual bool saveAs() = 0;
|
virtual void saveAsync (std::function<void (bool)>) = 0;
|
||||||
|
virtual void saveAsAsync (std::function<void (bool)>) = 0;
|
||||||
virtual bool hasFileBeenModifiedExternally() = 0;
|
virtual bool hasFileBeenModifiedExternally() = 0;
|
||||||
virtual void reloadFromFile() = 0;
|
virtual void reloadFromFile() = 0;
|
||||||
virtual std::unique_ptr<Component> createEditor() = 0;
|
virtual std::unique_ptr<Component> createEditor() = 0;
|
||||||
|
|
@ -72,14 +73,20 @@ public:
|
||||||
|
|
||||||
bool canOpenFile (const File& file);
|
bool canOpenFile (const File& file);
|
||||||
Document* openFile (Project* project, const File& file);
|
Document* openFile (Project* project, const File& file);
|
||||||
bool closeDocument (int index, SaveIfNeeded saveIfNeeded);
|
|
||||||
bool closeDocument (Document* document, SaveIfNeeded saveIfNeeded);
|
void closeDocumentAsync (Document* document, SaveIfNeeded saveIfNeeded, std::function<void (bool)> callback);
|
||||||
bool closeAll (SaveIfNeeded askUserToSave);
|
bool closeDocumentWithoutSaving (Document* document);
|
||||||
bool closeAllDocumentsUsingProject (Project& project, SaveIfNeeded saveIfNeeded);
|
|
||||||
void closeFile (const File& f, SaveIfNeeded saveIfNeeded);
|
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 anyFilesNeedSaving() const;
|
||||||
bool saveAll();
|
|
||||||
FileBasedDocument::SaveResult saveIfNeededAndUserAgrees (Document* doc);
|
void saveAllSyncWithoutAsking();
|
||||||
|
void saveIfNeededAndUserAgrees (Document* doc, std::function<void (FileBasedDocument::SaveResult)>);
|
||||||
|
|
||||||
void reloadModifiedFiles();
|
void reloadModifiedFiles();
|
||||||
void fileHasBeenRenamed (const File& oldFile, const File& newFile);
|
void fileHasBeenRenamed (const File& oldFile, const File& newFile);
|
||||||
|
|
||||||
|
|
@ -112,11 +119,19 @@ public:
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
void closeLastDocumentUsingProjectRecursive (WeakReference<OpenDocumentManager>,
|
||||||
|
Project*,
|
||||||
|
SaveIfNeeded,
|
||||||
|
std::function<void (bool)>);
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
OwnedArray<DocumentType> types;
|
OwnedArray<DocumentType> types;
|
||||||
OwnedArray<Document> documents;
|
OwnedArray<Document> documents;
|
||||||
Array<DocumentCloseListener*> listeners;
|
Array<DocumentCloseListener*> listeners;
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenDocumentManager)
|
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();
|
return temp.overwriteTargetFileWithTemporary();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SourceCodeDocument::save()
|
bool SourceCodeDocument::saveSyncWithoutAsking()
|
||||||
{
|
{
|
||||||
if (writeCodeDocToFile (getFile(), getCodeDocument()))
|
if (writeCodeDocToFile (getFile(), getCodeDocument()))
|
||||||
{
|
{
|
||||||
|
|
@ -110,14 +110,28 @@ bool SourceCodeDocument::save()
|
||||||
return false;
|
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))
|
void SourceCodeDocument::saveAsAsync (std::function<void (bool)> callback)
|
||||||
return true;
|
{
|
||||||
|
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)
|
void SourceCodeDocument::updateLastState (CodeEditorComponent& editor)
|
||||||
|
|
@ -642,18 +656,31 @@ void CppCodeEditorComponent::performPopupMenuAction (int menuItemID)
|
||||||
|
|
||||||
void CppCodeEditorComponent::insertComponentClass()
|
void CppCodeEditorComponent::insertComponentClass()
|
||||||
{
|
{
|
||||||
AlertWindow aw (TRANS ("Insert a new Component class"),
|
asyncAlertWindow = std::make_unique<AlertWindow> (TRANS ("Insert a new Component class"),
|
||||||
TRANS ("Please enter a name for the new class"),
|
TRANS ("Please enter a name for the new class"),
|
||||||
AlertWindow::NoIcon, nullptr);
|
AlertWindow::NoIcon,
|
||||||
|
nullptr);
|
||||||
|
|
||||||
const char* classNameField = "Class Name";
|
const String classNameField { "Class Name" };
|
||||||
|
|
||||||
aw.addTextEditor (classNameField, String(), String(), false);
|
asyncAlertWindow->addTextEditor (classNameField, String(), String(), false);
|
||||||
aw.addButton (TRANS ("Insert Code"), 1, KeyPress (KeyPress::returnKey));
|
asyncAlertWindow->addButton (TRANS ("Insert Code"), 1, KeyPress (KeyPress::returnKey));
|
||||||
aw.addButton (TRANS ("Cancel"), 0, KeyPress (KeyPress::escapeKey));
|
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();
|
auto className = aw.getTextEditorContents (classNameField).trim();
|
||||||
|
|
||||||
if (className == build_tools::makeValidIdentifier (className, false, true, false))
|
if (className == build_tools::makeValidIdentifier (className, false, true, false))
|
||||||
|
|
@ -661,8 +688,10 @@ void CppCodeEditorComponent::insertComponentClass()
|
||||||
String code (BinaryData::jucer_InlineComponentTemplate_h);
|
String code (BinaryData::jucer_InlineComponentTemplate_h);
|
||||||
code = code.replace ("%%component_class%%", className);
|
code = code.replace ("%%component_class%%", className);
|
||||||
|
|
||||||
insertTextAtCaret (code);
|
parent->insertTextAtCaret (code);
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
parent->insertComponentClass();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,8 +80,9 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void reloadFromFile() override;
|
void reloadFromFile() override;
|
||||||
bool save() override;
|
bool saveSyncWithoutAsking() override;
|
||||||
bool saveAs() 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> createEditor() override;
|
||||||
std::unique_ptr<Component> createViewer() override { return createEditor(); }
|
std::unique_ptr<Component> createViewer() override { return createEditor(); }
|
||||||
|
|
@ -132,6 +133,9 @@ protected:
|
||||||
std::unique_ptr<CodeEditorComponent::State> lastState;
|
std::unique_ptr<CodeEditorComponent::State> lastState;
|
||||||
|
|
||||||
void reloadInternal();
|
void reloadInternal();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<FileChooser> chooser;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GenericCodeEditorComponent;
|
class GenericCodeEditorComponent;
|
||||||
|
|
@ -235,5 +239,7 @@ public:
|
||||||
private:
|
private:
|
||||||
void insertComponentClass();
|
void insertComponentClass();
|
||||||
|
|
||||||
|
std::unique_ptr<AlertWindow> asyncAlertWindow;
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CppCodeEditorComponent)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CppCodeEditorComponent)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ protected:
|
||||||
String colourIdCode, colourName, xmlTagName;
|
String colourIdCode, colourName, xmlTagName;
|
||||||
};
|
};
|
||||||
|
|
||||||
OwnedArray <ComponentColourInfo> colours;
|
OwnedArray<ComponentColourInfo> colours;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
JUCE_DECLARE_NON_COPYABLE (ComponentTypeHandler)
|
JUCE_DECLARE_NON_COPYABLE (ComponentTypeHandler)
|
||||||
|
|
|
||||||
|
|
@ -672,13 +672,13 @@ private:
|
||||||
m.addItem (i + 1, "Delete tab " + String (i)
|
m.addItem (i + 1, "Delete tab " + String (i)
|
||||||
+ ": \"" + names[i] + "\"");
|
+ ": \"" + names[i] + "\"");
|
||||||
|
|
||||||
const int r = m.showAt (this);
|
PopupMenu::Options options{};
|
||||||
|
m.showMenuAsync (PopupMenu::Options().withTargetComponent (this), [this] (int r)
|
||||||
if (r > 0)
|
|
||||||
{
|
{
|
||||||
document.perform (new RemoveTabAction (component, *document.getComponentLayout(), r - 1),
|
if (r > 0)
|
||||||
"Remove a tab");
|
document.perform (new RemoveTabAction (component, *document.getComponentLayout(), r - 1),
|
||||||
}
|
"Remove a tab");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
String getButtonText() const
|
String getButtonText() const
|
||||||
|
|
@ -1131,11 +1131,13 @@ private:
|
||||||
m.addItem (1, "Move this tab up", tabIndex > 0);
|
m.addItem (1, "Move this tab up", tabIndex > 0);
|
||||||
m.addItem (2, "Move this tab down", tabIndex < totalNumTabs - 1);
|
m.addItem (2, "Move this tab down", tabIndex < totalNumTabs - 1);
|
||||||
|
|
||||||
const int r = m.showAt (this);
|
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)),
|
if (r != 0)
|
||||||
"Move a tab");
|
document.perform (new MoveTabAction (component, *document.getComponentLayout(), tabIndex, tabIndex + (r == 2 ? 1 : -1)),
|
||||||
|
"Move a tab");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
String getButtonText() const
|
String getButtonText() const
|
||||||
|
|
|
||||||
|
|
@ -74,14 +74,16 @@ public:
|
||||||
{
|
{
|
||||||
if (newIndex == 0)
|
if (newIndex == 0)
|
||||||
{
|
{
|
||||||
String resource (document.getResources()
|
document.getResources()
|
||||||
.browseForResource ("Select an image file to add as a resource",
|
.browseForResource ("Select an image file to add as a resource",
|
||||||
"*.jpg;*.jpeg;*.png;*.gif;*.svg",
|
"*.jpg;*.jpeg;*.png;*.gif;*.svg",
|
||||||
File(),
|
File(),
|
||||||
String()));
|
String(),
|
||||||
|
[this] (String resource)
|
||||||
if (resource.isNotEmpty())
|
{
|
||||||
setResource (resource);
|
if (resource.isNotEmpty())
|
||||||
|
setResource (resource);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -660,7 +660,6 @@ void PaintElement::updateSiblingComps()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void PaintElement::showPopupMenu()
|
void PaintElement::showPopupMenu()
|
||||||
{
|
{
|
||||||
auto* commandManager = &ProjucerApplication::getCommandManager();
|
auto* commandManager = &ProjucerApplication::getCommandManager();
|
||||||
|
|
@ -685,5 +684,5 @@ void PaintElement::showPopupMenu()
|
||||||
m.addCommandItem (commandManager, StandardApplicationCommandIDs::paste);
|
m.addCommandItem (commandManager, StandardApplicationCommandIDs::paste);
|
||||||
m.addCommandItem (commandManager, StandardApplicationCommandIDs::del);
|
m.addCommandItem (commandManager, StandardApplicationCommandIDs::del);
|
||||||
|
|
||||||
m.show();
|
m.showMenuAsync ({});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ protected:
|
||||||
|
|
||||||
void siblingComponentsChanged();
|
void siblingComponentsChanged();
|
||||||
|
|
||||||
OwnedArray <ElementSiblingComponent> siblingComponents;
|
OwnedArray<ElementSiblingComponent> siblingComponents;
|
||||||
|
|
||||||
void updateSiblingComps();
|
void updateSiblingComps();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ public:
|
||||||
private:
|
private:
|
||||||
friend class PathPoint;
|
friend class PathPoint;
|
||||||
friend class PathPointComponent;
|
friend class PathPointComponent;
|
||||||
OwnedArray <PathPoint> points;
|
OwnedArray<PathPoint> points;
|
||||||
bool nonZeroWinding;
|
bool nonZeroWinding;
|
||||||
mutable Path path;
|
mutable Path path;
|
||||||
mutable Rectangle<int> lastPathBounds;
|
mutable Rectangle<int> lastPathBounds;
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,15 @@ public:
|
||||||
button.setConnectedEdges (TextButton::ConnectedOnLeft | TextButton::ConnectedOnRight);
|
button.setConnectedEdges (TextButton::ConnectedOnLeft | TextButton::ConnectedOnRight);
|
||||||
button.onClick = [this]
|
button.onClick = [this]
|
||||||
{
|
{
|
||||||
if (showMenu (layout))
|
SafePointer<PositionPropertyBase> safeThis { this };
|
||||||
refresh(); // (to clear the text editor if it's got focus)
|
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));
|
textEditor.reset (new PositionPropLabel (*this));
|
||||||
|
|
@ -173,7 +180,7 @@ public:
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool showMenu (ComponentLayout* compLayout)
|
void showMenu (ComponentLayout* compLayout, std::function<void (bool)> callback)
|
||||||
{
|
{
|
||||||
RelativePositionedRectangle rpr (getPosition());
|
RelativePositionedRectangle rpr (getPosition());
|
||||||
PositionedRectangle p (rpr.rect);
|
PositionedRectangle p (rpr.rect);
|
||||||
|
|
@ -255,127 +262,135 @@ public:
|
||||||
m.addSubMenu ("Relative to", compLayout->getRelativeTargetMenu (component, (int) dimension));
|
m.addSubMenu ("Relative to", compLayout->getRelativeTargetMenu (component, (int) dimension));
|
||||||
}
|
}
|
||||||
|
|
||||||
WeakReference<Component> ref (this);
|
SafePointer<PositionPropertyBase> ref (this);
|
||||||
|
|
||||||
const int menuResult = m.showAt (&button);
|
m.showMenuAsync (PopupMenu::Options().withTargetComponent (&button),
|
||||||
|
[ref, compLayout, callback, xAnchor, yAnchor, xMode, yMode, sizeW, sizeH, p, rpr] (int menuResult) mutable
|
||||||
if (menuResult == 0 || ref == nullptr)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
switch (menuResult)
|
|
||||||
{
|
{
|
||||||
case 10:
|
if (menuResult == 0 || ref == nullptr)
|
||||||
if (dimension == componentX)
|
{
|
||||||
xMode = PositionedRectangle::absoluteFromParentTopLeft;
|
callback (false);
|
||||||
else
|
return;
|
||||||
yMode = PositionedRectangle::absoluteFromParentTopLeft;
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case 11:
|
switch (menuResult)
|
||||||
if (dimension == componentX)
|
{
|
||||||
xMode = PositionedRectangle::absoluteFromParentBottomRight;
|
case 10:
|
||||||
else
|
if (ref->dimension == componentX)
|
||||||
yMode = PositionedRectangle::absoluteFromParentBottomRight;
|
xMode = PositionedRectangle::absoluteFromParentTopLeft;
|
||||||
break;
|
else
|
||||||
|
yMode = PositionedRectangle::absoluteFromParentTopLeft;
|
||||||
|
break;
|
||||||
|
|
||||||
case 12:
|
case 11:
|
||||||
if (dimension == componentX)
|
if (ref->dimension == componentX)
|
||||||
xMode = PositionedRectangle::absoluteFromParentCentre;
|
xMode = PositionedRectangle::absoluteFromParentBottomRight;
|
||||||
else
|
else
|
||||||
yMode = PositionedRectangle::absoluteFromParentCentre;
|
yMode = PositionedRectangle::absoluteFromParentBottomRight;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 13:
|
case 12:
|
||||||
if (dimension == componentX)
|
if (ref->dimension == componentX)
|
||||||
xMode = PositionedRectangle::proportionOfParentSize;
|
xMode = PositionedRectangle::absoluteFromParentCentre;
|
||||||
else
|
else
|
||||||
yMode = PositionedRectangle::proportionOfParentSize;
|
yMode = PositionedRectangle::absoluteFromParentCentre;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 14:
|
case 13:
|
||||||
if (dimension == componentX)
|
if (ref->dimension == componentX)
|
||||||
xAnchor = PositionedRectangle::anchorAtLeftOrTop;
|
xMode = PositionedRectangle::proportionOfParentSize;
|
||||||
else
|
else
|
||||||
yAnchor = PositionedRectangle::anchorAtLeftOrTop;
|
yMode = PositionedRectangle::proportionOfParentSize;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 15:
|
case 14:
|
||||||
if (dimension == componentX)
|
if (ref->dimension == componentX)
|
||||||
xAnchor = PositionedRectangle::anchorAtCentre;
|
xAnchor = PositionedRectangle::anchorAtLeftOrTop;
|
||||||
else
|
else
|
||||||
yAnchor = PositionedRectangle::anchorAtCentre;
|
yAnchor = PositionedRectangle::anchorAtLeftOrTop;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 16:
|
case 15:
|
||||||
if (dimension == componentX)
|
if (ref->dimension == componentX)
|
||||||
xAnchor = PositionedRectangle::anchorAtRightOrBottom;
|
xAnchor = PositionedRectangle::anchorAtCentre;
|
||||||
else
|
else
|
||||||
yAnchor = PositionedRectangle::anchorAtRightOrBottom;
|
yAnchor = PositionedRectangle::anchorAtCentre;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 20:
|
case 16:
|
||||||
if (dimension == componentWidth)
|
if (ref->dimension == componentX)
|
||||||
sizeW = PositionedRectangle::absoluteSize;
|
xAnchor = PositionedRectangle::anchorAtRightOrBottom;
|
||||||
else
|
else
|
||||||
sizeH = PositionedRectangle::absoluteSize;
|
yAnchor = PositionedRectangle::anchorAtRightOrBottom;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 21:
|
case 20:
|
||||||
if (dimension == componentWidth)
|
if (ref->dimension == componentWidth)
|
||||||
sizeW = PositionedRectangle::proportionalSize;
|
sizeW = PositionedRectangle::absoluteSize;
|
||||||
else
|
else
|
||||||
sizeH = PositionedRectangle::proportionalSize;
|
sizeH = PositionedRectangle::absoluteSize;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 22:
|
case 21:
|
||||||
if (dimension == componentWidth)
|
if (ref->dimension == componentWidth)
|
||||||
sizeW = PositionedRectangle::parentSizeMinusAbsolute;
|
sizeW = PositionedRectangle::proportionalSize;
|
||||||
else
|
else
|
||||||
sizeH = PositionedRectangle::parentSizeMinusAbsolute;
|
sizeH = PositionedRectangle::proportionalSize;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
case 22:
|
||||||
if (allowRelativeOptions && compLayout != nullptr)
|
if (ref->dimension == componentWidth)
|
||||||
compLayout->processRelativeTargetMenuResult (component, (int) dimension, menuResult);
|
sizeW = PositionedRectangle::parentSizeMinusAbsolute;
|
||||||
break;
|
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)
|
const auto parentArea = [&]() -> Rectangle<int>
|
||||||
parentArea.setSize (component->getParentWidth(), component->getParentHeight());
|
{
|
||||||
else if (auto pre = dynamic_cast<PaintRoutineEditor*> (component->getParentComponent()))
|
if (ref->component->findParentComponentOfClass<ComponentLayoutEditor>() != nullptr)
|
||||||
parentArea = pre->getComponentArea();
|
return { ref->component->getParentWidth(), ref->component->getParentHeight() };
|
||||||
else
|
|
||||||
jassertfalse;
|
|
||||||
|
|
||||||
int x, xw, y, yh, w, h;
|
if (auto pre = dynamic_cast<PaintRoutineEditor*> (ref->component->getParentComponent()))
|
||||||
rpr.getRelativeTargetBounds (parentArea, compLayout, x, xw, y, yh, w, h);
|
return pre->getComponentArea();
|
||||||
|
|
||||||
PositionedRectangle xyRect (p);
|
jassertfalse;
|
||||||
PositionedRectangle whRect (p);
|
return {};
|
||||||
|
}();
|
||||||
|
|
||||||
xyRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
|
int x, xw, y, yh, w, h;
|
||||||
Rectangle<int> (x, y, xw, yh));
|
rpr.getRelativeTargetBounds (parentArea, compLayout, x, xw, y, yh, w, h);
|
||||||
|
|
||||||
whRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
|
PositionedRectangle xyRect (p);
|
||||||
Rectangle<int> (x, y, w, h));
|
PositionedRectangle whRect (p);
|
||||||
|
|
||||||
p.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
|
xyRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
|
||||||
Rectangle<int> (x, y, xw, yh));
|
Rectangle<int> (x, y, xw, yh));
|
||||||
|
|
||||||
p.setX (xyRect.getX());
|
whRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
|
||||||
p.setY (xyRect.getY());
|
Rectangle<int> (x, y, w, h));
|
||||||
p.setWidth (whRect.getWidth());
|
|
||||||
p.setHeight (whRect.getHeight());
|
|
||||||
|
|
||||||
if (p != rpr.rect)
|
p.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
|
||||||
{
|
Rectangle<int> (x, y, xw, yh));
|
||||||
rpr.rect = p;
|
|
||||||
setPosition (rpr);
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
void resized()
|
||||||
|
|
|
||||||
|
|
@ -282,7 +282,7 @@ void ComponentLayoutEditor::mouseDown (const MouseEvent& e)
|
||||||
for (int i = 0; i < ObjectTypes::numComponentTypes; ++i)
|
for (int i = 0; i < ObjectTypes::numComponentTypes; ++i)
|
||||||
m.addCommandItem (commandManager, JucerCommandIDs::newComponentBase + i);
|
m.addCommandItem (commandManager, JucerCommandIDs::newComponentBase + i);
|
||||||
|
|
||||||
m.show();
|
m.showMenuAsync (PopupMenu::Options());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -387,7 +387,7 @@ bool ComponentLayoutEditor::isInterestedInDragSource (const SourceDetails& dragS
|
||||||
|
|
||||||
void ComponentLayoutEditor::itemDropped (const SourceDetails& dragSourceDetails)
|
void ComponentLayoutEditor::itemDropped (const SourceDetails& dragSourceDetails)
|
||||||
{
|
{
|
||||||
OwnedArray <Project::Item> selectedNodes;
|
OwnedArray<Project::Item> selectedNodes;
|
||||||
ProjectContentComponent::getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
|
ProjectContentComponent::getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
|
||||||
|
|
||||||
StringArray filenames;
|
StringArray filenames;
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ void PaintRoutineEditor::mouseDown (const MouseEvent& e)
|
||||||
for (int i = 0; i < ObjectTypes::numElementTypes; ++i)
|
for (int i = 0; i < ObjectTypes::numElementTypes; ++i)
|
||||||
m.addCommandItem (commandManager, JucerCommandIDs::newElementBase + i);
|
m.addCommandItem (commandManager, JucerCommandIDs::newElementBase + i);
|
||||||
|
|
||||||
m.show();
|
m.showMenuAsync (PopupMenu::Options());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ public:
|
||||||
{
|
{
|
||||||
if (auto* r = document.getResources() [row])
|
if (auto* r = document.getResources() [row])
|
||||||
document.getResources().browseForResource ("Select a file to replace this resource", "*",
|
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")
|
delButton ("Delete selected resources")
|
||||||
{
|
{
|
||||||
addAndMakeVisible (addButton);
|
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);
|
addAndMakeVisible (reloadAllButton);
|
||||||
reloadAllButton.onClick = [this] { reloadAll(); };
|
reloadAllButton.onClick = [this] { reloadAll(); };
|
||||||
|
|
@ -258,16 +261,12 @@ void ResourceEditorPanel::reloadAll()
|
||||||
StringArray failed;
|
StringArray failed;
|
||||||
|
|
||||||
for (int i = 0; i < document.getResources().size(); ++i)
|
for (int i = 0; i < document.getResources().size(); ++i)
|
||||||
{
|
|
||||||
if (! document.getResources().reload (i))
|
if (! document.getResources().reload (i))
|
||||||
failed.add (document.getResources().getResourceNames() [i]);
|
failed.add (document.getResources().getResourceNames() [i]);
|
||||||
}
|
|
||||||
|
|
||||||
if (failed.size() > 0)
|
if (failed.size() > 0)
|
||||||
{
|
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||||
AlertWindow::showMessageBox (AlertWindow::WarningIcon,
|
TRANS("Reloading resources"),
|
||||||
TRANS("Reloading resources"),
|
TRANS("The following resources couldn't be reloaded from their original files:\n\n")
|
||||||
TRANS("The following resources couldn't be reloaded from their original files:\n\n")
|
+ failed.joinIntoString (", "));
|
||||||
+ failed.joinIntoString (", "));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,6 @@
|
||||||
#include "jucer_JucerDocument.h"
|
#include "jucer_JucerDocument.h"
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
BinaryResources::BinaryResources()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
BinaryResources::~BinaryResources()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
BinaryResources& BinaryResources::operator= (const BinaryResources& other)
|
BinaryResources& BinaryResources::operator= (const BinaryResources& other)
|
||||||
{
|
{
|
||||||
for (auto* r : other.resources)
|
for (auto* r : other.resources)
|
||||||
|
|
@ -130,15 +122,20 @@ bool BinaryResources::reload (const int index)
|
||||||
File (resources [index]->originalFilename));
|
File (resources [index]->originalFilename));
|
||||||
}
|
}
|
||||||
|
|
||||||
String BinaryResources::browseForResource (const String& title,
|
void BinaryResources::browseForResource (const String& title,
|
||||||
const String& wildcard,
|
const String& wildcard,
|
||||||
const File& fileToStartFrom,
|
const File& fileToStartFrom,
|
||||||
const String& resourceToReplace)
|
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);
|
String name (resourceToReplace);
|
||||||
|
|
||||||
if (name.isEmpty())
|
if (name.isEmpty())
|
||||||
|
|
@ -146,17 +143,15 @@ String BinaryResources::browseForResource (const String& title,
|
||||||
|
|
||||||
if (! add (name, fc.getResult()))
|
if (! add (name, fc.getResult()))
|
||||||
{
|
{
|
||||||
AlertWindow::showMessageBox (AlertWindow::WarningIcon,
|
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||||
TRANS("Adding Resource"),
|
TRANS("Adding Resource"),
|
||||||
TRANS("Failed to load the file!"));
|
TRANS("Failed to load the file!"));
|
||||||
|
|
||||||
name.clear();
|
name.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
return name;
|
callback (name);
|
||||||
}
|
});
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String BinaryResources::findUniqueName (const String& rootName) const
|
String BinaryResources::findUniqueName (const String& rootName) const
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,6 @@ class BinaryResources
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
BinaryResources();
|
|
||||||
~BinaryResources();
|
|
||||||
|
|
||||||
BinaryResources& operator= (const BinaryResources& other);
|
BinaryResources& operator= (const BinaryResources& other);
|
||||||
|
|
||||||
void loadFromCpp (const File& cppFileLocation, const String& cpp);
|
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 add (const String& name, const String& originalFileName, const MemoryBlock& data);
|
||||||
void remove (const int index);
|
void remove (const int index);
|
||||||
bool reload (const int index);
|
bool reload (const int index);
|
||||||
String browseForResource (const String& title, const String& wildcard,
|
void browseForResource (const String& title, const String& wildcard,
|
||||||
const File& fileToStartFrom, const String& resourceToReplace);
|
const File& fileToStartFrom, const String& resourceToReplace,
|
||||||
|
std::function<void (String)> callback);
|
||||||
|
|
||||||
String findUniqueName (const String& rootName) const;
|
String findUniqueName (const String& rootName) const;
|
||||||
|
|
||||||
|
|
@ -86,12 +84,13 @@ public:
|
||||||
|
|
||||||
void fillInGeneratedCode (GeneratedCode& code) const;
|
void fillInGeneratedCode (GeneratedCode& code) const;
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
JucerDocument* document;
|
|
||||||
OwnedArray <BinaryResource> resources;
|
|
||||||
|
|
||||||
BinaryResource* findResource (const String& name) const noexcept;
|
BinaryResource* findResource (const String& name) const noexcept;
|
||||||
void changed();
|
void changed();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
JucerDocument* document;
|
||||||
|
OwnedArray<BinaryResource> resources;
|
||||||
|
std::unique_ptr<FileChooser> chooser;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
JucerDocument* document;
|
JucerDocument* document;
|
||||||
OwnedArray <Component> components;
|
OwnedArray<Component> components;
|
||||||
SelectedItemSet <Component*> selected;
|
SelectedItemSet <Component*> selected;
|
||||||
int nextCompUID;
|
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;
|
auto& odm = ProjucerApplication::getApp().openDocumentManager;
|
||||||
|
|
||||||
if (auto* header = odm.openFile (nullptr, getFile().withFileExtension (".h")))
|
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);
|
if (parent == nullptr)
|
||||||
return true;
|
return;
|
||||||
}
|
|
||||||
|
if (saveResult)
|
||||||
|
ProjucerApplication::getApp()
|
||||||
|
.openDocumentManager
|
||||||
|
.closeFileWithoutSaving (parent->getFile().withFileExtension (".h"));
|
||||||
|
|
||||||
|
callback (saveResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
callback (false);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Component> createEditor() override
|
std::unique_ptr<Component> createEditor() override
|
||||||
|
|
@ -733,6 +760,8 @@ public:
|
||||||
bool canOpenFile (const File& f) override { return JucerDocument::isValidJucerCppFile (f); }
|
bool canOpenFile (const File& f) override { return JucerDocument::isValidJucerCppFile (f); }
|
||||||
Document* openFile (Project* p, const File& f) override { return new JucerComponentDocument (p, f); }
|
Document* openFile (Project* p, const File& f) override { return new JucerComponentDocument (p, f); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
JUCE_DECLARE_WEAK_REFERENCEABLE (JucerComponentDocument)
|
||||||
};
|
};
|
||||||
|
|
||||||
OpenDocumentManager::DocumentType* createGUIDocumentType();
|
OpenDocumentManager::DocumentType* createGUIDocumentType();
|
||||||
|
|
@ -744,53 +773,65 @@ OpenDocumentManager::DocumentType* createGUIDocumentType()
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
struct NewGUIComponentWizard : public NewFileWizard::Type
|
struct NewGUIComponentWizard : public NewFileWizard::Type
|
||||||
{
|
{
|
||||||
NewGUIComponentWizard() {}
|
NewGUIComponentWizard (Project& proj)
|
||||||
|
: project (proj)
|
||||||
|
{}
|
||||||
|
|
||||||
String getName() override { return "GUI Component"; }
|
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");
|
if (newFile != File())
|
||||||
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 (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 (auto* header = dynamic_cast<SourceCodeDocument*> (odm.openFile (&project, headerFile)))
|
||||||
|
|
||||||
if (jucerDoc != nullptr)
|
|
||||||
{
|
{
|
||||||
jucerDoc->setClassName (newFile.getFileNameWithoutExtension());
|
std::unique_ptr<JucerDocument> jucerDoc (new ComponentDocument (cpp));
|
||||||
|
|
||||||
jucerDoc->flushChangesToDocuments (&project, true);
|
if (jucerDoc != nullptr)
|
||||||
jucerDoc.reset();
|
{
|
||||||
|
jucerDoc->setClassName (newFile.getFileNameWithoutExtension());
|
||||||
|
|
||||||
cpp->save();
|
jucerDoc->flushChangesToDocuments (&project, true);
|
||||||
header->save();
|
jucerDoc.reset();
|
||||||
odm.closeDocument (cpp, OpenDocumentManager::SaveIfNeeded::yes);
|
|
||||||
odm.closeDocument (header, OpenDocumentManager::SaveIfNeeded::yes);
|
|
||||||
|
|
||||||
parent.addFileRetainingSortOrder (headerFile, true);
|
for (auto* doc : { cpp, header })
|
||||||
parent.addFileRetainingSortOrder (cppFile, true);
|
{
|
||||||
|
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 (Project&);
|
||||||
NewFileWizard::Type* createGUIComponentWizard()
|
NewFileWizard::Type* createGUIComponentWizard (Project& p)
|
||||||
{
|
{
|
||||||
return new NewGUIComponentWizard();
|
return new NewGUIComponentWizard (p);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ public:
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
private:
|
private:
|
||||||
OwnedArray <PaintElement> elements;
|
OwnedArray<PaintElement> elements;
|
||||||
SelectedItemSet <PaintElement*> selectedElements;
|
SelectedItemSet <PaintElement*> selectedElements;
|
||||||
SelectedItemSet <PathPoint*> selectedPoints;
|
SelectedItemSet <PathPoint*> selectedPoints;
|
||||||
JucerDocument* document;
|
JucerDocument* document;
|
||||||
|
|
|
||||||
|
|
@ -664,15 +664,16 @@ void EnabledModulesList::addModuleInteractive (const String& moduleID)
|
||||||
|
|
||||||
void EnabledModulesList::addModuleFromUserSelectedFile()
|
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, {});
|
chooser->launchAsync (flags, [this] (const FileChooser& fc)
|
||||||
|
|
||||||
if (fc.browseForDirectory())
|
|
||||||
{
|
{
|
||||||
lastLocation = fc.getResult();
|
if (fc.getResult() == File{})
|
||||||
addModuleOfferingToCopy (lastLocation, true);
|
return;
|
||||||
}
|
|
||||||
|
addModuleOfferingToCopy (fc.getResult(), true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void EnabledModulesList::addModuleOfferingToCopy (const File& f, bool isFromUserSpecifiedFolder)
|
void EnabledModulesList::addModuleOfferingToCopy (const File& f, bool isFromUserSpecifiedFolder)
|
||||||
|
|
|
||||||
|
|
@ -142,5 +142,7 @@ private:
|
||||||
CriticalSection stateLock;
|
CriticalSection stateLock;
|
||||||
ValueTree state;
|
ValueTree state;
|
||||||
|
|
||||||
|
std::unique_ptr<FileChooser> chooser;
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EnabledModulesList)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EnabledModulesList)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -78,13 +78,25 @@ public:
|
||||||
|
|
||||||
void deleteItem() override
|
void deleteItem() override
|
||||||
{
|
{
|
||||||
if (AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, "Delete Exporter",
|
WeakReference<ExporterItem> safeThis { this };
|
||||||
"Are you sure you want to delete this export target?"))
|
AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
|
||||||
|
"Delete Exporter",
|
||||||
|
"Are you sure you want to delete this export target?",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nullptr,
|
||||||
|
ModalCallbackFunction::create ([safeThis] (int result)
|
||||||
{
|
{
|
||||||
closeSettingsPage();
|
if (safeThis == nullptr)
|
||||||
ValueTree parent (exporter->settings.getParent());
|
return;
|
||||||
parent.removeChild (exporter->settings, project.getUndoManagerFor (parent));
|
|
||||||
}
|
if (result == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
safeThis->closeSettingsPage();
|
||||||
|
auto parent = safeThis->exporter->settings.getParent();
|
||||||
|
parent.removeChild (safeThis->exporter->settings, safeThis->project.getUndoManagerFor (parent));
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
void addSubItems() override
|
void addSubItems() override
|
||||||
|
|
@ -117,7 +129,7 @@ public:
|
||||||
if (resultCode == 1)
|
if (resultCode == 1)
|
||||||
exporter->addNewConfiguration (false);
|
exporter->addNewConfiguration (false);
|
||||||
else if (resultCode == 2)
|
else if (resultCode == 2)
|
||||||
project.saveProject (exporter.get());
|
project.saveProject (Async::yes, exporter.get(), nullptr);
|
||||||
else if (resultCode == 3)
|
else if (resultCode == 3)
|
||||||
deleteAllSelectedItems();
|
deleteAllSelectedItems();
|
||||||
}
|
}
|
||||||
|
|
@ -200,6 +212,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExporterItem)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExporterItem)
|
||||||
|
JUCE_DECLARE_WEAK_REFERENCEABLE (ExporterItem)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -231,12 +244,24 @@ public:
|
||||||
|
|
||||||
void deleteItem() override
|
void deleteItem() override
|
||||||
{
|
{
|
||||||
if (AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, "Delete Configuration",
|
WeakReference<ConfigItem> parent { this };
|
||||||
"Are you sure you want to delete this configuration?"))
|
AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
|
||||||
|
"Delete Configuration",
|
||||||
|
"Are you sure you want to delete this configuration?",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
nullptr,
|
||||||
|
ModalCallbackFunction::create ([parent] (int result)
|
||||||
{
|
{
|
||||||
closeSettingsPage();
|
if (parent == nullptr)
|
||||||
config->removeFromExporter();
|
return;
|
||||||
}
|
|
||||||
|
if (result == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
parent->closeSettingsPage();
|
||||||
|
parent->config->removeFromExporter();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
void showPopupMenu (Point<int> p) override
|
void showPopupMenu (Point<int> p) override
|
||||||
|
|
@ -297,6 +322,8 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConfigItem)
|
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;
|
jassertfalse;
|
||||||
auto maxFilesToList = 10;
|
return;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
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())
|
if (! f.moveToTrash())
|
||||||
{
|
{
|
||||||
|
|
@ -136,15 +122,48 @@ public:
|
||||||
pcc->hideEditor();
|
pcc->hideEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
om.closeFile (itemToRemove->getFile(), OpenDocumentManager::SaveIfNeeded::no);
|
om.closeFileWithoutSaving (itemToRemove->getFile());
|
||||||
itemToRemove->deleteItem();
|
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
|
virtual void revealInFinder() const
|
||||||
|
|
@ -155,17 +174,24 @@ public:
|
||||||
virtual void browseToAddExistingFiles()
|
virtual void browseToAddExistingFiles()
|
||||||
{
|
{
|
||||||
auto location = item.isGroup() ? item.determineGroupFolder() : getFile();
|
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;
|
StringArray files;
|
||||||
|
|
||||||
for (int i = 0; i < fc.getResults().size(); ++i)
|
for (int i = 0; i < fc.getResults().size(); ++i)
|
||||||
files.add (fc.getResults().getReference(i).getFullPathName());
|
files.add (fc.getResults().getReference(i).getFullPathName());
|
||||||
|
|
||||||
addFilesRetainingSortOrder (files);
|
addFilesRetainingSortOrder (files);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void checkFileStatus() // (recursive)
|
virtual void checkFileStatus() // (recursive)
|
||||||
|
|
@ -192,7 +218,7 @@ public:
|
||||||
p->addFilesRetainingSortOrder (files);
|
p->addFilesRetainingSortOrder (files);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void moveSelectedItemsTo (OwnedArray <Project::Item>&, int /*insertIndex*/)
|
virtual void moveSelectedItemsTo (OwnedArray<Project::Item>&, int /*insertIndex*/)
|
||||||
{
|
{
|
||||||
jassertfalse;
|
jassertfalse;
|
||||||
}
|
}
|
||||||
|
|
@ -269,7 +295,7 @@ public:
|
||||||
void filesDropped (const StringArray& files, int insertIndex) override
|
void filesDropped (const StringArray& files, int insertIndex) override
|
||||||
{
|
{
|
||||||
if (files.size() == 1 && File (files[0]).hasFileExtension (Project::projectFileExtension))
|
if (files.size() == 1 && File (files[0]).hasFileExtension (Project::projectFileExtension))
|
||||||
ProjucerApplication::getApp().openFile (files[0]);
|
ProjucerApplication::getApp().openFile (files[0], [] (bool) {});
|
||||||
else
|
else
|
||||||
addFilesAtIndex (files, insertIndex);
|
addFilesAtIndex (files, insertIndex);
|
||||||
}
|
}
|
||||||
|
|
@ -444,6 +470,11 @@ protected:
|
||||||
|
|
||||||
return -1;
|
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 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
|
String getDisplayName() const override
|
||||||
{
|
{
|
||||||
|
|
@ -490,8 +521,9 @@ public:
|
||||||
{
|
{
|
||||||
if (newName != File::createLegalFileName (newName))
|
if (newName != File::createLegalFileName (newName))
|
||||||
{
|
{
|
||||||
AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
|
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||||
"That filename contained some illegal characters!");
|
"File Rename",
|
||||||
|
"That filename contained some illegal characters!");
|
||||||
triggerAsyncRename (item);
|
triggerAsyncRename (item);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -506,30 +538,42 @@ public:
|
||||||
|
|
||||||
if (correspondingItem.isValid())
|
if (correspondingItem.isValid())
|
||||||
{
|
{
|
||||||
if (AlertWindow::showOkCancelBox (AlertWindow::NoIcon, "File Rename",
|
WeakReference<SourceFileItem> parent { this };
|
||||||
"Do you also want to rename the corresponding file \"" + correspondingFile.getFileName()
|
AlertWindow::showOkCancelBox (AlertWindow::NoIcon,
|
||||||
+ "\" to match?"))
|
"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",
|
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||||
"Failed to rename \"" + oldFile.getFullPathName() + "\"!\n\nCheck your file permissions!");
|
"File Rename",
|
||||||
|
"Failed to rename \"" + oldFile.getFullPathName() + "\"!\n\nCheck your file permissions!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! correspondingItem.renameFile (newFile.withFileExtension (correspondingFile.getFileExtension())))
|
if (! correspondingItem.renameFile (newFile.withFileExtension (correspondingFile.getFileExtension())))
|
||||||
{
|
{
|
||||||
AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
|
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||||
"Failed to rename \"" + correspondingFile.getFullPathName() + "\"!\n\nCheck your file permissions!");
|
"File Rename",
|
||||||
|
"Failed to rename \"" + correspondingFile.getFullPathName() + "\"!\n\nCheck your file permissions!");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! item.renameFile (newFile))
|
if (! item.renameFile (newFile))
|
||||||
{
|
{
|
||||||
AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
|
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||||
"Failed to rename the file!\n\nCheck your file permissions!");
|
"File Rename",
|
||||||
|
"Failed to rename the file!\n\nCheck your file permissions!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -600,6 +644,8 @@ public:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JUCE_DECLARE_WEAK_REFERENCEABLE (SourceFileItem)
|
||||||
};
|
};
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
@ -799,7 +845,7 @@ public:
|
||||||
m.addItem (1002, "Add Existing Files...");
|
m.addItem (1002, "Add Existing Files...");
|
||||||
|
|
||||||
m.addSeparator();
|
m.addSeparator();
|
||||||
NewFileWizard().addWizardsToMenu (m);
|
wizard.addWizardsToMenu (m);
|
||||||
}
|
}
|
||||||
|
|
||||||
void processCreateFileMenuItem (int menuID)
|
void processCreateFileMenuItem (int menuID)
|
||||||
|
|
@ -811,7 +857,7 @@ public:
|
||||||
|
|
||||||
default:
|
default:
|
||||||
jassert (getProject() != nullptr);
|
jassert (getProject() != nullptr);
|
||||||
NewFileWizard().runWizardFromMenu (menuID, *getProject(), item);
|
wizard.runWizardFromMenu (menuID, *getProject(), item);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -832,4 +878,5 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
String searchFilter;
|
String searchFilter;
|
||||||
|
NewFileWizard wizard;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,7 @@ private:
|
||||||
Array<Value> exporterModulePathValues, globalPathValues;
|
Array<Value> exporterModulePathValues, globalPathValues;
|
||||||
Value useGlobalPathValue;
|
Value useGlobalPathValue;
|
||||||
|
|
||||||
OwnedArray <Project::ConfigFlag> configFlags;
|
OwnedArray<Project::ConfigFlag> configFlags;
|
||||||
|
|
||||||
PropertyGroupComponent group;
|
PropertyGroupComponent group;
|
||||||
Project& project;
|
Project& project;
|
||||||
|
|
|
||||||
|
|
@ -248,7 +248,7 @@ void HeaderComponent::initialiseButtons()
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (auto exporter = getSelectedExporter())
|
if (auto exporter = getSelectedExporter())
|
||||||
project->openProjectInIDE (*exporter, true);
|
project->openProjectInIDE (*exporter, true, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,12 @@
|
||||||
|
|
||||||
#include "Sidebar/jucer_Sidebar.h"
|
#include "Sidebar/jucer_Sidebar.h"
|
||||||
|
|
||||||
NewFileWizard::Type* createGUIComponentWizard();
|
struct WizardHolder
|
||||||
|
{
|
||||||
|
std::unique_ptr<NewFileWizard::Type> wizard;
|
||||||
|
};
|
||||||
|
|
||||||
|
NewFileWizard::Type* createGUIComponentWizard (Project&);
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
ProjectContentComponent::ProjectContentComponent()
|
ProjectContentComponent::ProjectContentComponent()
|
||||||
|
|
@ -305,7 +310,7 @@ void ProjectContentComponent::closeDocument()
|
||||||
if (currentDocument != nullptr)
|
if (currentDocument != nullptr)
|
||||||
{
|
{
|
||||||
ProjucerApplication::getApp().openDocumentManager
|
ProjucerApplication::getApp().openDocumentManager
|
||||||
.closeDocument (currentDocument, OpenDocumentManager::SaveIfNeeded::yes);
|
.closeDocumentAsync (currentDocument, OpenDocumentManager::SaveIfNeeded::yes, nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -315,35 +320,49 @@ void ProjectContentComponent::closeDocument()
|
||||||
|
|
||||||
static void showSaveWarning (OpenDocumentManager::Document* currentDocument)
|
static void showSaveWarning (OpenDocumentManager::Document* currentDocument)
|
||||||
{
|
{
|
||||||
AlertWindow::showMessageBox (AlertWindow::WarningIcon,
|
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||||
TRANS("Save failed!"),
|
TRANS("Save failed!"),
|
||||||
TRANS("Couldn't save the file:")
|
TRANS("Couldn't save the file:")
|
||||||
+ "\n" + currentDocument->getFile().getFullPathName());
|
+ "\n" + currentDocument->getFile().getFullPathName());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectContentComponent::saveDocument()
|
void ProjectContentComponent::saveDocumentAsync()
|
||||||
{
|
{
|
||||||
if (currentDocument != nullptr)
|
if (currentDocument != nullptr)
|
||||||
{
|
{
|
||||||
if (! currentDocument->save())
|
SafePointer<ProjectContentComponent> parent { this };
|
||||||
showSaveWarning (currentDocument);
|
currentDocument->saveAsync ([parent] (bool savedSuccessfully)
|
||||||
|
{
|
||||||
|
if (parent == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
refreshProjectTreeFileStatuses();
|
if (! savedSuccessfully)
|
||||||
|
showSaveWarning (parent->currentDocument);
|
||||||
|
|
||||||
|
parent->refreshProjectTreeFileStatuses();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
saveProject();
|
saveProjectAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectContentComponent::saveAs()
|
void ProjectContentComponent::saveAsAsync()
|
||||||
{
|
{
|
||||||
if (currentDocument != nullptr)
|
if (currentDocument != nullptr)
|
||||||
{
|
{
|
||||||
if (! currentDocument->saveAs())
|
SafePointer<ProjectContentComponent> parent { this };
|
||||||
showSaveWarning (currentDocument);
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProjectContentComponent::saveProject()
|
void ProjectContentComponent::saveProjectAsync()
|
||||||
{
|
{
|
||||||
if (project != nullptr)
|
if (project != nullptr)
|
||||||
return (project->save (true, true) == FileBasedDocument::savedOk);
|
project->saveAsync (true, true, nullptr);
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectContentComponent::closeProject()
|
void ProjectContentComponent::closeProject()
|
||||||
{
|
{
|
||||||
if (auto* mw = findParentComponentOfClass<MainWindow>())
|
if (auto* mw = findParentComponentOfClass<MainWindow>())
|
||||||
mw->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes);
|
mw->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectContentComponent::showProjectSettings()
|
void ProjectContentComponent::showProjectSettings()
|
||||||
|
|
@ -481,7 +498,7 @@ void ProjectContentComponent::openInSelectedIDE (bool saveFirst)
|
||||||
{
|
{
|
||||||
if (project != nullptr)
|
if (project != nullptr)
|
||||||
if (auto selectedExporter = headerComponent.getSelectedExporter())
|
if (auto selectedExporter = headerComponent.getSelectedExporter())
|
||||||
project->openProjectInIDE (*selectedExporter, saveFirst);
|
project->openProjectInIDE (*selectedExporter, saveFirst, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectContentComponent::showNewExporterMenu()
|
void ProjectContentComponent::showNewExporterMenu()
|
||||||
|
|
@ -787,7 +804,7 @@ void ProjectContentComponent::getCommandInfo (const CommandID commandID, Applica
|
||||||
bool ProjectContentComponent::perform (const InvocationInfo& info)
|
bool ProjectContentComponent::perform (const InvocationInfo& info)
|
||||||
{
|
{
|
||||||
// don't allow the project to be saved again if it's currently saving
|
// 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;
|
return false;
|
||||||
|
|
||||||
switch (info.commandID)
|
switch (info.commandID)
|
||||||
|
|
@ -818,14 +835,14 @@ bool ProjectContentComponent::perform (const InvocationInfo& info)
|
||||||
|
|
||||||
switch (info.commandID)
|
switch (info.commandID)
|
||||||
{
|
{
|
||||||
case CommandIDs::saveProject: saveProject(); break;
|
case CommandIDs::saveProject: saveProjectAsync(); break;
|
||||||
case CommandIDs::closeProject: closeProject(); break;
|
case CommandIDs::closeProject: closeProject(); break;
|
||||||
case CommandIDs::saveDocument: saveDocument(); break;
|
case CommandIDs::saveDocument: saveDocumentAsync(); break;
|
||||||
case CommandIDs::saveDocumentAs: saveAs(); break;
|
case CommandIDs::saveDocumentAs: saveAsAsync(); break;
|
||||||
case CommandIDs::closeDocument: closeDocument(); break;
|
case CommandIDs::closeDocument: closeDocument(); break;
|
||||||
case CommandIDs::goToPreviousDoc: goToPreviousFile(); break;
|
case CommandIDs::goToPreviousDoc: goToPreviousFile(); break;
|
||||||
case CommandIDs::goToNextDoc: goToNextFile(); break;
|
case CommandIDs::goToNextDoc: goToNextFile(); break;
|
||||||
case CommandIDs::goToCounterpart: goToCounterpart(); break;
|
case CommandIDs::goToCounterpart: goToCounterpart(); break;
|
||||||
|
|
||||||
case CommandIDs::showProjectSettings: showProjectSettings(); break;
|
case CommandIDs::showProjectSettings: showProjectSettings(); break;
|
||||||
case CommandIDs::showFileExplorerPanel: showFilesPanel(); break;
|
case CommandIDs::showFileExplorerPanel: showFilesPanel(); break;
|
||||||
|
|
@ -866,8 +883,9 @@ void ProjectContentComponent::addNewGUIFile()
|
||||||
{
|
{
|
||||||
if (project != nullptr)
|
if (project != nullptr)
|
||||||
{
|
{
|
||||||
std::unique_ptr<NewFileWizard::Type> wizard (createGUIComponentWizard());
|
wizardHolder = std::make_unique<WizardHolder>();
|
||||||
wizard->createNewFile (*project, project->getMainGroup());
|
wizardHolder->wizard.reset (createGUIComponentWizard (*project));
|
||||||
|
wizardHolder->wizard->createNewFile (*project, project->getMainGroup());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
#include "jucer_ContentViewComponent.h"
|
#include "jucer_ContentViewComponent.h"
|
||||||
|
|
||||||
class Sidebar;
|
class Sidebar;
|
||||||
|
struct WizardHolder;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
class ProjectContentComponent : public Component,
|
class ProjectContentComponent : public Component,
|
||||||
|
|
@ -57,8 +58,8 @@ public:
|
||||||
void hideDocument (OpenDocumentManager::Document*);
|
void hideDocument (OpenDocumentManager::Document*);
|
||||||
OpenDocumentManager::Document* getCurrentDocument() const { return currentDocument; }
|
OpenDocumentManager::Document* getCurrentDocument() const { return currentDocument; }
|
||||||
void closeDocument();
|
void closeDocument();
|
||||||
void saveDocument();
|
void saveDocumentAsync();
|
||||||
void saveAs();
|
void saveAsAsync();
|
||||||
|
|
||||||
void hideEditor();
|
void hideEditor();
|
||||||
void setScrollableEditorComponent (std::unique_ptr<Component> component);
|
void setScrollableEditorComponent (std::unique_ptr<Component> component);
|
||||||
|
|
@ -72,7 +73,7 @@ public:
|
||||||
bool canGoToCounterpart() const;
|
bool canGoToCounterpart() const;
|
||||||
bool goToCounterpart();
|
bool goToCounterpart();
|
||||||
|
|
||||||
bool saveProject();
|
void saveProjectAsync();
|
||||||
void closeProject();
|
void closeProject();
|
||||||
void openInSelectedIDE (bool saveFirst);
|
void openInSelectedIDE (bool saveFirst);
|
||||||
void showNewExporterMenu();
|
void showNewExporterMenu();
|
||||||
|
|
@ -145,6 +146,8 @@ private:
|
||||||
bool isForeground = false;
|
bool isForeground = false;
|
||||||
int lastViewedTab = 0;
|
int lastViewedTab = 0;
|
||||||
|
|
||||||
|
std::unique_ptr<WizardHolder> wizardHolder;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectContentComponent)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectContentComponent)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -66,12 +66,22 @@ void Project::ProjectFileModificationPoller::reloadProjectFromDisk()
|
||||||
{
|
{
|
||||||
if (auto* mw = ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (projectFile))
|
if (auto* mw = ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (projectFile))
|
||||||
{
|
{
|
||||||
mw->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::no);
|
Component::SafePointer<MainWindow> parent { mw };
|
||||||
mw->openFile (projectFile);
|
mw->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::no, [parent, oldTemporaryDirectory, projectFile] (bool)
|
||||||
|
{
|
||||||
|
if (parent == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
if (oldTemporaryDirectory != File())
|
parent->openFile (projectFile, [parent, oldTemporaryDirectory] (bool openedSuccessfully)
|
||||||
if (auto* newProject = mw->getProject())
|
{
|
||||||
newProject->setTemporaryDirectory (oldTemporaryDirectory);
|
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()
|
void Project::ProjectFileModificationPoller::resaveProject()
|
||||||
{
|
{
|
||||||
reset();
|
reset();
|
||||||
project.saveProject();
|
project.saveProject (Async::yes, nullptr, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
@ -122,7 +132,7 @@ Project::~Project()
|
||||||
|
|
||||||
auto& app = ProjucerApplication::getApp();
|
auto& app = ProjucerApplication::getApp();
|
||||||
|
|
||||||
app.openDocumentManager.closeAllDocumentsUsingProject (*this, OpenDocumentManager::SaveIfNeeded::no);
|
app.openDocumentManager.closeAllDocumentsUsingProjectWithoutSaving (*this);
|
||||||
|
|
||||||
if (! app.isRunningCommandLine)
|
if (! app.isRunningCommandLine)
|
||||||
app.getLicenseController().removeListener (this);
|
app.getLicenseController().removeListener (this);
|
||||||
|
|
@ -397,9 +407,9 @@ void Project::removeDefunctExporters()
|
||||||
if (ProjucerApplication::getApp().isRunningCommandLine)
|
if (ProjucerApplication::getApp().isRunningCommandLine)
|
||||||
std::cout << "WARNING! The " + oldExporters[key] + " Exporter is deprecated. The exporter will be removed from this project." << std::endl;
|
std::cout << "WARNING! The " + oldExporters[key] + " Exporter is deprecated. The exporter will be removed from this project." << std::endl;
|
||||||
else
|
else
|
||||||
AlertWindow::showMessageBox (AlertWindow::WarningIcon,
|
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||||
TRANS (oldExporters[key]),
|
TRANS (oldExporters[key]),
|
||||||
TRANS ("The " + oldExporters[key] + " Exporter is deprecated. The exporter will be removed from this project."));
|
TRANS ("The " + oldExporters[key] + " Exporter is deprecated. The exporter will be removed from this project."));
|
||||||
|
|
||||||
exporters.removeChild (oldExporter, nullptr);
|
exporters.removeChild (oldExporter, nullptr);
|
||||||
}
|
}
|
||||||
|
|
@ -664,40 +674,73 @@ Result Project::saveDocument (const File& file)
|
||||||
jassert (file == getFile());
|
jassert (file == getFile());
|
||||||
ignoreUnused (file);
|
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())
|
if (isSaveAndExportDisabled())
|
||||||
return Result::fail ("Save and export is disabled.");
|
{
|
||||||
|
onCompletion (Result::fail ("Save and export is disabled."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isSaving)
|
if (saver != nullptr)
|
||||||
return Result::ok();
|
{
|
||||||
|
onCompletion (Result::ok());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isTemporaryProject())
|
if (isTemporaryProject())
|
||||||
{
|
{
|
||||||
saveAndMoveTemporaryProject (false);
|
saveAndMoveTemporaryProject (false);
|
||||||
return Result::ok();
|
onCompletion (Result::ok());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProjectSettings();
|
updateProjectSettings();
|
||||||
|
|
||||||
if (! ProjucerApplication::getApp().isRunningCommandLine)
|
if (! ProjucerApplication::getApp().isRunningCommandLine)
|
||||||
{
|
{
|
||||||
ProjucerApplication::getApp().openDocumentManager.saveAll();
|
ProjucerApplication::getApp().openDocumentManager.saveAllSyncWithoutAsking();
|
||||||
|
|
||||||
if (! isTemporaryProject())
|
if (! isTemporaryProject())
|
||||||
registerRecentFile (getFile());
|
registerRecentFile (getFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
const ScopedValueSetter<bool> vs (isSaving, true, false);
|
WeakReference<Project> ref (this);
|
||||||
|
|
||||||
ProjectSaver saver (*this);
|
saver = std::make_unique<ProjectSaver> (*this);
|
||||||
return saver.save (exporterToSave);
|
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();)
|
for (ExporterIterator exporter (*this); exporter.next();)
|
||||||
{
|
{
|
||||||
|
|
@ -706,33 +749,57 @@ Result Project::openProjectInIDE (ProjectExporter& exporterToOpen, bool saveFirs
|
||||||
if (isTemporaryProject())
|
if (isTemporaryProject())
|
||||||
{
|
{
|
||||||
saveAndMoveTemporaryProject (true);
|
saveAndMoveTemporaryProject (true);
|
||||||
return Result::ok();
|
|
||||||
|
if (onCompletion != nullptr)
|
||||||
|
onCompletion (Result::ok());
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (saveFirst)
|
if (saveFirst)
|
||||||
{
|
{
|
||||||
auto result = saveProject();
|
struct Callback
|
||||||
|
{
|
||||||
|
void operator() (Result saveResult) noexcept
|
||||||
|
{
|
||||||
|
if (! saveResult.wasOk())
|
||||||
|
{
|
||||||
|
if (onCompletion != nullptr)
|
||||||
|
onCompletion (saveResult);
|
||||||
|
|
||||||
if (! result.wasOk())
|
return;
|
||||||
return result;
|
}
|
||||||
|
|
||||||
|
// 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();
|
exporter->launchProject();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result::ok();
|
if (onCompletion != nullptr)
|
||||||
|
onCompletion (Result::ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
Result Project::saveResourcesOnly()
|
Result Project::saveResourcesOnly()
|
||||||
{
|
{
|
||||||
ProjectSaver saver (*this);
|
saver = std::make_unique<ProjectSaver> (*this);
|
||||||
return saver.saveResourcesOnly();
|
return saver->saveResourcesOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Project::hasIncompatibleLicenseTypeAndSplashScreenSetting() const
|
bool Project::hasIncompatibleLicenseTypeAndSplashScreenSetting() const
|
||||||
|
|
@ -988,37 +1055,41 @@ void Project::setTemporaryDirectory (const File& dir) noexcept
|
||||||
|
|
||||||
void Project::saveAndMoveTemporaryProject (bool openInIDE)
|
void Project::saveAndMoveTemporaryProject (bool openInIDE)
|
||||||
{
|
{
|
||||||
FileChooser fc ("Save Project");
|
chooser = std::make_unique<FileChooser> ("Save Project");
|
||||||
fc.browseForDirectory();
|
auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
|
||||||
|
|
||||||
auto newParentDirectory = fc.getResult();
|
chooser->launchAsync (flags, [this, openInIDE] (const FileChooser& fc)
|
||||||
|
|
||||||
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()))
|
|
||||||
{
|
{
|
||||||
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)
|
tempDirectory.copyDirectoryTo (newDirectory);
|
||||||
safeWindow->moveProject (newDirectory.getChildFile (oldJucerFileName),
|
tempDirectory.deleteRecursively();
|
||||||
openInIDE ? MainWindow::OpenInIDE::yes
|
tempDirectory = File();
|
||||||
: MainWindow::OpenInIDE::no);
|
|
||||||
|
// 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 (Project& p) : index (-1), project (p) {}
|
||||||
Project::ExporterIterator::~ExporterIterator() {}
|
|
||||||
|
|
||||||
bool Project::ExporterIterator::next()
|
bool Project::ExporterIterator::next()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
class ProjectExporter;
|
class ProjectExporter;
|
||||||
class LibraryModule;
|
class LibraryModule;
|
||||||
class EnabledModulesList;
|
class EnabledModulesList;
|
||||||
|
class ProjectSaver;
|
||||||
|
|
||||||
namespace ProjectMessages
|
namespace ProjectMessages
|
||||||
{
|
{
|
||||||
|
|
@ -109,6 +110,8 @@ namespace ProjectMessages
|
||||||
using MessageAction = std::pair<String, std::function<void()>>;
|
using MessageAction = std::pair<String, std::function<void()>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class Async { no, yes };
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
class Project : public FileBasedDocument,
|
class Project : public FileBasedDocument,
|
||||||
private ValueTree::Listener,
|
private ValueTree::Listener,
|
||||||
|
|
@ -125,10 +128,11 @@ public:
|
||||||
String getDocumentTitle() override;
|
String getDocumentTitle() override;
|
||||||
Result loadDocument (const File& file) override;
|
Result loadDocument (const File& file) override;
|
||||||
Result saveDocument (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 saveResourcesOnly();
|
||||||
Result openProjectInIDE (ProjectExporter& exporterToOpen, bool saveFirst);
|
void openProjectInIDE (ProjectExporter& exporterToOpen, bool saveFirst, std::function<void (Result)> onCompletion);
|
||||||
|
|
||||||
File getLastDocumentOpened() override;
|
File getLastDocumentOpened() override;
|
||||||
void setLastDocumentOpened (const File& file) override;
|
void setLastDocumentOpened (const File& file) override;
|
||||||
|
|
@ -433,7 +437,6 @@ public:
|
||||||
struct ExporterIterator
|
struct ExporterIterator
|
||||||
{
|
{
|
||||||
ExporterIterator (Project& project);
|
ExporterIterator (Project& project);
|
||||||
~ExporterIterator();
|
|
||||||
|
|
||||||
bool next();
|
bool next();
|
||||||
|
|
||||||
|
|
@ -486,7 +489,7 @@ public:
|
||||||
String getUniqueTargetFolderSuffixForExporter (const Identifier& exporterIdentifier, const String& baseTargetFolder);
|
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(); }
|
bool isTemporaryProject() const noexcept { return tempDirectory != File(); }
|
||||||
File getTemporaryDirectory() const noexcept { return tempDirectory; }
|
File getTemporaryDirectory() const noexcept { return tempDirectory; }
|
||||||
|
|
@ -572,7 +575,6 @@ private:
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
friend class Item;
|
friend class Item;
|
||||||
bool isSaving = false;
|
|
||||||
StringPairArray parsedPreprocessorDefs;
|
StringPairArray parsedPreprocessorDefs;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
@ -612,12 +614,21 @@ private:
|
||||||
void updateCLionWarning (bool showWarning);
|
void updateCLionWarning (bool showWarning);
|
||||||
void updateModuleNotFoundWarning (bool showWarning);
|
void updateModuleNotFoundWarning (bool showWarning);
|
||||||
|
|
||||||
|
void openProjectInIDEImpl (ExporterIterator exporter,
|
||||||
|
String exporterToOpen,
|
||||||
|
bool saveFirst,
|
||||||
|
std::function<void (Result)> onCompletion);
|
||||||
|
|
||||||
ValueTree projectMessages { ProjectMessages::Ids::projectMessages, {},
|
ValueTree projectMessages { ProjectMessages::Ids::projectMessages, {},
|
||||||
{ { ProjectMessages::Ids::notification, {} }, { ProjectMessages::Ids::warning, {} } } };
|
{ { ProjectMessages::Ids::notification, {} }, { ProjectMessages::Ids::warning, {} } } };
|
||||||
std::map<Identifier, std::vector<ProjectMessages::MessageAction>> messageActions;
|
std::map<Identifier, std::vector<ProjectMessages::MessageAction>> messageActions;
|
||||||
|
|
||||||
ProjectFileModificationPoller fileModificationPoller { *this };
|
ProjectFileModificationPoller fileModificationPoller { *this };
|
||||||
|
|
||||||
|
std::unique_ptr<FileChooser> chooser;
|
||||||
|
std::unique_ptr<ProjectSaver> saver;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Project)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Project)
|
||||||
|
JUCE_DECLARE_WEAK_REFERENCEABLE (Project)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -42,17 +42,32 @@ ProjectSaver::ProjectSaver (Project& p)
|
||||||
generatedFilesGroup.setID (generatedGroupID);
|
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);
|
if (ref == nullptr)
|
||||||
thread.runThread();
|
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()
|
Result ProjectSaver::saveResourcesOnly()
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ class ProjectSaver
|
||||||
public:
|
public:
|
||||||
ProjectSaver (Project& projectToSave);
|
ProjectSaver (Project& projectToSave);
|
||||||
|
|
||||||
Result save (ProjectExporter* exporterToSave = nullptr);
|
void save (Async async, ProjectExporter* exporterToSave, std::function<void (Result)> onCompletion);
|
||||||
Result saveResourcesOnly();
|
Result saveResourcesOnly();
|
||||||
void saveBasicProjectItems (const OwnedArray<LibraryModule>& modules, const String& appConfigUserContent);
|
void saveBasicProjectItems (const OwnedArray<LibraryModule>& modules, const String& appConfigUserContent);
|
||||||
|
|
||||||
|
|
@ -52,21 +52,30 @@ private:
|
||||||
struct SaveThreadWithProgressWindow : public ThreadWithProgressWindow
|
struct SaveThreadWithProgressWindow : public ThreadWithProgressWindow
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SaveThreadWithProgressWindow (ProjectSaver& ps, ProjectExporter* exporterToSave)
|
SaveThreadWithProgressWindow (ProjectSaver& ps,
|
||||||
|
ProjectExporter* exporterToSave,
|
||||||
|
std::function<void (Result)> onCompletionIn)
|
||||||
: ThreadWithProgressWindow ("Saving...", true, false),
|
: ThreadWithProgressWindow ("Saving...", true, false),
|
||||||
saver (ps),
|
saver (ps),
|
||||||
specifiedExporterToSave (exporterToSave)
|
specifiedExporterToSave (exporterToSave),
|
||||||
{}
|
onCompletion (std::move (onCompletionIn))
|
||||||
|
{
|
||||||
|
jassert (onCompletion != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
void run() override
|
void run() override
|
||||||
{
|
{
|
||||||
setProgress (-1);
|
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;
|
ProjectSaver& saver;
|
||||||
Result result = Result::ok();
|
|
||||||
ProjectExporter* specifiedExporterToSave;
|
ProjectExporter* specifiedExporterToSave;
|
||||||
|
std::function<void (Result)> onCompletion;
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE (SaveThreadWithProgressWindow)
|
JUCE_DECLARE_NON_COPYABLE (SaveThreadWithProgressWindow)
|
||||||
};
|
};
|
||||||
|
|
@ -86,6 +95,7 @@ private:
|
||||||
OwnedArray<LibraryModule> getModules();
|
OwnedArray<LibraryModule> getModules();
|
||||||
|
|
||||||
Result saveProject (ProjectExporter* specifiedExporterToSave);
|
Result saveProject (ProjectExporter* specifiedExporterToSave);
|
||||||
|
void saveProjectAsync (ProjectExporter* exporterToSave, std::function<void (Result)> onCompletion);
|
||||||
|
|
||||||
template <typename WriterCallback>
|
template <typename WriterCallback>
|
||||||
void writeOrRemoveGeneratedFile (const String& name, WriterCallback&& writerCallback);
|
void writeOrRemoveGeneratedFile (const String& name, WriterCallback&& writerCallback);
|
||||||
|
|
@ -118,8 +128,11 @@ private:
|
||||||
CriticalSection errorLock;
|
CriticalSection errorLock;
|
||||||
StringArray errors;
|
StringArray errors;
|
||||||
|
|
||||||
|
std::unique_ptr<SaveThreadWithProgressWindow> saveThread;
|
||||||
|
|
||||||
bool hasBinaryData = false;
|
bool hasBinaryData = false;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSaver)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSaver)
|
||||||
|
JUCE_DECLARE_WEAK_REFERENCEABLE (ProjectSaver)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -60,16 +60,15 @@ namespace
|
||||||
class NewCppFileWizard : public NewFileWizard::Type
|
class NewCppFileWizard : public NewFileWizard::Type
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NewCppFileWizard() {}
|
|
||||||
|
|
||||||
String getName() override { return "CPP File"; }
|
String getName() override { return "CPP File"; }
|
||||||
|
|
||||||
void createNewFile (Project&, Project::Item parent) override
|
void createNewFile (Project&, Project::Item parent) override
|
||||||
{
|
{
|
||||||
const File newFile (askUserToChooseNewFile ("SourceCode.cpp", "*.cpp", parent));
|
askUserToChooseNewFile ("SourceCode.cpp", "*.cpp", parent, [parent] (File newFile)
|
||||||
|
{
|
||||||
if (newFile != File())
|
if (newFile != File())
|
||||||
create (parent, newFile, "jucer_NewCppFileTemplate_cpp");
|
create (parent, newFile, "jucer_NewCppFileTemplate_cpp");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool create (Project::Item parent, const File& newFile, const char* templateName)
|
static bool create (Project::Item parent, const File& newFile, const char* templateName)
|
||||||
|
|
@ -89,16 +88,15 @@ public:
|
||||||
class NewHeaderFileWizard : public NewFileWizard::Type
|
class NewHeaderFileWizard : public NewFileWizard::Type
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NewHeaderFileWizard() {}
|
|
||||||
|
|
||||||
String getName() override { return "Header File"; }
|
String getName() override { return "Header File"; }
|
||||||
|
|
||||||
void createNewFile (Project&, Project::Item parent) override
|
void createNewFile (Project&, Project::Item parent) override
|
||||||
{
|
{
|
||||||
const File newFile (askUserToChooseNewFile ("SourceCode.h", "*.h", parent));
|
askUserToChooseNewFile ("SourceCode.h", "*.h", parent, [parent] (File newFile)
|
||||||
|
{
|
||||||
if (newFile != File())
|
if (newFile != File())
|
||||||
create (parent, newFile, "jucer_NewCppFileTemplate_h");
|
create (parent, newFile, "jucer_NewCppFileTemplate_h");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool create (Project::Item parent, const File& newFile, const char* templateName)
|
static bool create (Project::Item parent, const File& newFile, const char* templateName)
|
||||||
|
|
@ -118,19 +116,15 @@ public:
|
||||||
class NewCppAndHeaderFileWizard : public NewFileWizard::Type
|
class NewCppAndHeaderFileWizard : public NewFileWizard::Type
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NewCppAndHeaderFileWizard() {}
|
|
||||||
|
|
||||||
String getName() override { return "CPP & Header File"; }
|
String getName() override { return "CPP & Header File"; }
|
||||||
|
|
||||||
void createNewFile (Project&, Project::Item parent) override
|
void createNewFile (Project&, Project::Item parent) override
|
||||||
{
|
{
|
||||||
const File newFile (askUserToChooseNewFile ("SourceCode.h", "*.h;*.cpp", parent));
|
askUserToChooseNewFile ("SourceCode.h", "*.h;*.cpp", parent, [parent] (File newFile)
|
||||||
|
|
||||||
if (newFile != File())
|
|
||||||
{
|
{
|
||||||
if (NewCppFileWizard::create (parent, newFile.withFileExtension ("h"), "jucer_NewCppFileTemplate_h"))
|
if (NewCppFileWizard::create (parent, newFile.withFileExtension ("h"), "jucer_NewCppFileTemplate_h"))
|
||||||
NewCppFileWizard::create (parent, newFile.withFileExtension ("cpp"), "jucer_NewCppFileTemplate_cpp");
|
NewCppFileWizard::create (parent, newFile.withFileExtension ("cpp"), "jucer_NewCppFileTemplate_cpp");
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -138,37 +132,11 @@ public:
|
||||||
class NewComponentFileWizard : public NewFileWizard::Type
|
class NewComponentFileWizard : public NewFileWizard::Type
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NewComponentFileWizard() {}
|
|
||||||
|
|
||||||
String getName() override { return "Component class (split between a CPP & header)"; }
|
String getName() override { return "Component class (split between a CPP & header)"; }
|
||||||
|
|
||||||
void createNewFile (Project&, Project::Item parent) override
|
void createNewFile (Project&, Project::Item parent) override
|
||||||
{
|
{
|
||||||
for (;;)
|
createNewFileInternal (parent);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool create (const String& className, Project::Item parent,
|
static bool create (const String& className, Project::Item parent,
|
||||||
|
|
@ -198,14 +166,61 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getClassNameFieldName() { return "Class Name"; }
|
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
|
class NewSingleFileComponentFileWizard : public NewComponentFileWizard
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NewSingleFileComponentFileWizard() {}
|
|
||||||
|
|
||||||
String getName() override { return "Component class (in a single source file)"; }
|
String getName() override { return "Component class (in a single source file)"; }
|
||||||
|
|
||||||
void createFiles (Project::Item parent, const String& className, const File& newFile) override
|
void createFiles (Project::Item parent, const String& className, const File& newFile) override
|
||||||
|
|
@ -218,24 +233,28 @@ public:
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
void NewFileWizard::Type::showFailedToWriteMessage (const File& file)
|
void NewFileWizard::Type::showFailedToWriteMessage (const File& file)
|
||||||
{
|
{
|
||||||
AlertWindow::showMessageBox (AlertWindow::WarningIcon,
|
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||||
"Failed to Create File!",
|
"Failed to Create File!",
|
||||||
"Couldn't write to the file: " + file.getFullPathName());
|
"Couldn't write to the file: " + file.getFullPathName());
|
||||||
}
|
}
|
||||||
|
|
||||||
File NewFileWizard::Type::askUserToChooseNewFile (const String& suggestedFilename, const String& wildcard,
|
void NewFileWizard::Type::askUserToChooseNewFile (const String& suggestedFilename, const String& wildcard,
|
||||||
const Project::Item& projectGroupToAddTo)
|
const Project::Item& projectGroupToAddTo,
|
||||||
|
std::function<void (File)> callback)
|
||||||
{
|
{
|
||||||
FileChooser fc ("Select File to Create",
|
chooser = std::make_unique<FileChooser> ("Select File to Create",
|
||||||
projectGroupToAddTo.determineGroupFolder()
|
projectGroupToAddTo.determineGroupFolder()
|
||||||
.getChildFile (suggestedFilename)
|
.getChildFile (suggestedFilename)
|
||||||
.getNonexistentSibling(),
|
.getNonexistentSibling(),
|
||||||
wildcard);
|
wildcard);
|
||||||
|
auto flags = FileBrowserComponent::saveMode
|
||||||
|
| FileBrowserComponent::canSelectFiles
|
||||||
|
| FileBrowserComponent::warnAboutOverwriting;
|
||||||
|
|
||||||
if (fc.browseForFileToSave (true))
|
chooser->launchAsync (flags, [callback] (const FileChooser& fc)
|
||||||
return fc.getResult();
|
{
|
||||||
|
callback (fc.getResult());
|
||||||
return {};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
|
||||||
|
|
@ -48,10 +48,14 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
File askUserToChooseNewFile (const String& suggestedFilename, const String& wildcard,
|
void askUserToChooseNewFile (const String& suggestedFilename, const String& wildcard,
|
||||||
const Project::Item& projectGroupToAddTo);
|
const Project::Item& projectGroupToAddTo,
|
||||||
|
std::function<void (File)> callback);
|
||||||
|
|
||||||
static void showFailedToWriteMessage (const File& file);
|
static void showFailedToWriteMessage (const File& file);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<FileChooser> chooser;
|
||||||
};
|
};
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
|
||||||
|
|
@ -134,17 +134,29 @@ private:
|
||||||
|
|
||||||
if (isDirectory)
|
if (isDirectory)
|
||||||
{
|
{
|
||||||
FileChooser chooser ("Select directory", currentFile);
|
chooser = std::make_unique<FileChooser> ("Select directory", currentFile);
|
||||||
|
auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
|
||||||
|
|
||||||
if (chooser.browseForDirectory())
|
chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc)
|
||||||
setTo (chooser.getResult());
|
{
|
||||||
|
if (fc.getResult() == File{})
|
||||||
|
return;
|
||||||
|
|
||||||
|
setTo (fc.getResult());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
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())
|
chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc)
|
||||||
setTo (chooser.getResult());
|
{
|
||||||
|
if (fc.getResult() == File{})
|
||||||
|
return;
|
||||||
|
|
||||||
|
setTo (fc.getResult());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,6 +201,8 @@ private:
|
||||||
String wildcards;
|
String wildcards;
|
||||||
File root;
|
File root;
|
||||||
|
|
||||||
|
std::unique_ptr<FileChooser> chooser;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilePathPropertyComponent)
|
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. */
|
/** Pops up a dialog letting the user save the processor's state to a file. */
|
||||||
void askUserToSaveState (const String& fileSuffix = String())
|
void askUserToSaveState (const String& fileSuffix = String())
|
||||||
{
|
{
|
||||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
stateFileChooser = std::make_unique<FileChooser> (TRANS("Save current state"),
|
||||||
FileChooser fc (TRANS("Save current state"), getLastFile(), getFilePatterns (fileSuffix));
|
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);
|
setLastFile (fc);
|
||||||
|
|
||||||
MemoryBlock data;
|
MemoryBlock data;
|
||||||
|
|
@ -205,20 +212,23 @@ public:
|
||||||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||||
TRANS("Error whilst saving"),
|
TRANS("Error whilst saving"),
|
||||||
TRANS("Couldn't write to the specified file!"));
|
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. */
|
/** Pops up a dialog letting the user re-load the processor's state from a file. */
|
||||||
void askUserToLoadState (const String& fileSuffix = String())
|
void askUserToLoadState (const String& fileSuffix = String())
|
||||||
{
|
{
|
||||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
stateFileChooser = std::make_unique<FileChooser> (TRANS("Load a saved state"),
|
||||||
FileChooser fc (TRANS("Load a saved state"), getLastFile(), getFilePatterns (fileSuffix));
|
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);
|
setLastFile (fc);
|
||||||
|
|
||||||
MemoryBlock data;
|
MemoryBlock data;
|
||||||
|
|
@ -229,10 +239,7 @@ public:
|
||||||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||||
TRANS("Error whilst loading"),
|
TRANS("Error whilst loading"),
|
||||||
TRANS("Couldn't read from the specified file!"));
|
TRANS("Couldn't read from the specified file!"));
|
||||||
}
|
});
|
||||||
#else
|
|
||||||
ignoreUnused (fileSuffix);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
@ -407,6 +414,8 @@ public:
|
||||||
std::unique_ptr<AudioDeviceManager::AudioDeviceSetup> options;
|
std::unique_ptr<AudioDeviceManager::AudioDeviceSetup> options;
|
||||||
Array<MidiDeviceInfo> lastMidiDevices;
|
Array<MidiDeviceInfo> lastMidiDevices;
|
||||||
|
|
||||||
|
std::unique_ptr<FileChooser> stateFileChooser;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/* This class can be used to ensure that audio callbacks use buffers with a
|
/* This class can be used to ensure that audio callbacks use buffers with a
|
||||||
predictable maximum size.
|
predictable maximum size.
|
||||||
|
|
|
||||||
|
|
@ -335,7 +335,7 @@ namespace juce
|
||||||
#elif ! defined (JUCE_MODAL_LOOPS_PERMITTED)
|
#elif ! defined (JUCE_MODAL_LOOPS_PERMITTED)
|
||||||
/** Some operating environments don't provide a modal loop mechanism, so this flag can be
|
/** 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. */
|
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
|
#endif
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ public:
|
||||||
*/
|
*/
|
||||||
bool hasStopMessageBeenSent() const noexcept { return quitMessagePosted.get() != 0; }
|
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.
|
/** Synchronously dispatches messages until a given time has elapsed.
|
||||||
|
|
||||||
Returns false if a quit message has been posted by a call to stopDispatchLoop(),
|
Returns false if a quit message has been posted by a call to stopDispatchLoop(),
|
||||||
|
|
|
||||||
|
|
@ -2020,7 +2020,7 @@ public:
|
||||||
virtual void handleCommandMessage (int commandId);
|
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.
|
/** Runs a component modally, waiting until the loop terminates.
|
||||||
|
|
||||||
This method first makes the component visible, brings it to the front and
|
This method first makes the component visible, brings it to the front and
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ public:
|
||||||
*/
|
*/
|
||||||
bool cancelAllModalComponents();
|
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
|
/** Runs the event loop until the currently topmost modal component is dismissed, and
|
||||||
returns the exit code for that component.
|
returns the exit code for that component.
|
||||||
*/
|
*/
|
||||||
|
|
@ -164,6 +164,7 @@ class JUCE_API ModalCallbackFunction
|
||||||
public:
|
public:
|
||||||
/** This is a utility function to create a ModalComponentManager::Callback that will
|
/** This is a utility function to create a ModalComponentManager::Callback that will
|
||||||
call a lambda function.
|
call a lambda function.
|
||||||
|
|
||||||
The lambda that you supply must take an integer parameter, which is the result code that
|
The lambda that you supply must take an integer parameter, which is the result code that
|
||||||
was returned when the modal component was dismissed.
|
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.
|
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
|
e.g. @code
|
||||||
void loadMooseFile()
|
void loadMooseFile()
|
||||||
{
|
{
|
||||||
FileChooser myChooser ("Please select the moose you want to load...",
|
myChooser = std::make_unique<FileChooser> ("Please select the moose you want to load...",
|
||||||
File::getSpecialLocation (File::userHomeDirectory),
|
File::getSpecialLocation (File::userHomeDirectory),
|
||||||
"*.moose");
|
"*.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);
|
loadMoose (mooseFile);
|
||||||
}
|
}
|
||||||
|
|
@ -125,7 +121,7 @@ public:
|
||||||
~FileChooser();
|
~FileChooser();
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
#if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN
|
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||||
/** Shows a dialog box to choose a file to open.
|
/** Shows a dialog box to choose a file to open.
|
||||||
|
|
||||||
This will display the dialog box modally, using an "open file" mode, so that
|
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.
|
browseForFileToOpen() for more info about the behaviour of this method.
|
||||||
*/
|
*/
|
||||||
bool browseForMultipleFilesOrDirectories (FilePreviewComponent* previewComponent = nullptr);
|
bool browseForMultipleFilesOrDirectories (FilePreviewComponent* previewComponent = nullptr);
|
||||||
#endif
|
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
/** Runs a dialog box for the given set of option flags.
|
/** Runs a dialog box for the given set of option flags.
|
||||||
|
|
@ -193,6 +188,7 @@ public:
|
||||||
@see FileBrowserComponent::FileChooserFlags
|
@see FileBrowserComponent::FileChooserFlags
|
||||||
*/
|
*/
|
||||||
bool showDialog (int flags, FilePreviewComponent* previewComponent);
|
bool showDialog (int flags, FilePreviewComponent* previewComponent);
|
||||||
|
#endif
|
||||||
|
|
||||||
/** Use this method to launch the file browser window asynchronously.
|
/** Use this method to launch the file browser window asynchronously.
|
||||||
|
|
||||||
|
|
@ -200,12 +196,9 @@ public:
|
||||||
structure and will launch it modally, returning immediately.
|
structure and will launch it modally, returning immediately.
|
||||||
|
|
||||||
You must specify a callback which is called when the file browser is
|
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.
|
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
|
You must ensure that the lifetime of the callback object is longer than
|
||||||
the lifetime of the file-chooser.
|
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
|
This is a Juce-based file dialog box; to use a native file chooser, see the
|
||||||
FileChooser class.
|
FileChooser class.
|
||||||
|
|
||||||
To use one of these, create it and call its show() method. e.g.
|
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
WildcardFileFilter wildcardFilter ("*.foo", String(), "Foo files");
|
wildcardFilter = std::make_unique<WildcardFileFilter> ("*.foo", String(), "Foo files");
|
||||||
|
|
||||||
FileBrowserComponent browser (FileBrowserComponent::canSelectFiles,
|
browser = std::make_unique<FileBrowserComponent> (FileBrowserComponent::canSelectFiles,
|
||||||
File(),
|
File(),
|
||||||
&wildcardFilter,
|
wildcardFilter.get(),
|
||||||
nullptr);
|
nullptr);
|
||||||
|
|
||||||
FileChooserDialogBox dialogBox ("Open some kind of file",
|
dialogBox = std::make_unique<FileChooserDialogBox> ("Open some kind of file",
|
||||||
"Please choose some kind of file that you want to open...",
|
"Please choose some kind of file that you want to open...",
|
||||||
browser,
|
*browser,
|
||||||
false,
|
false,
|
||||||
Colours::lightgrey);
|
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
|
@endcode
|
||||||
|
|
||||||
|
|
@ -99,7 +104,7 @@ public:
|
||||||
~FileChooserDialogBox() override;
|
~FileChooserDialogBox() override;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
#if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN
|
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||||
/** Displays and runs the dialog box modally.
|
/** Displays and runs the dialog box modally.
|
||||||
|
|
||||||
This will show the box with the specified size, returning true if the user
|
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)
|
void FileSearchPathListComponent::returnKeyPressed (int row)
|
||||||
{
|
{
|
||||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
chooser = std::make_unique<FileChooser> (TRANS("Change folder..."), path[row], "*");
|
||||||
FileChooser chooser (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.remove (row);
|
||||||
path.add (chooser.getResult(), row);
|
path.add (fc.getResult(), row);
|
||||||
changed();
|
changed();
|
||||||
}
|
});
|
||||||
#else
|
|
||||||
ignoreUnused (row);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSearchPathListComponent::listBoxItemDoubleClicked (int row, const MouseEvent&)
|
void FileSearchPathListComponent::listBoxItemDoubleClicked (int row, const MouseEvent&)
|
||||||
|
|
@ -226,16 +226,17 @@ void FileSearchPathListComponent::addPath()
|
||||||
if (start == File())
|
if (start == File())
|
||||||
start = File::getCurrentWorkingDirectory();
|
start = File::getCurrentWorkingDirectory();
|
||||||
|
|
||||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
chooser = std::make_unique<FileChooser> (TRANS("Add a folder..."), start, "*");
|
||||||
FileChooser chooser (TRANS("Add a folder..."), start, "*");
|
auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
|
||||||
|
|
||||||
if (chooser.browseForDirectory())
|
chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc)
|
||||||
path.add (chooser.getResult(), listBox.getSelectedRow());
|
{
|
||||||
|
if (fc.getResult() == File{})
|
||||||
|
return;
|
||||||
|
|
||||||
changed();
|
path.add (fc.getResult(), listBox.getSelectedRow());
|
||||||
#else
|
changed();
|
||||||
jassertfalse; // needs rewriting to deal with non-modal environments
|
});
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSearchPathListComponent::deleteSelected()
|
void FileSearchPathListComponent::deleteSelected()
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,7 @@ private:
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
FileSearchPath path;
|
FileSearchPath path;
|
||||||
File defaultBrowseTarget;
|
File defaultBrowseTarget;
|
||||||
|
std::unique_ptr<FileChooser> chooser;
|
||||||
|
|
||||||
ListBox listBox;
|
ListBox listBox;
|
||||||
TextButton addButton, removeButton, changeButton;
|
TextButton addButton, removeButton, changeButton;
|
||||||
|
|
|
||||||
|
|
@ -114,22 +114,22 @@ File FilenameComponent::getLocationToBrowse()
|
||||||
|
|
||||||
void FilenameComponent::showChooser()
|
void FilenameComponent::showChooser()
|
||||||
{
|
{
|
||||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
chooser = std::make_unique<FileChooser> (isDir ? TRANS ("Choose a new directory")
|
||||||
FileChooser fc (isDir ? TRANS ("Choose a new directory")
|
: TRANS ("Choose a new file"),
|
||||||
: TRANS ("Choose a new file"),
|
getLocationToBrowse(),
|
||||||
getLocationToBrowse(),
|
wildcard);
|
||||||
wildcard);
|
|
||||||
|
|
||||||
if (isDir ? fc.browseForDirectory()
|
auto chooserFlags = isDir ? FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories
|
||||||
: (isSaving ? fc.browseForFileToSave (false)
|
: FileBrowserComponent::canSelectFiles | (isSaving ? FileBrowserComponent::saveMode
|
||||||
: fc.browseForFileToOpen()))
|
: FileBrowserComponent::openMode);
|
||||||
|
|
||||||
|
chooser->launchAsync (chooserFlags, [this] (const FileChooser&)
|
||||||
{
|
{
|
||||||
setCurrentFile (fc.getResult(), true);
|
if (chooser->getResult() == File{})
|
||||||
}
|
return;
|
||||||
#else
|
|
||||||
ignoreUnused (isSaving);
|
setCurrentFile (chooser->getResult(), true);
|
||||||
jassertfalse; // needs rewriting to deal with non-modal environments
|
});
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FilenameComponent::isInterestedInFileDrag (const StringArray&)
|
bool FilenameComponent::isInterestedInFileDrag (const StringArray&)
|
||||||
|
|
|
||||||
|
|
@ -73,21 +73,21 @@ public:
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
/** Creates a FilenameComponent.
|
/** Creates a FilenameComponent.
|
||||||
|
|
||||||
@param name the name for this component.
|
@param name the name for this component.
|
||||||
@param currentFile the file to initially show in the box
|
@param currentFile the file to initially show in the box
|
||||||
@param canEditFilename if true, the user can manually edit the filename; if false,
|
@param canEditFilename if true, the user can manually edit the filename; if false,
|
||||||
they can only change it by browsing for a new file
|
they can only change it by browsing for a new file
|
||||||
@param isDirectory if true, the file will be treated as a directory, and
|
@param isDirectory if true, the file will be treated as a directory, and
|
||||||
an appropriate directory browser used
|
an appropriate directory browser used
|
||||||
@param isForSaving if true, the file browser will allow non-existent files to
|
@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
|
be picked, as the file is assumed to be used for saving rather
|
||||||
than loading
|
than loading
|
||||||
@param fileBrowserWildcard a wildcard pattern to use in the file browser - e.g. "*.txt;*.foo".
|
@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 "*"
|
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
|
@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
|
to any filenames that are entered or chosen
|
||||||
@param textWhenNothingSelected the message to display in the box before any filename is entered. (This
|
@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,
|
FilenameComponent (const String& name,
|
||||||
const File& currentFile,
|
const File& currentFile,
|
||||||
|
|
@ -216,6 +216,10 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
void handleAsyncUpdate() override;
|
||||||
|
|
||||||
|
void showChooser();
|
||||||
|
|
||||||
ComboBox filenameBox;
|
ComboBox filenameBox;
|
||||||
String lastFilename;
|
String lastFilename;
|
||||||
std::unique_ptr<Button> browseButton;
|
std::unique_ptr<Button> browseButton;
|
||||||
|
|
@ -224,9 +228,7 @@ private:
|
||||||
String wildcard, enforcedSuffix, browseButtonText;
|
String wildcard, enforcedSuffix, browseButtonText;
|
||||||
ListenerList <FilenameComponentListener> listeners;
|
ListenerList <FilenameComponentListener> listeners;
|
||||||
File defaultBrowseFile;
|
File defaultBrowseFile;
|
||||||
|
std::unique_ptr<FileChooser> chooser;
|
||||||
void showChooser();
|
|
||||||
void handleAsyncUpdate() override;
|
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilenameComponent)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilenameComponent)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@ public:
|
||||||
/** Destructor. */
|
/** Destructor. */
|
||||||
~ImagePreviewComponent() override;
|
~ImagePreviewComponent() override;
|
||||||
|
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
/** @internal */
|
/** @internal */
|
||||||
void selectedFileChanged (const File& newSelectedFile) override;
|
void selectedFileChanged (const File& newSelectedFile) override;
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ void MultiDocumentPanelWindow::maximiseButtonPressed()
|
||||||
void MultiDocumentPanelWindow::closeButtonPressed()
|
void MultiDocumentPanelWindow::closeButtonPressed()
|
||||||
{
|
{
|
||||||
if (auto* owner = getOwner())
|
if (auto* owner = getOwner())
|
||||||
owner->closeDocument (getContentComponent(), true);
|
owner->closeDocumentAsync (getContentComponent(), true, nullptr);
|
||||||
else
|
else
|
||||||
jassertfalse; // these windows are only designed to be used inside a MultiDocumentPanel!
|
jassertfalse; // these windows are only designed to be used inside a MultiDocumentPanel!
|
||||||
}
|
}
|
||||||
|
|
@ -95,10 +95,7 @@ MultiDocumentPanel::MultiDocumentPanel()
|
||||||
setOpaque (true);
|
setOpaque (true);
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiDocumentPanel::~MultiDocumentPanel()
|
MultiDocumentPanel::~MultiDocumentPanel() = default;
|
||||||
{
|
|
||||||
closeAllDocuments (false);
|
|
||||||
}
|
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
namespace MultiDocHelpers
|
namespace MultiDocHelpers
|
||||||
|
|
@ -109,6 +106,7 @@ namespace MultiDocHelpers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||||
bool MultiDocumentPanel::closeAllDocuments (const bool checkItsOkToCloseFirst)
|
bool MultiDocumentPanel::closeAllDocuments (const bool checkItsOkToCloseFirst)
|
||||||
{
|
{
|
||||||
while (! components.isEmpty())
|
while (! components.isEmpty())
|
||||||
|
|
@ -117,6 +115,52 @@ bool MultiDocumentPanel::closeAllDocuments (const bool checkItsOkToCloseFirst)
|
||||||
|
|
||||||
return true;
|
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()
|
MultiDocumentPanelWindow* MultiDocumentPanel::createNewDocumentWindow()
|
||||||
{
|
{
|
||||||
|
|
@ -217,6 +261,89 @@ bool MultiDocumentPanel::addDocument (Component* const component,
|
||||||
return true;
|
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,
|
bool MultiDocumentPanel::closeDocument (Component* component,
|
||||||
const bool checkItsOkToCloseFirst)
|
const bool checkItsOkToCloseFirst)
|
||||||
{
|
{
|
||||||
|
|
@ -232,78 +359,7 @@ bool MultiDocumentPanel::closeDocument (Component* component,
|
||||||
if (checkItsOkToCloseFirst && ! tryToCloseDocument (component))
|
if (checkItsOkToCloseFirst && ! tryToCloseDocument (component))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
component->removeComponentListener (this);
|
closeDocumentInternal (component);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -314,6 +370,54 @@ bool MultiDocumentPanel::closeDocument (Component* component,
|
||||||
|
|
||||||
JUCE_END_IGNORE_WARNINGS_MSVC
|
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
|
int MultiDocumentPanel::getNumDocuments() const noexcept
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,7 @@ public:
|
||||||
~MultiDocumentPanel() override;
|
~MultiDocumentPanel() override;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||||
/** Tries to close all the documents.
|
/** Tries to close all the documents.
|
||||||
|
|
||||||
If checkItsOkToCloseFirst is true, then the tryToCloseDocument() method will
|
If checkItsOkToCloseFirst is true, then the tryToCloseDocument() method will
|
||||||
|
|
@ -120,6 +121,22 @@ public:
|
||||||
@see closeDocument
|
@see closeDocument
|
||||||
*/
|
*/
|
||||||
bool closeAllDocuments (bool checkItsOkToCloseFirst);
|
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.
|
/** Adds a document component to the panel.
|
||||||
|
|
||||||
|
|
@ -142,6 +159,7 @@ public:
|
||||||
Colour backgroundColour,
|
Colour backgroundColour,
|
||||||
bool deleteWhenRemoved);
|
bool deleteWhenRemoved);
|
||||||
|
|
||||||
|
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||||
/** Closes one of the documents.
|
/** Closes one of the documents.
|
||||||
|
|
||||||
If checkItsOkToCloseFirst is true, then the tryToCloseDocument() method will
|
If checkItsOkToCloseFirst is true, then the tryToCloseDocument() method will
|
||||||
|
|
@ -158,6 +176,25 @@ public:
|
||||||
*/
|
*/
|
||||||
bool closeDocument (Component* component,
|
bool closeDocument (Component* component,
|
||||||
bool checkItsOkToCloseFirst);
|
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.
|
/** Returns the number of open document windows.
|
||||||
|
|
||||||
|
|
@ -248,6 +285,7 @@ public:
|
||||||
TabbedComponent* getCurrentTabbedComponent() const noexcept { return tabComponent.get(); }
|
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
|
/** A subclass must override this to say whether its currently ok for a document
|
||||||
to be closed.
|
to be closed.
|
||||||
|
|
||||||
|
|
@ -269,7 +307,32 @@ public:
|
||||||
|
|
||||||
@see closeDocument, FileBasedDocument::saveIfNeededAndUserAgrees()
|
@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.
|
/** Creates a new window to be used for a document.
|
||||||
|
|
||||||
|
|
@ -288,12 +351,12 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
LayoutMode mode = MaximisedWindowsWithTabs;
|
void closeDocumentInternal (Component*);
|
||||||
Array<Component*> components;
|
static void closeLastDocumentRecursive (SafePointer<MultiDocumentPanel>,
|
||||||
std::unique_ptr<TabbedComponent> tabComponent;
|
bool,
|
||||||
Colour backgroundColour { Colours::lightblue };
|
std::function<void (bool)>);
|
||||||
int maximumNumDocuments = 0, numDocsBeforeTabsUsed = 0;
|
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
struct TabbedComponentInternal;
|
struct TabbedComponentInternal;
|
||||||
friend class MultiDocumentPanelWindow;
|
friend class MultiDocumentPanelWindow;
|
||||||
|
|
||||||
|
|
@ -301,6 +364,12 @@ private:
|
||||||
void updateOrder();
|
void updateOrder();
|
||||||
void addWindow (Component*);
|
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)
|
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.
|
/** 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
|
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:
|
// 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.
|
/** 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
|
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
|
it'll show a box with just an ok button
|
||||||
@returns true if the ok button was pressed, false if they pressed cancel.
|
@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,
|
static bool JUCE_CALLTYPE showNativeDialogBox (const String& title,
|
||||||
const String& bodyText,
|
const String& bodyText,
|
||||||
bool isOkCancel);
|
bool isOkCancel);
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ public:
|
||||||
*/
|
*/
|
||||||
DialogWindow* create();
|
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
|
/** Launches and runs the dialog modally, returning the status code that was
|
||||||
used to terminate the modal loop.
|
used to terminate the modal loop.
|
||||||
|
|
||||||
|
|
@ -201,7 +201,7 @@ public:
|
||||||
bool shouldBeResizable = false,
|
bool shouldBeResizable = false,
|
||||||
bool useBottomRightCornerResizer = 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.
|
/** Easy way of quickly showing a dialog box containing a given component.
|
||||||
|
|
||||||
Note: This method has been superseded by the DialogWindow::LaunchOptions structure,
|
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
|
alert window should be associated with. Depending on the look
|
||||||
and feel, this might be used for positioning of the alert window.
|
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,
|
static void JUCE_CALLTYPE showMessageBox (AlertWindow::AlertIconType iconType,
|
||||||
const String& title,
|
const String& title,
|
||||||
const String& message,
|
const String& message,
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ public:
|
||||||
~ThreadWithProgressWindow() override;
|
~ThreadWithProgressWindow() override;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
#if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN
|
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||||
/** Starts the thread and waits for it to finish.
|
/** Starts the thread and waits for it to finish.
|
||||||
|
|
||||||
This will start the thread, make the dialog box appear, and wait until either
|
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
|
@see setChangedFlag, changed
|
||||||
*/
|
*/
|
||||||
bool hasChangedSinceSaved() const { return changedSinceSave; }
|
bool hasChangedSinceSaved() const;
|
||||||
|
|
||||||
/** Called to indicate that the document has changed and needs saving.
|
/** Called to indicate that the document has changed and needs saving.
|
||||||
|
|
||||||
|
|
@ -98,7 +98,7 @@ public:
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
/** Tries to open a file.
|
/** 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
|
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.
|
a message box is shown telling the user there was an error.
|
||||||
|
|
||||||
|
|
@ -110,6 +110,22 @@ public:
|
||||||
bool showMessageOnFailure,
|
bool showMessageOnFailure,
|
||||||
bool showWaitCursor = true);
|
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.
|
/** Asks the user for a file and tries to load it.
|
||||||
|
|
||||||
This will pop up a dialog box using the title, file extension and
|
This will pop up a dialog box using the title, file extension and
|
||||||
|
|
@ -122,6 +138,19 @@ public:
|
||||||
@see loadFrom
|
@see loadFrom
|
||||||
*/
|
*/
|
||||||
Result loadFromUserSpecifiedFile (bool showMessageOnFailure);
|
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
|
/** 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. */
|
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.
|
/** 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
|
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,
|
SaveResult save (bool askUserForFileIfNotSpecified,
|
||||||
bool showMessageOnFailure);
|
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
|
/** 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.
|
it if they say yes.
|
||||||
|
|
||||||
|
|
@ -169,7 +218,31 @@ public:
|
||||||
@see save, saveAs, saveAsInteractive
|
@see save, saveAs, saveAsInteractive
|
||||||
*/
|
*/
|
||||||
SaveResult saveIfNeededAndUserAgrees();
|
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.
|
/** Tries to save the document to a specified file.
|
||||||
|
|
||||||
If this succeeds, it'll also change the document's internal file (as returned by
|
If this succeeds, it'll also change the document's internal file (as returned by
|
||||||
|
|
@ -192,6 +265,45 @@ public:
|
||||||
bool askUserForFileIfNotSpecified,
|
bool askUserForFileIfNotSpecified,
|
||||||
bool showMessageOnFailure,
|
bool showMessageOnFailure,
|
||||||
bool showWaitCursor = true);
|
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.
|
/** Prompts the user for a filename and tries to save to it.
|
||||||
|
|
||||||
|
|
@ -201,10 +313,12 @@ public:
|
||||||
to this file.
|
to this file.
|
||||||
|
|
||||||
@param warnAboutOverwritingExistingFiles if true and the file exists, it'll ask
|
@param warnAboutOverwritingExistingFiles if true and the file exists, it'll ask
|
||||||
the user first if they want to overwrite it
|
the user first if they want to overwrite it
|
||||||
@see saveIfNeededAndUserAgrees, save, saveAs
|
@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.
|
/** 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()
|
It is changed when one of the load or save methods is used, or when setFile()
|
||||||
is used to explicitly set it.
|
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.
|
/** Sets the file that this document thinks it was loaded from.
|
||||||
|
|
||||||
|
|
@ -224,7 +338,6 @@ public:
|
||||||
*/
|
*/
|
||||||
void setFile (const File& newFile);
|
void setFile (const File& newFile);
|
||||||
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
/** Overload this to return the title of the document.
|
/** Overload this to return the title of the document.
|
||||||
|
|
@ -239,11 +352,33 @@ protected:
|
||||||
*/
|
*/
|
||||||
virtual Result loadDocument (const File& file) = 0;
|
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.
|
/** This method should try to write your document to the given file.
|
||||||
@returns a Result object to indicate the whether there was an error.
|
@returns a Result object to indicate the whether there was an error.
|
||||||
*/
|
*/
|
||||||
virtual Result saveDocument (const File& file) = 0;
|
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
|
/** This is used for dialog boxes to make them open at the last folder you
|
||||||
were using.
|
were using.
|
||||||
|
|
||||||
|
|
@ -277,21 +412,18 @@ protected:
|
||||||
*/
|
*/
|
||||||
virtual void setLastDocumentOpened (const File& file) = 0;
|
virtual void setLastDocumentOpened (const File& file) = 0;
|
||||||
|
|
||||||
#if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN
|
/** This is called by saveAsInteractiveAsync() to allow you to optionally customise the
|
||||||
/** This is called by saveAsInteractive() to allow you to optionally customise the
|
|
||||||
filename that the user is presented with in the save dialog.
|
filename that the user is presented with in the save dialog.
|
||||||
The defaultFile parameter is an initial suggestion based on what the class knows
|
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
|
about the current document - you can return a variation on this file with a different
|
||||||
extension, etc, or just return something completely different.
|
extension, etc, or just return something completely different.
|
||||||
*/
|
*/
|
||||||
virtual File getSuggestedSaveAsFile (const File& defaultFile);
|
virtual File getSuggestedSaveAsFile (const File& defaultFile);
|
||||||
#endif
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
File documentFile;
|
class Pimpl;
|
||||||
bool changedSinceSave = false;
|
std::unique_ptr<Pimpl> pimpl;
|
||||||
String fileExtension, fileWildcard, openFileDialogTitle, saveFileDialogTitle;
|
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileBasedDocument)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileBasedDocument)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -317,7 +317,7 @@ public:
|
||||||
GroupAlertBehaviour groupAlertBehaviour = alertAll;
|
GroupAlertBehaviour groupAlertBehaviour = alertAll;
|
||||||
|
|
||||||
int timeoutAfterMs = 0; /**< specifies a duration in milliseconds, after which the notification should be
|
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()
|
WebViewKeyEquivalentResponder()
|
||||||
: WebViewBase ("WebViewKeyEquivalentResponder_")
|
: WebViewBase ("WebViewKeyEquivalentResponder_")
|
||||||
{
|
{
|
||||||
addIvar<WebViewKeyEquivalentResponder*> ("owner");
|
|
||||||
addMethod (@selector (performKeyEquivalent:), performKeyEquivalent, @encode (BOOL), "@:@");
|
addMethod (@selector (performKeyEquivalent:), performKeyEquivalent, @encode (BOOL), "@:@");
|
||||||
registerClass();
|
registerClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static WebViewKeyEquivalentResponder* getOwner (id self)
|
|
||||||
{
|
|
||||||
return getIvar<WebViewKeyEquivalentResponder*> (self, "owner");
|
|
||||||
}
|
|
||||||
|
|
||||||
static BOOL performKeyEquivalent (id self, SEL selector, NSEvent* event)
|
static BOOL performKeyEquivalent (id self, SEL selector, NSEvent* event)
|
||||||
{
|
{
|
||||||
NSResponder* first = [[self window] firstResponder];
|
NSResponder* first = [[self window] firstResponder];
|
||||||
|
|
@ -225,9 +219,42 @@ private:
|
||||||
static void runOpenPanel (id, SEL, WKWebView*, WKOpenPanelParameters* parameters, WKFrameInfo*,
|
static void runOpenPanel (id, SEL, WKWebView*, WKOpenPanelParameters* parameters, WKFrameInfo*,
|
||||||
void (^completionHandler)(NSArray<NSURL*>*))
|
void (^completionHandler)(NSArray<NSURL*>*))
|
||||||
{
|
{
|
||||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
using CompletionHandlerType = decltype (completionHandler);
|
||||||
FileChooser chooser (TRANS("Select the file you want to upload..."),
|
|
||||||
File::getSpecialLocation (File::userHomeDirectory), "*");
|
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
|
auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles
|
||||||
| ([parameters allowsMultipleSelection] ? FileBrowserComponent::canSelectMultipleItems : 0);
|
| ([parameters allowsMultipleSelection] ? FileBrowserComponent::canSelectMultipleItems : 0);
|
||||||
|
|
@ -237,24 +264,17 @@ private:
|
||||||
flags |= FileBrowserComponent::canSelectDirectories;
|
flags |= FileBrowserComponent::canSelectDirectories;
|
||||||
#endif
|
#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()];
|
auto urls = [NSMutableArray arrayWithCapacity: (NSUInteger) results.size()];
|
||||||
|
|
||||||
for (auto& f : results)
|
for (auto& f : results)
|
||||||
[urls addObject: [NSURL fileURLWithPath: juceStringToNS (f.getFullPathName())]];
|
[urls addObject: [NSURL fileURLWithPath: juceStringToNS (f.getFullPathName())]];
|
||||||
|
|
||||||
completionHandler (urls);
|
wrapper->callHandler (urls);
|
||||||
}
|
delete wrapper;
|
||||||
else
|
});
|
||||||
{
|
|
||||||
completionHandler (nil);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
ignoreUnused (parameters, completionHandler);
|
|
||||||
jassertfalse; // Can't use this without modal loops being enabled!
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
@ -270,8 +290,6 @@ class WebBrowserComponent::Pimpl
|
||||||
public:
|
public:
|
||||||
Pimpl (WebBrowserComponent* owner)
|
Pimpl (WebBrowserComponent* owner)
|
||||||
{
|
{
|
||||||
ignoreUnused (owner);
|
|
||||||
|
|
||||||
#if JUCE_MAC
|
#if JUCE_MAC
|
||||||
static WebViewKeyEquivalentResponder webviewClass;
|
static WebViewKeyEquivalentResponder webviewClass;
|
||||||
webView = (WKWebView*) webviewClass.createInstance();
|
webView = (WKWebView*) webviewClass.createInstance();
|
||||||
|
|
@ -421,20 +439,37 @@ private:
|
||||||
|
|
||||||
static void runOpenPanel (id, SEL, WebView*, id<WebOpenPanelResultListener> resultListener, BOOL allowMultipleFiles)
|
static void runOpenPanel (id, SEL, WebView*, id<WebOpenPanelResultListener> resultListener, BOOL allowMultipleFiles)
|
||||||
{
|
{
|
||||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
struct DeletedFileChooserWrapper : private DeletedAtShutdown
|
||||||
FileChooser chooser (TRANS("Select the file you want to upload..."),
|
|
||||||
File::getSpecialLocation (File::userHomeDirectory), "*");
|
|
||||||
|
|
||||||
if (allowMultipleFiles ? chooser.browseForMultipleFilesToOpen()
|
|
||||||
: chooser.browseForFileToOpen())
|
|
||||||
{
|
{
|
||||||
for (auto& f : chooser.getResults())
|
DeletedFileChooserWrapper (std::unique_ptr<FileChooser> fc, id<WebOpenPanelResultListener> rl)
|
||||||
[resultListener chooseFilename: juceStringToNS (f.getFullPathName())];
|
: chooser (std::move (fc)), listener (rl)
|
||||||
}
|
{
|
||||||
#else
|
[listener retain];
|
||||||
ignoreUnused (resultListener, allowMultipleFiles);
|
}
|
||||||
jassertfalse; // Can't use this without modal loops being enabled!
|
|
||||||
#endif
|
~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