- clean working copy for leaving SVN

This commit is contained in:
2022-06-28 19:04:46 +02:00
parent 62a2d47cb3
commit 762b8f0e7d
1966 changed files with 699872 additions and 126 deletions
@@ -0,0 +1,628 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#include "jucer_Application.h"
#include "jucer_AppearanceSettings.h"
namespace AppearanceColours
{
struct ColourInfo
{
const char* name;
int colourID;
bool mustBeOpaque;
bool applyToEditorOnly;
};
static const ColourInfo colours[] =
{
{ "Main Window Bkgd", mainBackgroundColourId, true, false },
{ "Treeview Highlight", TreeView::selectedItemBackgroundColourId, false, false },
{ "Code Background", CodeEditorComponent::backgroundColourId, true, false },
{ "Line Number Bkgd", CodeEditorComponent::lineNumberBackgroundId, false, false },
{ "Line Numbers", CodeEditorComponent::lineNumberTextId, false, false },
{ "Plain Text", CodeEditorComponent::defaultTextColourId, false, false },
{ "Selected Text Bkgd", CodeEditorComponent::highlightColourId, false, false },
{ "Caret", CaretComponent::caretColourId, false, true }
};
enum
{
numColours = sizeof (AppearanceColours::colours) / sizeof (AppearanceColours::colours[0])
};
}
//==============================================================================
AppearanceSettings::AppearanceSettings (bool updateAppWhenChanged)
: settings ("COLOUR_SCHEME")
{
if (! IntrojucerApp::getApp().isRunningCommandLine)
{
IntrojucerLookAndFeel lf;
for (int i = 0; i < AppearanceColours::numColours; ++i)
getColourValue (AppearanceColours::colours[i].name) = lf.findColour (AppearanceColours::colours[i].colourID).toString();
CodeDocument doc;
CPlusPlusCodeTokeniser tokeniser;
CodeEditorComponent editor (doc, &tokeniser);
const CodeEditorComponent::ColourScheme cs (editor.getColourScheme());
for (int i = cs.types.size(); --i >= 0;)
{
CodeEditorComponent::ColourScheme::TokenType& t = cs.types.getReference(i);
getColourValue (t.name) = t.colour.toString();
}
getCodeFontValue() = getDefaultCodeFont().toString();
if (updateAppWhenChanged)
settings.addListener (this);
}
}
File AppearanceSettings::getSchemesFolder()
{
File f (getGlobalProperties().getFile().getSiblingFile ("Schemes"));
f.createDirectory();
return f;
}
void AppearanceSettings::writeDefaultSchemeFile (const String& xmlString, const String& name)
{
const File file (getSchemesFolder().getChildFile (name).withFileExtension (getSchemeFileSuffix()));
AppearanceSettings settings (false);
ScopedPointer<XmlElement> xml (XmlDocument::parse (xmlString));
if (xml != nullptr)
settings.readFromXML (*xml);
settings.writeToFile (file);
}
void AppearanceSettings::refreshPresetSchemeList()
{
writeDefaultSchemeFile (BinaryData::colourscheme_dark_xml, "Default (Dark)");
writeDefaultSchemeFile (BinaryData::colourscheme_light_xml, "Default (Light)");
Array<File> newSchemes;
getSchemesFolder().findChildFiles (newSchemes, File::findFiles, false, String ("*") + getSchemeFileSuffix());
if (newSchemes != presetSchemeFiles)
{
presetSchemeFiles.swapWith (newSchemes);
IntrojucerApp::getCommandManager().commandStatusChanged();
}
}
StringArray AppearanceSettings::getPresetSchemes()
{
StringArray s;
for (int i = 0; i < presetSchemeFiles.size(); ++i)
s.add (presetSchemeFiles.getReference(i).getFileNameWithoutExtension());
return s;
}
void AppearanceSettings::selectPresetScheme (int index)
{
readFromFile (presetSchemeFiles [index]);
}
bool AppearanceSettings::readFromXML (const XmlElement& xml)
{
if (xml.hasTagName (settings.getType().toString()))
{
const ValueTree newSettings (ValueTree::fromXml (xml));
// we'll manually copy across the new properties to the existing tree so that
// any open editors will be kept up to date..
settings.copyPropertiesFrom (newSettings, nullptr);
for (int i = settings.getNumChildren(); --i >= 0;)
{
ValueTree c (settings.getChild (i));
const ValueTree newValue (newSettings.getChildWithProperty (Ids::name, c.getProperty (Ids::name)));
if (newValue.isValid())
c.copyPropertiesFrom (newValue, nullptr);
}
return true;
}
return false;
}
bool AppearanceSettings::readFromFile (const File& file)
{
const ScopedPointer<XmlElement> xml (XmlDocument::parse (file));
return xml != nullptr && readFromXML (*xml);
}
bool AppearanceSettings::writeToFile (const File& file) const
{
const ScopedPointer<XmlElement> xml (settings.createXml());
return xml != nullptr && xml->writeToFile (file, String::empty);
}
Font AppearanceSettings::getDefaultCodeFont()
{
return Font (Font::getDefaultMonospacedFontName(), Font::getDefaultStyle(), 13.0f);
}
StringArray AppearanceSettings::getColourNames() const
{
StringArray s;
for (int i = 0; i < settings.getNumChildren(); ++i)
{
const ValueTree c (settings.getChild(i));
if (c.hasType ("COLOUR"))
s.add (c [Ids::name]);
}
return s;
}
void AppearanceSettings::updateColourScheme()
{
applyToLookAndFeel (LookAndFeel::getDefaultLookAndFeel());
IntrojucerApp::getApp().mainWindowList.sendLookAndFeelChange();
}
void AppearanceSettings::applyToLookAndFeel (LookAndFeel& lf) const
{
for (int i = 0; i < AppearanceColours::numColours; ++i)
{
Colour col;
if (getColour (AppearanceColours::colours[i].name, col))
{
if (AppearanceColours::colours[i].mustBeOpaque)
col = Colours::white.overlaidWith (col);
if (! AppearanceColours::colours[i].applyToEditorOnly)
lf.setColour (AppearanceColours::colours[i].colourID, col);
}
}
lf.setColour (ScrollBar::thumbColourId, lf.findColour (mainBackgroundColourId).contrasting().withAlpha (0.13f));
}
void AppearanceSettings::applyToCodeEditor (CodeEditorComponent& editor) const
{
CodeEditorComponent::ColourScheme cs (editor.getColourScheme());
for (int i = cs.types.size(); --i >= 0;)
{
CodeEditorComponent::ColourScheme::TokenType& t = cs.types.getReference(i);
getColour (t.name, t.colour);
}
editor.setColourScheme (cs);
editor.setFont (getCodeFont());
for (int i = 0; i < AppearanceColours::numColours; ++i)
{
if (AppearanceColours::colours[i].applyToEditorOnly)
{
Colour col;
if (getColour (AppearanceColours::colours[i].name, col))
editor.setColour (AppearanceColours::colours[i].colourID, col);
}
}
editor.setColour (ScrollBar::thumbColourId, editor.findColour (CodeEditorComponent::backgroundColourId)
.contrasting()
.withAlpha (0.13f));
}
Font AppearanceSettings::getCodeFont() const
{
const String fontString (settings [Ids::font].toString());
if (fontString.isEmpty())
return getDefaultCodeFont();
return Font::fromString (fontString);
}
Value AppearanceSettings::getCodeFontValue()
{
return settings.getPropertyAsValue (Ids::font, nullptr);
}
Value AppearanceSettings::getColourValue (const String& colourName)
{
ValueTree c (settings.getChildWithProperty (Ids::name, colourName));
if (! c.isValid())
{
c = ValueTree ("COLOUR");
c.setProperty (Ids::name, colourName, nullptr);
settings.addChild (c, -1, nullptr);
}
return c.getPropertyAsValue (Ids::colour, nullptr);
}
bool AppearanceSettings::getColour (const String& name, Colour& result) const
{
const ValueTree colour (settings.getChildWithProperty (Ids::name, name));
if (colour.isValid())
{
result = Colour::fromString (colour [Ids::colour].toString());
return true;
}
return false;
}
//==============================================================================
struct AppearanceEditor
{
class FontScanPanel : public Component,
private Timer
{
public:
FontScanPanel()
{
fontsToScan = Font::findAllTypefaceNames();
startTimer (1);
}
void paint (Graphics& g) override
{
g.fillAll (Colours::darkgrey);
g.setFont (14.0f);
g.setColour (Colours::white);
g.drawFittedText ("Scanning for fonts..", getLocalBounds(), Justification::centred, 2);
const int size = 30;
getLookAndFeel().drawSpinningWaitAnimation (g, Colours::white, (getWidth() - size) / 2, getHeight() / 2 - 50, size, size);
}
void timerCallback() override
{
repaint();
if (fontsToScan.size() == 0)
{
getAppSettings().monospacedFontNames = fontsFound;
if (DialogWindow* w = findParentComponentOfClass<DialogWindow>())
w->setContentOwned (new EditorPanel(), false);
}
else
{
if (isMonospacedTypeface (fontsToScan[0]))
fontsFound.add (fontsToScan[0]);
fontsToScan.remove (0);
}
}
// A rather hacky trick to select only the fixed-pitch fonts..
// This is unfortunately a bit slow, but will work on all platforms.
static bool isMonospacedTypeface (const String& name)
{
const Font font (name, 20.0f, Font::plain);
const int width = font.getStringWidth ("....");
return width == font.getStringWidth ("WWWW")
&& width == font.getStringWidth ("0000")
&& width == font.getStringWidth ("1111")
&& width == font.getStringWidth ("iiii");
}
StringArray fontsToScan, fontsFound;
};
//==============================================================================
class EditorPanel : public Component,
private ButtonListener
{
public:
EditorPanel()
: loadButton ("Load Scheme..."),
saveButton ("Save Scheme...")
{
rebuildProperties();
addAndMakeVisible (panel);
loadButton.setColour (TextButton::buttonColourId, Colours::lightgrey.withAlpha (0.5f));
saveButton.setColour (TextButton::buttonColourId, Colours::lightgrey.withAlpha (0.5f));
loadButton.setColour (TextButton::textColourOffId, Colours::white);
saveButton.setColour (TextButton::textColourOffId, Colours::white);
addAndMakeVisible (loadButton);
addAndMakeVisible (saveButton);
loadButton.addListener (this);
saveButton.addListener (this);
}
void rebuildProperties()
{
AppearanceSettings& scheme = getAppSettings().appearance;
Array <PropertyComponent*> props;
Value fontValue (scheme.getCodeFontValue());
props.add (FontNameValueSource::createProperty ("Code Editor Font", fontValue));
props.add (FontSizeValueSource::createProperty ("Font Size", fontValue));
const StringArray colourNames (scheme.getColourNames());
for (int i = 0; i < colourNames.size(); ++i)
props.add (new ColourPropertyComponent (nullptr, colourNames[i],
scheme.getColourValue (colourNames[i]),
Colours::white, false));
panel.clear();
panel.addProperties (props);
}
void resized() override
{
Rectangle<int> r (getLocalBounds());
panel.setBounds (r.removeFromTop (getHeight() - 28).reduced (4, 2));
loadButton.setBounds (r.removeFromLeft (getWidth() / 2).reduced (10, 4));
saveButton.setBounds (r.reduced (10, 3));
}
private:
PropertyPanel panel;
TextButton loadButton, saveButton;
void buttonClicked (Button* b) override
{
if (b == &loadButton)
loadScheme();
else
saveScheme();
}
void saveScheme()
{
FileChooser fc ("Select a file in which to save this colour-scheme...",
getAppSettings().appearance.getSchemesFolder()
.getNonexistentChildFile ("Scheme", AppearanceSettings::getSchemeFileSuffix()),
AppearanceSettings::getSchemeFileWildCard());
if (fc.browseForFileToSave (true))
{
File file (fc.getResult().withFileExtension (AppearanceSettings::getSchemeFileSuffix()));
getAppSettings().appearance.writeToFile (file);
getAppSettings().appearance.refreshPresetSchemeList();
}
}
void loadScheme()
{
FileChooser fc ("Please select a colour-scheme file to load...",
getAppSettings().appearance.getSchemesFolder(),
AppearanceSettings::getSchemeFileWildCard());
if (fc.browseForFileToOpen())
{
if (getAppSettings().appearance.readFromFile (fc.getResult()))
rebuildProperties();
}
}
JUCE_DECLARE_NON_COPYABLE (EditorPanel)
};
//==============================================================================
class FontNameValueSource : public ValueSourceFilter
{
public:
FontNameValueSource (const Value& source) : ValueSourceFilter (source) {}
var getValue() const override
{
return Font::fromString (sourceValue.toString()).getTypefaceName();
}
void setValue (const var& newValue) override
{
Font font (Font::fromString (sourceValue.toString()));
font.setTypefaceName (newValue.toString().isEmpty() ? Font::getDefaultMonospacedFontName()
: newValue.toString());
sourceValue = font.toString();
}
static ChoicePropertyComponent* createProperty (const String& title, const Value& value)
{
StringArray fontNames = getAppSettings().monospacedFontNames;
Array<var> values;
values.add (Font::getDefaultMonospacedFontName());
values.add (var());
for (int i = 0; i < fontNames.size(); ++i)
values.add (fontNames[i]);
StringArray names;
names.add ("<Default Monospaced>");
names.add (String::empty);
names.addArray (getAppSettings().monospacedFontNames);
return new ChoicePropertyComponent (Value (new FontNameValueSource (value)),
title, names, values);
}
};
//==============================================================================
class FontSizeValueSource : public ValueSourceFilter
{
public:
FontSizeValueSource (const Value& source) : ValueSourceFilter (source) {}
var getValue() const override
{
return Font::fromString (sourceValue.toString()).getHeight();
}
void setValue (const var& newValue) override
{
sourceValue = Font::fromString (sourceValue.toString()).withHeight (newValue).toString();
}
static PropertyComponent* createProperty (const String& title, const Value& value)
{
return new SliderPropertyComponent (Value (new FontSizeValueSource (value)),
title, 5.0, 40.0, 0.1, 0.5);
}
};
};
void AppearanceSettings::showEditorWindow (ScopedPointer<Component>& ownerPointer)
{
if (ownerPointer != nullptr)
{
ownerPointer->toFront (true);
}
else
{
Component* content;
if (getAppSettings().monospacedFontNames.size() == 0)
content = new AppearanceEditor::FontScanPanel();
else
content = new AppearanceEditor::EditorPanel();
const int width = 350;
new FloatingToolWindow ("Appearance Settings",
"colourSchemeEditorPos",
content, ownerPointer,
width, 500,
width, 200, width, 1000);
}
}
//==============================================================================
IntrojucerLookAndFeel::IntrojucerLookAndFeel()
{
setColour (mainBackgroundColourId, Colour::greyLevel (0.8f));
}
int IntrojucerLookAndFeel::getTabButtonBestWidth (TabBarButton&, int) { return 120; }
static Colour getTabBackgroundColour (TabBarButton& button)
{
const Colour bkg (button.findColour (mainBackgroundColourId).contrasting (0.15f));
if (button.isFrontTab())
return bkg.overlaidWith (Colours::yellow.withAlpha (0.5f));
return bkg;
}
void IntrojucerLookAndFeel::drawTabButton (TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown)
{
const Rectangle<int> activeArea (button.getActiveArea());
const Colour bkg (getTabBackgroundColour (button));
g.setGradientFill (ColourGradient (bkg.brighter (0.1f), 0, (float) activeArea.getY(),
bkg.darker (0.1f), 0, (float) activeArea.getBottom(), false));
g.fillRect (activeArea);
g.setColour (button.findColour (mainBackgroundColourId).darker (0.3f));
g.drawRect (activeArea);
const float alpha = button.isEnabled() ? ((isMouseOver || isMouseDown) ? 1.0f : 0.8f) : 0.3f;
const Colour col (bkg.contrasting().withMultipliedAlpha (alpha));
TextLayout textLayout;
LookAndFeel_V3::createTabTextLayout (button, (float) activeArea.getWidth(), (float) activeArea.getHeight(), col, textLayout);
textLayout.draw (g, button.getTextArea().toFloat());
}
static Range<float> getBrightnessRange (const Image& im)
{
float minB = 1.0f, maxB = 0;
const int w = im.getWidth();
const int h = im.getHeight();
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
const float b = im.getPixelAt (x, y).getBrightness();
minB = jmin (minB, b);
maxB = jmax (maxB, b);
}
}
return Range<float> (minB, maxB);
}
void IntrojucerLookAndFeel::fillWithBackgroundTexture (Graphics& g)
{
const Colour bkg (findColour (mainBackgroundColourId));
if (backgroundTextureBaseColour != bkg)
{
backgroundTextureBaseColour = bkg;
const Image original (ImageCache::getFromMemory (BinaryData::background_tile_png,
BinaryData::background_tile_pngSize));
const int w = original.getWidth();
const int h = original.getHeight();
backgroundTexture = Image (Image::RGB, w, h, false);
const Range<float> brightnessRange (getBrightnessRange (original));
const float brightnessOffset = (brightnessRange.getStart() + brightnessRange.getEnd()) / 2.0f;
const float brightnessScale = 0.025f / brightnessRange.getLength();
const float bkgB = bkg.getBrightness();
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
const float b = (original.getPixelAt (x, y).getBrightness() - brightnessOffset) * brightnessScale;
backgroundTexture.setPixelAt (x, y, bkg.withBrightness (jlimit (0.0f, 1.0f, bkgB + b)));
}
}
}
g.setTiledImageFill (backgroundTexture, 0, 0, 1.0f);
g.fillAll();
}
void IntrojucerLookAndFeel::fillWithBackgroundTexture (Component& c, Graphics& g)
{
dynamic_cast<IntrojucerLookAndFeel&> (c.getLookAndFeel()).fillWithBackgroundTexture (g);
}
@@ -0,0 +1,100 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef __JUCER_APPEARANCESETTINGS_JUCEHEADER__
#define __JUCER_APPEARANCESETTINGS_JUCEHEADER__
class AppearanceSettings : private ValueTree::Listener
{
public:
AppearanceSettings (bool updateAppWhenChanged);
bool readFromFile (const File& file);
bool readFromXML (const XmlElement&);
bool writeToFile (const File& file) const;
void updateColourScheme();
void applyToCodeEditor (CodeEditorComponent& editor) const;
StringArray getColourNames() const;
Value getColourValue (const String& colourName);
bool getColour (const String& name, Colour& resultIfFound) const;
Font getCodeFont() const;
Value getCodeFontValue();
ValueTree settings;
static File getSchemesFolder();
StringArray getPresetSchemes();
void refreshPresetSchemeList();
void selectPresetScheme (int index);
static Font getDefaultCodeFont();
static void showEditorWindow (ScopedPointer<Component>& ownerPointer);
static const char* getSchemeFileSuffix() { return ".scheme"; }
static const char* getSchemeFileWildCard() { return "*.scheme"; }
private:
Array<File> presetSchemeFiles;
static void writeDefaultSchemeFile (const String& xml, const String& name);
void applyToLookAndFeel (LookAndFeel&) const;
void valueTreePropertyChanged (ValueTree&, const Identifier&) override { updateColourScheme(); }
void valueTreeChildAdded (ValueTree&, ValueTree&) override { updateColourScheme(); }
void valueTreeChildRemoved (ValueTree&, ValueTree&) override { updateColourScheme(); }
void valueTreeChildOrderChanged (ValueTree&) override { updateColourScheme(); }
void valueTreeParentChanged (ValueTree&) override { updateColourScheme(); }
void valueTreeRedirected (ValueTree&) override { updateColourScheme(); }
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AppearanceSettings)
};
//==============================================================================
class IntrojucerLookAndFeel : public LookAndFeel_V3
{
public:
IntrojucerLookAndFeel();
void fillWithBackgroundTexture (Graphics&);
static void fillWithBackgroundTexture (Component&, Graphics&);
void drawTabButton (TabBarButton& button, Graphics&, bool isMouseOver, bool isMouseDown) override;
void drawTabAreaBehindFrontButton (TabbedButtonBar&, Graphics&, int, int) override {}
int getTabButtonBestWidth (TabBarButton&, int tabDepth) override;
private:
Image backgroundTexture;
Colour backgroundTextureBaseColour;
};
#endif // __JUCER_APPEARANCESETTINGS_JUCEHEADER__
@@ -0,0 +1,587 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef __JUCER_APPLICATION_JUCEHEADER__
#define __JUCER_APPLICATION_JUCEHEADER__
#include "../jucer_Headers.h"
#include "jucer_MainWindow.h"
#include "jucer_CommandLine.h"
#include "../Project/jucer_Module.h"
#include "jucer_AutoUpdater.h"
#include "../Code Editor/jucer_SourceCodeEditor.h"
void createGUIEditorMenu (PopupMenu&);
void handleGUIEditorMenuCommand (int);
void registerGUIEditorCommands();
//==============================================================================
class IntrojucerApp : public JUCEApplication
{
public:
//==============================================================================
IntrojucerApp() : isRunningCommandLine (false) {}
//==============================================================================
void initialise (const String& commandLine) override
{
LookAndFeel::setDefaultLookAndFeel (&lookAndFeel);
settings = new StoredSettings();
if (commandLine.isNotEmpty())
{
const int appReturnCode = performCommandLine (commandLine);
if (appReturnCode != commandLineNotPerformed)
{
isRunningCommandLine = true;
setApplicationReturnValue (appReturnCode);
quit();
return;
}
}
if (sendCommandLineToPreexistingInstance())
{
DBG ("Another instance is running - quitting...");
quit();
return;
}
initialiseLogger ("log_");
icons = new Icons();
initCommandManager();
menuModel = new MainMenuModel();
doExtraInitialisation();
settings->appearance.refreshPresetSchemeList();
ImageCache::setCacheTimeout (30 * 1000);
if (commandLine.trim().isNotEmpty() && ! commandLine.trim().startsWithChar ('-'))
anotherInstanceStarted (commandLine);
else
mainWindowList.reopenLastProjects();
mainWindowList.createWindowIfNoneAreOpen();
#if JUCE_MAC
MenuBarModel::setMacMainMenu (menuModel, nullptr, "Open Recent");
#endif
versionChecker = new LatestVersionChecker();
}
void shutdown() override
{
versionChecker = nullptr;
appearanceEditorWindow = nullptr;
utf8Window = nullptr;
svgPathWindow = nullptr;
mainWindowList.forceCloseAllWindows();
openDocumentManager.clear();
#if JUCE_MAC
MenuBarModel::setMacMainMenu (nullptr);
#endif
menuModel = nullptr;
commandManager = nullptr;
settings = nullptr;
LookAndFeel::setDefaultLookAndFeel (nullptr);
if (! isRunningCommandLine)
Logger::writeToLog ("Shutdown");
deleteLogger();
}
//==============================================================================
void systemRequestedQuit() override
{
if (ModalComponentManager::getInstance()->cancelAllModalComponents())
{
new AsyncQuitRetrier();
}
else
{
if (closeAllMainWindows())
quit();
}
}
//==============================================================================
const String getApplicationName() override { return "Introjucer"; }
const String getApplicationVersion() override { return ProjectInfo::versionString; }
bool moreThanOneInstanceAllowed() override
{
return true; // this is handled manually in initialise()
}
void anotherInstanceStarted (const String& commandLine) override
{
openFile (File (commandLine.unquoted()));
}
static IntrojucerApp& getApp()
{
IntrojucerApp* const app = dynamic_cast<IntrojucerApp*> (JUCEApplication::getInstance());
jassert (app != nullptr);
return *app;
}
static ApplicationCommandManager& getCommandManager()
{
ApplicationCommandManager* cm = IntrojucerApp::getApp().commandManager;
jassert (cm != nullptr);
return *cm;
}
//==============================================================================
class MainMenuModel : public MenuBarModel
{
public:
MainMenuModel()
{
setApplicationCommandManagerToWatch (&getCommandManager());
}
StringArray getMenuBarNames() override
{
return getApp().getMenuNames();
}
PopupMenu getMenuForIndex (int /*topLevelMenuIndex*/, const String& menuName) override
{
PopupMenu menu;
getApp().createMenu (menu, menuName);
return menu;
}
void menuItemSelected (int menuItemID, int /*topLevelMenuIndex*/) override
{
getApp().handleMainMenuCommand (menuItemID);
}
};
enum
{
recentProjectsBaseID = 100,
activeDocumentsBaseID = 300,
colourSchemeBaseID = 1000
};
virtual StringArray getMenuNames()
{
const char* const names[] = { "File", "Edit", "View", "Window", "GUI Editor", "Tools", nullptr };
return StringArray (names);
}
virtual void createMenu (PopupMenu& menu, const String& menuName)
{
if (menuName == "File") createFileMenu (menu);
else if (menuName == "Edit") createEditMenu (menu);
else if (menuName == "View") createViewMenu (menu);
else if (menuName == "Window") createWindowMenu (menu);
else if (menuName == "Tools") createToolsMenu (menu);
else if (menuName == "GUI Editor") createGUIEditorMenu (menu);
else jassertfalse; // names have changed?
}
virtual void createFileMenu (PopupMenu& menu)
{
menu.addCommandItem (commandManager, CommandIDs::newProject);
menu.addSeparator();
menu.addCommandItem (commandManager, CommandIDs::open);
PopupMenu recentFiles;
settings->recentFiles.createPopupMenuItems (recentFiles, recentProjectsBaseID, true, true);
menu.addSubMenu ("Open Recent", recentFiles);
menu.addSeparator();
menu.addCommandItem (commandManager, CommandIDs::closeDocument);
menu.addCommandItem (commandManager, CommandIDs::saveDocument);
menu.addCommandItem (commandManager, CommandIDs::saveDocumentAs);
menu.addCommandItem (commandManager, CommandIDs::saveAll);
menu.addSeparator();
menu.addCommandItem (commandManager, CommandIDs::closeProject);
menu.addCommandItem (commandManager, CommandIDs::saveProject);
menu.addSeparator();
menu.addCommandItem (commandManager, CommandIDs::openInIDE);
menu.addCommandItem (commandManager, CommandIDs::saveAndOpenInIDE);
#if ! JUCE_MAC
menu.addSeparator();
menu.addCommandItem (commandManager, StandardApplicationCommandIDs::quit);
#endif
}
virtual void createEditMenu (PopupMenu& menu)
{
menu.addCommandItem (commandManager, StandardApplicationCommandIDs::undo);
menu.addCommandItem (commandManager, StandardApplicationCommandIDs::redo);
menu.addSeparator();
menu.addCommandItem (commandManager, StandardApplicationCommandIDs::cut);
menu.addCommandItem (commandManager, StandardApplicationCommandIDs::copy);
menu.addCommandItem (commandManager, StandardApplicationCommandIDs::paste);
menu.addCommandItem (commandManager, StandardApplicationCommandIDs::del);
menu.addCommandItem (commandManager, StandardApplicationCommandIDs::selectAll);
menu.addCommandItem (commandManager, StandardApplicationCommandIDs::deselectAll);
menu.addSeparator();
menu.addCommandItem (commandManager, CommandIDs::showFindPanel);
menu.addCommandItem (commandManager, CommandIDs::findSelection);
menu.addCommandItem (commandManager, CommandIDs::findNext);
menu.addCommandItem (commandManager, CommandIDs::findPrevious);
}
virtual void createViewMenu (PopupMenu& menu)
{
menu.addCommandItem (commandManager, CommandIDs::showFilePanel);
menu.addCommandItem (commandManager, CommandIDs::showConfigPanel);
menu.addCommandItem (commandManager, CommandIDs::showProjectSettings);
menu.addCommandItem (commandManager, CommandIDs::showProjectModules);
menu.addSeparator();
createColourSchemeItems (menu);
}
void createColourSchemeItems (PopupMenu& menu)
{
menu.addCommandItem (commandManager, CommandIDs::showAppearanceSettings);
const StringArray presetSchemes (settings->appearance.getPresetSchemes());
if (presetSchemes.size() > 0)
{
PopupMenu schemes;
for (int i = 0; i < presetSchemes.size(); ++i)
schemes.addItem (colourSchemeBaseID + i, presetSchemes[i]);
menu.addSubMenu ("Colour Scheme", schemes);
}
}
virtual void createWindowMenu (PopupMenu& menu)
{
menu.addCommandItem (commandManager, CommandIDs::closeWindow);
menu.addSeparator();
menu.addCommandItem (commandManager, CommandIDs::goToPreviousDoc);
menu.addCommandItem (commandManager, CommandIDs::goToNextDoc);
menu.addCommandItem (commandManager, CommandIDs::goToCounterpart);
menu.addSeparator();
const int numDocs = jmin (50, openDocumentManager.getNumOpenDocuments());
for (int i = 0; i < numDocs; ++i)
{
OpenDocumentManager::Document* doc = openDocumentManager.getOpenDocument(i);
menu.addItem (activeDocumentsBaseID + i, doc->getName());
}
menu.addSeparator();
menu.addCommandItem (commandManager, CommandIDs::closeAllDocuments);
}
virtual void createToolsMenu (PopupMenu& menu)
{
menu.addCommandItem (commandManager, CommandIDs::showUTF8Tool);
menu.addCommandItem (commandManager, CommandIDs::showSVGPathTool);
menu.addCommandItem (commandManager, CommandIDs::showTranslationTool);
}
virtual void handleMainMenuCommand (int menuItemID)
{
if (menuItemID >= recentProjectsBaseID && menuItemID < recentProjectsBaseID + 100)
{
// open a file from the "recent files" menu
openFile (settings->recentFiles.getFile (menuItemID - recentProjectsBaseID));
}
else if (menuItemID >= activeDocumentsBaseID && menuItemID < activeDocumentsBaseID + 200)
{
if (OpenDocumentManager::Document* doc = openDocumentManager.getOpenDocument (menuItemID - activeDocumentsBaseID))
mainWindowList.openDocument (doc, true);
else
jassertfalse;
}
else if (menuItemID >= colourSchemeBaseID && menuItemID < colourSchemeBaseID + 200)
{
settings->appearance.selectPresetScheme (menuItemID - colourSchemeBaseID);
}
else
{
handleGUIEditorMenuCommand (menuItemID);
}
}
//==============================================================================
void getAllCommands (Array <CommandID>& commands) override
{
JUCEApplication::getAllCommands (commands);
const CommandID ids[] = { CommandIDs::newProject,
CommandIDs::open,
CommandIDs::closeAllDocuments,
CommandIDs::saveAll,
CommandIDs::showAppearanceSettings,
CommandIDs::showUTF8Tool,
CommandIDs::showSVGPathTool };
commands.addArray (ids, numElementsInArray (ids));
}
void getCommandInfo (CommandID commandID, ApplicationCommandInfo& result) override
{
switch (commandID)
{
case CommandIDs::newProject:
result.setInfo ("New Project...", "Creates a new Jucer project", CommandCategories::general, 0);
result.defaultKeypresses.add (KeyPress ('n', ModifierKeys::commandModifier, 0));
break;
case CommandIDs::open:
result.setInfo ("Open...", "Opens a Jucer project", CommandCategories::general, 0);
result.defaultKeypresses.add (KeyPress ('o', ModifierKeys::commandModifier, 0));
break;
case CommandIDs::showAppearanceSettings:
result.setInfo ("Fonts and Colours...", "Shows the appearance settings window.", CommandCategories::general, 0);
break;
case CommandIDs::closeAllDocuments:
result.setInfo ("Close All Documents", "Closes all open documents", CommandCategories::general, 0);
result.setActive (openDocumentManager.getNumOpenDocuments() > 0);
break;
case CommandIDs::saveAll:
result.setInfo ("Save All", "Saves all open documents", CommandCategories::general, 0);
result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier | ModifierKeys::altModifier, 0));
break;
case CommandIDs::showUTF8Tool:
result.setInfo ("UTF-8 String-Literal Helper", "Shows the UTF-8 string literal utility", CommandCategories::general, 0);
break;
case CommandIDs::showSVGPathTool:
result.setInfo ("SVG Path Helper", "Shows the SVG->Path data conversion utility", CommandCategories::general, 0);
break;
default:
JUCEApplication::getCommandInfo (commandID, result);
break;
}
}
bool perform (const InvocationInfo& info) override
{
switch (info.commandID)
{
case CommandIDs::newProject: createNewProject(); break;
case CommandIDs::open: askUserToOpenFile(); break;
case CommandIDs::saveAll: openDocumentManager.saveAll(); break;
case CommandIDs::closeAllDocuments: closeAllDocuments (true); break;
case CommandIDs::showUTF8Tool: showUTF8ToolWindow (utf8Window); break;
case CommandIDs::showSVGPathTool: showSVGPathDataToolWindow (svgPathWindow); break;
case CommandIDs::showAppearanceSettings: AppearanceSettings::showEditorWindow (appearanceEditorWindow); break;
default: return JUCEApplication::perform (info);
}
return true;
}
//==============================================================================
void createNewProject()
{
MainWindow* mw = mainWindowList.getOrCreateEmptyWindow();
mw->showNewProjectWizard();
mainWindowList.avoidSuperimposedWindows (mw);
}
virtual void updateNewlyOpenedProject (Project&) {}
void askUserToOpenFile()
{
FileChooser fc ("Open File");
if (fc.browseForFileToOpen())
openFile (fc.getResult());
}
bool openFile (const File& file)
{
return mainWindowList.openFile (file);
}
bool closeAllDocuments (bool askUserToSave)
{
return openDocumentManager.closeAll (askUserToSave);
}
virtual bool closeAllMainWindows()
{
return mainWindowList.askAllWindowsToClose();
}
//==============================================================================
void initialiseLogger (const char* filePrefix)
{
if (logger == nullptr)
{
logger = FileLogger::createDateStampedLogger (getLogFolderName(), filePrefix, ".txt",
getApplicationName() + " " + getApplicationVersion()
+ " --- Build date: " __DATE__);
Logger::setCurrentLogger (logger);
}
}
struct FileWithTime
{
FileWithTime (const File& f) : file (f), time (f.getLastModificationTime()) {}
FileWithTime() {}
bool operator< (const FileWithTime& other) const { return time < other.time; }
bool operator== (const FileWithTime& other) const { return time == other.time; }
File file;
Time time;
};
void deleteLogger()
{
const int maxNumLogFilesToKeep = 50;
Logger::setCurrentLogger (nullptr);
if (logger != nullptr)
{
Array<File> logFiles;
logger->getLogFile().getParentDirectory().findChildFiles (logFiles, File::findFiles, false);
if (logFiles.size() > maxNumLogFilesToKeep)
{
Array <FileWithTime> files;
for (int i = 0; i < logFiles.size(); ++i)
files.addUsingDefaultSort (logFiles.getReference(i));
for (int i = 0; i < files.size() - maxNumLogFilesToKeep; ++i)
files.getReference(i).file.deleteFile();
}
}
logger = nullptr;
}
virtual void doExtraInitialisation() {}
virtual void addExtraConfigItems (Project&, TreeViewItem&) {}
#if JUCE_LINUX
virtual String getLogFolderName() const { return "~/.config/Introjucer/Logs"; }
#else
virtual String getLogFolderName() const { return "com.juce.introjucer"; }
#endif
virtual PropertiesFile::Options getPropertyFileOptionsFor (const String& filename)
{
PropertiesFile::Options options;
options.applicationName = filename;
options.filenameSuffix = "settings";
options.osxLibrarySubFolder = "Application Support";
#if JUCE_LINUX
options.folderName = "~/.config/Introjucer";
#else
options.folderName = "Introjucer";
#endif
return options;
}
virtual Component* createProjectContentComponent() const
{
return new ProjectContentComponent();
}
//==============================================================================
IntrojucerLookAndFeel lookAndFeel;
ScopedPointer<StoredSettings> settings;
ScopedPointer<Icons> icons;
ScopedPointer<MainMenuModel> menuModel;
MainWindowList mainWindowList;
OpenDocumentManager openDocumentManager;
ScopedPointer<ApplicationCommandManager> commandManager;
ScopedPointer<Component> appearanceEditorWindow, utf8Window, svgPathWindow;
ScopedPointer<FileLogger> logger;
bool isRunningCommandLine;
private:
ScopedPointer<LatestVersionChecker> versionChecker;
class AsyncQuitRetrier : private Timer
{
public:
AsyncQuitRetrier() { startTimer (500); }
void timerCallback() override
{
stopTimer();
delete this;
if (JUCEApplicationBase* app = JUCEApplicationBase::getInstance())
app->systemRequestedQuit();
}
JUCE_DECLARE_NON_COPYABLE (AsyncQuitRetrier)
};
void initCommandManager()
{
commandManager = new ApplicationCommandManager();
commandManager->registerAllCommandsForTarget (this);
{
CodeDocument doc;
CppCodeEditorComponent ed (File::nonexistent, doc);
commandManager->registerAllCommandsForTarget (&ed);
}
registerGUIEditorCommands();
}
};
#endif // __JUCER_APPLICATION_JUCEHEADER__
@@ -0,0 +1,422 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCER_AUTOUPDATER_H_INCLUDED
#define JUCER_AUTOUPDATER_H_INCLUDED
//==============================================================================
class LatestVersionChecker : private Thread,
private Timer
{
public:
LatestVersionChecker() : Thread ("Updater"),
hasAttemptedToReadWebsite (false)
{
startTimer (2000);
}
~LatestVersionChecker()
{
stopThread (20000);
}
static URL getLatestVersionURL()
{
return URL ("http://www.juce.com/juce/updates/updatelist.php");
}
void checkForNewVersion()
{
hasAttemptedToReadWebsite = true;
{
const ScopedPointer<InputStream> in (getLatestVersionURL().createInputStream (false));
if (in == nullptr || threadShouldExit())
return; // can't connect: fail silently.
jsonReply = JSON::parse (in->readEntireStreamAsString());
}
if (threadShouldExit())
return;
if (jsonReply.isArray() || jsonReply.isObject())
startTimer (100);
}
void processResult (var reply)
{
if (reply.isArray())
{
askUserAboutNewVersion (VersionInfo (reply[0]));
}
else if (reply.isObject())
{
// In the far-distant future, this may be contacting a defunct
// URL, so hopefully the website will contain a helpful message
// for the user..
String message = reply.getProperty ("message", var()).toString();
if (message.isNotEmpty())
{
AlertWindow::showMessageBox (AlertWindow::WarningIcon,
TRANS("JUCE Updater"),
message);
}
}
}
struct VersionInfo
{
VersionInfo (var v)
{
version = v.getProperty ("version", var()).toString().trim();
url = v.getProperty (
#if JUCE_MAC
"url_osx",
#elif JUCE_WINDOWS
"url_win",
#elif JUCE_LINUX
"url_linux",
#endif
var()).toString();
}
bool isDifferentVersionToCurrent() const
{
return version != JUCE_STRINGIFY(JUCE_MAJOR_VERSION)
"." JUCE_STRINGIFY(JUCE_MINOR_VERSION)
"." JUCE_STRINGIFY(JUCE_BUILDNUMBER)
&& version.containsChar ('.')
&& version.length() > 2;
}
String version;
URL url;
};
void askUserAboutNewVersion (const VersionInfo& info)
{
if (info.isDifferentVersionToCurrent())
{
File appParentFolder (File::getSpecialLocation (File::currentApplicationFile).getParentDirectory());
if (isZipFolder (appParentFolder))
{
int result = AlertWindow::showYesNoCancelBox (AlertWindow::InfoIcon,
TRANS("Download JUCE version 123?").replace ("123", info.version),
TRANS("A new version of JUCE is available - would you like to overwrite the folder:\n\n"
"xfldrx\n\n"
" ..with the latest version from juce.com?\n\n"
"(Please note that this will overwrite everything in that folder!)")
.replace ("xfldrx", appParentFolder.getFullPathName()),
TRANS("Overwrite"),
TRANS("Choose another folder..."),
TRANS("Cancel"));
if (result == 1)
DownloadNewVersionThread::performDownload (info.url, appParentFolder);
if (result == 2)
askUserForLocationToDownload (info);
}
else
{
if (AlertWindow::showOkCancelBox (AlertWindow::InfoIcon,
TRANS("Download JUCE version 123?").replace ("123", info.version),
TRANS("A new version of JUCE is available - would you like to download it?")))
askUserForLocationToDownload (info);
}
}
}
void askUserForLocationToDownload (const VersionInfo& info)
{
File targetFolder (findDefaultModulesFolder());
if (isJuceModulesFolder (targetFolder))
targetFolder = targetFolder.getParentDirectory();
FileChooser chooser (TRANS("Please select the location into which you'd like to install the new version"),
targetFolder);
if (chooser.browseForDirectory())
{
targetFolder = chooser.getResult();
if (isJuceModulesFolder (targetFolder))
targetFolder = targetFolder.getParentDirectory();
if (targetFolder.getChildFile ("JUCE").isDirectory())
targetFolder = targetFolder.getChildFile ("JUCE");
if (targetFolder.getChildFile (".git").isDirectory())
{
AlertWindow::showMessageBox (AlertWindow::WarningIcon,
TRANS ("Downloading new JUCE version"),
TRANS ("This folder is a GIT repository!\n\n"
"You should use a \"git pull\" to update it to the latest version. "
"Or to use the Introjucer to get an update, you should select an empty "
"folder into which you'd like to download the new code."));
return;
}
if (isJuceFolder (targetFolder))
{
if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
TRANS("Overwrite existing JUCE folder?"),
TRANS("Do you want to overwrite the folder:\n\n"
"xfldrx\n\n"
" ..with the latest version from juce.com?\n\n"
"(Please note that this will overwrite everything in that folder!)")
.replace ("xfldrx", targetFolder.getFullPathName())))
{
return;
}
}
else
{
targetFolder = targetFolder.getChildFile ("JUCE").getNonexistentSibling();
}
DownloadNewVersionThread::performDownload (info.url, targetFolder);
}
}
static bool isZipFolder (const File& f)
{
return f.getChildFile ("modules").isDirectory()
&& f.getChildFile ("extras").isDirectory()
&& f.getChildFile ("examples").isDirectory()
&& ! f.getChildFile (".git").isDirectory();
}
private:
void timerCallback() override
{
stopTimer();
if (hasAttemptedToReadWebsite)
processResult (jsonReply);
else
startThread (3);
}
void run() override
{
checkForNewVersion();
}
var jsonReply;
bool hasAttemptedToReadWebsite;
URL newVersionToDownload;
//==============================================================================
class DownloadNewVersionThread : public ThreadWithProgressWindow
{
public:
DownloadNewVersionThread (URL u, File target)
: ThreadWithProgressWindow ("Downloading New Version", true, true),
result (Result::ok()),
url (u), targetFolder (target)
{
}
static void performDownload (URL u, File targetFolder)
{
DownloadNewVersionThread d (u, targetFolder);
if (d.runThread())
{
if (d.result.failed())
{
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
"Installation Failed",
d.result.getErrorMessage());
}
else
{
new RelaunchTimer (targetFolder);
}
}
}
void run() override
{
setProgress (-1.0);
MemoryBlock zipData;
result = download (zipData);
if (result.wasOk() && ! threadShouldExit())
result = unzip (zipData);
}
Result download (MemoryBlock& dest)
{
setStatusMessage ("Downloading...");
const ScopedPointer<InputStream> in (url.createInputStream (false, nullptr, nullptr, String::empty, 10000));
if (in != nullptr)
{
int64 total = 0;
MemoryOutputStream mo (dest, true);
for (;;)
{
if (threadShouldExit())
return Result::fail ("cancel");
int64 written = mo.writeFromInputStream (*in, 8192);
if (written == 0)
break;
total += written;
setStatusMessage (String (TRANS ("Downloading... (123)"))
.replace ("123", File::descriptionOfSizeInBytes (total)));
}
return Result::ok();
}
return Result::fail ("Failed to download from: " + url.toString (false));
}
Result unzip (const MemoryBlock& data)
{
setStatusMessage ("Installing...");
File unzipTarget;
bool isUsingTempFolder = false;
{
MemoryInputStream input (data, false);
ZipFile zip (input);
if (zip.getNumEntries() == 0)
return Result::fail ("The downloaded file wasn't a valid JUCE file!");
unzipTarget = targetFolder;
if (unzipTarget.exists())
{
isUsingTempFolder = true;
unzipTarget = targetFolder.getNonexistentSibling();
if (! unzipTarget.createDirectory())
return Result::fail ("Couldn't create a folder to unzip the new version!");
}
Result r (zip.uncompressTo (unzipTarget));
if (r.failed())
{
if (isUsingTempFolder)
unzipTarget.deleteRecursively();
return r;
}
}
if (isUsingTempFolder)
{
File oldFolder (targetFolder.getSiblingFile (targetFolder.getFileNameWithoutExtension() + "_old").getNonexistentSibling());
if (! targetFolder.moveFileTo (oldFolder))
{
unzipTarget.deleteRecursively();
return Result::fail ("Could not remove the existing folder!");
}
if (! unzipTarget.moveFileTo (targetFolder))
{
unzipTarget.deleteRecursively();
return Result::fail ("Could not overwrite the existing folder!");
}
}
return Result::ok();
}
Result result;
URL url;
File targetFolder;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DownloadNewVersionThread)
};
struct RelaunchTimer : private Timer
{
RelaunchTimer (const File& f) : parentFolder (f)
{
startTimer (1500);
}
void timerCallback() override
{
stopTimer();
File app = parentFolder.getChildFile (
#if JUCE_MAC
"Introjucer.app");
#elif JUCE_WINDOWS
"Introjucer.exe");
#elif JUCE_LINUX
"Introjucer");
#endif
JUCEApplication::quit();
if (app.exists())
{
app.setExecutePermission (true);
#if JUCE_MAC
app.getChildFile("Contents")
.getChildFile("MacOS")
.getChildFile("Introjucer").setExecutePermission (true);
#endif
app.startAsProcess();
}
delete this;
}
File parentFolder;
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LatestVersionChecker)
};
#endif // JUCER_AUTOUPDATER_H_INCLUDED
@@ -0,0 +1,73 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
/**
A namespace to hold all the possible command IDs.
*/
namespace CommandIDs
{
enum
{
newProject = 0x200010,
open = 0x200020,
closeDocument = 0x200030,
saveDocument = 0x200040,
saveDocumentAs = 0x200041,
closeProject = 0x200051,
saveProject = 0x200060,
saveAll = 0x200080,
openInIDE = 0x200072,
saveAndOpenInIDE = 0x200073,
showUTF8Tool = 0x200076,
showAppearanceSettings = 0x200077,
showConfigPanel = 0x200074,
showFilePanel = 0x200078,
showTranslationTool = 0x200079,
showProjectSettings = 0x20007a,
showProjectModules = 0x20007b,
showSVGPathTool = 0x20007c,
closeWindow = 0x201001,
closeAllDocuments = 0x201000,
goToPreviousDoc = 0x201002,
goToNextDoc = 0x201003,
goToCounterpart = 0x201004,
deleteSelectedItem = 0x201005,
showFindPanel = 0x2010a0,
findSelection = 0x2010a1,
findNext = 0x2010a2,
findPrevious = 0x2010a3
};
}
namespace CommandCategories
{
static const char* const general = "General";
static const char* const editing = "Editing";
static const char* const view = "View";
static const char* const windows = "Windows";
}
@@ -0,0 +1,382 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#include "../Project/jucer_Project.h"
#include "../Project/jucer_Module.h"
#include "jucer_CommandLine.h"
//==============================================================================
namespace
{
static void hideDockIcon()
{
#if JUCE_MAC
Process::setDockIconVisible (false);
#endif
}
static File getFile (const String& filename)
{
return File::getCurrentWorkingDirectory().getChildFile (filename.unquoted());
}
static bool matchArgument (const String& arg, const String& possible)
{
return arg == possible
|| arg == "-" + possible
|| arg == "--" + possible;
}
static bool checkArgumentCount (const StringArray& args, int minNumArgs)
{
if (args.size() < minNumArgs)
{
std::cout << "Not enough arguments!" << std::endl;
return false;
}
return true;
}
//==============================================================================
struct LoadedProject
{
LoadedProject()
{
hideDockIcon();
}
int load (const File& projectFile)
{
hideDockIcon();
if (! projectFile.exists())
{
std::cout << "The file " << projectFile.getFullPathName() << " doesn't exist!" << std::endl;
return 1;
}
if (! projectFile.hasFileExtension (Project::projectFileExtension))
{
std::cout << projectFile.getFullPathName() << " isn't a valid jucer project file!" << std::endl;
return 1;
}
project = new Project (projectFile);
if (! project->loadFrom (projectFile, true))
{
project = nullptr;
std::cout << "Failed to load the project file: " << projectFile.getFullPathName() << std::endl;
return 1;
}
return 0;
}
int save (bool justSaveResources)
{
if (project != nullptr)
{
Result error (justSaveResources ? project->saveResourcesOnly (project->getFile())
: project->saveProject (project->getFile(), true));
if (error.failed())
{
std::cout << "Error when saving: " << error.getErrorMessage() << std::endl;
return 1;
}
project = nullptr;
}
return 0;
}
ScopedPointer<Project> project;
};
//==============================================================================
/* Running a command-line of the form "introjucer --resave foobar.jucer" will try to load
that project and re-export all of its targets.
*/
static int resaveProject (const StringArray& args, bool justSaveResources)
{
if (! checkArgumentCount (args, 2))
return 1;
LoadedProject proj;
int res = proj.load (getFile (args[1]));
if (res != 0)
return res;
std::cout << (justSaveResources ? "Re-saving project resources: "
: "Re-saving file: ")
<< proj.project->getFile().getFullPathName() << std::endl;
return proj.save (justSaveResources);
}
//==============================================================================
static int setVersion (const StringArray& args)
{
if (! checkArgumentCount (args, 3))
return 1;
LoadedProject proj;
int res = proj.load (getFile (args[2]));
if (res != 0)
return res;
String version (args[1].trim());
std::cout << "Setting project version: " << version << std::endl;
proj.project->getVersionValue() = version;
return proj.save (false);
}
//==============================================================================
static int bumpVersion (const StringArray& args)
{
if (! checkArgumentCount (args, 2))
return 1;
LoadedProject proj;
int res = proj.load (getFile (args[1]));
if (res != 0)
return res;
String version = proj.project->getVersionString();
version = version.upToLastOccurrenceOf (".", true, false)
+ String (version.getTrailingIntValue() + 1);
std::cout << "Bumping project version to: " << version << std::endl;
proj.project->getVersionValue() = version;
return proj.save (false);
}
//==============================================================================
int showStatus (const StringArray& args)
{
hideDockIcon();
if (! checkArgumentCount (args, 2))
return 1;
LoadedProject proj;
int res = proj.load (getFile (args[1]));
if (res != 0)
return res;
std::cout << "Project file: " << proj.project->getFile().getFullPathName() << std::endl
<< "Name: " << proj.project->getTitle() << std::endl
<< "UID: " << proj.project->getProjectUID() << std::endl;
EnabledModuleList& modules = proj.project->getModules();
const int numModules = modules.getNumModules();
if (numModules > 0)
{
std::cout << "Modules:" << std::endl;
for (int i = 0; i < numModules; ++i)
std::cout << " " << modules.getModuleID (i) << std::endl;
}
return 0;
}
//==============================================================================
static String getModulePackageName (const LibraryModule& module)
{
return module.getID() + ".jucemodule";
}
static int zipModule (const File& targetFolder, const File& moduleFolder)
{
jassert (targetFolder.isDirectory());
const File moduleFolderParent (moduleFolder.getParentDirectory());
LibraryModule module (moduleFolder.getChildFile (ModuleDescription::getManifestFileName()));
if (! module.isValid())
{
std::cout << moduleFolder.getFullPathName() << " is not a valid module folder!" << std::endl;
return 1;
}
const File targetFile (targetFolder.getChildFile (getModulePackageName (module)));
ZipFile::Builder zip;
{
DirectoryIterator i (moduleFolder, true, "*", File::findFiles);
while (i.next())
if (! i.getFile().isHidden())
zip.addFile (i.getFile(), 9, i.getFile().getRelativePathFrom (moduleFolderParent));
}
std::cout << "Writing: " << targetFile.getFullPathName() << std::endl;
TemporaryFile temp (targetFile);
ScopedPointer<FileOutputStream> out (temp.getFile().createOutputStream());
bool ok = out != nullptr && zip.writeToStream (*out, nullptr);
out = nullptr;
ok = ok && temp.overwriteTargetFileWithTemporary();
if (! ok)
{
std::cout << "Failed to write to the target file: " << targetFile.getFullPathName() << std::endl;
return 1;
}
return 0;
}
static int buildModules (const StringArray& args, const bool buildAllWithIndex)
{
hideDockIcon();
if (! checkArgumentCount (args, 3))
return 1;
const File targetFolder (getFile (args[1]));
if (! targetFolder.isDirectory())
{
std::cout << "The first argument must be the directory to put the result." << std::endl;
return 1;
}
if (buildAllWithIndex)
{
const File folderToSearch (getFile (args[2]));
DirectoryIterator i (folderToSearch, false, "*", File::findDirectories);
var infoList;
while (i.next())
{
LibraryModule module (i.getFile().getChildFile (ModuleDescription::getManifestFileName()));
if (module.isValid())
{
const int result = zipModule (targetFolder, i.getFile());
if (result != 0)
return result;
var moduleInfo (new DynamicObject());
moduleInfo.getDynamicObject()->setProperty ("file", getModulePackageName (module));
moduleInfo.getDynamicObject()->setProperty ("info", module.moduleInfo.moduleInfo);
infoList.append (moduleInfo);
}
}
const File indexFile (targetFolder.getChildFile ("modulelist"));
std::cout << "Writing: " << indexFile.getFullPathName() << std::endl;
indexFile.replaceWithText (JSON::toString (infoList), false, false);
}
else
{
for (int i = 2; i < args.size(); ++i)
{
const int result = zipModule (targetFolder, getFile (args[i]));
if (result != 0)
return result;
}
}
return 0;
}
//==============================================================================
static int showHelp()
{
hideDockIcon();
std::cout << "The Introjucer!" << std::endl
<< std::endl
<< "Usage: " << std::endl
<< std::endl
<< " introjucer --resave project_file" << std::endl
<< " Resaves all files and resources in a project." << std::endl
<< std::endl
<< " introjucer --resave-resources project_file" << std::endl
<< " Resaves just the binary resources for a project." << std::endl
<< std::endl
<< " introjucer --set-version version_number project_file" << std::endl
<< " Updates the version number in a project." << std::endl
<< std::endl
<< " introjucer --bump-version project_file" << std::endl
<< " Updates the minor version number in a project by 1." << std::endl
<< std::endl
<< " introjucer --status project_file" << std::endl
<< " Displays information about a project." << std::endl
<< std::endl
<< " introjucer --buildmodule target_folder module_folder" << std::endl
<< " Zips a module into a downloadable file format." << std::endl
<< std::endl
<< " introjucer --buildallmodules target_folder module_folder" << std::endl
<< " Zips all modules in a given folder and creates an index for them." << std::endl
<< std::endl;
return 0;
}
}
//==============================================================================
int performCommandLine (const String& commandLine)
{
StringArray args;
args.addTokens (commandLine, true);
args.trim();
if (matchArgument (args[0], "help")) return showHelp();
if (matchArgument (args[0], "resave")) return resaveProject (args, false);
if (matchArgument (args[0], "resave-resources")) return resaveProject (args, true);
if (matchArgument (args[0], "set-version")) return setVersion (args);
if (matchArgument (args[0], "bump-version")) return bumpVersion (args);
if (matchArgument (args[0], "buildmodule")) return buildModules (args, false);
if (matchArgument (args[0], "buildallmodules")) return buildModules (args, true);
if (matchArgument (args[0], "status")) return showStatus (args);
return commandLineNotPerformed;
}
@@ -0,0 +1,34 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef __JUCER_COMMANDLINE_JUCEHEADER__
#define __JUCER_COMMANDLINE_JUCEHEADER__
int performCommandLine (const String& commandLine);
enum { commandLineNotPerformed = 0x72346231 };
#endif // __JUCER_COMMANDLINE_JUCEHEADER__
@@ -0,0 +1,58 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef __JUCER_COMMONHEADERS_JUCEHEADER__
#define __JUCER_COMMONHEADERS_JUCEHEADER__
#include "../Utility/jucer_StoredSettings.h"
#include "../Utility/jucer_Icons.h"
#include "../Utility/jucer_MiscUtilities.h"
#include "../Utility/jucer_CodeHelpers.h"
#include "../Utility/jucer_FileHelpers.h"
#include "../Utility/jucer_RelativePath.h"
#include "../Utility/jucer_ValueSourceHelpers.h"
#include "../Utility/jucer_PresetIDs.h"
#include "jucer_CommandIDs.h"
//==============================================================================
const char* const projectItemDragType = "Project Items";
const char* const drawableItemDragType = "Drawable Items";
const char* const componentItemDragType = "Components";
const char* const sourceFileExtensions = "cpp;mm;m;c;cc;cxx;s;asm";
const char* const headerFileExtensions = "h;hpp;hxx;hh;inl";
const char* const cOrCppFileExtensions = "cpp;cc;cxx;c";
const char* const cppFileExtensions = "cpp;cc;cxx";
const char* const objCFileExtensions = "mm;m";
const char* const asmFileExtensions = "s;S;asm";
const char* const sourceOrHeaderFileExtensions = "cpp;mm;m;c;cc;cxx;s;S;asm;h;hpp;hxx;hh;inl";
const char* const fileTypesToCompileByDefault = "cpp;mm;c;m;cc;cxx;s;S;asm;r";
enum ColourIds
{
mainBackgroundColourId = 0x2340000,
};
#endif // __JUCER_COMMONHEADERS_JUCEHEADER__
@@ -0,0 +1,59 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#include "jucer_DocumentEditorComponent.h"
#include "../Project/jucer_ProjectContentComponent.h"
#include "../Application/jucer_Application.h"
//==============================================================================
DocumentEditorComponent::DocumentEditorComponent (OpenDocumentManager::Document* doc)
: document (doc)
{
IntrojucerApp::getApp().openDocumentManager.addListener (this);
}
DocumentEditorComponent::~DocumentEditorComponent()
{
IntrojucerApp::getApp().openDocumentManager.removeListener (this);
}
bool DocumentEditorComponent::documentAboutToClose (OpenDocumentManager::Document* closingDoc)
{
if (document == closingDoc)
{
jassert (document != nullptr);
if (ProjectContentComponent* pcc = findParentComponentOfClass<ProjectContentComponent>())
pcc->hideDocument (document);
}
return true;
}
void DocumentEditorComponent::setEditedState (bool /*hasBeenEdited*/)
{
if (ProjectContentComponent* pcc = findParentComponentOfClass<ProjectContentComponent>())
pcc->updateMainWindowTitle();
}
@@ -0,0 +1,56 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef __JUCER_DOCUMENTEDITORCOMPONENT_JUCEHEADER__
#define __JUCER_DOCUMENTEDITORCOMPONENT_JUCEHEADER__
#include "jucer_OpenDocumentManager.h"
//==============================================================================
/**
*/
class DocumentEditorComponent : public Component,
public OpenDocumentManager::DocumentCloseListener
{
public:
//==============================================================================
DocumentEditorComponent (OpenDocumentManager::Document* document);
~DocumentEditorComponent();
OpenDocumentManager::Document* getDocument() const { return document; }
bool documentAboutToClose (OpenDocumentManager::Document*) override;
protected:
OpenDocumentManager::Document* document;
void setEditedState (bool hasBeenEdited);
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DocumentEditorComponent)
};
#endif // __JUCER_DOCUMENTEDITORCOMPONENT_JUCEHEADER__
@@ -0,0 +1,131 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef __JUCER_FILEPREVIEWCOMPONENT_JUCEHEADER__
#define __JUCER_FILEPREVIEWCOMPONENT_JUCEHEADER__
//==============================================================================
/**
*/
class ItemPreviewComponent : public Component
{
public:
ItemPreviewComponent (const File& f) : file (f)
{
setOpaque (true);
tryToLoadImage();
}
void paint (Graphics& g)
{
IntrojucerLookAndFeel::fillWithBackgroundTexture (*this, g);
if (drawable != nullptr)
{
Rectangle<float> contentBounds (drawable->getDrawableBounds());
if (DrawableComposite* dc = dynamic_cast<DrawableComposite*> (drawable.get()))
{
Rectangle<float> r (dc->getContentArea().resolve (nullptr));
if (! r.isEmpty())
contentBounds = r;
}
Rectangle<float> area = RectanglePlacement (RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize)
.appliedTo (contentBounds, Rectangle<float> (4.0f, 22.0f, getWidth() - 8.0f, getHeight() - 26.0f));
Path p;
p.addRectangle (area);
DropShadow (Colours::black.withAlpha (0.5f), 6, Point<int> (0, 1)).drawForPath (g, p);
g.fillCheckerBoard (area.getSmallestIntegerContainer(), 24, 24,
Colour (0xffffffff), Colour (0xffeeeeee));
drawable->draw (g, 1.0f, RectanglePlacement (RectanglePlacement::stretchToFit)
.getTransformToFit (contentBounds, area.toFloat()));
}
g.setFont (Font (14.0f, Font::bold));
g.setColour (findColour (mainBackgroundColourId).contrasting());
g.drawMultiLineText (facts.joinIntoString ("\n"), 10, 15, getWidth() - 16);
}
private:
StringArray facts;
File file;
ScopedPointer<Drawable> drawable;
void tryToLoadImage()
{
facts.clear();
facts.add (file.getFullPathName());
drawable = nullptr;
{
ScopedPointer<InputStream> input (file.createInputStream());
if (input != nullptr)
{
const int64 totalSize = input->getTotalLength();
String formatName;
if (ImageFileFormat* format = ImageFileFormat::findImageFormatForStream (*input))
formatName = " " + format->getFormatName();
input = nullptr;
Image image (ImageCache::getFromFile (file));
if (image.isValid())
{
DrawableImage* d = new DrawableImage();
d->setImage (image);
drawable = d;
facts.add (String (image.getWidth()) + " x " + String (image.getHeight()) + formatName);
}
if (totalSize > 0)
facts.add (File::descriptionOfSizeInBytes (totalSize));
}
}
if (drawable == nullptr)
{
ScopedPointer<XmlElement> svg (XmlDocument::parse (file));
if (svg != nullptr)
drawable = Drawable::createFromSVG (*svg);
}
facts.removeEmptyStrings (true);
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ItemPreviewComponent)
};
#endif // __JUCER_FILEPREVIEWCOMPONENT_JUCEHEADER__
@@ -0,0 +1,27 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#include "jucer_Application.h"
START_JUCE_APPLICATION (IntrojucerApp)
@@ -0,0 +1,581 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#include "../jucer_Headers.h"
#include "jucer_Application.h"
#include "jucer_MainWindow.h"
#include "jucer_OpenDocumentManager.h"
#include "../Code Editor/jucer_SourceCodeEditor.h"
#include "../Wizards/jucer_NewProjectWizardClasses.h"
#include "../Utility/jucer_JucerTreeViewBase.h"
//==============================================================================
MainWindow::MainWindow()
: DocumentWindow (IntrojucerApp::getApp().getApplicationName(),
Colour::greyLevel (0.6f),
DocumentWindow::allButtons,
false)
{
setUsingNativeTitleBar (true);
#if ! JUCE_MAC
setMenuBar (IntrojucerApp::getApp().menuModel);
#endif
createProjectContentCompIfNeeded();
setResizable (true, false);
centreWithSize (800, 600);
ApplicationCommandManager& commandManager = IntrojucerApp::getCommandManager();
// Register all the app commands..
commandManager.registerAllCommandsForTarget (this);
commandManager.registerAllCommandsForTarget (getProjectContentComponent());
// update key mappings..
{
commandManager.getKeyMappings()->resetToDefaultMappings();
ScopedPointer<XmlElement> keys (getGlobalProperties().getXmlValue ("keyMappings"));
if (keys != nullptr)
commandManager.getKeyMappings()->restoreFromXml (*keys);
addKeyListener (commandManager.getKeyMappings());
}
// don't want the window to take focus when the title-bar is clicked..
setWantsKeyboardFocus (false);
getLookAndFeel().setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
setResizeLimits (600, 500, 32000, 32000);
}
MainWindow::~MainWindow()
{
#if ! JUCE_MAC
setMenuBar (nullptr);
#endif
removeKeyListener (IntrojucerApp::getCommandManager().getKeyMappings());
// save the current size and position to our settings file..
getGlobalProperties().setValue ("lastMainWindowPos", getWindowStateAsString());
clearContentComponent();
currentProject = nullptr;
}
void MainWindow::createProjectContentCompIfNeeded()
{
if (getProjectContentComponent() == nullptr)
{
clearContentComponent();
setContentOwned (IntrojucerApp::getApp().createProjectContentComponent(), false);
jassert (getProjectContentComponent() != nullptr);
}
}
void MainWindow::makeVisible()
{
restoreWindowPosition();
setVisible (true);
addToDesktop(); // (must add before restoring size so that fullscreen will work)
restoreWindowPosition();
getContentComponent()->grabKeyboardFocus();
}
ProjectContentComponent* MainWindow::getProjectContentComponent() const
{
return dynamic_cast<ProjectContentComponent*> (getContentComponent());
}
void MainWindow::closeButtonPressed()
{
IntrojucerApp::getApp().mainWindowList.closeWindow (this);
}
bool MainWindow::closeProject (Project* project)
{
jassert (project == currentProject && project != nullptr);
if (project == nullptr)
return true;
project->getStoredProperties().setValue (getProjectWindowPosName(), getWindowStateAsString());
if (ProjectContentComponent* const pcc = getProjectContentComponent())
{
pcc->saveTreeViewState();
pcc->saveOpenDocumentList();
pcc->hideEditor();
}
if (! IntrojucerApp::getApp().openDocumentManager.closeAllDocumentsUsingProject (*project, true))
return false;
FileBasedDocument::SaveResult r = project->saveIfNeededAndUserAgrees();
if (r == FileBasedDocument::savedOk)
{
setProject (nullptr);
return true;
}
return false;
}
bool MainWindow::closeCurrentProject()
{
return currentProject == nullptr || closeProject (currentProject);
}
void MainWindow::setProject (Project* newProject)
{
createProjectContentCompIfNeeded();
getProjectContentComponent()->setProject (newProject);
currentProject = newProject;
getProjectContentComponent()->updateMainWindowTitle();
IntrojucerApp::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)
|| IntrojucerApp::getApp().openDocumentManager.canOpenFile (file));
}
bool MainWindow::openFile (const File& file)
{
createProjectContentCompIfNeeded();
if (file.hasFileExtension (Project::projectFileExtension))
{
ScopedPointer<Project> newDoc (new Project (file));
Result result (newDoc->loadFrom (file, true));
if (result.wasOk() && closeCurrentProject())
{
setProject (newDoc);
newDoc.release()->setChangedFlag (false);
jassert (getProjectContentComponent() != nullptr);
getProjectContentComponent()->reloadLastOpenDocuments();
return true;
}
}
else if (file.exists())
{
return getProjectContentComponent()->showEditorForFile (file, true);
}
return false;
}
bool MainWindow::isInterestedInFileDrag (const StringArray& filenames)
{
for (int i = filenames.size(); --i >= 0;)
if (canOpenFile (File (filenames[i])))
return true;
return false;
}
void MainWindow::filesDropped (const StringArray& filenames, int /*mouseX*/, int /*mouseY*/)
{
for (int i = filenames.size(); --i >= 0;)
{
const File f (filenames[i]);
if (canOpenFile (f) && openFile (f))
break;
}
}
bool MainWindow::shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails,
StringArray& files, bool& canMoveFiles)
{
if (TreeView* tv = dynamic_cast<TreeView*> (sourceDetails.sourceComponent.get()))
{
Array<JucerTreeViewBase*> selected;
for (int i = tv->getNumSelectedItems(); --i >= 0;)
if (JucerTreeViewBase* b = dynamic_cast<JucerTreeViewBase*> (tv->getSelectedItem(i)))
selected.add (b);
if (selected.size() > 0)
{
for (int i = selected.size(); --i >= 0;)
{
if (JucerTreeViewBase* jtvb = selected.getUnchecked(i))
{
const File f (jtvb->getDraggableFile());
if (f.existsAsFile())
files.add (f.getFullPathName());
}
}
canMoveFiles = false;
return files.size() > 0;
}
}
return false;
}
void MainWindow::activeWindowStatusChanged()
{
DocumentWindow::activeWindowStatusChanged();
if (ProjectContentComponent* const pcc = getProjectContentComponent())
pcc->updateMissingFileStatuses();
IntrojucerApp::getApp().openDocumentManager.reloadModifiedFiles();
}
void MainWindow::updateTitle (const String& documentName)
{
String name (IntrojucerApp::getApp().getApplicationName());
if (currentProject != nullptr)
name << " - " << currentProject->getDocumentTitle();
if (documentName.isNotEmpty())
name << " - " << documentName;
setName (name);
}
void MainWindow::showNewProjectWizard()
{
jassert (currentProject == nullptr);
setContentOwned (createNewProjectWizardComponent(), true);
centreWithSize (900, 630);
setVisible (true);
addToDesktop();
getContentComponent()->grabKeyboardFocus();
}
//==============================================================================
ApplicationCommandTarget* MainWindow::getNextCommandTarget()
{
return nullptr;
}
void MainWindow::getAllCommands (Array <CommandID>& commands)
{
const CommandID ids[] = { CommandIDs::closeWindow };
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;
default:
break;
}
}
bool MainWindow::perform (const InvocationInfo& info)
{
switch (info.commandID)
{
case CommandIDs::closeWindow:
closeButtonPressed();
break;
default:
return false;
}
return true;
}
//==============================================================================
MainWindowList::MainWindowList()
{
}
void MainWindowList::forceCloseAllWindows()
{
windows.clear();
}
bool MainWindowList::askAllWindowsToClose()
{
saveCurrentlyOpenProjectList();
while (windows.size() > 0)
{
if (! windows[0]->closeCurrentProject())
return false;
windows.remove (0);
}
return true;
}
void MainWindowList::createWindowIfNoneAreOpen()
{
if (windows.size() == 0)
createNewMainWindow()->showNewProjectWizard();
}
void MainWindowList::closeWindow (MainWindow* w)
{
jassert (windows.contains (w));
#if ! JUCE_MAC
if (windows.size() == 1)
{
JUCEApplicationBase::getInstance()->systemRequestedQuit();
}
else
#endif
{
if (w->closeCurrentProject())
{
windows.removeObject (w);
saveCurrentlyOpenProjectList();
}
}
}
void MainWindowList::openDocument (OpenDocumentManager::Document* doc, bool grabFocus)
{
getOrCreateFrontmostWindow()->getProjectContentComponent()->showDocument (doc, grabFocus);
}
bool MainWindowList::openFile (const File& file)
{
for (int i = windows.size(); --i >= 0;)
{
MainWindow* const w = windows.getUnchecked(i);
if (w->getProject() != nullptr && w->getProject()->getFile() == file)
{
w->toFront (true);
return true;
}
}
if (file.hasFileExtension (Project::projectFileExtension))
{
MainWindow* const w = getOrCreateEmptyWindow();
bool ok = w->openFile (file);
w->makeVisible();
avoidSuperimposedWindows (w);
return ok;
}
if (file.exists())
return getOrCreateFrontmostWindow()->openFile (file);
return false;
}
MainWindow* MainWindowList::createNewMainWindow()
{
MainWindow* const w = new MainWindow();
windows.add (w);
w->restoreWindowPosition();
avoidSuperimposedWindows (w);
return w;
}
MainWindow* MainWindowList::getOrCreateFrontmostWindow()
{
if (windows.size() == 0)
{
MainWindow* w = createNewMainWindow();
avoidSuperimposedWindows (w);
w->makeVisible();
return w;
}
for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
{
MainWindow* 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;)
{
MainWindow* mw = dynamic_cast<MainWindow*> (Desktop::getInstance().getComponent (i));
if (windows.contains (mw) && mw->getProject() == nullptr)
return mw;
}
return createNewMainWindow();
}
void MainWindowList::updateAllWindowTitles()
{
for (int i = 0; i < windows.size(); ++i)
if (ProjectContentComponent* pc = windows.getUnchecked(i)->getProjectContentComponent())
pc->updateMainWindowTitle();
}
void MainWindowList::avoidSuperimposedWindows (MainWindow* const mw)
{
for (int i = windows.size(); --i >= 0;)
{
MainWindow* const other = windows.getUnchecked(i);
const Rectangle<int> b1 (mw->getBounds());
const Rectangle<int> b2 (other->getBounds());
if (mw != other
&& std::abs (b1.getX() - b2.getX()) < 3
&& std::abs (b1.getY() - b2.getY()) < 3
&& std::abs (b1.getRight() - b2.getRight()) < 3
&& std::abs (b1.getBottom() - b2.getBottom()) < 3)
{
int dx = 40, dy = 30;
if (b1.getCentreX() >= mw->getScreenBounds().getCentreX()) dx = -dx;
if (b1.getCentreY() >= mw->getScreenBounds().getCentreY()) dy = -dy;
mw->setBounds (b1.translated (dx, dy));
}
}
}
void MainWindowList::saveCurrentlyOpenProjectList()
{
Array<File> projects;
Desktop& desktop = Desktop::getInstance();
for (int i = 0; i < desktop.getNumComponents(); ++i)
{
if (MainWindow* const mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
if (Project* p = mw->getProject())
projects.add (p->getFile());
}
getAppSettings().setLastProjects (projects);
}
void MainWindowList::reopenLastProjects()
{
Array<File> projects (getAppSettings().getLastProjects());
for (int i = 0; i < projects.size(); ++ i)
openFile (projects.getReference(i));
}
void MainWindowList::sendLookAndFeelChange()
{
for (int i = windows.size(); --i >= 0;)
windows.getUnchecked(i)->sendLookAndFeelChange();
}
Project* MainWindowList::getFrontmostProject()
{
Desktop& desktop = Desktop::getInstance();
for (int i = desktop.getNumComponents(); --i >= 0;)
if (MainWindow* const mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
if (Project* p = mw->getProject())
return p;
return nullptr;
}
File findDefaultModulesFolder (bool mustContainJuceCoreModule)
{
const MainWindowList& windows = IntrojucerApp::getApp().mainWindowList;
for (int i = windows.windows.size(); --i >= 0;)
{
if (Project* p = windows.windows.getUnchecked (i)->getProject())
{
const File f (EnabledModuleList::findDefaultModulesFolder (*p));
if (isJuceModulesFolder (f) || (f.isDirectory() && ! mustContainJuceCoreModule))
return f;
}
}
if (mustContainJuceCoreModule)
return findDefaultModulesFolder (false);
File f (File::getSpecialLocation (File::currentApplicationFile));
for (;;)
{
File parent (f.getParentDirectory());
if (parent == f || ! parent.isDirectory())
break;
if (isJuceFolder (parent))
return parent.getChildFile ("modules");
f = parent;
}
return File::nonexistent;
}
@@ -0,0 +1,123 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef __JUCER_MAINWINDOW_JUCEHEADER__
#define __JUCER_MAINWINDOW_JUCEHEADER__
#include "../Project/jucer_ProjectContentComponent.h"
//==============================================================================
/**
The big top-level window where everything happens.
*/
class MainWindow : public DocumentWindow,
public ApplicationCommandTarget,
public FileDragAndDropTarget,
public DragAndDropContainer
{
public:
//==============================================================================
MainWindow();
~MainWindow();
//==============================================================================
void closeButtonPressed() override;
//==============================================================================
bool canOpenFile (const File& file) const;
bool openFile (const File& file);
void setProject (Project* newProject);
Project* getProject() const { return currentProject; }
void makeVisible();
void restoreWindowPosition();
bool closeProject (Project* project);
bool closeCurrentProject();
void showNewProjectWizard();
bool isInterestedInFileDrag (const StringArray& files) override;
void filesDropped (const StringArray& filenames, int mouseX, int mouseY) override;
void activeWindowStatusChanged() override;
void updateTitle (const String& documentName);
ProjectContentComponent* getProjectContentComponent() const;
//==============================================================================
ApplicationCommandTarget* getNextCommandTarget() override;
void getAllCommands (Array <CommandID>& commands) override;
void getCommandInfo (CommandID commandID, ApplicationCommandInfo& result) override;
bool perform (const InvocationInfo& info) override;
bool shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails,
StringArray& files, bool& canMoveFiles) override;
private:
ScopedPointer <Project> currentProject;
static const char* getProjectWindowPosName() { return "projectWindowPos"; }
void createProjectContentCompIfNeeded();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
};
//==============================================================================
class MainWindowList
{
public:
MainWindowList();
void forceCloseAllWindows();
bool askAllWindowsToClose();
void closeWindow (MainWindow*);
void createWindowIfNoneAreOpen();
void openDocument (OpenDocumentManager::Document*, bool grabFocus);
bool openFile (const File& file);
MainWindow* createNewMainWindow();
MainWindow* getOrCreateFrontmostWindow();
MainWindow* getOrCreateEmptyWindow();
Project* getFrontmostProject();
void reopenLastProjects();
void saveCurrentlyOpenProjectList();
void updateAllWindowTitles();
void avoidSuperimposedWindows (MainWindow*);
void sendLookAndFeelChange();
OwnedArray<MainWindow> windows;
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindowList)
};
#endif // __JUCER_MAINWINDOW_JUCEHEADER__
@@ -0,0 +1,424 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#include "jucer_OpenDocumentManager.h"
#include "jucer_FilePreviewComponent.h"
#include "../Code Editor/jucer_SourceCodeEditor.h"
#include "jucer_Application.h"
//==============================================================================
class UnknownDocument : public OpenDocumentManager::Document
{
public:
UnknownDocument (Project* p, const File& f)
: project (p), file (f)
{
reloadFromFile();
}
//==============================================================================
struct Type : public OpenDocumentManager::DocumentType
{
bool canOpenFile (const File&) override { return true; }
Document* openFile (Project* p, const File& f) override { return new UnknownDocument (p, f); }
};
//==============================================================================
bool loadedOk() const override { return true; }
bool isForFile (const File& f) const override { return file == f; }
bool isForNode (const ValueTree&) const override { return false; }
bool refersToProject (Project& p) const override { return project == &p; }
Project* getProject() const override { return project; }
bool needsSaving() const override { return false; }
bool save() override { return true; }
bool saveAs() override { return false; }
bool hasFileBeenModifiedExternally() override { return fileModificationTime != file.getLastModificationTime(); }
void reloadFromFile() override { fileModificationTime = file.getLastModificationTime(); }
String getName() const override { return file.getFileName(); }
File getFile() const override { return file; }
Component* createEditor() override { return new ItemPreviewComponent (file); }
Component* createViewer() override { return createEditor(); }
void fileHasBeenRenamed (const File& newFile) override { file = newFile; }
String getState() const override { return String::empty; }
void restoreState (const String&) override {}
String getType() const override
{
if (file.getFileExtension().isNotEmpty())
return file.getFileExtension() + " file";
jassertfalse;
return "Unknown";
}
private:
Project* const project;
File file;
Time fileModificationTime;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnknownDocument)
};
//==============================================================================
OpenDocumentManager::DocumentType* createGUIDocumentType();
OpenDocumentManager::OpenDocumentManager()
{
registerType (new UnknownDocument::Type());
registerType (new SourceCodeDocument::Type());
registerType (createGUIDocumentType());
}
OpenDocumentManager::~OpenDocumentManager()
{
}
void OpenDocumentManager::clear()
{
documents.clear();
types.clear();
}
//==============================================================================
void OpenDocumentManager::registerType (DocumentType* type)
{
types.add (type);
}
//==============================================================================
void OpenDocumentManager::addListener (DocumentCloseListener* listener)
{
listeners.addIfNotAlreadyThere (listener);
}
void OpenDocumentManager::removeListener (DocumentCloseListener* listener)
{
listeners.removeFirstMatchingValue (listener);
}
//==============================================================================
bool OpenDocumentManager::canOpenFile (const File& file)
{
for (int i = types.size(); --i >= 0;)
if (types.getUnchecked(i)->canOpenFile (file))
return true;
return false;
}
OpenDocumentManager::Document* OpenDocumentManager::openFile (Project* project, const File& file)
{
for (int i = documents.size(); --i >= 0;)
if (documents.getUnchecked(i)->isForFile (file))
return documents.getUnchecked(i);
Document* d = nullptr;
for (int i = types.size(); --i >= 0 && d == nullptr;)
{
if (types.getUnchecked(i)->canOpenFile (file))
{
d = types.getUnchecked(i)->openFile (project, file);
jassert (d != nullptr);
}
}
jassert (d != nullptr); // should always at least have been picked up by UnknownDocument
documents.add (d);
IntrojucerApp::getCommandManager().commandStatusChanged();
return d;
}
int OpenDocumentManager::getNumOpenDocuments() const
{
return documents.size();
}
OpenDocumentManager::Document* OpenDocumentManager::getOpenDocument (int index) const
{
return documents.getUnchecked (index);
}
FileBasedDocument::SaveResult OpenDocumentManager::saveIfNeededAndUserAgrees (OpenDocumentManager::Document* doc)
{
if (! doc->needsSaving())
return FileBasedDocument::savedOk;
const int r = AlertWindow::showYesNoCancelBox (AlertWindow::QuestionIcon,
TRANS("Closing document..."),
TRANS("Do you want to save the changes to \"")
+ doc->getName() + "\"?",
TRANS("Save"),
TRANS("Discard changes"),
TRANS("Cancel"));
if (r == 1) // save changes
return doc->save() ? FileBasedDocument::savedOk
: FileBasedDocument::failedToWriteToFile;
if (r == 2) // discard changes
return FileBasedDocument::savedOk;
return FileBasedDocument::userCancelledSave;
}
bool OpenDocumentManager::closeDocument (int index, bool saveIfNeeded)
{
if (Document* doc = documents [index])
{
if (saveIfNeeded)
if (saveIfNeededAndUserAgrees (doc) != FileBasedDocument::savedOk)
return false;
bool canClose = true;
for (int i = listeners.size(); --i >= 0;)
if (DocumentCloseListener* l = listeners[i])
if (! l->documentAboutToClose (doc))
canClose = false;
if (! canClose)
return false;
documents.remove (index);
IntrojucerApp::getCommandManager().commandStatusChanged();
}
return true;
}
bool OpenDocumentManager::closeDocument (Document* document, bool saveIfNeeded)
{
return closeDocument (documents.indexOf (document), saveIfNeeded);
}
void OpenDocumentManager::closeFile (const File& f, bool saveIfNeeded)
{
for (int i = documents.size(); --i >= 0;)
if (Document* d = documents[i])
if (d->isForFile (f))
closeDocument (i, saveIfNeeded);
}
bool OpenDocumentManager::closeAll (bool askUserToSave)
{
for (int i = getNumOpenDocuments(); --i >= 0;)
if (! closeDocument (i, askUserToSave))
return false;
return true;
}
bool OpenDocumentManager::closeAllDocumentsUsingProject (Project& project, bool saveIfNeeded)
{
for (int i = documents.size(); --i >= 0;)
if (Document* d = documents[i])
if (d->refersToProject (project))
if (! closeDocument (i, saveIfNeeded))
return false;
return true;
}
bool OpenDocumentManager::anyFilesNeedSaving() const
{
for (int i = documents.size(); --i >= 0;)
if (documents.getUnchecked (i)->needsSaving())
return true;
return false;
}
bool OpenDocumentManager::saveAll()
{
for (int i = documents.size(); --i >= 0;)
{
if (! documents.getUnchecked (i)->save())
return false;
IntrojucerApp::getApp().mainWindowList.updateAllWindowTitles();
IntrojucerApp::getCommandManager().commandStatusChanged();
}
return true;
}
void OpenDocumentManager::reloadModifiedFiles()
{
for (int i = documents.size(); --i >= 0;)
{
Document* d = documents.getUnchecked (i);
if (d->hasFileBeenModifiedExternally())
d->reloadFromFile();
}
}
void OpenDocumentManager::fileHasBeenRenamed (const File& oldFile, const File& newFile)
{
for (int i = documents.size(); --i >= 0;)
{
Document* d = documents.getUnchecked (i);
if (d->isForFile (oldFile))
d->fileHasBeenRenamed (newFile);
}
}
//==============================================================================
RecentDocumentList::RecentDocumentList()
{
IntrojucerApp::getApp().openDocumentManager.addListener (this);
}
RecentDocumentList::~RecentDocumentList()
{
IntrojucerApp::getApp().openDocumentManager.removeListener (this);
}
void RecentDocumentList::clear()
{
previousDocs.clear();
nextDocs.clear();
}
void RecentDocumentList::newDocumentOpened (OpenDocumentManager::Document* document)
{
if (document != nullptr && document != getCurrentDocument())
{
nextDocs.clear();
previousDocs.add (document);
}
}
bool RecentDocumentList::canGoToPrevious() const
{
return previousDocs.size() > 1;
}
bool RecentDocumentList::canGoToNext() const
{
return nextDocs.size() > 0;
}
OpenDocumentManager::Document* RecentDocumentList::getPrevious()
{
if (! canGoToPrevious())
return nullptr;
nextDocs.insert (0, previousDocs.remove (previousDocs.size() - 1));
return previousDocs.getLast();
}
OpenDocumentManager::Document* RecentDocumentList::getNext()
{
if (! canGoToNext())
return nullptr;
OpenDocumentManager::Document* d = nextDocs.remove (0);
previousDocs.add (d);
return d;
}
OpenDocumentManager::Document* RecentDocumentList::getClosestPreviousDocOtherThan (OpenDocumentManager::Document* oneToAvoid) const
{
for (int i = previousDocs.size(); --i >= 0;)
if (previousDocs.getUnchecked(i) != oneToAvoid)
return previousDocs.getUnchecked(i);
return nullptr;
}
bool RecentDocumentList::documentAboutToClose (OpenDocumentManager::Document* document)
{
previousDocs.removeAllInstancesOf (document);
nextDocs.removeAllInstancesOf (document);
jassert (! previousDocs.contains (document));
jassert (! nextDocs.contains (document));
return true;
}
static void restoreDocList (Project& project, Array <OpenDocumentManager::Document*>& list, const XmlElement* xml)
{
if (xml != nullptr)
{
OpenDocumentManager& odm = IntrojucerApp::getApp().openDocumentManager;
forEachXmlChildElementWithTagName (*xml, e, "DOC")
{
const File file (e->getStringAttribute ("file"));
if (file.exists())
{
if (OpenDocumentManager::Document* doc = odm.openFile (&project, file))
{
doc->restoreState (e->getStringAttribute ("state"));
list.add (doc);
}
}
}
}
}
void RecentDocumentList::restoreFromXML (Project& project, const XmlElement& xml)
{
clear();
if (xml.hasTagName ("RECENT_DOCUMENTS"))
{
restoreDocList (project, previousDocs, xml.getChildByName ("PREVIOUS"));
restoreDocList (project, nextDocs, xml.getChildByName ("NEXT"));
}
}
static void saveDocList (const Array <OpenDocumentManager::Document*>& list, XmlElement& xml)
{
for (int i = 0; i < list.size(); ++i)
{
const OpenDocumentManager::Document& doc = *list.getUnchecked(i);
XmlElement* e = xml.createNewChildElement ("DOC");
e->setAttribute ("file", doc.getFile().getFullPathName());
e->setAttribute ("state", doc.getState());
}
}
XmlElement* RecentDocumentList::createXML() const
{
XmlElement* xml = new XmlElement ("RECENT_DOCUMENTS");
saveDocList (previousDocs, *xml->createNewChildElement ("PREVIOUS"));
saveDocList (nextDocs, *xml->createNewChildElement ("NEXT"));
return xml;
}
@@ -0,0 +1,153 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef __JUCER_OPENDOCUMENTMANAGER_JUCEHEADER__
#define __JUCER_OPENDOCUMENTMANAGER_JUCEHEADER__
#include "../Project/jucer_Project.h"
//==============================================================================
/**
*/
class OpenDocumentManager
{
public:
//==============================================================================
OpenDocumentManager();
~OpenDocumentManager();
//==============================================================================
class Document
{
public:
Document() {}
virtual ~Document() {}
virtual bool loadedOk() const = 0;
virtual bool isForFile (const File& file) const = 0;
virtual bool isForNode (const ValueTree& node) const = 0;
virtual bool refersToProject (Project& project) const = 0;
virtual Project* getProject() const = 0;
virtual String getName() const = 0;
virtual String getType() const = 0;
virtual File getFile() const = 0;
virtual bool needsSaving() const = 0;
virtual bool save() = 0;
virtual bool saveAs() = 0;
virtual bool hasFileBeenModifiedExternally() = 0;
virtual void reloadFromFile() = 0;
virtual Component* createEditor() = 0;
virtual Component* createViewer() = 0;
virtual void fileHasBeenRenamed (const File& newFile) = 0;
virtual String getState() const = 0;
virtual void restoreState (const String& state) = 0;
virtual File getCounterpartFile() const { return File::nonexistent; }
};
//==============================================================================
int getNumOpenDocuments() const;
Document* getOpenDocument (int index) const;
void clear();
bool canOpenFile (const File& file);
Document* openFile (Project* project, const File& file);
bool closeDocument (int index, bool saveIfNeeded);
bool closeDocument (Document* document, bool saveIfNeeded);
bool closeAll (bool askUserToSave);
bool closeAllDocumentsUsingProject (Project& project, bool saveIfNeeded);
void closeFile (const File& f, bool saveIfNeeded);
bool anyFilesNeedSaving() const;
bool saveAll();
FileBasedDocument::SaveResult saveIfNeededAndUserAgrees (Document* doc);
void reloadModifiedFiles();
void fileHasBeenRenamed (const File& oldFile, const File& newFile);
//==============================================================================
class DocumentCloseListener
{
public:
DocumentCloseListener() {}
virtual ~DocumentCloseListener() {}
// return false to force it to stop.
virtual bool documentAboutToClose (Document* document) = 0;
};
void addListener (DocumentCloseListener*);
void removeListener (DocumentCloseListener*);
//==============================================================================
class DocumentType
{
public:
DocumentType() {}
virtual ~DocumentType() {}
virtual bool canOpenFile (const File& file) = 0;
virtual Document* openFile (Project* project, const File& file) = 0;
};
void registerType (DocumentType* type);
private:
OwnedArray<DocumentType> types;
OwnedArray<Document> documents;
Array<DocumentCloseListener*> listeners;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenDocumentManager)
};
//==============================================================================
class RecentDocumentList : private OpenDocumentManager::DocumentCloseListener
{
public:
RecentDocumentList();
~RecentDocumentList();
void clear();
void newDocumentOpened (OpenDocumentManager::Document* document);
OpenDocumentManager::Document* getCurrentDocument() const { return previousDocs.getLast(); }
bool canGoToPrevious() const;
bool canGoToNext() const;
OpenDocumentManager::Document* getPrevious();
OpenDocumentManager::Document* getNext();
OpenDocumentManager::Document* getClosestPreviousDocOtherThan (OpenDocumentManager::Document* oneToAvoid) const;
void restoreFromXML (Project& project, const XmlElement& xml);
XmlElement* createXML() const;
private:
bool documentAboutToClose (OpenDocumentManager::Document*);
Array<OpenDocumentManager::Document*> previousDocs, nextDocs;
};
#endif // __JUCER_OPENDOCUMENTMANAGER_JUCEHEADER__