1052 lines
31 KiB
C++
1052 lines
31 KiB
C++
|
/*
|
||
|
==============================================================================
|
||
|
|
||
|
This file is part of the JUCE library.
|
||
|
Copyright (c) 2020 - Raw Material Software Limited
|
||
|
|
||
|
JUCE is an open source library subject to commercial or open-source
|
||
|
licensing.
|
||
|
|
||
|
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||
|
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||
|
|
||
|
End User License Agreement: www.juce.com/juce-6-licence
|
||
|
Privacy Policy: www.juce.com/juce-privacy-policy
|
||
|
|
||
|
Or: You may also use this code under the terms of the GPL v3 (see
|
||
|
www.gnu.org/licenses).
|
||
|
|
||
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||
|
DISCLAIMED.
|
||
|
|
||
|
==============================================================================
|
||
|
*/
|
||
|
|
||
|
#include "../Application/jucer_Headers.h"
|
||
|
#include "jucer_Application.h"
|
||
|
#include "jucer_MainWindow.h"
|
||
|
#include "StartPage/jucer_StartPageComponent.h"
|
||
|
#include "../Utility/UI/jucer_JucerTreeViewBase.h"
|
||
|
#include "../ProjectSaving/jucer_ProjectSaver.h"
|
||
|
#include "UserAccount/jucer_LoginFormComponent.h"
|
||
|
#include "../Project/UI/jucer_ProjectContentComponent.h"
|
||
|
|
||
|
//==============================================================================
|
||
|
class BlurOverlayWithComponent : public Component,
|
||
|
private ComponentMovementWatcher,
|
||
|
private AsyncUpdater
|
||
|
{
|
||
|
public:
|
||
|
BlurOverlayWithComponent (MainWindow& window, std::unique_ptr<Component> comp)
|
||
|
: ComponentMovementWatcher (&window),
|
||
|
mainWindow (window),
|
||
|
componentToShow (std::move (comp))
|
||
|
{
|
||
|
kernel.createGaussianBlur (1.25f);
|
||
|
|
||
|
addAndMakeVisible (*componentToShow);
|
||
|
|
||
|
setAlwaysOnTop (true);
|
||
|
setOpaque (true);
|
||
|
setVisible (true);
|
||
|
|
||
|
static_cast<Component&> (mainWindow).addChildComponent (this);
|
||
|
componentMovedOrResized (true, true);
|
||
|
|
||
|
enterModalState();
|
||
|
}
|
||
|
|
||
|
void resized() override
|
||
|
{
|
||
|
setBounds (mainWindow.getLocalBounds());
|
||
|
componentToShow->centreWithSize (componentToShow->getWidth(), componentToShow->getHeight());
|
||
|
refreshBackgroundImage();
|
||
|
}
|
||
|
|
||
|
void paint (Graphics& g) override
|
||
|
{
|
||
|
g.drawImage (componentImage, getLocalBounds().toFloat());
|
||
|
}
|
||
|
|
||
|
void inputAttemptWhenModal() override
|
||
|
{
|
||
|
mainWindow.hideLoginFormOverlay();
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
void componentPeerChanged() override {}
|
||
|
|
||
|
void componentVisibilityChanged() override {}
|
||
|
using ComponentMovementWatcher::componentVisibilityChanged;
|
||
|
|
||
|
void componentMovedOrResized (bool, bool) override { triggerAsyncUpdate(); }
|
||
|
using ComponentMovementWatcher::componentMovedOrResized;
|
||
|
|
||
|
void handleAsyncUpdate() override { resized(); }
|
||
|
|
||
|
void mouseUp (const MouseEvent& event) override
|
||
|
{
|
||
|
if (event.eventComponent == this)
|
||
|
mainWindow.hideLoginFormOverlay();
|
||
|
}
|
||
|
|
||
|
void lookAndFeelChanged() override
|
||
|
{
|
||
|
refreshBackgroundImage();
|
||
|
repaint();
|
||
|
}
|
||
|
|
||
|
void refreshBackgroundImage()
|
||
|
{
|
||
|
setAlwaysOnTop (false);
|
||
|
toBack();
|
||
|
|
||
|
auto parentBounds = mainWindow.getBounds();
|
||
|
|
||
|
componentImage = mainWindow.createComponentSnapshot (mainWindow.getLocalBounds())
|
||
|
.rescaled (roundToInt ((float) parentBounds.getWidth() / 1.75f),
|
||
|
roundToInt ((float) parentBounds.getHeight() / 1.75f));
|
||
|
|
||
|
kernel.applyToImage (componentImage, componentImage, getLocalBounds());
|
||
|
|
||
|
setAlwaysOnTop (true);
|
||
|
toFront (true);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
MainWindow& mainWindow;
|
||
|
std::unique_ptr<Component> componentToShow;
|
||
|
|
||
|
ImageConvolutionKernel kernel { 3 };
|
||
|
Image componentImage;
|
||
|
|
||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BlurOverlayWithComponent)
|
||
|
};
|
||
|
|
||
|
//==============================================================================
|
||
|
MainWindow::MainWindow()
|
||
|
: DocumentWindow (ProjucerApplication::getApp().getApplicationName(),
|
||
|
ProjucerApplication::getApp().lookAndFeel.getCurrentColourScheme()
|
||
|
.getUIColour (LookAndFeel_V4::ColourScheme::UIColour::windowBackground),
|
||
|
DocumentWindow::allButtons,
|
||
|
false)
|
||
|
{
|
||
|
setUsingNativeTitleBar (true);
|
||
|
setResizable (true, false);
|
||
|
setResizeLimits (600, 500, 32000, 32000);
|
||
|
|
||
|
#if ! JUCE_MAC
|
||
|
setMenuBar (ProjucerApplication::getApp().getMenuModel());
|
||
|
#endif
|
||
|
|
||
|
createProjectContentCompIfNeeded();
|
||
|
|
||
|
auto& commandManager = ProjucerApplication::getCommandManager();
|
||
|
|
||
|
auto registerAllAppCommands = [&]
|
||
|
{
|
||
|
commandManager.registerAllCommandsForTarget (this);
|
||
|
commandManager.registerAllCommandsForTarget (getProjectContentComponent());
|
||
|
};
|
||
|
|
||
|
auto updateAppKeyMappings = [&]
|
||
|
{
|
||
|
commandManager.getKeyMappings()->resetToDefaultMappings();
|
||
|
|
||
|
if (auto keys = getGlobalProperties().getXmlValue ("keyMappings"))
|
||
|
commandManager.getKeyMappings()->restoreFromXml (*keys);
|
||
|
|
||
|
addKeyListener (commandManager.getKeyMappings());
|
||
|
};
|
||
|
|
||
|
registerAllAppCommands();
|
||
|
updateAppKeyMappings();
|
||
|
|
||
|
setWantsKeyboardFocus (false);
|
||
|
getLookAndFeel().setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
|
||
|
|
||
|
projectNameValue.addListener (this);
|
||
|
|
||
|
centreWithSize (800, 600);
|
||
|
}
|
||
|
|
||
|
MainWindow::~MainWindow()
|
||
|
{
|
||
|
#if ! JUCE_MAC
|
||
|
setMenuBar (nullptr);
|
||
|
#endif
|
||
|
|
||
|
removeKeyListener (ProjucerApplication::getCommandManager().getKeyMappings());
|
||
|
|
||
|
// save the current size and position to our settings file..
|
||
|
getGlobalProperties().setValue ("lastMainWindowPos", getWindowStateAsString());
|
||
|
|
||
|
clearContentComponent();
|
||
|
}
|
||
|
|
||
|
void MainWindow::createProjectContentCompIfNeeded()
|
||
|
{
|
||
|
if (getProjectContentComponent() == nullptr)
|
||
|
{
|
||
|
clearContentComponent();
|
||
|
setContentOwned (new ProjectContentComponent(), false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void MainWindow::updateTitleBarIcon()
|
||
|
{
|
||
|
if (auto* peer = getPeer())
|
||
|
{
|
||
|
if (currentProject != nullptr)
|
||
|
{
|
||
|
peer->setRepresentedFile (currentProject->getFile());
|
||
|
peer->setIcon (ImageCache::getFromMemory (BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
peer->setRepresentedFile ({});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void MainWindow::makeVisible()
|
||
|
{
|
||
|
setVisible (true);
|
||
|
addToDesktop();
|
||
|
restoreWindowPosition();
|
||
|
updateTitleBarIcon();
|
||
|
getContentComponent()->grabKeyboardFocus();
|
||
|
}
|
||
|
|
||
|
ProjectContentComponent* MainWindow::getProjectContentComponent() const
|
||
|
{
|
||
|
return dynamic_cast<ProjectContentComponent*> (getContentComponent());
|
||
|
}
|
||
|
|
||
|
void MainWindow::closeButtonPressed()
|
||
|
{
|
||
|
ProjucerApplication::getApp().mainWindowList.closeWindow (this);
|
||
|
}
|
||
|
|
||
|
void MainWindow::closeCurrentProject (OpenDocumentManager::SaveIfNeeded askUserToSave, std::function<void (bool)> callback)
|
||
|
{
|
||
|
if (currentProject == nullptr)
|
||
|
{
|
||
|
if (callback != nullptr)
|
||
|
callback (true);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
currentProject->getStoredProperties().setValue (getProjectWindowPosName(), getWindowStateAsString());
|
||
|
|
||
|
if (auto* pcc = getProjectContentComponent())
|
||
|
{
|
||
|
pcc->saveOpenDocumentList();
|
||
|
pcc->hideEditor();
|
||
|
}
|
||
|
|
||
|
ProjucerApplication::getApp().openDocumentManager
|
||
|
.closeAllDocumentsUsingProjectAsync (*currentProject,
|
||
|
askUserToSave,
|
||
|
[parent = SafePointer<MainWindow> { this }, askUserToSave, callback] (bool closedSuccessfully)
|
||
|
{
|
||
|
if (parent == nullptr)
|
||
|
return;
|
||
|
|
||
|
if (! closedSuccessfully)
|
||
|
{
|
||
|
if (callback != nullptr)
|
||
|
callback (false);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
auto setProjectAndCallback = [parent, callback]
|
||
|
{
|
||
|
parent->setProject (nullptr);
|
||
|
|
||
|
if (callback != nullptr)
|
||
|
callback (true);
|
||
|
};
|
||
|
|
||
|
if (askUserToSave == OpenDocumentManager::SaveIfNeeded::no)
|
||
|
{
|
||
|
setProjectAndCallback();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
parent->currentProject->saveIfNeededAndUserAgreesAsync ([parent, setProjectAndCallback, callback] (FileBasedDocument::SaveResult saveResult)
|
||
|
{
|
||
|
if (parent == nullptr)
|
||
|
return;
|
||
|
|
||
|
if (saveResult == FileBasedDocument::savedOk)
|
||
|
setProjectAndCallback();
|
||
|
else if (callback != nullptr)
|
||
|
callback (false);
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void MainWindow::moveProject (File newProjectFileToOpen, OpenInIDE openInIDE)
|
||
|
{
|
||
|
closeCurrentProject (OpenDocumentManager::SaveIfNeeded::no,
|
||
|
[parent = SafePointer<MainWindow> { this }, newProjectFileToOpen, openInIDE] (bool)
|
||
|
{
|
||
|
if (parent == nullptr)
|
||
|
return;
|
||
|
|
||
|
parent->openFile (newProjectFileToOpen, [parent, openInIDE] (bool openedSuccessfully)
|
||
|
{
|
||
|
if (parent == nullptr)
|
||
|
return;
|
||
|
|
||
|
if (openedSuccessfully && parent->currentProject != nullptr && openInIDE == OpenInIDE::yes)
|
||
|
ProjucerApplication::getApp().getCommandManager().invokeDirectly (CommandIDs::openInIDE, false);
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void MainWindow::setProject (std::unique_ptr<Project> newProject)
|
||
|
{
|
||
|
if (newProject == nullptr)
|
||
|
{
|
||
|
if (auto* content = getProjectContentComponent())
|
||
|
content->setProject (nullptr);
|
||
|
|
||
|
currentProject.reset();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
currentProject = std::move (newProject);
|
||
|
|
||
|
createProjectContentCompIfNeeded();
|
||
|
getProjectContentComponent()->setProject (currentProject.get());
|
||
|
}
|
||
|
|
||
|
projectNameValue.referTo (currentProject != nullptr ? currentProject->getProjectValue (Ids::name) : Value());
|
||
|
initialiseProjectWindow();
|
||
|
|
||
|
ProjucerApplication::getCommandManager().commandStatusChanged();
|
||
|
}
|
||
|
|
||
|
void MainWindow::restoreWindowPosition()
|
||
|
{
|
||
|
String windowState;
|
||
|
|
||
|
if (currentProject != nullptr)
|
||
|
windowState = currentProject->getStoredProperties().getValue (getProjectWindowPosName());
|
||
|
|
||
|
if (windowState.isEmpty())
|
||
|
windowState = getGlobalProperties().getValue ("lastMainWindowPos");
|
||
|
|
||
|
restoreWindowStateFromString (windowState);
|
||
|
}
|
||
|
|
||
|
bool MainWindow::canOpenFile (const File& file) const
|
||
|
{
|
||
|
return (! file.isDirectory())
|
||
|
&& (file.hasFileExtension (Project::projectFileExtension)
|
||
|
|| ProjucerApplication::getApp().openDocumentManager.canOpenFile (file));
|
||
|
}
|
||
|
|
||
|
void MainWindow::openFile (const File& file, std::function<void (bool)> callback)
|
||
|
{
|
||
|
if (file.hasFileExtension (Project::projectFileExtension))
|
||
|
{
|
||
|
auto newDoc = std::make_unique<Project> (file);
|
||
|
auto result = newDoc->loadFrom (file, true);
|
||
|
|
||
|
if (result.wasOk())
|
||
|
{
|
||
|
closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes,
|
||
|
[parent = SafePointer<MainWindow> { this },
|
||
|
sharedDoc = std::make_shared<std::unique_ptr<Project>> (std::move (newDoc)),
|
||
|
callback] (bool saveResult)
|
||
|
{
|
||
|
if (parent == nullptr)
|
||
|
return;
|
||
|
|
||
|
if (saveResult)
|
||
|
{
|
||
|
parent->setProject (std::move (*sharedDoc.get()));
|
||
|
parent->currentProject->setChangedFlag (false);
|
||
|
|
||
|
parent->createProjectContentCompIfNeeded();
|
||
|
parent->getProjectContentComponent()->reloadLastOpenDocuments();
|
||
|
|
||
|
parent->currentProject->updateDeprecatedProjectSettingsInteractively();
|
||
|
}
|
||
|
|
||
|
if (callback != nullptr)
|
||
|
callback (saveResult);
|
||
|
});
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (callback != nullptr)
|
||
|
callback (false);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (file.exists())
|
||
|
{
|
||
|
SafePointer<MainWindow> parent { this };
|
||
|
auto createCompAndShowEditor = [parent, file, callback]
|
||
|
{
|
||
|
if (parent != nullptr)
|
||
|
{
|
||
|
parent->createProjectContentCompIfNeeded();
|
||
|
|
||
|
if (callback != nullptr)
|
||
|
callback (parent->getProjectContentComponent()->showEditorForFile (file, true));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if (isPIPFile (file))
|
||
|
{
|
||
|
openPIP (file, [parent, createCompAndShowEditor, callback] (bool openedSuccessfully)
|
||
|
{
|
||
|
if (parent == nullptr)
|
||
|
return;
|
||
|
|
||
|
if (openedSuccessfully)
|
||
|
{
|
||
|
if (callback != nullptr)
|
||
|
callback (true);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
createCompAndShowEditor();
|
||
|
});
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
createCompAndShowEditor();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (callback != nullptr)
|
||
|
callback (false);
|
||
|
}
|
||
|
|
||
|
void MainWindow::openPIP (const File& pipFile, std::function<void (bool)> callback)
|
||
|
{
|
||
|
auto generator = std::make_shared<PIPGenerator> (pipFile);
|
||
|
|
||
|
if (! generator->hasValidPIP())
|
||
|
{
|
||
|
if (callback != nullptr)
|
||
|
callback (false);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
auto generatorResult = generator->createJucerFile();
|
||
|
|
||
|
if (generatorResult != Result::ok())
|
||
|
{
|
||
|
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||
|
"PIP Error.",
|
||
|
generatorResult.getErrorMessage());
|
||
|
|
||
|
if (callback != nullptr)
|
||
|
callback (false);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (! generator->createMainCpp())
|
||
|
{
|
||
|
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||
|
"PIP Error.",
|
||
|
"Failed to create Main.cpp.");
|
||
|
|
||
|
if (callback != nullptr)
|
||
|
callback (false);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
openFile (generator->getJucerFile(), [parent = SafePointer<MainWindow> { this }, generator, callback] (bool openedSuccessfully)
|
||
|
{
|
||
|
if (parent == nullptr)
|
||
|
return;
|
||
|
|
||
|
if (! openedSuccessfully)
|
||
|
{
|
||
|
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
||
|
"PIP Error.",
|
||
|
"Failed to open .jucer file.");
|
||
|
|
||
|
if (callback != nullptr)
|
||
|
callback (false);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
parent->setupTemporaryPIPProject (*generator);
|
||
|
|
||
|
if (callback != nullptr)
|
||
|
callback (true);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void MainWindow::setupTemporaryPIPProject (PIPGenerator& generator)
|
||
|
{
|
||
|
jassert (currentProject != nullptr);
|
||
|
|
||
|
currentProject->setTemporaryDirectory (generator.getOutputDirectory());
|
||
|
|
||
|
if (auto* pcc = getProjectContentComponent())
|
||
|
{
|
||
|
auto fileToDisplay = generator.getPIPFile();
|
||
|
|
||
|
if (fileToDisplay != File())
|
||
|
{
|
||
|
pcc->showEditorForFile (fileToDisplay, true);
|
||
|
|
||
|
if (auto* sourceCodeEditor = dynamic_cast <SourceCodeEditor*> (pcc->getEditorComponent()))
|
||
|
sourceCodeEditor->editor->scrollToLine (findBestLineToScrollToForClass (StringArray::fromLines (fileToDisplay.loadFileAsString()),
|
||
|
generator.getMainClassName(), currentProject->getProjectType().isAudioPlugin()));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool MainWindow::isInterestedInFileDrag (const StringArray& filenames)
|
||
|
{
|
||
|
for (auto& filename : filenames)
|
||
|
if (canOpenFile (File (filename)))
|
||
|
return true;
|
||
|
|
||
|
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*/)
|
||
|
{
|
||
|
filesDroppedRecursive (this, filenames);
|
||
|
}
|
||
|
|
||
|
bool MainWindow::shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails,
|
||
|
StringArray& files, bool& canMoveFiles)
|
||
|
{
|
||
|
if (auto* tv = dynamic_cast<TreeView*> (sourceDetails.sourceComponent.get()))
|
||
|
{
|
||
|
Array<JucerTreeViewBase*> selected;
|
||
|
|
||
|
for (int i = tv->getNumSelectedItems(); --i >= 0;)
|
||
|
if (auto* b = dynamic_cast<JucerTreeViewBase*> (tv->getSelectedItem(i)))
|
||
|
selected.add (b);
|
||
|
|
||
|
if (! selected.isEmpty())
|
||
|
{
|
||
|
for (int i = selected.size(); --i >= 0;)
|
||
|
{
|
||
|
if (auto* jtvb = selected.getUnchecked(i))
|
||
|
{
|
||
|
auto f = jtvb->getDraggableFile();
|
||
|
|
||
|
if (f.existsAsFile())
|
||
|
files.add (f.getFullPathName());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
canMoveFiles = false;
|
||
|
return ! files.isEmpty();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void MainWindow::activeWindowStatusChanged()
|
||
|
{
|
||
|
DocumentWindow::activeWindowStatusChanged();
|
||
|
|
||
|
if (auto* pcc = getProjectContentComponent())
|
||
|
pcc->updateMissingFileStatuses();
|
||
|
|
||
|
ProjucerApplication::getApp().openDocumentManager.reloadModifiedFiles();
|
||
|
}
|
||
|
|
||
|
void MainWindow::initialiseProjectWindow()
|
||
|
{
|
||
|
setResizable (true, false);
|
||
|
updateTitleBarIcon();
|
||
|
}
|
||
|
|
||
|
void MainWindow::showStartPage()
|
||
|
{
|
||
|
jassert (currentProject == nullptr);
|
||
|
|
||
|
setContentOwned (new StartPageComponent ([this] (std::unique_ptr<Project>&& newProject) { setProject (std::move (newProject)); },
|
||
|
[this] (const File& exampleFile) { openFile (exampleFile, nullptr); }),
|
||
|
true);
|
||
|
|
||
|
setResizable (false, false);
|
||
|
setName ("New Project");
|
||
|
addToDesktop();
|
||
|
centreWithSize (getContentComponent()->getWidth(), getContentComponent()->getHeight());
|
||
|
|
||
|
setVisible (true);
|
||
|
getContentComponent()->grabKeyboardFocus();
|
||
|
}
|
||
|
|
||
|
void MainWindow::showLoginFormOverlay()
|
||
|
{
|
||
|
blurOverlayComponent = std::make_unique<BlurOverlayWithComponent> (*this, std::make_unique<LoginFormComponent> (*this));
|
||
|
loginFormOpen = true;
|
||
|
}
|
||
|
|
||
|
void MainWindow::hideLoginFormOverlay()
|
||
|
{
|
||
|
blurOverlayComponent.reset();
|
||
|
loginFormOpen = false;
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
ApplicationCommandTarget* MainWindow::getNextCommandTarget()
|
||
|
{
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
void MainWindow::getAllCommands (Array <CommandID>& commands)
|
||
|
{
|
||
|
const CommandID ids[] =
|
||
|
{
|
||
|
CommandIDs::closeWindow,
|
||
|
CommandIDs::goToPreviousWindow,
|
||
|
CommandIDs::goToNextWindow
|
||
|
};
|
||
|
|
||
|
commands.addArray (ids, numElementsInArray (ids));
|
||
|
}
|
||
|
|
||
|
void MainWindow::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
|
||
|
{
|
||
|
switch (commandID)
|
||
|
{
|
||
|
case CommandIDs::closeWindow:
|
||
|
result.setInfo ("Close Window", "Closes the current window", CommandCategories::general, 0);
|
||
|
result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier, 0));
|
||
|
break;
|
||
|
|
||
|
case CommandIDs::goToPreviousWindow:
|
||
|
result.setInfo ("Previous Window", "Activates the previous window", CommandCategories::general, 0);
|
||
|
result.setActive (ProjucerApplication::getApp().mainWindowList.windows.size() > 1);
|
||
|
result.defaultKeypresses.add (KeyPress (KeyPress::tabKey, ModifierKeys::shiftModifier | ModifierKeys::ctrlModifier, 0));
|
||
|
break;
|
||
|
|
||
|
case CommandIDs::goToNextWindow:
|
||
|
result.setInfo ("Next Window", "Activates the next window", CommandCategories::general, 0);
|
||
|
result.setActive (ProjucerApplication::getApp().mainWindowList.windows.size() > 1);
|
||
|
result.defaultKeypresses.add (KeyPress (KeyPress::tabKey, ModifierKeys::ctrlModifier, 0));
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool MainWindow::perform (const InvocationInfo& info)
|
||
|
{
|
||
|
switch (info.commandID)
|
||
|
{
|
||
|
case CommandIDs::closeWindow:
|
||
|
closeButtonPressed();
|
||
|
break;
|
||
|
|
||
|
case CommandIDs::goToPreviousWindow:
|
||
|
ProjucerApplication::getApp().mainWindowList.goToSiblingWindow (this, -1);
|
||
|
break;
|
||
|
|
||
|
case CommandIDs::goToNextWindow:
|
||
|
ProjucerApplication::getApp().mainWindowList.goToSiblingWindow (this, 1);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void MainWindow::valueChanged (Value& value)
|
||
|
{
|
||
|
if (value == projectNameValue)
|
||
|
setName (currentProject != nullptr ? currentProject->getProjectNameString() + " - Projucer"
|
||
|
: "Projucer");
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
MainWindowList::MainWindowList()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void MainWindowList::forceCloseAllWindows()
|
||
|
{
|
||
|
windows.clear();
|
||
|
}
|
||
|
|
||
|
static void askAllWindowsToCloseRecursive (WeakReference<MainWindowList> parent, std::function<void (bool)> callback)
|
||
|
{
|
||
|
if (parent->windows.size() == 0)
|
||
|
{
|
||
|
if (callback != nullptr)
|
||
|
callback (true);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
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()
|
||
|
{
|
||
|
if (windows.isEmpty())
|
||
|
createNewMainWindow()->showStartPage();
|
||
|
}
|
||
|
|
||
|
void MainWindowList::closeWindow (MainWindow* w)
|
||
|
{
|
||
|
jassert (windows.contains (w));
|
||
|
|
||
|
#if ! JUCE_MAC
|
||
|
if (windows.size() == 1 && ! isInReopenLastProjects)
|
||
|
{
|
||
|
JUCEApplicationBase::getInstance()->systemRequestedQuit();
|
||
|
}
|
||
|
else
|
||
|
#endif
|
||
|
{
|
||
|
w->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes,
|
||
|
[parent = WeakReference<MainWindowList> { this }, w] (bool closedSuccessfully)
|
||
|
{
|
||
|
if (parent == nullptr)
|
||
|
return;
|
||
|
|
||
|
if (closedSuccessfully)
|
||
|
{
|
||
|
parent->windows.removeObject (w);
|
||
|
parent->saveCurrentlyOpenProjectList();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void MainWindowList::goToSiblingWindow (MainWindow* w, int delta)
|
||
|
{
|
||
|
auto index = windows.indexOf (w);
|
||
|
|
||
|
if (index >= 0)
|
||
|
if (auto* next = windows[(index + delta + windows.size()) % windows.size()])
|
||
|
next->toFront (true);
|
||
|
}
|
||
|
|
||
|
void MainWindowList::openDocument (OpenDocumentManager::Document* doc, bool grabFocus)
|
||
|
{
|
||
|
auto& desktop = Desktop::getInstance();
|
||
|
|
||
|
for (int i = desktop.getNumComponents(); --i >= 0;)
|
||
|
{
|
||
|
if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
|
||
|
{
|
||
|
if (auto* pcc = mw->getProjectContentComponent())
|
||
|
{
|
||
|
if (pcc->hasFileInRecentList (doc->getFile()))
|
||
|
{
|
||
|
mw->toFront (true);
|
||
|
mw->getProjectContentComponent()->showDocument (doc, grabFocus);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
getFrontmostWindow()->getProjectContentComponent()->showDocument (doc, grabFocus);
|
||
|
}
|
||
|
|
||
|
void MainWindowList::openFile (const File& file, std::function<void (bool)> callback, bool openInBackground)
|
||
|
{
|
||
|
if (! file.exists())
|
||
|
{
|
||
|
if (callback != nullptr)
|
||
|
callback (false);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for (auto* w : windows)
|
||
|
{
|
||
|
if (w->getProject() != nullptr && w->getProject()->getFile() == file)
|
||
|
{
|
||
|
w->toFront (true);
|
||
|
|
||
|
if (callback != nullptr)
|
||
|
callback (true);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
WeakReference<MainWindowList> parent { this };
|
||
|
|
||
|
if (file.hasFileExtension (Project::projectFileExtension)
|
||
|
|| isPIPFile (file))
|
||
|
{
|
||
|
WeakReference<Component> previousFrontWindow (getFrontmostWindow());
|
||
|
|
||
|
auto* w = getOrCreateEmptyWindow();
|
||
|
jassert (w != nullptr);
|
||
|
|
||
|
w->openFile (file, [parent, previousFrontWindow, w, openInBackground, callback] (bool openedSuccessfully)
|
||
|
{
|
||
|
if (parent == nullptr)
|
||
|
return;
|
||
|
|
||
|
if (openedSuccessfully)
|
||
|
{
|
||
|
w->makeVisible();
|
||
|
w->setResizable (true, false);
|
||
|
parent->checkWindowBounds (*w);
|
||
|
|
||
|
if (openInBackground && previousFrontWindow != nullptr)
|
||
|
previousFrontWindow->toFront (true);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
parent->closeWindow (w);
|
||
|
}
|
||
|
|
||
|
if (callback != nullptr)
|
||
|
callback (openedSuccessfully);
|
||
|
});
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
getFrontmostWindow()->openFile (file, [parent, callback] (bool openedSuccessfully)
|
||
|
{
|
||
|
if (parent != nullptr && callback != nullptr)
|
||
|
callback (openedSuccessfully);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
MainWindow* MainWindowList::createNewMainWindow()
|
||
|
{
|
||
|
windows.add (new MainWindow());
|
||
|
return windows.getLast();
|
||
|
}
|
||
|
|
||
|
MainWindow* MainWindowList::getFrontmostWindow (bool createIfNotFound)
|
||
|
{
|
||
|
if (windows.isEmpty())
|
||
|
{
|
||
|
if (createIfNotFound)
|
||
|
{
|
||
|
auto* w = createNewMainWindow();
|
||
|
jassert (w != nullptr);
|
||
|
|
||
|
w->makeVisible();
|
||
|
checkWindowBounds (*w);
|
||
|
|
||
|
return w;
|
||
|
}
|
||
|
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
|
||
|
{
|
||
|
auto* mw = dynamic_cast<MainWindow*> (Desktop::getInstance().getComponent (i));
|
||
|
|
||
|
if (windows.contains (mw))
|
||
|
return mw;
|
||
|
}
|
||
|
|
||
|
return windows.getLast();
|
||
|
}
|
||
|
|
||
|
MainWindow* MainWindowList::getOrCreateEmptyWindow()
|
||
|
{
|
||
|
if (windows.size() == 0)
|
||
|
return createNewMainWindow();
|
||
|
|
||
|
for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
|
||
|
{
|
||
|
auto* mw = dynamic_cast<MainWindow*> (Desktop::getInstance().getComponent (i));
|
||
|
|
||
|
if (windows.contains (mw) && mw->getProject() == nullptr)
|
||
|
return mw;
|
||
|
}
|
||
|
|
||
|
return createNewMainWindow();
|
||
|
}
|
||
|
|
||
|
MainWindow* MainWindowList::getMainWindowForFile (const File& file)
|
||
|
{
|
||
|
if (windows.size() > 0)
|
||
|
{
|
||
|
for (auto* window : windows)
|
||
|
{
|
||
|
if (auto* project = window->getProject())
|
||
|
{
|
||
|
if (project->getFile() == file)
|
||
|
return window;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
MainWindow* MainWindowList::getMainWindowWithLoginFormOpen()
|
||
|
{
|
||
|
for (auto* window : windows)
|
||
|
if (window->isShowingLoginForm())
|
||
|
return window;
|
||
|
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
void MainWindowList::checkWindowBounds (MainWindow& windowToCheck)
|
||
|
{
|
||
|
auto avoidSuperimposedWindows = [&]
|
||
|
{
|
||
|
for (auto* otherWindow : windows)
|
||
|
{
|
||
|
if (otherWindow == nullptr || otherWindow == &windowToCheck)
|
||
|
continue;
|
||
|
|
||
|
auto boundsToCheck = windowToCheck.getScreenBounds();
|
||
|
auto otherBounds = otherWindow->getScreenBounds();
|
||
|
|
||
|
if (std::abs (boundsToCheck.getX() - otherBounds.getX()) < 3
|
||
|
&& std::abs (boundsToCheck.getY() - otherBounds.getY()) < 3
|
||
|
&& std::abs (boundsToCheck.getRight() - otherBounds.getRight()) < 3
|
||
|
&& std::abs (boundsToCheck.getBottom() - otherBounds.getBottom()) < 3)
|
||
|
{
|
||
|
int dx = 40, dy = 30;
|
||
|
|
||
|
if (otherBounds.getCentreX() >= boundsToCheck.getCentreX()) dx = -dx;
|
||
|
if (otherBounds.getCentreY() >= boundsToCheck.getCentreY()) dy = -dy;
|
||
|
|
||
|
windowToCheck.setBounds (boundsToCheck.translated (dx, dy));
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
auto ensureWindowIsFullyOnscreen = [&]
|
||
|
{
|
||
|
auto windowBounds = windowToCheck.getScreenBounds();
|
||
|
auto screenLimits = Desktop::getInstance().getDisplays().getDisplayForRect (windowBounds)->userArea;
|
||
|
|
||
|
if (auto* peer = windowToCheck.getPeer())
|
||
|
peer->getFrameSize().subtractFrom (screenLimits);
|
||
|
|
||
|
auto constrainedX = jlimit (screenLimits.getX(), jmax (screenLimits.getX(), screenLimits.getRight() - windowBounds.getWidth()), windowBounds.getX());
|
||
|
auto constrainedY = jlimit (screenLimits.getY(), jmax (screenLimits.getY(), screenLimits.getBottom() - windowBounds.getHeight()), windowBounds.getY());
|
||
|
|
||
|
Point<int> constrainedTopLeft (constrainedX, constrainedY);
|
||
|
|
||
|
if (windowBounds.getPosition() != constrainedTopLeft)
|
||
|
windowToCheck.setTopLeftPosition (constrainedTopLeft);
|
||
|
};
|
||
|
|
||
|
avoidSuperimposedWindows();
|
||
|
ensureWindowIsFullyOnscreen();
|
||
|
}
|
||
|
|
||
|
void MainWindowList::saveCurrentlyOpenProjectList()
|
||
|
{
|
||
|
Array<File> projects;
|
||
|
auto& desktop = Desktop::getInstance();
|
||
|
|
||
|
for (int i = 0; i < desktop.getNumComponents(); ++i)
|
||
|
{
|
||
|
if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
|
||
|
if (auto* p = mw->getProject())
|
||
|
if (! p->isTemporaryProject())
|
||
|
projects.add (p->getFile());
|
||
|
}
|
||
|
|
||
|
getAppSettings().setLastProjects (projects);
|
||
|
}
|
||
|
|
||
|
void MainWindowList::reopenLastProjects()
|
||
|
{
|
||
|
const ScopedValueSetter<bool> setter (isInReopenLastProjects, true);
|
||
|
|
||
|
for (auto& p : getAppSettings().getLastProjects())
|
||
|
if (p.existsAsFile())
|
||
|
openFile (p, nullptr, true);
|
||
|
}
|
||
|
|
||
|
void MainWindowList::sendLookAndFeelChange()
|
||
|
{
|
||
|
for (auto* w : windows)
|
||
|
w->sendLookAndFeelChange();
|
||
|
}
|
||
|
|
||
|
Project* MainWindowList::getFrontmostProject()
|
||
|
{
|
||
|
auto& desktop = Desktop::getInstance();
|
||
|
|
||
|
for (int i = desktop.getNumComponents(); --i >= 0;)
|
||
|
if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
|
||
|
if (auto* p = mw->getProject())
|
||
|
return p;
|
||
|
|
||
|
return nullptr;
|
||
|
}
|