mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-13 00:04:19 +00:00
Changed case of examples folder name.
This commit is contained in:
parent
46547bf8d6
commit
a626425764
2262 changed files with 0 additions and 0 deletions
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
JUCEApplication::JUCEApplication() {}
|
||||
JUCEApplication::~JUCEApplication() {}
|
||||
|
||||
//==============================================================================
|
||||
JUCEApplication* JUCE_CALLTYPE JUCEApplication::getInstance() noexcept
|
||||
{
|
||||
return dynamic_cast <JUCEApplication*> (JUCEApplicationBase::getInstance());
|
||||
}
|
||||
|
||||
bool JUCEApplication::moreThanOneInstanceAllowed() { return true; }
|
||||
void JUCEApplication::anotherInstanceStarted (const String&) {}
|
||||
|
||||
void JUCEApplication::suspended() {}
|
||||
void JUCEApplication::resumed() {}
|
||||
|
||||
void JUCEApplication::systemRequestedQuit() { quit(); }
|
||||
|
||||
void JUCEApplication::unhandledException (const std::exception*, const String&, int)
|
||||
{
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ApplicationCommandTarget* JUCEApplication::getNextCommandTarget()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void JUCEApplication::getAllCommands (Array<CommandID>& commands)
|
||||
{
|
||||
commands.add (StandardApplicationCommandIDs::quit);
|
||||
}
|
||||
|
||||
void JUCEApplication::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
|
||||
{
|
||||
if (commandID == StandardApplicationCommandIDs::quit)
|
||||
{
|
||||
result.setInfo (TRANS("Quit"),
|
||||
TRANS("Quits the application"),
|
||||
"Application", 0);
|
||||
|
||||
result.defaultKeypresses.add (KeyPress ('q', ModifierKeys::commandModifier, 0));
|
||||
}
|
||||
}
|
||||
|
||||
bool JUCEApplication::perform (const InvocationInfo& info)
|
||||
{
|
||||
if (info.commandID == StandardApplicationCommandIDs::quit)
|
||||
{
|
||||
systemRequestedQuit();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MAC
|
||||
extern void juce_initialiseMacMainMenu();
|
||||
#endif
|
||||
|
||||
bool JUCEApplication::initialiseApp()
|
||||
{
|
||||
if (JUCEApplicationBase::initialiseApp())
|
||||
{
|
||||
#if JUCE_MAC
|
||||
juce_initialiseMacMainMenu(); // (needs to get the app's name)
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_APPLICATION_H_INCLUDED
|
||||
#define JUCE_APPLICATION_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An instance of this class is used to specify initialisation and shutdown
|
||||
code for the application.
|
||||
|
||||
Any application that wants to run an event loop must declare a subclass of
|
||||
JUCEApplicationBase or JUCEApplication, and implement its various pure virtual
|
||||
methods.
|
||||
|
||||
It then needs to use the START_JUCE_APPLICATION macro somewhere in a CPP file
|
||||
to declare an instance of this class and generate suitable platform-specific
|
||||
boilerplate code to launch the app.
|
||||
|
||||
Note that this class is derived from JUCEApplicationBase, which contains most
|
||||
of the useful methods and functionality. This derived class is here simply as
|
||||
a convenient way to also inherit from an ApplicationCommandTarget, and to implement
|
||||
default versions of some of the pure virtual base class methods. But you can derive
|
||||
your app object directly from JUCEApplicationBase if you want to, and by doing so
|
||||
can avoid having a dependency on the juce_gui_basics module.
|
||||
|
||||
e.g. @code
|
||||
class MyJUCEApp : public JUCEApplication
|
||||
{
|
||||
public:
|
||||
MyJUCEApp() {}
|
||||
~MyJUCEApp() {}
|
||||
|
||||
void initialise (const String& commandLine) override
|
||||
{
|
||||
myMainWindow = new MyApplicationWindow();
|
||||
myMainWindow->setBounds (100, 100, 400, 500);
|
||||
myMainWindow->setVisible (true);
|
||||
}
|
||||
|
||||
void shutdown() override
|
||||
{
|
||||
myMainWindow = nullptr;
|
||||
}
|
||||
|
||||
const String getApplicationName() override
|
||||
{
|
||||
return "Super JUCE-o-matic";
|
||||
}
|
||||
|
||||
const String getApplicationVersion() override
|
||||
{
|
||||
return "1.0";
|
||||
}
|
||||
|
||||
private:
|
||||
ScopedPointer<MyApplicationWindow> myMainWindow;
|
||||
};
|
||||
|
||||
// this generates boilerplate code to launch our app class:
|
||||
START_JUCE_APPLICATION (MyJUCEApp)
|
||||
@endcode
|
||||
|
||||
@see JUCEApplicationBase, START_JUCE_APPLICATION
|
||||
*/
|
||||
class JUCE_API JUCEApplication : public JUCEApplicationBase,
|
||||
public ApplicationCommandTarget
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Constructs a JUCE app object.
|
||||
|
||||
If subclasses implement a constructor or destructor, they shouldn't call any
|
||||
JUCE code in there - put your startup/shutdown code in initialise() and
|
||||
shutdown() instead.
|
||||
*/
|
||||
JUCEApplication();
|
||||
|
||||
/** Destructor.
|
||||
|
||||
If subclasses implement a constructor or destructor, they shouldn't call any
|
||||
JUCE code in there - put your startup/shutdown code in initialise() and
|
||||
shutdown() instead.
|
||||
*/
|
||||
~JUCEApplication();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the global instance of the application object being run. */
|
||||
static JUCEApplication* JUCE_CALLTYPE getInstance() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the application's name. */
|
||||
virtual const String getApplicationName() = 0;
|
||||
|
||||
/** Returns the application's version number. */
|
||||
virtual const String getApplicationVersion() = 0;
|
||||
|
||||
/** Checks whether multiple instances of the app are allowed.
|
||||
|
||||
If you application class returns true for this, more than one instance is
|
||||
permitted to run (except on OSX where the OS automatically stops you launching
|
||||
a second instance of an app without explicitly starting it from the command-line).
|
||||
|
||||
If it's false, the second instance won't start, but it you will still get a
|
||||
callback to anotherInstanceStarted() to tell you about this - which
|
||||
gives you a chance to react to what the user was trying to do.
|
||||
*/
|
||||
bool moreThanOneInstanceAllowed() override;
|
||||
|
||||
/** Indicates that the user has tried to start up another instance of the app.
|
||||
This will get called even if moreThanOneInstanceAllowed() is false.
|
||||
*/
|
||||
void anotherInstanceStarted (const String& commandLine) override;
|
||||
|
||||
/** Called when the operating system is trying to close the application.
|
||||
|
||||
The default implementation of this method is to call quit(), but it may
|
||||
be overloaded to ignore the request or do some other special behaviour
|
||||
instead. For example, you might want to offer the user the chance to save
|
||||
their changes before quitting, and give them the chance to cancel.
|
||||
|
||||
If you want to send a quit signal to your app, this is the correct method
|
||||
to call, because it means that requests that come from the system get handled
|
||||
in the same way as those from your own application code. So e.g. you'd
|
||||
call this method from a "quit" item on a menu bar.
|
||||
*/
|
||||
void systemRequestedQuit() override;
|
||||
|
||||
/** This method is called when the application is being put into background mode
|
||||
by the operating system.
|
||||
*/
|
||||
void suspended() override;
|
||||
|
||||
/** This method is called when the application is being woken from background mode
|
||||
by the operating system.
|
||||
*/
|
||||
void resumed() override;
|
||||
|
||||
/** If any unhandled exceptions make it through to the message dispatch loop, this
|
||||
callback will be triggered, in case you want to log them or do some other
|
||||
type of error-handling.
|
||||
|
||||
If the type of exception is derived from the std::exception class, the pointer
|
||||
passed-in will be valid. If the exception is of unknown type, this pointer
|
||||
will be null.
|
||||
*/
|
||||
void unhandledException (const std::exception* e,
|
||||
const String& sourceFilename,
|
||||
int lineNumber) override;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
ApplicationCommandTarget* getNextCommandTarget();
|
||||
/** @internal */
|
||||
void getCommandInfo (CommandID, ApplicationCommandInfo&);
|
||||
/** @internal */
|
||||
void getAllCommands (Array<CommandID>&);
|
||||
/** @internal */
|
||||
bool perform (const InvocationInfo&);
|
||||
|
||||
private:
|
||||
bool initialiseApp() override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (JUCEApplication)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_APPLICATION_H_INCLUDED
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
ArrowButton::ArrowButton (const String& name, float arrowDirectionInRadians, Colour arrowColour)
|
||||
: Button (name), colour (arrowColour)
|
||||
{
|
||||
path.addTriangle (0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f);
|
||||
path.applyTransform (AffineTransform::rotation (float_Pi * 2.0f * arrowDirectionInRadians, 0.5f, 0.5f));
|
||||
}
|
||||
|
||||
ArrowButton::~ArrowButton() {}
|
||||
|
||||
void ArrowButton::paintButton (Graphics& g, bool /*isMouseOverButton*/, bool isButtonDown)
|
||||
{
|
||||
Path p (path);
|
||||
|
||||
const float offset = isButtonDown ? 1.0f : 0.0f;
|
||||
p.applyTransform (path.getTransformToScaleToFit (offset, offset, getWidth() - 3.0f, getHeight() - 3.0f, false));
|
||||
|
||||
DropShadow (Colours::black.withAlpha (0.3f), isButtonDown ? 2 : 4, Point<int>()).drawForPath (g, p);
|
||||
|
||||
g.setColour (colour);
|
||||
g.fillPath (p);
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_ARROWBUTTON_H_INCLUDED
|
||||
#define JUCE_ARROWBUTTON_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A button with an arrow in it.
|
||||
|
||||
@see Button
|
||||
*/
|
||||
class JUCE_API ArrowButton : public Button
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an ArrowButton.
|
||||
|
||||
@param buttonName the name to give the button
|
||||
@param arrowDirection the direction the arrow should point in, where 0.0 is
|
||||
pointing right, 0.25 is down, 0.5 is left, 0.75 is up
|
||||
@param arrowColour the colour to use for the arrow
|
||||
*/
|
||||
ArrowButton (const String& buttonName,
|
||||
float arrowDirection,
|
||||
Colour arrowColour);
|
||||
|
||||
/** Destructor. */
|
||||
~ArrowButton();
|
||||
|
||||
/** @internal */
|
||||
void paintButton (Graphics&, bool isMouseOverButton, bool isButtonDown) override;
|
||||
|
||||
private:
|
||||
Colour colour;
|
||||
Path path;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ArrowButton)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_ARROWBUTTON_H_INCLUDED
|
||||
|
|
@ -0,0 +1,674 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
class Button::CallbackHelper : public Timer,
|
||||
public ApplicationCommandManagerListener,
|
||||
public ValueListener,
|
||||
public KeyListener
|
||||
{
|
||||
public:
|
||||
CallbackHelper (Button& b) : button (b) {}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
button.repeatTimerCallback();
|
||||
}
|
||||
|
||||
bool keyStateChanged (bool, Component*) override
|
||||
{
|
||||
return button.keyStateChangedCallback();
|
||||
}
|
||||
|
||||
void valueChanged (Value& value) override
|
||||
{
|
||||
if (value.refersToSameSourceAs (button.isOn))
|
||||
button.setToggleState (button.isOn.getValue(), sendNotification);
|
||||
}
|
||||
|
||||
bool keyPressed (const KeyPress&, Component*) override
|
||||
{
|
||||
// returning true will avoid forwarding events for keys that we're using as shortcuts
|
||||
return button.isShortcutPressed();
|
||||
}
|
||||
|
||||
void applicationCommandInvoked (const ApplicationCommandTarget::InvocationInfo& info) override
|
||||
{
|
||||
if (info.commandID == button.commandID
|
||||
&& (info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) == 0)
|
||||
button.flashButtonState();
|
||||
}
|
||||
|
||||
void applicationCommandListChanged() override
|
||||
{
|
||||
button.applicationCommandListChangeCallback();
|
||||
}
|
||||
|
||||
private:
|
||||
Button& button;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHelper)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
Button::Button (const String& name)
|
||||
: Component (name),
|
||||
text (name),
|
||||
buttonPressTime (0),
|
||||
lastRepeatTime (0),
|
||||
commandManagerToUse (nullptr),
|
||||
autoRepeatDelay (-1),
|
||||
autoRepeatSpeed (0),
|
||||
autoRepeatMinimumDelay (-1),
|
||||
radioGroupId (0),
|
||||
connectedEdgeFlags (0),
|
||||
commandID(),
|
||||
buttonState (buttonNormal),
|
||||
lastToggleState (false),
|
||||
clickTogglesState (false),
|
||||
needsToRelease (false),
|
||||
needsRepainting (false),
|
||||
isKeyDown (false),
|
||||
triggerOnMouseDown (false),
|
||||
generateTooltip (false)
|
||||
{
|
||||
callbackHelper = new CallbackHelper (*this);
|
||||
|
||||
setWantsKeyboardFocus (true);
|
||||
isOn.addListener (callbackHelper);
|
||||
}
|
||||
|
||||
Button::~Button()
|
||||
{
|
||||
clearShortcuts();
|
||||
|
||||
if (commandManagerToUse != nullptr)
|
||||
commandManagerToUse->removeListener (callbackHelper);
|
||||
|
||||
isOn.removeListener (callbackHelper);
|
||||
callbackHelper = nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::setButtonText (const String& newText)
|
||||
{
|
||||
if (text != newText)
|
||||
{
|
||||
text = newText;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void Button::setTooltip (const String& newTooltip)
|
||||
{
|
||||
SettableTooltipClient::setTooltip (newTooltip);
|
||||
generateTooltip = false;
|
||||
}
|
||||
|
||||
void Button::updateAutomaticTooltip (const ApplicationCommandInfo& info)
|
||||
{
|
||||
if (generateTooltip && commandManagerToUse != nullptr)
|
||||
{
|
||||
String tt (info.description.isNotEmpty() ? info.description
|
||||
: info.shortName);
|
||||
|
||||
Array<KeyPress> keyPresses (commandManagerToUse->getKeyMappings()->getKeyPressesAssignedToCommand (commandID));
|
||||
|
||||
for (int i = 0; i < keyPresses.size(); ++i)
|
||||
{
|
||||
const String key (keyPresses.getReference(i).getTextDescription());
|
||||
|
||||
tt << " [";
|
||||
|
||||
if (key.length() == 1)
|
||||
tt << TRANS("shortcut") << ": '" << key << "']";
|
||||
else
|
||||
tt << key << ']';
|
||||
}
|
||||
|
||||
SettableTooltipClient::setTooltip (tt);
|
||||
}
|
||||
}
|
||||
|
||||
void Button::setConnectedEdges (const int newFlags)
|
||||
{
|
||||
if (connectedEdgeFlags != newFlags)
|
||||
{
|
||||
connectedEdgeFlags = newFlags;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::setToggleState (const bool shouldBeOn, const NotificationType notification)
|
||||
{
|
||||
if (shouldBeOn != lastToggleState)
|
||||
{
|
||||
WeakReference<Component> deletionWatcher (this);
|
||||
|
||||
if (shouldBeOn)
|
||||
{
|
||||
turnOffOtherButtonsInGroup (notification);
|
||||
|
||||
if (deletionWatcher == nullptr)
|
||||
return;
|
||||
}
|
||||
|
||||
if (getToggleState() != shouldBeOn) // this test means that if the value is void rather than explicitly set to
|
||||
isOn = shouldBeOn; // false, it won't be changed unless the required value is true.
|
||||
|
||||
lastToggleState = shouldBeOn;
|
||||
repaint();
|
||||
|
||||
if (notification != dontSendNotification)
|
||||
{
|
||||
// async callbacks aren't possible here
|
||||
jassert (notification != sendNotificationAsync);
|
||||
|
||||
sendClickMessage (ModifierKeys::getCurrentModifiers());
|
||||
|
||||
if (deletionWatcher == nullptr)
|
||||
return;
|
||||
}
|
||||
|
||||
if (notification != dontSendNotification)
|
||||
sendStateMessage();
|
||||
else
|
||||
buttonStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void Button::setToggleState (const bool shouldBeOn, bool sendChange)
|
||||
{
|
||||
setToggleState (shouldBeOn, sendChange ? sendNotification : dontSendNotification);
|
||||
}
|
||||
|
||||
void Button::setClickingTogglesState (const bool shouldToggle) noexcept
|
||||
{
|
||||
clickTogglesState = shouldToggle;
|
||||
|
||||
// if you've got clickTogglesState turned on, you shouldn't also connect the button
|
||||
// up to be a command invoker. Instead, your command handler must flip the state of whatever
|
||||
// it is that this button represents, and the button will update its state to reflect this
|
||||
// in the applicationCommandListChanged() method.
|
||||
jassert (commandManagerToUse == nullptr || ! clickTogglesState);
|
||||
}
|
||||
|
||||
bool Button::getClickingTogglesState() const noexcept
|
||||
{
|
||||
return clickTogglesState;
|
||||
}
|
||||
|
||||
void Button::setRadioGroupId (const int newGroupId, NotificationType notification)
|
||||
{
|
||||
if (radioGroupId != newGroupId)
|
||||
{
|
||||
radioGroupId = newGroupId;
|
||||
|
||||
if (lastToggleState)
|
||||
turnOffOtherButtonsInGroup (notification);
|
||||
}
|
||||
}
|
||||
|
||||
void Button::turnOffOtherButtonsInGroup (const NotificationType notification)
|
||||
{
|
||||
if (Component* const p = getParentComponent())
|
||||
{
|
||||
if (radioGroupId != 0)
|
||||
{
|
||||
WeakReference<Component> deletionWatcher (this);
|
||||
|
||||
for (int i = p->getNumChildComponents(); --i >= 0;)
|
||||
{
|
||||
Component* const c = p->getChildComponent (i);
|
||||
|
||||
if (c != this)
|
||||
{
|
||||
if (Button* const b = dynamic_cast <Button*> (c))
|
||||
{
|
||||
if (b->getRadioGroupId() == radioGroupId)
|
||||
{
|
||||
b->setToggleState (false, notification);
|
||||
|
||||
if (deletionWatcher == nullptr)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::enablementChanged()
|
||||
{
|
||||
updateState();
|
||||
repaint();
|
||||
}
|
||||
|
||||
Button::ButtonState Button::updateState()
|
||||
{
|
||||
return updateState (isMouseOver (true), isMouseButtonDown());
|
||||
}
|
||||
|
||||
Button::ButtonState Button::updateState (const bool over, const bool down)
|
||||
{
|
||||
ButtonState newState = buttonNormal;
|
||||
|
||||
if (isEnabled() && isVisible() && ! isCurrentlyBlockedByAnotherModalComponent())
|
||||
{
|
||||
if ((down && (over || (triggerOnMouseDown && buttonState == buttonDown))) || isKeyDown)
|
||||
newState = buttonDown;
|
||||
else if (over)
|
||||
newState = buttonOver;
|
||||
}
|
||||
|
||||
setState (newState);
|
||||
return newState;
|
||||
}
|
||||
|
||||
void Button::setState (const ButtonState newState)
|
||||
{
|
||||
if (buttonState != newState)
|
||||
{
|
||||
buttonState = newState;
|
||||
repaint();
|
||||
|
||||
if (buttonState == buttonDown)
|
||||
{
|
||||
buttonPressTime = Time::getApproximateMillisecondCounter();
|
||||
lastRepeatTime = 0;
|
||||
}
|
||||
|
||||
sendStateMessage();
|
||||
}
|
||||
}
|
||||
|
||||
bool Button::isDown() const noexcept { return buttonState == buttonDown; }
|
||||
bool Button::isOver() const noexcept { return buttonState != buttonNormal; }
|
||||
|
||||
void Button::buttonStateChanged() {}
|
||||
|
||||
uint32 Button::getMillisecondsSinceButtonDown() const noexcept
|
||||
{
|
||||
const uint32 now = Time::getApproximateMillisecondCounter();
|
||||
return now > buttonPressTime ? now - buttonPressTime : 0;
|
||||
}
|
||||
|
||||
void Button::setTriggeredOnMouseDown (const bool isTriggeredOnMouseDown) noexcept
|
||||
{
|
||||
triggerOnMouseDown = isTriggeredOnMouseDown;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::clicked()
|
||||
{
|
||||
}
|
||||
|
||||
void Button::clicked (const ModifierKeys&)
|
||||
{
|
||||
clicked();
|
||||
}
|
||||
|
||||
enum { clickMessageId = 0x2f3f4f99 };
|
||||
|
||||
void Button::triggerClick()
|
||||
{
|
||||
postCommandMessage (clickMessageId);
|
||||
}
|
||||
|
||||
void Button::internalClickCallback (const ModifierKeys& modifiers)
|
||||
{
|
||||
if (clickTogglesState)
|
||||
{
|
||||
const bool shouldBeOn = (radioGroupId != 0 || ! lastToggleState);
|
||||
|
||||
if (shouldBeOn != getToggleState())
|
||||
{
|
||||
setToggleState (shouldBeOn, sendNotification);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sendClickMessage (modifiers);
|
||||
}
|
||||
|
||||
void Button::flashButtonState()
|
||||
{
|
||||
if (isEnabled())
|
||||
{
|
||||
needsToRelease = true;
|
||||
setState (buttonDown);
|
||||
callbackHelper->startTimer (100);
|
||||
}
|
||||
}
|
||||
|
||||
void Button::handleCommandMessage (int commandId)
|
||||
{
|
||||
if (commandId == clickMessageId)
|
||||
{
|
||||
if (isEnabled())
|
||||
{
|
||||
flashButtonState();
|
||||
internalClickCallback (ModifierKeys::getCurrentModifiers());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Component::handleCommandMessage (commandId);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::addListener (ButtonListener* const newListener)
|
||||
{
|
||||
buttonListeners.add (newListener);
|
||||
}
|
||||
|
||||
void Button::removeListener (ButtonListener* const listener)
|
||||
{
|
||||
buttonListeners.remove (listener);
|
||||
}
|
||||
|
||||
void Button::sendClickMessage (const ModifierKeys& modifiers)
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
|
||||
if (commandManagerToUse != nullptr && commandID != 0)
|
||||
{
|
||||
ApplicationCommandTarget::InvocationInfo info (commandID);
|
||||
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromButton;
|
||||
info.originatingComponent = this;
|
||||
|
||||
commandManagerToUse->invoke (info, true);
|
||||
}
|
||||
|
||||
clicked (modifiers);
|
||||
|
||||
if (! checker.shouldBailOut())
|
||||
buttonListeners.callChecked (checker, &ButtonListener::buttonClicked, this); // (can't use Button::Listener due to idiotic VC2005 bug)
|
||||
}
|
||||
|
||||
void Button::sendStateMessage()
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
|
||||
buttonStateChanged();
|
||||
|
||||
if (! checker.shouldBailOut())
|
||||
buttonListeners.callChecked (checker, &ButtonListener::buttonStateChanged, this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::paint (Graphics& g)
|
||||
{
|
||||
if (needsToRelease && isEnabled())
|
||||
{
|
||||
needsToRelease = false;
|
||||
needsRepainting = true;
|
||||
}
|
||||
|
||||
paintButton (g, isOver(), isDown());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::mouseEnter (const MouseEvent&) { updateState (true, false); }
|
||||
void Button::mouseExit (const MouseEvent&) { updateState (false, false); }
|
||||
|
||||
void Button::mouseDown (const MouseEvent& e)
|
||||
{
|
||||
updateState (true, true);
|
||||
|
||||
if (isDown())
|
||||
{
|
||||
if (autoRepeatDelay >= 0)
|
||||
callbackHelper->startTimer (autoRepeatDelay);
|
||||
|
||||
if (triggerOnMouseDown)
|
||||
internalClickCallback (e.mods);
|
||||
}
|
||||
}
|
||||
|
||||
void Button::mouseUp (const MouseEvent& e)
|
||||
{
|
||||
const bool wasDown = isDown();
|
||||
const bool wasOver = isOver();
|
||||
updateState (isMouseOver(), false);
|
||||
|
||||
if (wasDown && wasOver && ! triggerOnMouseDown)
|
||||
internalClickCallback (e.mods);
|
||||
}
|
||||
|
||||
void Button::mouseDrag (const MouseEvent&)
|
||||
{
|
||||
const ButtonState oldState = buttonState;
|
||||
updateState (isMouseOver(), true);
|
||||
|
||||
if (autoRepeatDelay >= 0 && buttonState != oldState && isDown())
|
||||
callbackHelper->startTimer (autoRepeatSpeed);
|
||||
}
|
||||
|
||||
void Button::focusGained (FocusChangeType)
|
||||
{
|
||||
updateState();
|
||||
repaint();
|
||||
}
|
||||
|
||||
void Button::focusLost (FocusChangeType)
|
||||
{
|
||||
updateState();
|
||||
repaint();
|
||||
}
|
||||
|
||||
void Button::visibilityChanged()
|
||||
{
|
||||
needsToRelease = false;
|
||||
updateState();
|
||||
}
|
||||
|
||||
void Button::parentHierarchyChanged()
|
||||
{
|
||||
Component* const newKeySource = (shortcuts.size() == 0) ? nullptr : getTopLevelComponent();
|
||||
|
||||
if (newKeySource != keySource.get())
|
||||
{
|
||||
if (keySource != nullptr)
|
||||
keySource->removeKeyListener (callbackHelper);
|
||||
|
||||
keySource = newKeySource;
|
||||
|
||||
if (keySource != nullptr)
|
||||
keySource->addKeyListener (callbackHelper);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::setCommandToTrigger (ApplicationCommandManager* const newCommandManager,
|
||||
const CommandID newCommandID, const bool generateTip)
|
||||
{
|
||||
commandID = newCommandID;
|
||||
generateTooltip = generateTip;
|
||||
|
||||
if (commandManagerToUse != newCommandManager)
|
||||
{
|
||||
if (commandManagerToUse != nullptr)
|
||||
commandManagerToUse->removeListener (callbackHelper);
|
||||
|
||||
commandManagerToUse = newCommandManager;
|
||||
|
||||
if (commandManagerToUse != nullptr)
|
||||
commandManagerToUse->addListener (callbackHelper);
|
||||
|
||||
// if you've got clickTogglesState turned on, you shouldn't also connect the button
|
||||
// up to be a command invoker. Instead, your command handler must flip the state of whatever
|
||||
// it is that this button represents, and the button will update its state to reflect this
|
||||
// in the applicationCommandListChanged() method.
|
||||
jassert (commandManagerToUse == nullptr || ! clickTogglesState);
|
||||
}
|
||||
|
||||
if (commandManagerToUse != nullptr)
|
||||
applicationCommandListChangeCallback();
|
||||
else
|
||||
setEnabled (true);
|
||||
}
|
||||
|
||||
void Button::applicationCommandListChangeCallback()
|
||||
{
|
||||
if (commandManagerToUse != nullptr)
|
||||
{
|
||||
ApplicationCommandInfo info (0);
|
||||
|
||||
if (commandManagerToUse->getTargetForCommand (commandID, info) != nullptr)
|
||||
{
|
||||
updateAutomaticTooltip (info);
|
||||
setEnabled ((info.flags & ApplicationCommandInfo::isDisabled) == 0);
|
||||
setToggleState ((info.flags & ApplicationCommandInfo::isTicked) != 0, dontSendNotification);
|
||||
}
|
||||
else
|
||||
{
|
||||
setEnabled (false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::addShortcut (const KeyPress& key)
|
||||
{
|
||||
if (key.isValid())
|
||||
{
|
||||
jassert (! isRegisteredForShortcut (key)); // already registered!
|
||||
|
||||
shortcuts.add (key);
|
||||
parentHierarchyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void Button::clearShortcuts()
|
||||
{
|
||||
shortcuts.clear();
|
||||
parentHierarchyChanged();
|
||||
}
|
||||
|
||||
bool Button::isShortcutPressed() const
|
||||
{
|
||||
if (isShowing() && ! isCurrentlyBlockedByAnotherModalComponent())
|
||||
for (int i = shortcuts.size(); --i >= 0;)
|
||||
if (shortcuts.getReference(i).isCurrentlyDown())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Button::isRegisteredForShortcut (const KeyPress& key) const
|
||||
{
|
||||
for (int i = shortcuts.size(); --i >= 0;)
|
||||
if (key == shortcuts.getReference(i))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Button::keyStateChangedCallback()
|
||||
{
|
||||
if (! isEnabled())
|
||||
return false;
|
||||
|
||||
const bool wasDown = isKeyDown;
|
||||
isKeyDown = isShortcutPressed();
|
||||
|
||||
if (autoRepeatDelay >= 0 && (isKeyDown && ! wasDown))
|
||||
callbackHelper->startTimer (autoRepeatDelay);
|
||||
|
||||
updateState();
|
||||
|
||||
if (isEnabled() && wasDown && ! isKeyDown)
|
||||
{
|
||||
internalClickCallback (ModifierKeys::getCurrentModifiers());
|
||||
|
||||
// (return immediately - this button may now have been deleted)
|
||||
return true;
|
||||
}
|
||||
|
||||
return wasDown || isKeyDown;
|
||||
}
|
||||
|
||||
bool Button::keyPressed (const KeyPress& key)
|
||||
{
|
||||
if (isEnabled() && key.isKeyCode (KeyPress::returnKey))
|
||||
{
|
||||
triggerClick();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::setRepeatSpeed (const int initialDelayMillisecs,
|
||||
const int repeatMillisecs,
|
||||
const int minimumDelayInMillisecs) noexcept
|
||||
{
|
||||
autoRepeatDelay = initialDelayMillisecs;
|
||||
autoRepeatSpeed = repeatMillisecs;
|
||||
autoRepeatMinimumDelay = jmin (autoRepeatSpeed, minimumDelayInMillisecs);
|
||||
}
|
||||
|
||||
void Button::repeatTimerCallback()
|
||||
{
|
||||
if (needsRepainting)
|
||||
{
|
||||
callbackHelper->stopTimer();
|
||||
updateState();
|
||||
needsRepainting = false;
|
||||
}
|
||||
else if (autoRepeatSpeed > 0 && (isKeyDown || (updateState() == buttonDown)))
|
||||
{
|
||||
int repeatSpeed = autoRepeatSpeed;
|
||||
|
||||
if (autoRepeatMinimumDelay >= 0)
|
||||
{
|
||||
double timeHeldDown = jmin (1.0, getMillisecondsSinceButtonDown() / 4000.0);
|
||||
timeHeldDown *= timeHeldDown;
|
||||
|
||||
repeatSpeed = repeatSpeed + (int) (timeHeldDown * (autoRepeatMinimumDelay - repeatSpeed));
|
||||
}
|
||||
|
||||
repeatSpeed = jmax (1, repeatSpeed);
|
||||
|
||||
const uint32 now = Time::getMillisecondCounter();
|
||||
|
||||
// if we've been blocked from repeating often enough, speed up the repeat timer to compensate..
|
||||
if (lastRepeatTime != 0 && (int) (now - lastRepeatTime) > repeatSpeed * 2)
|
||||
repeatSpeed = jmax (1, repeatSpeed / 2);
|
||||
|
||||
lastRepeatTime = now;
|
||||
callbackHelper->startTimer (repeatSpeed);
|
||||
|
||||
internalClickCallback (ModifierKeys::getCurrentModifiers());
|
||||
}
|
||||
else if (! needsToRelease)
|
||||
{
|
||||
callbackHelper->stopTimer();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,514 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_BUTTON_H_INCLUDED
|
||||
#define JUCE_BUTTON_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A base class for buttons.
|
||||
|
||||
This contains all the logic for button behaviours such as enabling/disabling,
|
||||
responding to shortcut keystrokes, auto-repeating when held down, toggle-buttons
|
||||
and radio groups, etc.
|
||||
|
||||
@see TextButton, DrawableButton, ToggleButton
|
||||
*/
|
||||
class JUCE_API Button : public Component,
|
||||
public SettableTooltipClient
|
||||
{
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** Creates a button.
|
||||
|
||||
@param buttonName the text to put in the button (the component's name is also
|
||||
initially set to this string, but these can be changed later
|
||||
using the setName() and setButtonText() methods)
|
||||
*/
|
||||
explicit Button (const String& buttonName);
|
||||
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~Button();
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the button's text.
|
||||
@see getButtonText
|
||||
*/
|
||||
void setButtonText (const String& newText);
|
||||
|
||||
/** Returns the text displayed in the button.
|
||||
@see setButtonText
|
||||
*/
|
||||
const String& getButtonText() const { return text; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the button is currently being held down.
|
||||
@see isOver
|
||||
*/
|
||||
bool isDown() const noexcept;
|
||||
|
||||
/** Returns true if the mouse is currently over the button.
|
||||
This will be also be true if the button is being held down.
|
||||
@see isDown
|
||||
*/
|
||||
bool isOver() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** A button has an on/off state associated with it, and this changes that.
|
||||
|
||||
By default buttons are 'off' and for simple buttons that you click to perform
|
||||
an action you won't change this. Toggle buttons, however will want to
|
||||
change their state when turned on or off.
|
||||
|
||||
@param shouldBeOn whether to set the button's toggle state to be on or
|
||||
off. If it's a member of a button group, this will
|
||||
always try to turn it on, and to turn off any other
|
||||
buttons in the group
|
||||
@param notification determines the behaviour if the value changes - this
|
||||
can invoke a synchronous call to clicked(), but
|
||||
sendNotificationAsync is not supported
|
||||
@see getToggleState, setRadioGroupId
|
||||
*/
|
||||
void setToggleState (bool shouldBeOn, NotificationType notification);
|
||||
|
||||
/** Returns true if the button is 'on'.
|
||||
|
||||
By default buttons are 'off' and for simple buttons that you click to perform
|
||||
an action you won't change this. Toggle buttons, however will want to
|
||||
change their state when turned on or off.
|
||||
|
||||
@see setToggleState
|
||||
*/
|
||||
bool getToggleState() const noexcept { return isOn.getValue(); }
|
||||
|
||||
/** Returns the Value object that represents the botton's toggle state.
|
||||
You can use this Value object to connect the button's state to external values or setters,
|
||||
either by taking a copy of the Value, or by using Value::referTo() to make it point to
|
||||
your own Value object.
|
||||
@see getToggleState, Value
|
||||
*/
|
||||
Value& getToggleStateValue() noexcept { return isOn; }
|
||||
|
||||
/** This tells the button to automatically flip the toggle state when
|
||||
the button is clicked.
|
||||
|
||||
If set to true, then before the clicked() callback occurs, the toggle-state
|
||||
of the button is flipped.
|
||||
*/
|
||||
void setClickingTogglesState (bool shouldAutoToggleOnClick) noexcept;
|
||||
|
||||
/** Returns true if this button is set to be an automatic toggle-button.
|
||||
This returns the last value that was passed to setClickingTogglesState().
|
||||
*/
|
||||
bool getClickingTogglesState() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Enables the button to act as a member of a mutually-exclusive group
|
||||
of 'radio buttons'.
|
||||
|
||||
If the group ID is set to a non-zero number, then this button will
|
||||
act as part of a group of buttons with the same ID, only one of
|
||||
which can be 'on' at the same time. Note that when it's part of
|
||||
a group, clicking a toggle-button that's 'on' won't turn it off.
|
||||
|
||||
To find other buttons with the same ID, this button will search through
|
||||
its sibling components for ToggleButtons, so all the buttons for a
|
||||
particular group must be placed inside the same parent component.
|
||||
|
||||
Set the group ID back to zero if you want it to act as a normal toggle
|
||||
button again.
|
||||
|
||||
The notification argument lets you specify how other buttons should react
|
||||
to being turned on or off in response to this call.
|
||||
|
||||
@see getRadioGroupId
|
||||
*/
|
||||
void setRadioGroupId (int newGroupId, NotificationType notification = sendNotification);
|
||||
|
||||
/** Returns the ID of the group to which this button belongs.
|
||||
(See setRadioGroupId() for an explanation of this).
|
||||
*/
|
||||
int getRadioGroupId() const noexcept { return radioGroupId; }
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Used to receive callbacks when a button is clicked.
|
||||
|
||||
@see Button::addListener, Button::removeListener
|
||||
*/
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~Listener() {}
|
||||
|
||||
/** Called when the button is clicked. */
|
||||
virtual void buttonClicked (Button*) = 0;
|
||||
|
||||
/** Called when the button's state changes. */
|
||||
virtual void buttonStateChanged (Button*) {}
|
||||
};
|
||||
|
||||
/** Registers a listener to receive events when this button's state changes.
|
||||
If the listener is already registered, this will not register it again.
|
||||
@see removeListener
|
||||
*/
|
||||
void addListener (Listener* newListener);
|
||||
|
||||
/** Removes a previously-registered button listener
|
||||
@see addListener
|
||||
*/
|
||||
void removeListener (Listener* listener);
|
||||
|
||||
//==============================================================================
|
||||
/** Causes the button to act as if it's been clicked.
|
||||
|
||||
This will asynchronously make the button draw itself going down and up, and
|
||||
will then call back the clicked() method as if mouse was clicked on it.
|
||||
|
||||
@see clicked
|
||||
*/
|
||||
virtual void triggerClick();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets a command ID for this button to automatically invoke when it's clicked.
|
||||
|
||||
When the button is pressed, it will use the given manager to trigger the
|
||||
command ID.
|
||||
|
||||
Obviously be careful that the ApplicationCommandManager doesn't get deleted
|
||||
before this button is. To disable the command triggering, call this method and
|
||||
pass nullptr as the command manager.
|
||||
|
||||
If generateTooltip is true, then the button's tooltip will be automatically
|
||||
generated based on the name of this command and its current shortcut key.
|
||||
|
||||
@see addShortcut, getCommandID
|
||||
*/
|
||||
void setCommandToTrigger (ApplicationCommandManager* commandManagerToUse,
|
||||
CommandID commandID,
|
||||
bool generateTooltip);
|
||||
|
||||
/** Returns the command ID that was set by setCommandToTrigger(). */
|
||||
CommandID getCommandID() const noexcept { return commandID; }
|
||||
|
||||
//==============================================================================
|
||||
/** Assigns a shortcut key to trigger the button.
|
||||
|
||||
The button registers itself with its top-level parent component for keypresses.
|
||||
|
||||
Note that a different way of linking buttons to keypresses is by using the
|
||||
setCommandToTrigger() method to invoke a command.
|
||||
|
||||
@see clearShortcuts
|
||||
*/
|
||||
void addShortcut (const KeyPress&);
|
||||
|
||||
/** Removes all key shortcuts that had been set for this button.
|
||||
@see addShortcut
|
||||
*/
|
||||
void clearShortcuts();
|
||||
|
||||
/** Returns true if the given keypress is a shortcut for this button.
|
||||
@see addShortcut
|
||||
*/
|
||||
bool isRegisteredForShortcut (const KeyPress&) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets an auto-repeat speed for the button when it is held down.
|
||||
|
||||
(Auto-repeat is disabled by default).
|
||||
|
||||
@param initialDelayInMillisecs how long to wait after the mouse is pressed before
|
||||
triggering the next click. If this is zero, auto-repeat
|
||||
is disabled
|
||||
@param repeatDelayInMillisecs the frequently subsequent repeated clicks should be
|
||||
triggered
|
||||
@param minimumDelayInMillisecs if this is greater than 0, the auto-repeat speed will
|
||||
get faster, the longer the button is held down, up to the
|
||||
minimum interval specified here
|
||||
*/
|
||||
void setRepeatSpeed (int initialDelayInMillisecs,
|
||||
int repeatDelayInMillisecs,
|
||||
int minimumDelayInMillisecs = -1) noexcept;
|
||||
|
||||
/** Sets whether the button click should happen when the mouse is pressed or released.
|
||||
|
||||
By default the button is only considered to have been clicked when the mouse is
|
||||
released, but setting this to true will make it call the clicked() method as soon
|
||||
as the button is pressed.
|
||||
|
||||
This is useful if the button is being used to show a pop-up menu, as it allows
|
||||
the click to be used as a drag onto the menu.
|
||||
*/
|
||||
void setTriggeredOnMouseDown (bool isTriggeredOnMouseDown) noexcept;
|
||||
|
||||
/** Returns the number of milliseconds since the last time the button
|
||||
went into the 'down' state.
|
||||
*/
|
||||
uint32 getMillisecondsSinceButtonDown() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the tooltip for this button.
|
||||
@see TooltipClient, TooltipWindow
|
||||
*/
|
||||
void setTooltip (const String& newTooltip) override;
|
||||
|
||||
//==============================================================================
|
||||
/** A combination of these flags are used by setConnectedEdges(). */
|
||||
enum ConnectedEdgeFlags
|
||||
{
|
||||
ConnectedOnLeft = 1,
|
||||
ConnectedOnRight = 2,
|
||||
ConnectedOnTop = 4,
|
||||
ConnectedOnBottom = 8
|
||||
};
|
||||
|
||||
/** Hints about which edges of the button might be connected to adjoining buttons.
|
||||
|
||||
The value passed in is a bitwise combination of any of the values in the
|
||||
ConnectedEdgeFlags enum.
|
||||
|
||||
E.g. if you are placing two buttons adjacent to each other, you could use this to
|
||||
indicate which edges are touching, and the LookAndFeel might choose to drawn them
|
||||
without rounded corners on the edges that connect. It's only a hint, so the
|
||||
LookAndFeel can choose to ignore it if it's not relevent for this type of
|
||||
button.
|
||||
*/
|
||||
void setConnectedEdges (int connectedEdgeFlags);
|
||||
|
||||
/** Returns the set of flags passed into setConnectedEdges(). */
|
||||
int getConnectedEdgeFlags() const noexcept { return connectedEdgeFlags; }
|
||||
|
||||
/** Indicates whether the button adjoins another one on its left edge.
|
||||
@see setConnectedEdges
|
||||
*/
|
||||
bool isConnectedOnLeft() const noexcept { return (connectedEdgeFlags & ConnectedOnLeft) != 0; }
|
||||
|
||||
/** Indicates whether the button adjoins another one on its right edge.
|
||||
@see setConnectedEdges
|
||||
*/
|
||||
bool isConnectedOnRight() const noexcept { return (connectedEdgeFlags & ConnectedOnRight) != 0; }
|
||||
|
||||
/** Indicates whether the button adjoins another one on its top edge.
|
||||
@see setConnectedEdges
|
||||
*/
|
||||
bool isConnectedOnTop() const noexcept { return (connectedEdgeFlags & ConnectedOnTop) != 0; }
|
||||
|
||||
/** Indicates whether the button adjoins another one on its bottom edge.
|
||||
@see setConnectedEdges
|
||||
*/
|
||||
bool isConnectedOnBottom() const noexcept { return (connectedEdgeFlags & ConnectedOnBottom) != 0; }
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Used by setState(). */
|
||||
enum ButtonState
|
||||
{
|
||||
buttonNormal,
|
||||
buttonOver,
|
||||
buttonDown
|
||||
};
|
||||
|
||||
/** Can be used to force the button into a particular state.
|
||||
|
||||
This only changes the button's appearance, it won't trigger a click, or stop any mouse-clicks
|
||||
from happening.
|
||||
|
||||
The state that you set here will only last until it is automatically changed when the mouse
|
||||
enters or exits the button, or the mouse-button is pressed or released.
|
||||
*/
|
||||
void setState (ButtonState newState);
|
||||
|
||||
/** Returns the button's current over/down/up state. */
|
||||
ButtonState getState() const noexcept { return buttonState; }
|
||||
|
||||
// This method's parameters have changed - see the new version.
|
||||
JUCE_DEPRECATED (void setToggleState (bool, bool));
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
button-drawing functionality.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
virtual void drawButtonBackground (Graphics&, Button&, const Colour& backgroundColour,
|
||||
bool isMouseOverButton, bool isButtonDown) = 0;
|
||||
|
||||
virtual Font getTextButtonFont (TextButton&, int buttonHeight) = 0;
|
||||
virtual int getTextButtonWidthToFitText (TextButton&, int buttonHeight) = 0;
|
||||
|
||||
/** Draws the text for a TextButton. */
|
||||
virtual void drawButtonText (Graphics&, TextButton&, bool isMouseOverButton, bool isButtonDown) = 0;
|
||||
|
||||
/** Draws the contents of a standard ToggleButton. */
|
||||
virtual void drawToggleButton (Graphics&, ToggleButton&, bool isMouseOverButton, bool isButtonDown) = 0;
|
||||
|
||||
virtual void changeToggleButtonWidthToFitText (ToggleButton&) = 0;
|
||||
|
||||
virtual void drawTickBox (Graphics&, Component&, float x, float y, float w, float h,
|
||||
bool ticked, bool isEnabled, bool isMouseOverButton, bool isButtonDown) = 0;
|
||||
|
||||
virtual void drawDrawableButton (Graphics&, DrawableButton&, bool isMouseOverButton, bool isButtonDown) = 0;
|
||||
|
||||
private:
|
||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
|
||||
// These method have been deprecated: see their replacements above.
|
||||
virtual int getTextButtonFont (TextButton&) { return 0; }
|
||||
virtual int changeTextButtonWidthToFitText (TextButton&, int) { return 0; }
|
||||
#endif
|
||||
};
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** This method is called when the button has been clicked.
|
||||
|
||||
Subclasses can override this to perform whatever they actions they need
|
||||
to do.
|
||||
|
||||
Alternatively, a ButtonListener can be added to the button, and these listeners
|
||||
will be called when the click occurs.
|
||||
|
||||
@see triggerClick
|
||||
*/
|
||||
virtual void clicked();
|
||||
|
||||
/** This method is called when the button has been clicked.
|
||||
|
||||
By default it just calls clicked(), but you might want to override it to handle
|
||||
things like clicking when a modifier key is pressed, etc.
|
||||
|
||||
@see ModifierKeys
|
||||
*/
|
||||
virtual void clicked (const ModifierKeys& modifiers);
|
||||
|
||||
/** Subclasses should override this to actually paint the button's contents.
|
||||
|
||||
It's better to use this than the paint method, because it gives you information
|
||||
about the over/down state of the button.
|
||||
|
||||
@param g the graphics context to use
|
||||
@param isMouseOverButton true if the button is either in the 'over' or
|
||||
'down' state
|
||||
@param isButtonDown true if the button should be drawn in the 'down' position
|
||||
*/
|
||||
virtual void paintButton (Graphics& g,
|
||||
bool isMouseOverButton,
|
||||
bool isButtonDown) = 0;
|
||||
|
||||
/** Called when the button's up/down/over state changes.
|
||||
|
||||
Subclasses can override this if they need to do something special when the button
|
||||
goes up or down.
|
||||
|
||||
@see isDown, isOver
|
||||
*/
|
||||
virtual void buttonStateChanged();
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
virtual void internalClickCallback (const ModifierKeys&);
|
||||
/** @internal */
|
||||
void handleCommandMessage (int commandId) override;
|
||||
/** @internal */
|
||||
void mouseEnter (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseExit (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
using Component::keyStateChanged;
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void parentHierarchyChanged() override;
|
||||
/** @internal */
|
||||
void visibilityChanged() override;
|
||||
/** @internal */
|
||||
void focusGained (FocusChangeType) override;
|
||||
/** @internal */
|
||||
void focusLost (FocusChangeType) override;
|
||||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Array<KeyPress> shortcuts;
|
||||
WeakReference<Component> keySource;
|
||||
String text;
|
||||
ListenerList<Listener> buttonListeners;
|
||||
|
||||
class CallbackHelper;
|
||||
friend class CallbackHelper;
|
||||
friend struct ContainerDeletePolicy<CallbackHelper>;
|
||||
ScopedPointer<CallbackHelper> callbackHelper;
|
||||
uint32 buttonPressTime, lastRepeatTime;
|
||||
ApplicationCommandManager* commandManagerToUse;
|
||||
int autoRepeatDelay, autoRepeatSpeed, autoRepeatMinimumDelay;
|
||||
int radioGroupId, connectedEdgeFlags;
|
||||
CommandID commandID;
|
||||
ButtonState buttonState;
|
||||
|
||||
Value isOn;
|
||||
bool lastToggleState;
|
||||
bool clickTogglesState;
|
||||
bool needsToRelease;
|
||||
bool needsRepainting;
|
||||
bool isKeyDown;
|
||||
bool triggerOnMouseDown;
|
||||
bool generateTooltip;
|
||||
|
||||
void repeatTimerCallback();
|
||||
bool keyStateChangedCallback();
|
||||
void applicationCommandListChangeCallback();
|
||||
void updateAutomaticTooltip (const ApplicationCommandInfo&);
|
||||
|
||||
ButtonState updateState();
|
||||
ButtonState updateState (bool isOver, bool isDown);
|
||||
bool isShortcutPressed() const;
|
||||
void turnOffOtherButtonsInGroup (NotificationType);
|
||||
|
||||
void flashButtonState();
|
||||
void sendClickMessage (const ModifierKeys&);
|
||||
void sendStateMessage();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Button)
|
||||
};
|
||||
|
||||
#ifndef DOXYGEN
|
||||
/** This typedef is just for compatibility with old code and VC6 - newer code should use Button::Listener instead. */
|
||||
typedef Button::Listener ButtonListener;
|
||||
#endif
|
||||
|
||||
#endif // JUCE_BUTTON_H_INCLUDED
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
DrawableButton::DrawableButton (const String& name, const DrawableButton::ButtonStyle buttonStyle)
|
||||
: Button (name),
|
||||
style (buttonStyle),
|
||||
currentImage (nullptr),
|
||||
edgeIndent (3)
|
||||
{
|
||||
}
|
||||
|
||||
DrawableButton::~DrawableButton()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static Drawable* copyDrawableIfNotNull (const Drawable* const d)
|
||||
{
|
||||
return d != nullptr ? d->createCopy() : nullptr;
|
||||
}
|
||||
|
||||
void DrawableButton::setImages (const Drawable* normal,
|
||||
const Drawable* over,
|
||||
const Drawable* down,
|
||||
const Drawable* disabled,
|
||||
const Drawable* normalOn,
|
||||
const Drawable* overOn,
|
||||
const Drawable* downOn,
|
||||
const Drawable* disabledOn)
|
||||
{
|
||||
jassert (normal != nullptr); // you really need to give it at least a normal image..
|
||||
|
||||
normalImage = copyDrawableIfNotNull (normal);
|
||||
overImage = copyDrawableIfNotNull (over);
|
||||
downImage = copyDrawableIfNotNull (down);
|
||||
disabledImage = copyDrawableIfNotNull (disabled);
|
||||
normalImageOn = copyDrawableIfNotNull (normalOn);
|
||||
overImageOn = copyDrawableIfNotNull (overOn);
|
||||
downImageOn = copyDrawableIfNotNull (downOn);
|
||||
disabledImageOn = copyDrawableIfNotNull (disabledOn);
|
||||
|
||||
buttonStateChanged();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawableButton::setButtonStyle (const DrawableButton::ButtonStyle newStyle)
|
||||
{
|
||||
if (style != newStyle)
|
||||
{
|
||||
style = newStyle;
|
||||
buttonStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableButton::setEdgeIndent (const int numPixelsIndent)
|
||||
{
|
||||
edgeIndent = numPixelsIndent;
|
||||
repaint();
|
||||
resized();
|
||||
}
|
||||
|
||||
Rectangle<float> DrawableButton::getImageBounds() const
|
||||
{
|
||||
Rectangle<int> r (getLocalBounds());
|
||||
|
||||
if (style != ImageStretched)
|
||||
{
|
||||
int indentX = jmin (edgeIndent, proportionOfWidth (0.3f));
|
||||
int indentY = jmin (edgeIndent, proportionOfHeight (0.3f));
|
||||
|
||||
if (style == ImageOnButtonBackground)
|
||||
{
|
||||
indentX = jmax (getWidth() / 4, indentX);
|
||||
indentY = jmax (getHeight() / 4, indentY);
|
||||
}
|
||||
else if (style == ImageAboveTextLabel)
|
||||
{
|
||||
r = r.withTrimmedBottom (jmin (16, proportionOfHeight (0.25f)));
|
||||
}
|
||||
|
||||
r = r.reduced (indentX, indentY);
|
||||
}
|
||||
|
||||
return r.toFloat();
|
||||
}
|
||||
|
||||
void DrawableButton::resized()
|
||||
{
|
||||
Button::resized();
|
||||
|
||||
if (currentImage != nullptr)
|
||||
{
|
||||
if (style == ImageRaw)
|
||||
currentImage->setOriginWithOriginalSize (Point<float>());
|
||||
else
|
||||
currentImage->setTransformToFit (getImageBounds(),
|
||||
style == ImageStretched ? RectanglePlacement::stretchToFit
|
||||
: RectanglePlacement::centred);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableButton::buttonStateChanged()
|
||||
{
|
||||
repaint();
|
||||
|
||||
Drawable* imageToDraw = nullptr;
|
||||
float opacity = 1.0f;
|
||||
|
||||
if (isEnabled())
|
||||
{
|
||||
imageToDraw = getCurrentImage();
|
||||
}
|
||||
else
|
||||
{
|
||||
imageToDraw = getToggleState() ? disabledImageOn
|
||||
: disabledImage;
|
||||
|
||||
if (imageToDraw == nullptr)
|
||||
{
|
||||
opacity = 0.4f;
|
||||
imageToDraw = getNormalImage();
|
||||
}
|
||||
}
|
||||
|
||||
if (imageToDraw != currentImage)
|
||||
{
|
||||
removeChildComponent (currentImage);
|
||||
currentImage = imageToDraw;
|
||||
|
||||
if (currentImage != nullptr)
|
||||
{
|
||||
currentImage->setInterceptsMouseClicks (false, false);
|
||||
addAndMakeVisible (currentImage);
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
if (currentImage != nullptr)
|
||||
currentImage->setAlpha (opacity);
|
||||
}
|
||||
|
||||
void DrawableButton::enablementChanged()
|
||||
{
|
||||
Button::enablementChanged();
|
||||
buttonStateChanged();
|
||||
}
|
||||
|
||||
void DrawableButton::colourChanged()
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
void DrawableButton::paintButton (Graphics& g,
|
||||
const bool isMouseOverButton,
|
||||
const bool isButtonDown)
|
||||
{
|
||||
LookAndFeel& lf = getLookAndFeel();
|
||||
|
||||
if (style == ImageOnButtonBackground)
|
||||
lf.drawButtonBackground (g, *this,
|
||||
findColour (getToggleState() ? TextButton::buttonOnColourId
|
||||
: TextButton::buttonColourId),
|
||||
isMouseOverButton, isButtonDown);
|
||||
else
|
||||
lf.drawDrawableButton (g, *this, isMouseOverButton, isButtonDown);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Drawable* DrawableButton::getCurrentImage() const noexcept
|
||||
{
|
||||
if (isDown()) return getDownImage();
|
||||
if (isOver()) return getOverImage();
|
||||
|
||||
return getNormalImage();
|
||||
}
|
||||
|
||||
Drawable* DrawableButton::getNormalImage() const noexcept
|
||||
{
|
||||
return (getToggleState() && normalImageOn != nullptr) ? normalImageOn
|
||||
: normalImage;
|
||||
}
|
||||
|
||||
Drawable* DrawableButton::getOverImage() const noexcept
|
||||
{
|
||||
if (getToggleState())
|
||||
{
|
||||
if (overImageOn != nullptr) return overImageOn;
|
||||
if (normalImageOn != nullptr) return normalImageOn;
|
||||
}
|
||||
|
||||
return overImage != nullptr ? overImage : normalImage;
|
||||
}
|
||||
|
||||
Drawable* DrawableButton::getDownImage() const noexcept
|
||||
{
|
||||
if (Drawable* const d = getToggleState() ? downImageOn : downImage)
|
||||
return d;
|
||||
|
||||
return getOverImage();
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_DRAWABLEBUTTON_H_INCLUDED
|
||||
#define JUCE_DRAWABLEBUTTON_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A button that displays a Drawable.
|
||||
|
||||
Up to three Drawable objects can be given to this button, to represent the
|
||||
'normal', 'over' and 'down' states.
|
||||
|
||||
@see Button
|
||||
*/
|
||||
class JUCE_API DrawableButton : public Button
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
enum ButtonStyle
|
||||
{
|
||||
ImageFitted, /**< The button will just display the images, but will resize and centre them to fit inside it. */
|
||||
ImageRaw, /**< The button will just display the images in their normal size and position.
|
||||
This leaves it up to the caller to make sure the images are the correct size and position for the button. */
|
||||
ImageAboveTextLabel, /**< Draws the button as a text label across the bottom with the image resized and scaled to fit above it. */
|
||||
ImageOnButtonBackground, /**< Draws the button as a standard rounded-rectangle button with the image on top.
|
||||
Note that if you use this style, the colour IDs that control the button colour are
|
||||
TextButton::buttonColourId and TextButton::buttonOnColourId. */
|
||||
ImageStretched /**< Fills the button with a stretched version of the image. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a DrawableButton.
|
||||
|
||||
After creating one of these, use setImages() to specify the drawables to use.
|
||||
|
||||
@param buttonName the name to give the component
|
||||
@param buttonStyle the layout to use
|
||||
|
||||
@see ButtonStyle, setButtonStyle, setImages
|
||||
*/
|
||||
DrawableButton (const String& buttonName,
|
||||
ButtonStyle buttonStyle);
|
||||
|
||||
/** Destructor. */
|
||||
~DrawableButton();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets up the images to draw for the various button states.
|
||||
|
||||
The button will keep its own internal copies of these drawables.
|
||||
|
||||
@param normalImage the thing to draw for the button's 'normal' state. An internal copy
|
||||
will be made of the object passed-in if it is non-zero.
|
||||
@param overImage the thing to draw for the button's 'over' state - if this is
|
||||
zero, the button's normal image will be used when the mouse is
|
||||
over it. An internal copy will be made of the object passed-in
|
||||
if it is non-zero.
|
||||
@param downImage the thing to draw for the button's 'down' state - if this is
|
||||
zero, the 'over' image will be used instead (or the normal image
|
||||
as a last resort). An internal copy will be made of the object
|
||||
passed-in if it is non-zero.
|
||||
@param disabledImage an image to draw when the button is disabled. If this is zero,
|
||||
the normal image will be drawn with a reduced opacity instead.
|
||||
An internal copy will be made of the object passed-in if it is
|
||||
non-zero.
|
||||
@param normalImageOn same as the normalImage, but this is used when the button's toggle
|
||||
state is 'on'. If this is nullptr, the normal image is used instead
|
||||
@param overImageOn same as the overImage, but this is used when the button's toggle
|
||||
state is 'on'. If this is nullptr, the normalImageOn is drawn instead
|
||||
@param downImageOn same as the downImage, but this is used when the button's toggle
|
||||
state is 'on'. If this is nullptr, the overImageOn is drawn instead
|
||||
@param disabledImageOn same as the disabledImage, but this is used when the button's toggle
|
||||
state is 'on'. If this is nullptr, the normal image will be drawn instead
|
||||
with a reduced opacity
|
||||
*/
|
||||
void setImages (const Drawable* normalImage,
|
||||
const Drawable* overImage = nullptr,
|
||||
const Drawable* downImage = nullptr,
|
||||
const Drawable* disabledImage = nullptr,
|
||||
const Drawable* normalImageOn = nullptr,
|
||||
const Drawable* overImageOn = nullptr,
|
||||
const Drawable* downImageOn = nullptr,
|
||||
const Drawable* disabledImageOn = nullptr);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the button's style.
|
||||
@see ButtonStyle
|
||||
*/
|
||||
void setButtonStyle (ButtonStyle newStyle);
|
||||
|
||||
/** Returns the current style. */
|
||||
ButtonStyle getStyle() const noexcept { return style; }
|
||||
|
||||
//==============================================================================
|
||||
/** Gives the button an optional amount of space around the edge of the drawable.
|
||||
By default there's a gap of about 3 pixels.
|
||||
*/
|
||||
void setEdgeIndent (int numPixelsIndent);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the image that the button is currently displaying. */
|
||||
Drawable* getCurrentImage() const noexcept;
|
||||
|
||||
/** Returns the image that the button will use for its normal state. */
|
||||
Drawable* getNormalImage() const noexcept;
|
||||
/** Returns the image that the button will use when the mouse is over it. */
|
||||
Drawable* getOverImage() const noexcept;
|
||||
/** Returns the image that the button will use when the mouse is held down on it. */
|
||||
Drawable* getDownImage() const noexcept;
|
||||
|
||||
/** Can be overridden to specify a custom position for the image within the button. */
|
||||
virtual Rectangle<float> getImageBounds() const;
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the link.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
Note that when the ImageOnButtonBackground style is used, the colour IDs that control
|
||||
the button colour are TextButton::buttonColourId and TextButton::buttonOnColourId.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
textColourId = 0x1004010, /**< The colour to use for the button's text label. */
|
||||
textColourOnId = 0x1004013, /**< The colour to use for the button's text.when the button's toggle state is "on". */
|
||||
|
||||
backgroundColourId = 0x1004011, /**< The colour used to fill the button's background (when
|
||||
the button is toggled 'off'). Note that if you use the
|
||||
ImageOnButtonBackground style, you should use TextButton::buttonColourId
|
||||
to change the button's colour. */
|
||||
backgroundOnColourId = 0x1004012, /**< The colour used to fill the button's background (when
|
||||
the button is toggled 'on'). Note that if you use the
|
||||
ImageOnButtonBackground style, you should use TextButton::buttonOnColourId
|
||||
to change the button's colour. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paintButton (Graphics&, bool isMouseOverButton, bool isButtonDown) override;
|
||||
/** @internal */
|
||||
void buttonStateChanged() override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
ButtonStyle style;
|
||||
ScopedPointer<Drawable> normalImage, overImage, downImage, disabledImage,
|
||||
normalImageOn, overImageOn, downImageOn, disabledImageOn;
|
||||
Drawable* currentImage;
|
||||
int edgeIndent;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DrawableButton)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_DRAWABLEBUTTON_H_INCLUDED
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
HyperlinkButton::HyperlinkButton (const String& linkText,
|
||||
const URL& linkURL)
|
||||
: Button (linkText),
|
||||
url (linkURL),
|
||||
font (14.0f, Font::underlined),
|
||||
resizeFont (true),
|
||||
justification (Justification::centred)
|
||||
{
|
||||
setMouseCursor (MouseCursor::PointingHandCursor);
|
||||
setTooltip (linkURL.toString (false));
|
||||
}
|
||||
|
||||
HyperlinkButton::HyperlinkButton ()
|
||||
: Button (String::empty),
|
||||
font (14.0f, Font::underlined),
|
||||
resizeFont (true),
|
||||
justification (Justification::centred)
|
||||
{
|
||||
setMouseCursor (MouseCursor::PointingHandCursor);
|
||||
}
|
||||
|
||||
HyperlinkButton::~HyperlinkButton()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void HyperlinkButton::setFont (const Font& newFont,
|
||||
const bool resizeToMatchComponentHeight,
|
||||
Justification justificationType)
|
||||
{
|
||||
font = newFont;
|
||||
resizeFont = resizeToMatchComponentHeight;
|
||||
justification = justificationType;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void HyperlinkButton::setURL (const URL& newURL) noexcept
|
||||
{
|
||||
url = newURL;
|
||||
setTooltip (newURL.toString (false));
|
||||
}
|
||||
|
||||
Font HyperlinkButton::getFontToUse() const
|
||||
{
|
||||
if (resizeFont)
|
||||
return font.withHeight (getHeight() * 0.7f);
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
void HyperlinkButton::changeWidthToFitText()
|
||||
{
|
||||
setSize (getFontToUse().getStringWidth (getButtonText()) + 6, getHeight());
|
||||
}
|
||||
|
||||
void HyperlinkButton::colourChanged()
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void HyperlinkButton::clicked()
|
||||
{
|
||||
if (url.isWellFormed())
|
||||
url.launchInDefaultBrowser();
|
||||
}
|
||||
|
||||
void HyperlinkButton::paintButton (Graphics& g,
|
||||
bool isMouseOverButton,
|
||||
bool isButtonDown)
|
||||
{
|
||||
const Colour textColour (findColour (textColourId));
|
||||
|
||||
if (isEnabled())
|
||||
g.setColour ((isMouseOverButton) ? textColour.darker ((isButtonDown) ? 1.3f : 0.4f)
|
||||
: textColour);
|
||||
else
|
||||
g.setColour (textColour.withMultipliedAlpha (0.4f));
|
||||
|
||||
g.setFont (getFontToUse());
|
||||
|
||||
g.drawText (getButtonText(), getLocalBounds().reduced (1, 0),
|
||||
justification.getOnlyHorizontalFlags() | Justification::verticallyCentred,
|
||||
true);
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_HYPERLINKBUTTON_H_INCLUDED
|
||||
#define JUCE_HYPERLINKBUTTON_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A button showing an underlined weblink, that will launch the link
|
||||
when it's clicked.
|
||||
|
||||
@see Button
|
||||
*/
|
||||
class JUCE_API HyperlinkButton : public Button
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a HyperlinkButton.
|
||||
|
||||
@param linkText the text that will be displayed in the button - this is
|
||||
also set as the Component's name, but the text can be
|
||||
changed later with the Button::getButtonText() method
|
||||
@param linkURL the URL to launch when the user clicks the button
|
||||
*/
|
||||
HyperlinkButton (const String& linkText,
|
||||
const URL& linkURL);
|
||||
|
||||
/** Creates a HyperlinkButton. */
|
||||
HyperlinkButton();
|
||||
|
||||
/** Destructor. */
|
||||
~HyperlinkButton();
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the font to use for the text.
|
||||
|
||||
If resizeToMatchComponentHeight is true, the font's height will be adjusted
|
||||
to match the size of the component.
|
||||
*/
|
||||
void setFont (const Font& newFont,
|
||||
bool resizeToMatchComponentHeight,
|
||||
Justification justificationType = Justification::horizontallyCentred);
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the link.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
textColourId = 0x1001f00, /**< The colour to use for the URL text. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the URL that the button will trigger. */
|
||||
void setURL (const URL& newURL) noexcept;
|
||||
|
||||
/** Returns the URL that the button will trigger. */
|
||||
const URL& getURL() const noexcept { return url; }
|
||||
|
||||
//==============================================================================
|
||||
/** Resizes the button horizontally to fit snugly around the text.
|
||||
|
||||
This won't affect the button's height.
|
||||
*/
|
||||
void changeWidthToFitText();
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void clicked() override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
/** @internal */
|
||||
void paintButton (Graphics&, bool isMouseOver, bool isButtonDown) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
URL url;
|
||||
Font font;
|
||||
bool resizeFont;
|
||||
Justification justification;
|
||||
|
||||
Font getFontToUse() const;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HyperlinkButton)
|
||||
};
|
||||
|
||||
#endif // JUCE_HYPERLINKBUTTON_H_INCLUDED
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
ImageButton::ImageButton (const String& text_)
|
||||
: Button (text_),
|
||||
scaleImageToFit (true),
|
||||
preserveProportions (true),
|
||||
alphaThreshold (0)
|
||||
{
|
||||
}
|
||||
|
||||
ImageButton::~ImageButton()
|
||||
{
|
||||
}
|
||||
|
||||
void ImageButton::setImages (const bool resizeButtonNowToFitThisImage,
|
||||
const bool rescaleImagesWhenButtonSizeChanges,
|
||||
const bool preserveImageProportions,
|
||||
const Image& normalImage_,
|
||||
const float imageOpacityWhenNormal,
|
||||
Colour overlayColourWhenNormal,
|
||||
const Image& overImage_,
|
||||
const float imageOpacityWhenOver,
|
||||
Colour overlayColourWhenOver,
|
||||
const Image& downImage_,
|
||||
const float imageOpacityWhenDown,
|
||||
Colour overlayColourWhenDown,
|
||||
const float hitTestAlphaThreshold)
|
||||
{
|
||||
normalImage = normalImage_;
|
||||
overImage = overImage_;
|
||||
downImage = downImage_;
|
||||
|
||||
if (resizeButtonNowToFitThisImage && normalImage.isValid())
|
||||
{
|
||||
imageBounds.setSize (normalImage.getWidth(),
|
||||
normalImage.getHeight());
|
||||
|
||||
setSize (imageBounds.getWidth(), imageBounds.getHeight());
|
||||
}
|
||||
|
||||
scaleImageToFit = rescaleImagesWhenButtonSizeChanges;
|
||||
preserveProportions = preserveImageProportions;
|
||||
|
||||
normalOpacity = imageOpacityWhenNormal;
|
||||
normalOverlay = overlayColourWhenNormal;
|
||||
overOpacity = imageOpacityWhenOver;
|
||||
overOverlay = overlayColourWhenOver;
|
||||
downOpacity = imageOpacityWhenDown;
|
||||
downOverlay = overlayColourWhenDown;
|
||||
|
||||
alphaThreshold = (uint8) jlimit (0, 0xff, roundToInt (255.0f * hitTestAlphaThreshold));
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
Image ImageButton::getCurrentImage() const
|
||||
{
|
||||
if (isDown() || getToggleState())
|
||||
return getDownImage();
|
||||
|
||||
if (isOver())
|
||||
return getOverImage();
|
||||
|
||||
return getNormalImage();
|
||||
}
|
||||
|
||||
Image ImageButton::getNormalImage() const
|
||||
{
|
||||
return normalImage;
|
||||
}
|
||||
|
||||
Image ImageButton::getOverImage() const
|
||||
{
|
||||
return overImage.isValid() ? overImage
|
||||
: normalImage;
|
||||
}
|
||||
|
||||
Image ImageButton::getDownImage() const
|
||||
{
|
||||
return downImage.isValid() ? downImage
|
||||
: getOverImage();
|
||||
}
|
||||
|
||||
void ImageButton::paintButton (Graphics& g,
|
||||
bool isMouseOverButton,
|
||||
bool isButtonDown)
|
||||
{
|
||||
if (! isEnabled())
|
||||
{
|
||||
isMouseOverButton = false;
|
||||
isButtonDown = false;
|
||||
}
|
||||
|
||||
Image im (getCurrentImage());
|
||||
|
||||
if (im.isValid())
|
||||
{
|
||||
const int iw = im.getWidth();
|
||||
const int ih = im.getHeight();
|
||||
int w = getWidth();
|
||||
int h = getHeight();
|
||||
int x = (w - iw) / 2;
|
||||
int y = (h - ih) / 2;
|
||||
|
||||
if (scaleImageToFit)
|
||||
{
|
||||
if (preserveProportions)
|
||||
{
|
||||
int newW, newH;
|
||||
const float imRatio = ih / (float) iw;
|
||||
const float destRatio = h / (float) w;
|
||||
|
||||
if (imRatio > destRatio)
|
||||
{
|
||||
newW = roundToInt (h / imRatio);
|
||||
newH = h;
|
||||
}
|
||||
else
|
||||
{
|
||||
newW = w;
|
||||
newH = roundToInt (w * imRatio);
|
||||
}
|
||||
|
||||
x = (w - newW) / 2;
|
||||
y = (h - newH) / 2;
|
||||
w = newW;
|
||||
h = newH;
|
||||
}
|
||||
else
|
||||
{
|
||||
x = 0;
|
||||
y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (! scaleImageToFit)
|
||||
{
|
||||
w = iw;
|
||||
h = ih;
|
||||
}
|
||||
|
||||
imageBounds.setBounds (x, y, w, h);
|
||||
|
||||
const bool useDownImage = isButtonDown || getToggleState();
|
||||
|
||||
getLookAndFeel().drawImageButton (g, &im, x, y, w, h,
|
||||
useDownImage ? downOverlay
|
||||
: (isMouseOverButton ? overOverlay
|
||||
: normalOverlay),
|
||||
useDownImage ? downOpacity
|
||||
: (isMouseOverButton ? overOpacity
|
||||
: normalOpacity),
|
||||
*this);
|
||||
}
|
||||
}
|
||||
|
||||
bool ImageButton::hitTest (int x, int y)
|
||||
{
|
||||
if (alphaThreshold == 0)
|
||||
return true;
|
||||
|
||||
Image im (getCurrentImage());
|
||||
|
||||
return im.isNull() || ((! imageBounds.isEmpty())
|
||||
&& alphaThreshold < im.getPixelAt (((x - imageBounds.getX()) * im.getWidth()) / imageBounds.getWidth(),
|
||||
((y - imageBounds.getY()) * im.getHeight()) / imageBounds.getHeight()).getAlpha());
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_IMAGEBUTTON_H_INCLUDED
|
||||
#define JUCE_IMAGEBUTTON_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
As the title suggests, this is a button containing an image.
|
||||
|
||||
The colour and transparency of the image can be set to vary when the
|
||||
button state changes.
|
||||
|
||||
@see Button, ShapeButton, TextButton
|
||||
*/
|
||||
class JUCE_API ImageButton : public Button
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an ImageButton.
|
||||
|
||||
Use setImage() to specify the image to use. The colours and opacities that
|
||||
are specified here can be changed later using setImages().
|
||||
|
||||
@param name the name to give the component
|
||||
*/
|
||||
explicit ImageButton (const String& name = String::empty);
|
||||
|
||||
/** Destructor. */
|
||||
~ImageButton();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets up the images to draw in various states.
|
||||
|
||||
@param resizeButtonNowToFitThisImage if true, the button will be immediately
|
||||
resized to the same dimensions as the normal image
|
||||
@param rescaleImagesWhenButtonSizeChanges if true, the image will be rescaled to fit the
|
||||
button when the button's size changes
|
||||
@param preserveImageProportions if true then any rescaling of the image to fit
|
||||
the button will keep the image's x and y proportions
|
||||
correct - i.e. it won't distort its shape, although
|
||||
this might create gaps around the edges
|
||||
@param normalImage the image to use when the button is in its normal state.
|
||||
button no longer needs it.
|
||||
@param imageOpacityWhenNormal the opacity to use when drawing the normal image.
|
||||
@param overlayColourWhenNormal an overlay colour to use to fill the alpha channel of the
|
||||
normal image - if this colour is transparent, no overlay
|
||||
will be drawn. The overlay will be drawn over the top of the
|
||||
image, so you can basically add a solid or semi-transparent
|
||||
colour to the image to brighten or darken it
|
||||
@param overImage the image to use when the mouse is over the button. If
|
||||
you want to use the same image as was set in the normalImage
|
||||
parameter, this value can be a null image.
|
||||
@param imageOpacityWhenOver the opacity to use when drawing the image when the mouse
|
||||
is over the button
|
||||
@param overlayColourWhenOver an overlay colour to use to fill the alpha channel of the
|
||||
image when the mouse is over - if this colour is transparent,
|
||||
no overlay will be drawn
|
||||
@param downImage an image to use when the button is pressed down. If set
|
||||
to a null image, the 'over' image will be drawn instead (or the
|
||||
normal image if there isn't an 'over' image either).
|
||||
@param imageOpacityWhenDown the opacity to use when drawing the image when the button
|
||||
is pressed
|
||||
@param overlayColourWhenDown an overlay colour to use to fill the alpha channel of the
|
||||
image when the button is pressed down - if this colour is
|
||||
transparent, no overlay will be drawn
|
||||
@param hitTestAlphaThreshold if set to zero, the mouse is considered to be over the button
|
||||
whenever it's inside the button's bounding rectangle. If
|
||||
set to values higher than 0, the mouse will only be
|
||||
considered to be over the image when the value of the
|
||||
image's alpha channel at that position is greater than
|
||||
this level.
|
||||
*/
|
||||
void setImages (bool resizeButtonNowToFitThisImage,
|
||||
bool rescaleImagesWhenButtonSizeChanges,
|
||||
bool preserveImageProportions,
|
||||
const Image& normalImage,
|
||||
float imageOpacityWhenNormal,
|
||||
Colour overlayColourWhenNormal,
|
||||
const Image& overImage,
|
||||
float imageOpacityWhenOver,
|
||||
Colour overlayColourWhenOver,
|
||||
const Image& downImage,
|
||||
float imageOpacityWhenDown,
|
||||
Colour overlayColourWhenDown,
|
||||
float hitTestAlphaThreshold = 0.0f);
|
||||
|
||||
/** Returns the currently set 'normal' image. */
|
||||
Image getNormalImage() const;
|
||||
|
||||
/** Returns the image that's drawn when the mouse is over the button.
|
||||
|
||||
If a valid 'over' image has been set, this will return it; otherwise it'll
|
||||
just return the normal image.
|
||||
*/
|
||||
Image getOverImage() const;
|
||||
|
||||
/** Returns the image that's drawn when the button is held down.
|
||||
|
||||
If a valid 'down' image has been set, this will return it; otherwise it'll
|
||||
return the 'over' image or normal image, depending on what's available.
|
||||
*/
|
||||
Image getDownImage() const;
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes. */
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
virtual void drawImageButton (Graphics&, Image*,
|
||||
int imageX, int imageY, int imageW, int imageH,
|
||||
const Colour& overlayColour, float imageOpacity, ImageButton&) = 0;
|
||||
};
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
bool hitTest (int x, int y) override;
|
||||
/** @internal */
|
||||
void paintButton (Graphics&, bool isMouseOver, bool isButtonDown) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
bool scaleImageToFit, preserveProportions;
|
||||
uint8 alphaThreshold;
|
||||
Rectangle<int> imageBounds;
|
||||
Image normalImage, overImage, downImage;
|
||||
float normalOpacity, overOpacity, downOpacity;
|
||||
Colour normalOverlay, overOverlay, downOverlay;
|
||||
|
||||
Image getCurrentImage() const;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImageButton)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_IMAGEBUTTON_H_INCLUDED
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
ShapeButton::ShapeButton (const String& t, Colour n, Colour o, Colour d)
|
||||
: Button (t),
|
||||
normalColour (n), overColour (o), downColour (d),
|
||||
maintainShapeProportions (false),
|
||||
outlineWidth (0.0f)
|
||||
{
|
||||
}
|
||||
|
||||
ShapeButton::~ShapeButton() {}
|
||||
|
||||
void ShapeButton::setColours (Colour newNormalColour, Colour newOverColour, Colour newDownColour)
|
||||
{
|
||||
normalColour = newNormalColour;
|
||||
overColour = newOverColour;
|
||||
downColour = newDownColour;
|
||||
}
|
||||
|
||||
void ShapeButton::setOutline (Colour newOutlineColour, const float newOutlineWidth)
|
||||
{
|
||||
outlineColour = newOutlineColour;
|
||||
outlineWidth = newOutlineWidth;
|
||||
}
|
||||
|
||||
void ShapeButton::setBorderSize (BorderSize<int> newBorder)
|
||||
{
|
||||
border = newBorder;
|
||||
}
|
||||
|
||||
void ShapeButton::setShape (const Path& newShape,
|
||||
const bool resizeNowToFitThisShape,
|
||||
const bool maintainShapeProportions_,
|
||||
const bool hasShadow)
|
||||
{
|
||||
shape = newShape;
|
||||
maintainShapeProportions = maintainShapeProportions_;
|
||||
|
||||
shadow.setShadowProperties (DropShadow (Colours::black.withAlpha (0.5f), 3, Point<int>()));
|
||||
setComponentEffect (hasShadow ? &shadow : nullptr);
|
||||
|
||||
if (resizeNowToFitThisShape)
|
||||
{
|
||||
Rectangle<float> newBounds (shape.getBounds());
|
||||
|
||||
if (hasShadow)
|
||||
newBounds = newBounds.expanded (4.0f);
|
||||
|
||||
shape.applyTransform (AffineTransform::translation (-newBounds.getX(),
|
||||
-newBounds.getY()));
|
||||
|
||||
setSize (1 + (int) (newBounds.getWidth() + outlineWidth) + border.getLeftAndRight(),
|
||||
1 + (int) (newBounds.getHeight() + outlineWidth) + border.getTopAndBottom());
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
void ShapeButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown)
|
||||
{
|
||||
if (! isEnabled())
|
||||
{
|
||||
isMouseOverButton = false;
|
||||
isButtonDown = false;
|
||||
}
|
||||
|
||||
Rectangle<float> r (border.subtractedFrom (getLocalBounds()).toFloat().reduced (outlineWidth * 0.5f));
|
||||
|
||||
if (getComponentEffect() != nullptr)
|
||||
r = r.reduced (2.0f);
|
||||
|
||||
if (isButtonDown)
|
||||
{
|
||||
const float sizeReductionWhenPressed = 0.04f;
|
||||
|
||||
r = r.reduced (sizeReductionWhenPressed * r.getWidth(),
|
||||
sizeReductionWhenPressed * r.getHeight());
|
||||
}
|
||||
|
||||
const AffineTransform trans (shape.getTransformToScaleToFit (r, maintainShapeProportions));
|
||||
|
||||
g.setColour (isButtonDown ? downColour
|
||||
: isMouseOverButton ? overColour
|
||||
: normalColour);
|
||||
g.fillPath (shape, trans);
|
||||
|
||||
if (outlineWidth > 0.0f)
|
||||
{
|
||||
g.setColour (outlineColour);
|
||||
g.strokePath (shape, PathStrokeType (outlineWidth), trans);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_SHAPEBUTTON_H_INCLUDED
|
||||
#define JUCE_SHAPEBUTTON_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A button that contains a filled shape.
|
||||
|
||||
@see Button, ImageButton, TextButton, ArrowButton
|
||||
*/
|
||||
class JUCE_API ShapeButton : public Button
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a ShapeButton.
|
||||
|
||||
@param name a name to give the component - see Component::setName()
|
||||
@param normalColour the colour to fill the shape with when the mouse isn't over
|
||||
@param overColour the colour to use when the mouse is over the shape
|
||||
@param downColour the colour to use when the button is in the pressed-down state
|
||||
*/
|
||||
ShapeButton (const String& name,
|
||||
Colour normalColour,
|
||||
Colour overColour,
|
||||
Colour downColour);
|
||||
|
||||
/** Destructor. */
|
||||
~ShapeButton();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the shape to use.
|
||||
|
||||
@param newShape the shape to use
|
||||
@param resizeNowToFitThisShape if true, the button will be resized to fit the shape's bounds
|
||||
@param maintainShapeProportions if true, the shape's proportions will be kept fixed when
|
||||
the button is resized
|
||||
@param hasDropShadow if true, the button will be given a drop-shadow effect
|
||||
*/
|
||||
void setShape (const Path& newShape,
|
||||
bool resizeNowToFitThisShape,
|
||||
bool maintainShapeProportions,
|
||||
bool hasDropShadow);
|
||||
|
||||
/** Set the colours to use for drawing the shape.
|
||||
|
||||
@param normalColour the colour to fill the shape with when the mouse isn't over
|
||||
@param overColour the colour to use when the mouse is over the shape
|
||||
@param downColour the colour to use when the button is in the pressed-down state
|
||||
*/
|
||||
void setColours (Colour normalColour,
|
||||
Colour overColour,
|
||||
Colour downColour);
|
||||
|
||||
/** Sets up an outline to draw around the shape.
|
||||
|
||||
@param outlineColour the colour to use
|
||||
@param outlineStrokeWidth the thickness of line to draw
|
||||
*/
|
||||
void setOutline (Colour outlineColour, float outlineStrokeWidth);
|
||||
|
||||
/** This lets you specify a border to be left around the edge of the button when
|
||||
drawing the shape.
|
||||
*/
|
||||
void setBorderSize (BorderSize<int> border);
|
||||
|
||||
/** @internal */
|
||||
void paintButton (Graphics&, bool isMouseOverButton, bool isButtonDown) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Colour normalColour, overColour, downColour, outlineColour;
|
||||
DropShadowEffect shadow;
|
||||
Path shape;
|
||||
BorderSize<int> border;
|
||||
bool maintainShapeProportions;
|
||||
float outlineWidth;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ShapeButton)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_SHAPEBUTTON_H_INCLUDED
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
==============================================================================
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
TextButton::TextButton() : Button (String())
|
||||
{
|
||||
}
|
||||
|
||||
TextButton::TextButton (const String& name) : Button (name)
|
||||
{
|
||||
}
|
||||
|
||||
TextButton::TextButton (const String& name, const String& toolTip) : Button (name)
|
||||
{
|
||||
setTooltip (toolTip);
|
||||
}
|
||||
|
||||
TextButton::~TextButton()
|
||||
{
|
||||
}
|
||||
|
||||
void TextButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown)
|
||||
{
|
||||
LookAndFeel& lf = getLookAndFeel();
|
||||
|
||||
lf.drawButtonBackground (g, *this,
|
||||
findColour (getToggleState() ? buttonOnColourId : buttonColourId),
|
||||
isMouseOverButton, isButtonDown);
|
||||
|
||||
lf.drawButtonText (g, *this, isMouseOverButton, isButtonDown);
|
||||
}
|
||||
|
||||
void TextButton::colourChanged()
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
void TextButton::changeWidthToFitText()
|
||||
{
|
||||
changeWidthToFitText (getHeight());
|
||||
}
|
||||
|
||||
void TextButton::changeWidthToFitText (const int newHeight)
|
||||
{
|
||||
setSize (getBestWidthForHeight (newHeight), newHeight);
|
||||
}
|
||||
|
||||
int TextButton::getBestWidthForHeight (int buttonHeight)
|
||||
{
|
||||
return getLookAndFeel().getTextButtonWidthToFitText (*this, buttonHeight);
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_TEXTBUTTON_H_INCLUDED
|
||||
#define JUCE_TEXTBUTTON_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A button that uses the standard lozenge-shaped background with a line of
|
||||
text on it.
|
||||
|
||||
@see Button, DrawableButton
|
||||
*/
|
||||
class JUCE_API TextButton : public Button
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a TextButton. */
|
||||
TextButton();
|
||||
|
||||
/** Creates a TextButton.
|
||||
@param buttonName the text to put in the button (the component's name is also
|
||||
initially set to this string, but these can be changed later
|
||||
using the setName() and setButtonText() methods)
|
||||
*/
|
||||
explicit TextButton (const String& buttonName);
|
||||
|
||||
/** Creates a TextButton.
|
||||
@param buttonName the text to put in the button (the component's name is also
|
||||
initially set to this string, but these can be changed later
|
||||
using the setName() and setButtonText() methods)
|
||||
@param toolTip an optional string to use as a toolip
|
||||
*/
|
||||
TextButton (const String& buttonName, const String& toolTip);
|
||||
|
||||
/** Destructor. */
|
||||
~TextButton();
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the button.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
buttonColourId = 0x1000100, /**< The colour used to fill the button shape (when the button is toggled
|
||||
'off'). The look-and-feel class might re-interpret this to add
|
||||
effects, etc. */
|
||||
buttonOnColourId = 0x1000101, /**< The colour used to fill the button shape (when the button is toggled
|
||||
'on'). The look-and-feel class might re-interpret this to add
|
||||
effects, etc. */
|
||||
textColourOffId = 0x1000102, /**< The colour to use for the button's text when the button's toggle state is "off". */
|
||||
textColourOnId = 0x1000103 /**< The colour to use for the button's text.when the button's toggle state is "on". */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Changes this button's width to fit neatly around its current text, without
|
||||
changing its height.
|
||||
*/
|
||||
void changeWidthToFitText();
|
||||
|
||||
/** Resizes the button's width to fit neatly around its current text, and gives it
|
||||
the specified height.
|
||||
*/
|
||||
void changeWidthToFitText (int newHeight);
|
||||
|
||||
/** Returns the width that the LookAndFeel suggests would be best for this button if it
|
||||
had the given height.
|
||||
*/
|
||||
int getBestWidthForHeight (int buttonHeight);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paintButton (Graphics&, bool isMouseOverButton, bool isButtonDown) override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
|
||||
private:
|
||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
|
||||
// Note that this method has been removed - instead, see LookAndFeel::getTextButtonWidthToFitText()
|
||||
virtual int getFont() { return 0; }
|
||||
#endif
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TextButton)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_TEXTBUTTON_H_INCLUDED
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
ToggleButton::ToggleButton()
|
||||
: Button (String::empty)
|
||||
{
|
||||
setClickingTogglesState (true);
|
||||
}
|
||||
|
||||
ToggleButton::ToggleButton (const String& buttonText)
|
||||
: Button (buttonText)
|
||||
{
|
||||
setClickingTogglesState (true);
|
||||
}
|
||||
|
||||
ToggleButton::~ToggleButton()
|
||||
{
|
||||
}
|
||||
|
||||
void ToggleButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown)
|
||||
{
|
||||
getLookAndFeel().drawToggleButton (g, *this, isMouseOverButton, isButtonDown);
|
||||
}
|
||||
|
||||
void ToggleButton::changeWidthToFitText()
|
||||
{
|
||||
getLookAndFeel().changeToggleButtonWidthToFitText (*this);
|
||||
}
|
||||
|
||||
void ToggleButton::colourChanged()
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_TOGGLEBUTTON_H_INCLUDED
|
||||
#define JUCE_TOGGLEBUTTON_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A button that can be toggled on/off.
|
||||
|
||||
All buttons can be toggle buttons, but this lets you create one of the
|
||||
standard ones which has a tick-box and a text label next to it.
|
||||
|
||||
@see Button, DrawableButton, TextButton
|
||||
*/
|
||||
class JUCE_API ToggleButton : public Button
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a ToggleButton. */
|
||||
ToggleButton();
|
||||
|
||||
/** Creates a ToggleButton.
|
||||
|
||||
@param buttonText the text to put in the button (the component's name is also
|
||||
initially set to this string, but these can be changed later
|
||||
using the setName() and setButtonText() methods)
|
||||
*/
|
||||
explicit ToggleButton (const String& buttonText);
|
||||
|
||||
/** Destructor. */
|
||||
~ToggleButton();
|
||||
|
||||
//==============================================================================
|
||||
/** Resizes the button to fit neatly around its current text.
|
||||
The button's height won't be affected, only its width.
|
||||
*/
|
||||
void changeWidthToFitText();
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the button.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
textColourId = 0x1006501 /**< The colour to use for the button's text. */
|
||||
};
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paintButton (Graphics&, bool isMouseOverButton, bool isButtonDown) override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToggleButton)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_TOGGLEBUTTON_H_INCLUDED
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
ToolbarButton::ToolbarButton (const int iid, const String& buttonText,
|
||||
Drawable* const normalIm, Drawable* const toggledOnIm)
|
||||
: ToolbarItemComponent (iid, buttonText, true),
|
||||
normalImage (normalIm),
|
||||
toggledOnImage (toggledOnIm),
|
||||
currentImage (nullptr)
|
||||
{
|
||||
jassert (normalImage != nullptr);
|
||||
}
|
||||
|
||||
ToolbarButton::~ToolbarButton()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool ToolbarButton::getToolbarItemSizes (int toolbarDepth, bool /*isToolbarVertical*/, int& preferredSize, int& minSize, int& maxSize)
|
||||
{
|
||||
preferredSize = minSize = maxSize = toolbarDepth;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ToolbarButton::paintButtonArea (Graphics&, int /*width*/, int /*height*/, bool /*isMouseOver*/, bool /*isMouseDown*/)
|
||||
{
|
||||
}
|
||||
|
||||
void ToolbarButton::contentAreaChanged (const Rectangle<int>&)
|
||||
{
|
||||
buttonStateChanged();
|
||||
}
|
||||
|
||||
void ToolbarButton::setCurrentImage (Drawable* const newImage)
|
||||
{
|
||||
if (newImage != currentImage)
|
||||
{
|
||||
removeChildComponent (currentImage);
|
||||
currentImage = newImage;
|
||||
|
||||
if (currentImage != nullptr)
|
||||
{
|
||||
enablementChanged();
|
||||
addAndMakeVisible (currentImage);
|
||||
updateDrawable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ToolbarButton::updateDrawable()
|
||||
{
|
||||
if (currentImage != nullptr)
|
||||
{
|
||||
currentImage->setInterceptsMouseClicks (false, false);
|
||||
currentImage->setTransformToFit (getContentArea().toFloat(), RectanglePlacement::centred);
|
||||
currentImage->setAlpha (isEnabled() ? 1.0f : 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
void ToolbarButton::resized()
|
||||
{
|
||||
ToolbarItemComponent::resized();
|
||||
updateDrawable();
|
||||
}
|
||||
|
||||
void ToolbarButton::enablementChanged()
|
||||
{
|
||||
ToolbarItemComponent::enablementChanged();
|
||||
updateDrawable();
|
||||
}
|
||||
|
||||
Drawable* ToolbarButton::getImageToUse() const
|
||||
{
|
||||
if (getStyle() == Toolbar::textOnly)
|
||||
return nullptr;
|
||||
|
||||
if (getToggleState() && toggledOnImage != nullptr)
|
||||
return toggledOnImage;
|
||||
|
||||
return normalImage;
|
||||
}
|
||||
|
||||
void ToolbarButton::buttonStateChanged()
|
||||
{
|
||||
setCurrentImage (getImageToUse());
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_TOOLBARBUTTON_H_INCLUDED
|
||||
#define JUCE_TOOLBARBUTTON_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A type of button designed to go on a toolbar.
|
||||
|
||||
This simple button can have two Drawable objects specified - one for normal
|
||||
use and another one (optionally) for the button's "on" state if it's a
|
||||
toggle button.
|
||||
|
||||
@see Toolbar, ToolbarItemFactory, ToolbarItemComponent, Drawable, Button
|
||||
*/
|
||||
class JUCE_API ToolbarButton : public ToolbarItemComponent
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a ToolbarButton.
|
||||
|
||||
@param itemId the ID for this toolbar item type. This is passed through to the
|
||||
ToolbarItemComponent constructor
|
||||
@param labelText the text to display on the button (if the toolbar is using a style
|
||||
that shows text labels). This is passed through to the
|
||||
ToolbarItemComponent constructor
|
||||
@param normalImage a drawable object that the button should use as its icon. The object
|
||||
that is passed-in here will be kept by this object and will be
|
||||
deleted when no longer needed or when this button is deleted.
|
||||
@param toggledOnImage a drawable object that the button can use as its icon if the button
|
||||
is in a toggled-on state (see the Button::getToggleState() method). If
|
||||
nullptr is passed-in here, then the normal image will be used instead,
|
||||
regardless of the toggle state. The object that is passed-in here will be
|
||||
owned by this object and will be deleted when no longer needed or when
|
||||
this button is deleted.
|
||||
*/
|
||||
ToolbarButton (int itemId,
|
||||
const String& labelText,
|
||||
Drawable* normalImage,
|
||||
Drawable* toggledOnImage);
|
||||
|
||||
/** Destructor. */
|
||||
~ToolbarButton();
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
bool getToolbarItemSizes (int toolbarDepth, bool isToolbarVertical, int& preferredSize,
|
||||
int& minSize, int& maxSize) override;
|
||||
/** @internal */
|
||||
void paintButtonArea (Graphics&, int width, int height, bool isMouseOver, bool isMouseDown) override;
|
||||
/** @internal */
|
||||
void contentAreaChanged (const Rectangle<int>&) override;
|
||||
/** @internal */
|
||||
void buttonStateChanged() override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
ScopedPointer<Drawable> normalImage, toggledOnImage;
|
||||
Drawable* currentImage;
|
||||
|
||||
void updateDrawable();
|
||||
Drawable* getImageToUse() const;
|
||||
void setCurrentImage (Drawable*);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToolbarButton)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_TOOLBARBUTTON_H_INCLUDED
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_APPLICATIONCOMMANDID_H_INCLUDED
|
||||
#define JUCE_APPLICATIONCOMMANDID_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** A type used to hold the unique ID for an application command.
|
||||
|
||||
This is a numeric type, so it can be stored as an integer.
|
||||
|
||||
@see ApplicationCommandInfo, ApplicationCommandManager,
|
||||
ApplicationCommandTarget, KeyPressMappingSet
|
||||
*/
|
||||
typedef int CommandID;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** A set of general-purpose application command IDs.
|
||||
|
||||
Because these commands are likely to be used in most apps, they're defined
|
||||
here to help different apps to use the same numeric values for them.
|
||||
|
||||
Of course you don't have to use these, but some of them are used internally by
|
||||
Juce - e.g. the quit ID is recognised as a command by the JUCEApplication class.
|
||||
|
||||
@see ApplicationCommandInfo, ApplicationCommandManager,
|
||||
ApplicationCommandTarget, KeyPressMappingSet
|
||||
*/
|
||||
namespace StandardApplicationCommandIDs
|
||||
{
|
||||
enum
|
||||
{
|
||||
/** This command ID should be used to send a "Quit the App" command.
|
||||
|
||||
This command is recognised by the JUCEApplication class, so if it is invoked
|
||||
and no other ApplicationCommandTarget handles the event first, the JUCEApplication
|
||||
object will catch it and call JUCEApplicationBase::systemRequestedQuit().
|
||||
*/
|
||||
quit = 0x1001,
|
||||
|
||||
/** The command ID that should be used to send a "Delete" command. */
|
||||
del = 0x1002,
|
||||
|
||||
/** The command ID that should be used to send a "Cut" command. */
|
||||
cut = 0x1003,
|
||||
|
||||
/** The command ID that should be used to send a "Copy to clipboard" command. */
|
||||
copy = 0x1004,
|
||||
|
||||
/** The command ID that should be used to send a "Paste from clipboard" command. */
|
||||
paste = 0x1005,
|
||||
|
||||
/** The command ID that should be used to send a "Select all" command. */
|
||||
selectAll = 0x1006,
|
||||
|
||||
/** The command ID that should be used to send a "Deselect all" command. */
|
||||
deselectAll = 0x1007,
|
||||
|
||||
/** The command ID that should be used to send a "undo" command. */
|
||||
undo = 0x1008,
|
||||
|
||||
/** The command ID that should be used to send a "redo" command. */
|
||||
redo = 0x1009
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif // JUCE_APPLICATIONCOMMANDID_H_INCLUDED
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
ApplicationCommandInfo::ApplicationCommandInfo (const CommandID cid) noexcept
|
||||
: commandID (cid), flags (0)
|
||||
{
|
||||
}
|
||||
|
||||
void ApplicationCommandInfo::setInfo (const String& shortName_,
|
||||
const String& description_,
|
||||
const String& categoryName_,
|
||||
const int flags_) noexcept
|
||||
{
|
||||
shortName = shortName_;
|
||||
description = description_;
|
||||
categoryName = categoryName_;
|
||||
flags = flags_;
|
||||
}
|
||||
|
||||
void ApplicationCommandInfo::setActive (const bool b) noexcept
|
||||
{
|
||||
if (b)
|
||||
flags &= ~isDisabled;
|
||||
else
|
||||
flags |= isDisabled;
|
||||
}
|
||||
|
||||
void ApplicationCommandInfo::setTicked (const bool b) noexcept
|
||||
{
|
||||
if (b)
|
||||
flags |= isTicked;
|
||||
else
|
||||
flags &= ~isTicked;
|
||||
}
|
||||
|
||||
void ApplicationCommandInfo::addDefaultKeypress (const int keyCode, ModifierKeys modifiers) noexcept
|
||||
{
|
||||
defaultKeypresses.add (KeyPress (keyCode, modifiers, 0));
|
||||
}
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_APPLICATIONCOMMANDINFO_H_INCLUDED
|
||||
#define JUCE_APPLICATIONCOMMANDINFO_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Holds information describing an application command.
|
||||
|
||||
This object is used to pass information about a particular command, such as its
|
||||
name, description and other usage flags.
|
||||
|
||||
When an ApplicationCommandTarget is asked to provide information about the commands
|
||||
it can perform, this is the structure gets filled-in to describe each one.
|
||||
|
||||
@see ApplicationCommandTarget, ApplicationCommandTarget::getCommandInfo(),
|
||||
ApplicationCommandManager
|
||||
*/
|
||||
struct JUCE_API ApplicationCommandInfo
|
||||
{
|
||||
//==============================================================================
|
||||
explicit ApplicationCommandInfo (CommandID commandID) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets a number of the structures values at once.
|
||||
|
||||
The meanings of each of the parameters is described below, in the appropriate
|
||||
member variable's description.
|
||||
*/
|
||||
void setInfo (const String& shortName,
|
||||
const String& description,
|
||||
const String& categoryName,
|
||||
int flags) noexcept;
|
||||
|
||||
/** An easy way to set or remove the isDisabled bit in the structure's flags field.
|
||||
|
||||
If isActive is true, the flags member has the isDisabled bit cleared; if isActive
|
||||
is false, the bit is set.
|
||||
*/
|
||||
void setActive (bool isActive) noexcept;
|
||||
|
||||
/** An easy way to set or remove the isTicked bit in the structure's flags field.
|
||||
*/
|
||||
void setTicked (bool isTicked) noexcept;
|
||||
|
||||
/** Handy method for adding a keypress to the defaultKeypresses array.
|
||||
|
||||
This is just so you can write things like:
|
||||
@code
|
||||
myinfo.addDefaultKeypress ('s', ModifierKeys::commandModifier);
|
||||
@endcode
|
||||
instead of
|
||||
@code
|
||||
myinfo.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier));
|
||||
@endcode
|
||||
*/
|
||||
void addDefaultKeypress (int keyCode, ModifierKeys modifiers) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** The command's unique ID number.
|
||||
*/
|
||||
CommandID commandID;
|
||||
|
||||
/** A short name to describe the command.
|
||||
|
||||
This should be suitable for use in menus, on buttons that trigger the command, etc.
|
||||
|
||||
You can use the setInfo() method to quickly set this and some of the command's
|
||||
other properties.
|
||||
*/
|
||||
String shortName;
|
||||
|
||||
/** A longer description of the command.
|
||||
|
||||
This should be suitable for use in contexts such as a KeyMappingEditorComponent or
|
||||
pop-up tooltip describing what the command does.
|
||||
|
||||
You can use the setInfo() method to quickly set this and some of the command's
|
||||
other properties.
|
||||
*/
|
||||
String description;
|
||||
|
||||
/** A named category that the command fits into.
|
||||
|
||||
You can give your commands any category you like, and these will be displayed in
|
||||
contexts such as the KeyMappingEditorComponent, where the category is used to group
|
||||
commands together.
|
||||
|
||||
You can use the setInfo() method to quickly set this and some of the command's
|
||||
other properties.
|
||||
*/
|
||||
String categoryName;
|
||||
|
||||
/** A list of zero or more keypresses that should be used as the default keys for
|
||||
this command.
|
||||
|
||||
Methods such as KeyPressMappingSet::resetToDefaultMappings() will use the keypresses in
|
||||
this list to initialise the default set of key-to-command mappings.
|
||||
|
||||
@see addDefaultKeypress
|
||||
*/
|
||||
Array<KeyPress> defaultKeypresses;
|
||||
|
||||
//==============================================================================
|
||||
/** Flags describing the ways in which this command should be used.
|
||||
|
||||
A bitwise-OR of these values is stored in the ApplicationCommandInfo::flags
|
||||
variable.
|
||||
*/
|
||||
enum CommandFlags
|
||||
{
|
||||
/** Indicates that the command can't currently be performed.
|
||||
|
||||
The ApplicationCommandTarget::getCommandInfo() method must set this flag if it's
|
||||
not currently permissable to perform the command. If the flag is set, then
|
||||
components that trigger the command, e.g. PopupMenu, may choose to grey-out the
|
||||
command or show themselves as not being enabled.
|
||||
|
||||
@see ApplicationCommandInfo::setActive
|
||||
*/
|
||||
isDisabled = 1 << 0,
|
||||
|
||||
/** Indicates that the command should have a tick next to it on a menu.
|
||||
|
||||
If your command is shown on a menu and this is set, it'll show a tick next to
|
||||
it. Other components such as buttons may also use this flag to indicate that it
|
||||
is a value that can be toggled, and is currently in the 'on' state.
|
||||
|
||||
@see ApplicationCommandInfo::setTicked
|
||||
*/
|
||||
isTicked = 1 << 1,
|
||||
|
||||
/** If this flag is present, then when a KeyPressMappingSet invokes the command,
|
||||
it will call the command twice, once on key-down and again on key-up.
|
||||
|
||||
@see ApplicationCommandTarget::InvocationInfo
|
||||
*/
|
||||
wantsKeyUpDownCallbacks = 1 << 2,
|
||||
|
||||
/** If this flag is present, then a KeyMappingEditorComponent will not display the
|
||||
command in its list.
|
||||
*/
|
||||
hiddenFromKeyEditor = 1 << 3,
|
||||
|
||||
/** If this flag is present, then a KeyMappingEditorComponent will display the
|
||||
command in its list, but won't allow the assigned keypress to be changed.
|
||||
*/
|
||||
readOnlyInKeyEditor = 1 << 4,
|
||||
|
||||
/** If this flag is present and the command is invoked from a keypress, then any
|
||||
buttons or menus that are also connected to the command will not flash to
|
||||
indicate that they've been triggered.
|
||||
*/
|
||||
dontTriggerVisualFeedback = 1 << 5
|
||||
};
|
||||
|
||||
/** A bitwise-OR of the values specified in the CommandFlags enum.
|
||||
|
||||
You can use the setInfo() method to quickly set this and some of the command's
|
||||
other properties.
|
||||
*/
|
||||
int flags;
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_APPLICATIONCOMMANDINFO_H_INCLUDED
|
||||
|
|
@ -0,0 +1,312 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
ApplicationCommandManager::ApplicationCommandManager()
|
||||
: firstTarget (nullptr)
|
||||
{
|
||||
keyMappings = new KeyPressMappingSet (*this);
|
||||
Desktop::getInstance().addFocusChangeListener (this);
|
||||
}
|
||||
|
||||
ApplicationCommandManager::~ApplicationCommandManager()
|
||||
{
|
||||
Desktop::getInstance().removeFocusChangeListener (this);
|
||||
keyMappings = nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ApplicationCommandManager::clearCommands()
|
||||
{
|
||||
commands.clear();
|
||||
keyMappings->clearAllKeyPresses();
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
void ApplicationCommandManager::registerCommand (const ApplicationCommandInfo& newCommand)
|
||||
{
|
||||
// zero isn't a valid command ID!
|
||||
jassert (newCommand.commandID != 0);
|
||||
|
||||
// the name isn't optional!
|
||||
jassert (newCommand.shortName.isNotEmpty());
|
||||
|
||||
if (ApplicationCommandInfo* command = getMutableCommandForID (newCommand.commandID))
|
||||
{
|
||||
// Trying to re-register the same command ID with different parameters can often indicate a typo.
|
||||
// This assertion is here because I've found it useful catching some mistakes, but it may also cause
|
||||
// false alarms if you're deliberately updating some flags for a command.
|
||||
jassert (newCommand.shortName == getCommandForID (newCommand.commandID)->shortName
|
||||
&& newCommand.categoryName == getCommandForID (newCommand.commandID)->categoryName
|
||||
&& newCommand.defaultKeypresses == getCommandForID (newCommand.commandID)->defaultKeypresses
|
||||
&& (newCommand.flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor))
|
||||
== (getCommandForID (newCommand.commandID)->flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor)));
|
||||
|
||||
*command = newCommand;
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplicationCommandInfo* const newInfo = new ApplicationCommandInfo (newCommand);
|
||||
newInfo->flags &= ~ApplicationCommandInfo::isTicked;
|
||||
commands.add (newInfo);
|
||||
|
||||
keyMappings->resetToDefaultMapping (newCommand.commandID);
|
||||
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void ApplicationCommandManager::registerAllCommandsForTarget (ApplicationCommandTarget* target)
|
||||
{
|
||||
if (target != nullptr)
|
||||
{
|
||||
Array<CommandID> commandIDs;
|
||||
target->getAllCommands (commandIDs);
|
||||
|
||||
for (int i = 0; i < commandIDs.size(); ++i)
|
||||
{
|
||||
ApplicationCommandInfo info (commandIDs.getUnchecked(i));
|
||||
target->getCommandInfo (info.commandID, info);
|
||||
|
||||
registerCommand (info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApplicationCommandManager::removeCommand (const CommandID commandID)
|
||||
{
|
||||
for (int i = commands.size(); --i >= 0;)
|
||||
{
|
||||
if (commands.getUnchecked (i)->commandID == commandID)
|
||||
{
|
||||
commands.remove (i);
|
||||
triggerAsyncUpdate();
|
||||
|
||||
const Array<KeyPress> keys (keyMappings->getKeyPressesAssignedToCommand (commandID));
|
||||
|
||||
for (int j = keys.size(); --j >= 0;)
|
||||
keyMappings->removeKeyPress (keys.getReference (j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApplicationCommandManager::commandStatusChanged()
|
||||
{
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ApplicationCommandInfo* ApplicationCommandManager::getMutableCommandForID (CommandID commandID) const noexcept
|
||||
{
|
||||
for (int i = commands.size(); --i >= 0;)
|
||||
if (commands.getUnchecked(i)->commandID == commandID)
|
||||
return commands.getUnchecked(i);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ApplicationCommandInfo* ApplicationCommandManager::getCommandForID (const CommandID commandID) const noexcept
|
||||
{
|
||||
return getMutableCommandForID (commandID);
|
||||
}
|
||||
|
||||
String ApplicationCommandManager::getNameOfCommand (const CommandID commandID) const noexcept
|
||||
{
|
||||
if (const ApplicationCommandInfo* const ci = getCommandForID (commandID))
|
||||
return ci->shortName;
|
||||
|
||||
return String();
|
||||
}
|
||||
|
||||
String ApplicationCommandManager::getDescriptionOfCommand (const CommandID commandID) const noexcept
|
||||
{
|
||||
if (const ApplicationCommandInfo* const ci = getCommandForID (commandID))
|
||||
return ci->description.isNotEmpty() ? ci->description
|
||||
: ci->shortName;
|
||||
|
||||
return String();
|
||||
}
|
||||
|
||||
StringArray ApplicationCommandManager::getCommandCategories() const
|
||||
{
|
||||
StringArray s;
|
||||
|
||||
for (int i = 0; i < commands.size(); ++i)
|
||||
s.addIfNotAlreadyThere (commands.getUnchecked(i)->categoryName, false);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Array<CommandID> ApplicationCommandManager::getCommandsInCategory (const String& categoryName) const
|
||||
{
|
||||
Array<CommandID> results;
|
||||
|
||||
for (int i = 0; i < commands.size(); ++i)
|
||||
if (commands.getUnchecked(i)->categoryName == categoryName)
|
||||
results.add (commands.getUnchecked(i)->commandID);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool ApplicationCommandManager::invokeDirectly (const CommandID commandID, const bool asynchronously)
|
||||
{
|
||||
ApplicationCommandTarget::InvocationInfo info (commandID);
|
||||
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::direct;
|
||||
|
||||
return invoke (info, asynchronously);
|
||||
}
|
||||
|
||||
bool ApplicationCommandManager::invoke (const ApplicationCommandTarget::InvocationInfo& inf, const bool asynchronously)
|
||||
{
|
||||
// This call isn't thread-safe for use from a non-UI thread without locking the message
|
||||
// manager first..
|
||||
jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager());
|
||||
|
||||
bool ok = false;
|
||||
ApplicationCommandInfo commandInfo (0);
|
||||
|
||||
if (ApplicationCommandTarget* const target = getTargetForCommand (inf.commandID, commandInfo))
|
||||
{
|
||||
ApplicationCommandTarget::InvocationInfo info (inf);
|
||||
info.commandFlags = commandInfo.flags;
|
||||
|
||||
sendListenerInvokeCallback (info);
|
||||
ok = target->invoke (info, asynchronously);
|
||||
commandStatusChanged();
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ApplicationCommandTarget* ApplicationCommandManager::getFirstCommandTarget (const CommandID)
|
||||
{
|
||||
return firstTarget != nullptr ? firstTarget
|
||||
: findDefaultComponentTarget();
|
||||
}
|
||||
|
||||
void ApplicationCommandManager::setFirstCommandTarget (ApplicationCommandTarget* const newTarget) noexcept
|
||||
{
|
||||
firstTarget = newTarget;
|
||||
}
|
||||
|
||||
ApplicationCommandTarget* ApplicationCommandManager::getTargetForCommand (const CommandID commandID,
|
||||
ApplicationCommandInfo& upToDateInfo)
|
||||
{
|
||||
ApplicationCommandTarget* target = getFirstCommandTarget (commandID);
|
||||
|
||||
if (target == nullptr)
|
||||
target = JUCEApplication::getInstance();
|
||||
|
||||
if (target != nullptr)
|
||||
target = target->getTargetForCommand (commandID);
|
||||
|
||||
if (target != nullptr)
|
||||
{
|
||||
upToDateInfo.commandID = commandID;
|
||||
target->getCommandInfo (commandID, upToDateInfo);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ApplicationCommandTarget* ApplicationCommandManager::findTargetForComponent (Component* c)
|
||||
{
|
||||
ApplicationCommandTarget* target = dynamic_cast<ApplicationCommandTarget*> (c);
|
||||
|
||||
if (target == nullptr && c != nullptr)
|
||||
target = c->findParentComponentOfClass<ApplicationCommandTarget>();
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
ApplicationCommandTarget* ApplicationCommandManager::findDefaultComponentTarget()
|
||||
{
|
||||
Component* c = Component::getCurrentlyFocusedComponent();
|
||||
|
||||
if (c == nullptr)
|
||||
{
|
||||
if (TopLevelWindow* const activeWindow = TopLevelWindow::getActiveTopLevelWindow())
|
||||
{
|
||||
c = activeWindow->getPeer()->getLastFocusedSubcomponent();
|
||||
|
||||
if (c == nullptr)
|
||||
c = activeWindow;
|
||||
}
|
||||
}
|
||||
|
||||
if (c == nullptr && Process::isForegroundProcess())
|
||||
{
|
||||
Desktop& desktop = Desktop::getInstance();
|
||||
|
||||
// getting a bit desperate now: try all desktop comps..
|
||||
for (int i = desktop.getNumComponents(); --i >= 0;)
|
||||
if (ComponentPeer* const peer = desktop.getComponent(i)->getPeer())
|
||||
if (ApplicationCommandTarget* const target = findTargetForComponent (peer->getLastFocusedSubcomponent()))
|
||||
return target;
|
||||
}
|
||||
|
||||
if (c != nullptr)
|
||||
{
|
||||
// if we're focused on a ResizableWindow, chances are that it's the content
|
||||
// component that really should get the event. And if not, the event will
|
||||
// still be passed up to the top level window anyway, so let's send it to the
|
||||
// content comp.
|
||||
if (ResizableWindow* const resizableWindow = dynamic_cast<ResizableWindow*> (c))
|
||||
if (Component* const content = resizableWindow->getContentComponent())
|
||||
c = content;
|
||||
|
||||
if (ApplicationCommandTarget* const target = findTargetForComponent (c))
|
||||
return target;
|
||||
}
|
||||
|
||||
return JUCEApplication::getInstance();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ApplicationCommandManager::addListener (ApplicationCommandManagerListener* const listener)
|
||||
{
|
||||
listeners.add (listener);
|
||||
}
|
||||
|
||||
void ApplicationCommandManager::removeListener (ApplicationCommandManagerListener* const listener)
|
||||
{
|
||||
listeners.remove (listener);
|
||||
}
|
||||
|
||||
void ApplicationCommandManager::sendListenerInvokeCallback (const ApplicationCommandTarget::InvocationInfo& info)
|
||||
{
|
||||
listeners.call (&ApplicationCommandManagerListener::applicationCommandInvoked, info);
|
||||
}
|
||||
|
||||
void ApplicationCommandManager::handleAsyncUpdate()
|
||||
{
|
||||
listeners.call (&ApplicationCommandManagerListener::applicationCommandListChanged);
|
||||
}
|
||||
|
||||
void ApplicationCommandManager::globalFocusChanged (Component*)
|
||||
{
|
||||
commandStatusChanged();
|
||||
}
|
||||
|
|
@ -0,0 +1,353 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_APPLICATIONCOMMANDMANAGER_H_INCLUDED
|
||||
#define JUCE_APPLICATIONCOMMANDMANAGER_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
One of these objects holds a list of all the commands your app can perform,
|
||||
and despatches these commands when needed.
|
||||
|
||||
Application commands are a good way to trigger actions in your app, e.g. "Quit",
|
||||
"Copy", "Paste", etc. Menus, buttons and keypresses can all be given commands
|
||||
to invoke automatically, which means you don't have to handle the result of a menu
|
||||
or button click manually. Commands are despatched to ApplicationCommandTarget objects
|
||||
which can choose which events they want to handle.
|
||||
|
||||
This architecture also allows for nested ApplicationCommandTargets, so that for example
|
||||
you could have two different objects, one inside the other, both of which can respond to
|
||||
a "delete" command. Depending on which one has focus, the command will be sent to the
|
||||
appropriate place, regardless of whether it was triggered by a menu, keypress or some other
|
||||
method.
|
||||
|
||||
To set up your app to use commands, you'll need to do the following:
|
||||
|
||||
- Create a global ApplicationCommandManager to hold the list of all possible
|
||||
commands. (This will also manage a set of key-mappings for them).
|
||||
|
||||
- Make some of your UI components (or other objects) inherit from ApplicationCommandTarget.
|
||||
This allows the object to provide a list of commands that it can perform, and
|
||||
to handle them.
|
||||
|
||||
- Register each type of command using ApplicationCommandManager::registerAllCommandsForTarget(),
|
||||
or ApplicationCommandManager::registerCommand().
|
||||
|
||||
- If you want key-presses to trigger your commands, use the ApplicationCommandManager::getKeyMappings()
|
||||
method to access the key-mapper object, which you will need to register as a key-listener
|
||||
in whatever top-level component you're using. See the KeyPressMappingSet class for more help
|
||||
about setting this up.
|
||||
|
||||
- Use methods such as PopupMenu::addCommandItem() or Button::setCommandToTrigger() to
|
||||
cause these commands to be invoked automatically.
|
||||
|
||||
- Commands can be invoked directly by your code using ApplicationCommandManager::invokeDirectly().
|
||||
|
||||
When a command is invoked, the ApplicationCommandManager will try to choose the best
|
||||
ApplicationCommandTarget to receive the specified command. To do this it will use the
|
||||
current keyboard focus to see which component might be interested, and will search the
|
||||
component hierarchy for those that also implement the ApplicationCommandTarget interface.
|
||||
If an ApplicationCommandTarget isn't interested in the command that is being invoked, then
|
||||
the next one in line will be tried (see the ApplicationCommandTarget::getNextCommandTarget()
|
||||
method), and so on until ApplicationCommandTarget::getNextCommandTarget() returns nullptr.
|
||||
At this point if the command still hasn't been performed, it will be passed to the current
|
||||
JUCEApplication object (which is itself an ApplicationCommandTarget).
|
||||
|
||||
To exert some custom control over which ApplicationCommandTarget is chosen to invoke a command,
|
||||
you can override the ApplicationCommandManager::getFirstCommandTarget() method and choose
|
||||
the object yourself.
|
||||
|
||||
@see ApplicationCommandTarget, ApplicationCommandInfo
|
||||
*/
|
||||
class JUCE_API ApplicationCommandManager : private AsyncUpdater,
|
||||
private FocusChangeListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an ApplicationCommandManager.
|
||||
|
||||
Once created, you'll need to register all your app's commands with it, using
|
||||
ApplicationCommandManager::registerAllCommandsForTarget() or
|
||||
ApplicationCommandManager::registerCommand().
|
||||
*/
|
||||
ApplicationCommandManager();
|
||||
|
||||
/** Destructor.
|
||||
|
||||
Make sure that you don't delete this if pointers to it are still being used by
|
||||
objects such as PopupMenus or Buttons.
|
||||
*/
|
||||
virtual ~ApplicationCommandManager();
|
||||
|
||||
//==============================================================================
|
||||
/** Clears the current list of all commands.
|
||||
Note that this will also clear the contents of the KeyPressMappingSet.
|
||||
*/
|
||||
void clearCommands();
|
||||
|
||||
/** Adds a command to the list of registered commands.
|
||||
@see registerAllCommandsForTarget
|
||||
*/
|
||||
void registerCommand (const ApplicationCommandInfo& newCommand);
|
||||
|
||||
/** Adds all the commands that this target publishes to the manager's list.
|
||||
|
||||
This will use ApplicationCommandTarget::getAllCommands() and ApplicationCommandTarget::getCommandInfo()
|
||||
to get details about all the commands that this target can do, and will call
|
||||
registerCommand() to add each one to the manger's list.
|
||||
|
||||
@see registerCommand
|
||||
*/
|
||||
void registerAllCommandsForTarget (ApplicationCommandTarget* target);
|
||||
|
||||
/** Removes the command with a specified ID.
|
||||
Note that this will also remove any key mappings that are mapped to the command.
|
||||
*/
|
||||
void removeCommand (CommandID commandID);
|
||||
|
||||
/** This should be called to tell the manager that one of its registered commands may have changed
|
||||
its active status.
|
||||
|
||||
Because the command manager only finds out whether a command is active or inactive by querying
|
||||
the current ApplicationCommandTarget, this is used to tell it that things may have changed. It
|
||||
allows things like buttons to update their enablement, etc.
|
||||
|
||||
This method will cause an asynchronous call to ApplicationCommandManagerListener::applicationCommandListChanged()
|
||||
for any registered listeners.
|
||||
*/
|
||||
void commandStatusChanged();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of commands that have been registered.
|
||||
@see registerCommand
|
||||
*/
|
||||
int getNumCommands() const noexcept { return commands.size(); }
|
||||
|
||||
/** Returns the details about one of the registered commands.
|
||||
The index is between 0 and (getNumCommands() - 1).
|
||||
*/
|
||||
const ApplicationCommandInfo* getCommandForIndex (int index) const noexcept { return commands [index]; }
|
||||
|
||||
/** Returns the details about a given command ID.
|
||||
|
||||
This will search the list of registered commands for one with the given command
|
||||
ID number, and return its associated info. If no matching command is found, this
|
||||
will return 0.
|
||||
*/
|
||||
const ApplicationCommandInfo* getCommandForID (CommandID commandID) const noexcept;
|
||||
|
||||
/** Returns the name field for a command.
|
||||
|
||||
An empty string is returned if no command with this ID has been registered.
|
||||
@see getDescriptionOfCommand
|
||||
*/
|
||||
String getNameOfCommand (CommandID commandID) const noexcept;
|
||||
|
||||
/** Returns the description field for a command.
|
||||
|
||||
An empty string is returned if no command with this ID has been registered. If the
|
||||
command has no description, this will return its short name field instead.
|
||||
|
||||
@see getNameOfCommand
|
||||
*/
|
||||
String getDescriptionOfCommand (CommandID commandID) const noexcept;
|
||||
|
||||
/** Returns the list of categories.
|
||||
|
||||
This will go through all registered commands, and return a list of all the distinct
|
||||
categoryName values from their ApplicationCommandInfo structure.
|
||||
|
||||
@see getCommandsInCategory()
|
||||
*/
|
||||
StringArray getCommandCategories() const;
|
||||
|
||||
/** Returns a list of all the command UIDs in a particular category.
|
||||
@see getCommandCategories()
|
||||
*/
|
||||
Array<CommandID> getCommandsInCategory (const String& categoryName) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the manager's internal set of key mappings.
|
||||
|
||||
This object can be used to edit the keypresses. To actually link this object up
|
||||
to invoke commands when a key is pressed, see the comments for the KeyPressMappingSet
|
||||
class.
|
||||
|
||||
@see KeyPressMappingSet
|
||||
*/
|
||||
KeyPressMappingSet* getKeyMappings() const noexcept { return keyMappings; }
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Invokes the given command directly, sending it to the default target.
|
||||
This is just an easy way to call invoke() without having to fill out the InvocationInfo
|
||||
structure.
|
||||
*/
|
||||
bool invokeDirectly (CommandID commandID, bool asynchronously);
|
||||
|
||||
/** Sends a command to the default target.
|
||||
|
||||
This will choose a target using getFirstCommandTarget(), and send the specified command
|
||||
to it using the ApplicationCommandTarget::invoke() method. This means that if the
|
||||
first target can't handle the command, it will be passed on to targets further down the
|
||||
chain (see ApplicationCommandTarget::invoke() for more info).
|
||||
|
||||
@param invocationInfo this must be correctly filled-in, describing the context for
|
||||
the invocation.
|
||||
@param asynchronously if false, the command will be performed before this method returns.
|
||||
If true, a message will be posted so that the command will be performed
|
||||
later on the message thread, and this method will return immediately.
|
||||
|
||||
@see ApplicationCommandTarget::invoke
|
||||
*/
|
||||
bool invoke (const ApplicationCommandTarget::InvocationInfo& invocationInfo,
|
||||
bool asynchronously);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Chooses the ApplicationCommandTarget to which a command should be sent.
|
||||
|
||||
Whenever the manager needs to know which target a command should be sent to, it calls
|
||||
this method to determine the first one to try.
|
||||
|
||||
By default, this method will return the target that was set by calling setFirstCommandTarget().
|
||||
If no target is set, it will return the result of findDefaultComponentTarget().
|
||||
|
||||
If you need to make sure all commands go via your own custom target, then you can
|
||||
either use setFirstCommandTarget() to specify a single target, or override this method
|
||||
if you need more complex logic to choose one.
|
||||
|
||||
It may return nullptr if no targets are available.
|
||||
|
||||
@see getTargetForCommand, invoke, invokeDirectly
|
||||
*/
|
||||
virtual ApplicationCommandTarget* getFirstCommandTarget (CommandID commandID);
|
||||
|
||||
/** Sets a target to be returned by getFirstCommandTarget().
|
||||
|
||||
If this is set to nullptr, then getFirstCommandTarget() will by default return the
|
||||
result of findDefaultComponentTarget().
|
||||
|
||||
If you use this to set a target, make sure you call setFirstCommandTarget(nullptr)
|
||||
before deleting the target object.
|
||||
*/
|
||||
void setFirstCommandTarget (ApplicationCommandTarget* newTarget) noexcept;
|
||||
|
||||
/** Tries to find the best target to use to perform a given command.
|
||||
|
||||
This will call getFirstCommandTarget() to find the preferred target, and will
|
||||
check whether that target can handle the given command. If it can't, then it'll use
|
||||
ApplicationCommandTarget::getNextCommandTarget() to find the next one to try, and
|
||||
so on until no more are available.
|
||||
|
||||
If no targets are found that can perform the command, this method will return nullptr.
|
||||
|
||||
If a target is found, then it will get the target to fill-in the upToDateInfo
|
||||
structure with the latest info about that command, so that the caller can see
|
||||
whether the command is disabled, ticked, etc.
|
||||
*/
|
||||
ApplicationCommandTarget* getTargetForCommand (CommandID commandID,
|
||||
ApplicationCommandInfo& upToDateInfo);
|
||||
|
||||
//==============================================================================
|
||||
/** Registers a listener that will be called when various events occur. */
|
||||
void addListener (ApplicationCommandManagerListener* listener);
|
||||
|
||||
/** Deregisters a previously-added listener. */
|
||||
void removeListener (ApplicationCommandManagerListener* listener);
|
||||
|
||||
//==============================================================================
|
||||
/** Looks for a suitable command target based on which Components have the keyboard focus.
|
||||
|
||||
This is used by the default implementation of ApplicationCommandTarget::getFirstCommandTarget(),
|
||||
but is exposed here in case it's useful.
|
||||
|
||||
It tries to pick the best ApplicationCommandTarget by looking at focused components, top level
|
||||
windows, etc., and using the findTargetForComponent() method.
|
||||
*/
|
||||
static ApplicationCommandTarget* findDefaultComponentTarget();
|
||||
|
||||
/** Examines this component and all its parents in turn, looking for the first one
|
||||
which is an ApplicationCommandTarget.
|
||||
|
||||
Returns the first ApplicationCommandTarget that it finds, or nullptr if none of them
|
||||
implement that class.
|
||||
*/
|
||||
static ApplicationCommandTarget* findTargetForComponent (Component*);
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
OwnedArray<ApplicationCommandInfo> commands;
|
||||
ListenerList<ApplicationCommandManagerListener> listeners;
|
||||
ScopedPointer<KeyPressMappingSet> keyMappings;
|
||||
ApplicationCommandTarget* firstTarget;
|
||||
|
||||
void sendListenerInvokeCallback (const ApplicationCommandTarget::InvocationInfo&);
|
||||
void handleAsyncUpdate() override;
|
||||
void globalFocusChanged (Component*) override;
|
||||
ApplicationCommandInfo* getMutableCommandForID (CommandID) const noexcept;
|
||||
|
||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
|
||||
// This is just here to cause a compile error in old code that hasn't been changed to use the new
|
||||
// version of this method.
|
||||
virtual short getFirstCommandTarget() { return 0; }
|
||||
#endif
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ApplicationCommandManager)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A listener that receives callbacks from an ApplicationCommandManager when
|
||||
commands are invoked or the command list is changed.
|
||||
|
||||
@see ApplicationCommandManager::addListener, ApplicationCommandManager::removeListener
|
||||
|
||||
*/
|
||||
class JUCE_API ApplicationCommandManagerListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Destructor. */
|
||||
virtual ~ApplicationCommandManagerListener() {}
|
||||
|
||||
/** Called when an app command is about to be invoked. */
|
||||
virtual void applicationCommandInvoked (const ApplicationCommandTarget::InvocationInfo&) = 0;
|
||||
|
||||
/** Called when commands are registered or deregistered from the
|
||||
command manager, or when commands are made active or inactive.
|
||||
|
||||
Note that if you're using this to watch for changes to whether a command is disabled,
|
||||
you'll need to make sure that ApplicationCommandManager::commandStatusChanged() is called
|
||||
whenever the status of your command might have changed.
|
||||
*/
|
||||
virtual void applicationCommandListChanged() = 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // JUCE_APPLICATIONCOMMANDMANAGER_H_INCLUDED
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
class ApplicationCommandTarget::CommandMessage : public MessageManager::MessageBase
|
||||
{
|
||||
public:
|
||||
CommandMessage (ApplicationCommandTarget* const target, const InvocationInfo& inf)
|
||||
: owner (target), info (inf)
|
||||
{
|
||||
}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
if (ApplicationCommandTarget* const target = owner)
|
||||
target->tryToInvoke (info, false);
|
||||
}
|
||||
|
||||
private:
|
||||
WeakReference<ApplicationCommandTarget> owner;
|
||||
const InvocationInfo info;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (CommandMessage)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ApplicationCommandTarget::ApplicationCommandTarget()
|
||||
{
|
||||
}
|
||||
|
||||
ApplicationCommandTarget::~ApplicationCommandTarget()
|
||||
{
|
||||
masterReference.clear();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool ApplicationCommandTarget::tryToInvoke (const InvocationInfo& info, const bool async)
|
||||
{
|
||||
if (isCommandActive (info.commandID))
|
||||
{
|
||||
if (async)
|
||||
{
|
||||
(new CommandMessage (this, info))->post();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (perform (info))
|
||||
return true;
|
||||
|
||||
// Hmm.. your target claimed that it could perform this command, but failed to do so.
|
||||
// If it can't do it at the moment for some reason, it should clear the 'isActive' flag
|
||||
// when it returns the command's info.
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ApplicationCommandTarget* ApplicationCommandTarget::findFirstTargetParentComponent()
|
||||
{
|
||||
if (Component* const c = dynamic_cast <Component*> (this))
|
||||
return c->findParentComponentOfClass<ApplicationCommandTarget>();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ApplicationCommandTarget* ApplicationCommandTarget::getTargetForCommand (const CommandID commandID)
|
||||
{
|
||||
ApplicationCommandTarget* target = this;
|
||||
int depth = 0;
|
||||
|
||||
while (target != nullptr)
|
||||
{
|
||||
Array<CommandID> commandIDs;
|
||||
target->getAllCommands (commandIDs);
|
||||
|
||||
if (commandIDs.contains (commandID))
|
||||
return target;
|
||||
|
||||
target = target->getNextCommandTarget();
|
||||
|
||||
++depth;
|
||||
jassert (depth < 100); // could be a recursive command chain??
|
||||
jassert (target != this); // definitely a recursive command chain!
|
||||
|
||||
if (depth > 100 || target == this)
|
||||
break;
|
||||
}
|
||||
|
||||
if (target == nullptr)
|
||||
{
|
||||
target = JUCEApplication::getInstance();
|
||||
|
||||
if (target != nullptr)
|
||||
{
|
||||
Array<CommandID> commandIDs;
|
||||
target->getAllCommands (commandIDs);
|
||||
|
||||
if (commandIDs.contains (commandID))
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ApplicationCommandTarget::isCommandActive (const CommandID commandID)
|
||||
{
|
||||
ApplicationCommandInfo info (commandID);
|
||||
info.flags = ApplicationCommandInfo::isDisabled;
|
||||
|
||||
getCommandInfo (commandID, info);
|
||||
|
||||
return (info.flags & ApplicationCommandInfo::isDisabled) == 0;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool ApplicationCommandTarget::invoke (const InvocationInfo& info, const bool async)
|
||||
{
|
||||
ApplicationCommandTarget* target = this;
|
||||
int depth = 0;
|
||||
|
||||
while (target != nullptr)
|
||||
{
|
||||
if (target->tryToInvoke (info, async))
|
||||
return true;
|
||||
|
||||
target = target->getNextCommandTarget();
|
||||
|
||||
++depth;
|
||||
jassert (depth < 100); // could be a recursive command chain??
|
||||
jassert (target != this); // definitely a recursive command chain!
|
||||
|
||||
if (depth > 100 || target == this)
|
||||
break;
|
||||
}
|
||||
|
||||
if (target == nullptr)
|
||||
{
|
||||
target = JUCEApplication::getInstance();
|
||||
|
||||
if (target != nullptr)
|
||||
return target->tryToInvoke (info, async);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ApplicationCommandTarget::invokeDirectly (const CommandID commandID, const bool asynchronously)
|
||||
{
|
||||
ApplicationCommandTarget::InvocationInfo info (commandID);
|
||||
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::direct;
|
||||
|
||||
return invoke (info, asynchronously);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ApplicationCommandTarget::InvocationInfo::InvocationInfo (const CommandID command)
|
||||
: commandID (command),
|
||||
commandFlags (0),
|
||||
invocationMethod (direct),
|
||||
originatingComponent (nullptr),
|
||||
isKeyDown (false),
|
||||
millisecsSinceKeyPressed (0)
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_APPLICATIONCOMMANDTARGET_H_INCLUDED
|
||||
#define JUCE_APPLICATIONCOMMANDTARGET_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A command target publishes a list of command IDs that it can perform.
|
||||
|
||||
An ApplicationCommandManager despatches commands to targets, which must be
|
||||
able to provide information about what commands they can handle.
|
||||
|
||||
To create a target, you'll need to inherit from this class, implementing all of
|
||||
its pure virtual methods.
|
||||
|
||||
For info about how a target is chosen to receive a command, see
|
||||
ApplicationCommandManager::getFirstCommandTarget().
|
||||
|
||||
@see ApplicationCommandManager, ApplicationCommandInfo
|
||||
*/
|
||||
class JUCE_API ApplicationCommandTarget
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a command target. */
|
||||
ApplicationCommandTarget();
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~ApplicationCommandTarget();
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Contains contextual details about the invocation of a command.
|
||||
*/
|
||||
struct JUCE_API InvocationInfo
|
||||
{
|
||||
//==============================================================================
|
||||
InvocationInfo (const CommandID commandID);
|
||||
|
||||
//==============================================================================
|
||||
/** The UID of the command that should be performed. */
|
||||
CommandID commandID;
|
||||
|
||||
/** The command's flags.
|
||||
See ApplicationCommandInfo for a description of these flag values.
|
||||
*/
|
||||
int commandFlags;
|
||||
|
||||
//==============================================================================
|
||||
/** The types of context in which the command might be called. */
|
||||
enum InvocationMethod
|
||||
{
|
||||
direct = 0, /**< The command is being invoked directly by a piece of code. */
|
||||
fromKeyPress, /**< The command is being invoked by a key-press. */
|
||||
fromMenu, /**< The command is being invoked by a menu selection. */
|
||||
fromButton /**< The command is being invoked by a button click. */
|
||||
};
|
||||
|
||||
/** The type of event that triggered this command. */
|
||||
InvocationMethod invocationMethod;
|
||||
|
||||
//==============================================================================
|
||||
/** If triggered by a keypress or menu, this will be the component that had the
|
||||
keyboard focus at the time.
|
||||
|
||||
If triggered by a button, it may be set to that component, or it may be null.
|
||||
*/
|
||||
Component* originatingComponent;
|
||||
|
||||
//==============================================================================
|
||||
/** The keypress that was used to invoke it.
|
||||
|
||||
Note that this will be an invalid keypress if the command was invoked
|
||||
by some other means than a keyboard shortcut.
|
||||
*/
|
||||
KeyPress keyPress;
|
||||
|
||||
/** True if the callback is being invoked when the key is pressed,
|
||||
false if the key is being released.
|
||||
|
||||
@see KeyPressMappingSet::addCommand()
|
||||
*/
|
||||
bool isKeyDown;
|
||||
|
||||
/** If the key is being released, this indicates how long it had been held
|
||||
down for.
|
||||
|
||||
(Only relevant if isKeyDown is false.)
|
||||
*/
|
||||
int millisecsSinceKeyPressed;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** This must return the next target to try after this one.
|
||||
|
||||
When a command is being sent, and the first target can't handle
|
||||
that command, this method is used to determine the next target that should
|
||||
be tried.
|
||||
|
||||
It may return nullptr if it doesn't know of another target.
|
||||
|
||||
If your target is a Component, you would usually use the findFirstTargetParentComponent()
|
||||
method to return a parent component that might want to handle it.
|
||||
|
||||
@see invoke
|
||||
*/
|
||||
virtual ApplicationCommandTarget* getNextCommandTarget() = 0;
|
||||
|
||||
/** This must return a complete list of commands that this target can handle.
|
||||
|
||||
Your target should add all the command IDs that it handles to the array that is
|
||||
passed-in.
|
||||
*/
|
||||
virtual void getAllCommands (Array<CommandID>& commands) = 0;
|
||||
|
||||
/** This must provide details about one of the commands that this target can perform.
|
||||
|
||||
This will be called with one of the command IDs that the target provided in its
|
||||
getAllCommands() methods.
|
||||
|
||||
It should fill-in all appropriate fields of the ApplicationCommandInfo structure with
|
||||
suitable information about the command. (The commandID field will already have been filled-in
|
||||
by the caller).
|
||||
|
||||
The easiest way to set the info is using the ApplicationCommandInfo::setInfo() method to
|
||||
set all the fields at once.
|
||||
|
||||
If the command is currently inactive for some reason, this method must use
|
||||
ApplicationCommandInfo::setActive() to make that clear, (or it should set the isDisabled
|
||||
bit of the ApplicationCommandInfo::flags field).
|
||||
|
||||
Any default key-presses for the command should be appended to the
|
||||
ApplicationCommandInfo::defaultKeypresses field.
|
||||
|
||||
Note that if you change something that affects the status of the commands
|
||||
that would be returned by this method (e.g. something that makes some commands
|
||||
active or inactive), you should call ApplicationCommandManager::commandStatusChanged()
|
||||
to cause the manager to refresh its status.
|
||||
*/
|
||||
virtual void getCommandInfo (CommandID commandID, ApplicationCommandInfo& result) = 0;
|
||||
|
||||
/** This must actually perform the specified command.
|
||||
|
||||
If this target is able to perform the command specified by the commandID field of the
|
||||
InvocationInfo structure, then it should do so, and must return true.
|
||||
|
||||
If it can't handle this command, it should return false, which tells the caller to pass
|
||||
the command on to the next target in line.
|
||||
|
||||
@see invoke, ApplicationCommandManager::invoke
|
||||
*/
|
||||
virtual bool perform (const InvocationInfo& info) = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** Makes this target invoke a command.
|
||||
|
||||
Your code can call this method to invoke a command on this target, but normally
|
||||
you'd call it indirectly via ApplicationCommandManager::invoke() or
|
||||
ApplicationCommandManager::invokeDirectly().
|
||||
|
||||
If this target can perform the given command, it will call its perform() method to
|
||||
do so. If not, then getNextCommandTarget() will be used to determine the next target
|
||||
to try, and the command will be passed along to it.
|
||||
|
||||
@param invocationInfo this must be correctly filled-in, describing the context for
|
||||
the invocation.
|
||||
@param asynchronously if false, the command will be performed before this method returns.
|
||||
If true, a message will be posted so that the command will be performed
|
||||
later on the message thread, and this method will return immediately.
|
||||
@see perform, ApplicationCommandManager::invoke
|
||||
*/
|
||||
bool invoke (const InvocationInfo& invocationInfo,
|
||||
const bool asynchronously);
|
||||
|
||||
/** Invokes a given command directly on this target.
|
||||
|
||||
This is just an easy way to call invoke() without having to fill out the InvocationInfo
|
||||
structure.
|
||||
*/
|
||||
bool invokeDirectly (const CommandID commandID,
|
||||
const bool asynchronously);
|
||||
|
||||
//==============================================================================
|
||||
/** Searches this target and all subsequent ones for the first one that can handle
|
||||
the specified command.
|
||||
|
||||
This will use getNextCommandTarget() to determine the chain of targets to try
|
||||
after this one.
|
||||
*/
|
||||
ApplicationCommandTarget* getTargetForCommand (const CommandID commandID);
|
||||
|
||||
/** Checks whether this command can currently be performed by this target.
|
||||
|
||||
This will return true only if a call to getCommandInfo() doesn't set the
|
||||
isDisabled flag to indicate that the command is inactive.
|
||||
*/
|
||||
bool isCommandActive (const CommandID commandID);
|
||||
|
||||
/** If this object is a Component, this method will seach upwards in its current
|
||||
UI hierarchy for the next parent component that implements the
|
||||
ApplicationCommandTarget class.
|
||||
|
||||
If your target is a Component, this is a very handy method to use in your
|
||||
getNextCommandTarget() implementation.
|
||||
*/
|
||||
ApplicationCommandTarget* findFirstTargetParentComponent();
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
WeakReference<ApplicationCommandTarget>::Master masterReference;
|
||||
friend class WeakReference<ApplicationCommandTarget>;
|
||||
|
||||
class CommandMessage;
|
||||
friend class CommandMessage;
|
||||
|
||||
bool tryToInvoke (const InvocationInfo&, bool async);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ApplicationCommandTarget)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_APPLICATIONCOMMANDTARGET_H_INCLUDED
|
||||
|
|
@ -0,0 +1,412 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
KeyPressMappingSet::KeyPressMappingSet (ApplicationCommandManager& cm)
|
||||
: commandManager (cm)
|
||||
{
|
||||
Desktop::getInstance().addFocusChangeListener (this);
|
||||
}
|
||||
|
||||
KeyPressMappingSet::KeyPressMappingSet (const KeyPressMappingSet& other)
|
||||
: KeyListener(), ChangeBroadcaster(), FocusChangeListener(), commandManager (other.commandManager)
|
||||
{
|
||||
Desktop::getInstance().addFocusChangeListener (this);
|
||||
}
|
||||
|
||||
KeyPressMappingSet::~KeyPressMappingSet()
|
||||
{
|
||||
Desktop::getInstance().removeFocusChangeListener (this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Array<KeyPress> KeyPressMappingSet::getKeyPressesAssignedToCommand (const CommandID commandID) const
|
||||
{
|
||||
for (int i = 0; i < mappings.size(); ++i)
|
||||
if (mappings.getUnchecked(i)->commandID == commandID)
|
||||
return mappings.getUnchecked (i)->keypresses;
|
||||
|
||||
return Array<KeyPress>();
|
||||
}
|
||||
|
||||
void KeyPressMappingSet::addKeyPress (const CommandID commandID, const KeyPress& newKeyPress, int insertIndex)
|
||||
{
|
||||
// If you specify an upper-case letter but no shift key, how is the user supposed to press it!?
|
||||
// Stick to lower-case letters when defining a keypress, to avoid ambiguity.
|
||||
jassert (! (CharacterFunctions::isUpperCase (newKeyPress.getTextCharacter())
|
||||
&& ! newKeyPress.getModifiers().isShiftDown()));
|
||||
|
||||
if (findCommandForKeyPress (newKeyPress) != commandID)
|
||||
{
|
||||
if (newKeyPress.isValid())
|
||||
{
|
||||
for (int i = mappings.size(); --i >= 0;)
|
||||
{
|
||||
if (mappings.getUnchecked(i)->commandID == commandID)
|
||||
{
|
||||
mappings.getUnchecked(i)->keypresses.insert (insertIndex, newKeyPress);
|
||||
|
||||
sendChangeMessage();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (commandID))
|
||||
{
|
||||
CommandMapping* const cm = new CommandMapping();
|
||||
cm->commandID = commandID;
|
||||
cm->keypresses.add (newKeyPress);
|
||||
cm->wantsKeyUpDownCallbacks = (ci->flags & ApplicationCommandInfo::wantsKeyUpDownCallbacks) != 0;
|
||||
|
||||
mappings.add (cm);
|
||||
sendChangeMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
// If you hit this, you're trying to attach a keypress to a command ID that
|
||||
// doesn't exist, so the key is not being attached.
|
||||
jassertfalse;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void addKeyPresses (KeyPressMappingSet& set, const ApplicationCommandInfo* const ci)
|
||||
{
|
||||
for (int j = 0; j < ci->defaultKeypresses.size(); ++j)
|
||||
set.addKeyPress (ci->commandID, ci->defaultKeypresses.getReference (j));
|
||||
}
|
||||
|
||||
void KeyPressMappingSet::resetToDefaultMappings()
|
||||
{
|
||||
mappings.clear();
|
||||
|
||||
for (int i = 0; i < commandManager.getNumCommands(); ++i)
|
||||
addKeyPresses (*this, commandManager.getCommandForIndex (i));
|
||||
|
||||
sendChangeMessage();
|
||||
}
|
||||
|
||||
void KeyPressMappingSet::resetToDefaultMapping (const CommandID commandID)
|
||||
{
|
||||
clearAllKeyPresses (commandID);
|
||||
|
||||
if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (commandID))
|
||||
addKeyPresses (*this, ci);
|
||||
}
|
||||
|
||||
void KeyPressMappingSet::clearAllKeyPresses()
|
||||
{
|
||||
if (mappings.size() > 0)
|
||||
{
|
||||
sendChangeMessage();
|
||||
mappings.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void KeyPressMappingSet::clearAllKeyPresses (const CommandID commandID)
|
||||
{
|
||||
for (int i = mappings.size(); --i >= 0;)
|
||||
{
|
||||
if (mappings.getUnchecked(i)->commandID == commandID)
|
||||
{
|
||||
mappings.remove (i);
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyPressMappingSet::removeKeyPress (const KeyPress& keypress)
|
||||
{
|
||||
if (keypress.isValid())
|
||||
{
|
||||
for (int i = mappings.size(); --i >= 0;)
|
||||
{
|
||||
CommandMapping& cm = *mappings.getUnchecked(i);
|
||||
|
||||
for (int j = cm.keypresses.size(); --j >= 0;)
|
||||
{
|
||||
if (keypress == cm.keypresses [j])
|
||||
{
|
||||
cm.keypresses.remove (j);
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyPressMappingSet::removeKeyPress (const CommandID commandID, const int keyPressIndex)
|
||||
{
|
||||
for (int i = mappings.size(); --i >= 0;)
|
||||
{
|
||||
if (mappings.getUnchecked(i)->commandID == commandID)
|
||||
{
|
||||
mappings.getUnchecked(i)->keypresses.remove (keyPressIndex);
|
||||
sendChangeMessage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
CommandID KeyPressMappingSet::findCommandForKeyPress (const KeyPress& keyPress) const noexcept
|
||||
{
|
||||
for (int i = 0; i < mappings.size(); ++i)
|
||||
if (mappings.getUnchecked(i)->keypresses.contains (keyPress))
|
||||
return mappings.getUnchecked(i)->commandID;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool KeyPressMappingSet::containsMapping (const CommandID commandID, const KeyPress& keyPress) const noexcept
|
||||
{
|
||||
for (int i = mappings.size(); --i >= 0;)
|
||||
if (mappings.getUnchecked(i)->commandID == commandID)
|
||||
return mappings.getUnchecked(i)->keypresses.contains (keyPress);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void KeyPressMappingSet::invokeCommand (const CommandID commandID,
|
||||
const KeyPress& key,
|
||||
const bool isKeyDown,
|
||||
const int millisecsSinceKeyPressed,
|
||||
Component* const originatingComponent) const
|
||||
{
|
||||
ApplicationCommandTarget::InvocationInfo info (commandID);
|
||||
|
||||
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromKeyPress;
|
||||
info.isKeyDown = isKeyDown;
|
||||
info.keyPress = key;
|
||||
info.millisecsSinceKeyPressed = millisecsSinceKeyPressed;
|
||||
info.originatingComponent = originatingComponent;
|
||||
|
||||
commandManager.invoke (info, false);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool KeyPressMappingSet::restoreFromXml (const XmlElement& xmlVersion)
|
||||
{
|
||||
if (xmlVersion.hasTagName ("KEYMAPPINGS"))
|
||||
{
|
||||
if (xmlVersion.getBoolAttribute ("basedOnDefaults", true))
|
||||
{
|
||||
// if the XML was created as a set of differences from the default mappings,
|
||||
// (i.e. by calling createXml (true)), then we need to first restore the defaults.
|
||||
resetToDefaultMappings();
|
||||
}
|
||||
else
|
||||
{
|
||||
// if the XML was created calling createXml (false), then we need to clear all
|
||||
// the keys and treat the xml as describing the entire set of mappings.
|
||||
clearAllKeyPresses();
|
||||
}
|
||||
|
||||
forEachXmlChildElement (xmlVersion, map)
|
||||
{
|
||||
const CommandID commandId = map->getStringAttribute ("commandId").getHexValue32();
|
||||
|
||||
if (commandId != 0)
|
||||
{
|
||||
const KeyPress key (KeyPress::createFromDescription (map->getStringAttribute ("key")));
|
||||
|
||||
if (map->hasTagName ("MAPPING"))
|
||||
{
|
||||
addKeyPress (commandId, key);
|
||||
}
|
||||
else if (map->hasTagName ("UNMAPPING"))
|
||||
{
|
||||
for (int i = mappings.size(); --i >= 0;)
|
||||
if (mappings.getUnchecked(i)->commandID == commandId)
|
||||
mappings.getUnchecked(i)->keypresses.removeAllInstancesOf (key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
XmlElement* KeyPressMappingSet::createXml (const bool saveDifferencesFromDefaultSet) const
|
||||
{
|
||||
ScopedPointer<KeyPressMappingSet> defaultSet;
|
||||
|
||||
if (saveDifferencesFromDefaultSet)
|
||||
{
|
||||
defaultSet = new KeyPressMappingSet (commandManager);
|
||||
defaultSet->resetToDefaultMappings();
|
||||
}
|
||||
|
||||
XmlElement* const doc = new XmlElement ("KEYMAPPINGS");
|
||||
|
||||
doc->setAttribute ("basedOnDefaults", saveDifferencesFromDefaultSet);
|
||||
|
||||
for (int i = 0; i < mappings.size(); ++i)
|
||||
{
|
||||
const CommandMapping& cm = *mappings.getUnchecked(i);
|
||||
|
||||
for (int j = 0; j < cm.keypresses.size(); ++j)
|
||||
{
|
||||
if (defaultSet == nullptr
|
||||
|| ! defaultSet->containsMapping (cm.commandID, cm.keypresses.getReference (j)))
|
||||
{
|
||||
XmlElement* const map = doc->createNewChildElement ("MAPPING");
|
||||
|
||||
map->setAttribute ("commandId", String::toHexString ((int) cm.commandID));
|
||||
map->setAttribute ("description", commandManager.getDescriptionOfCommand (cm.commandID));
|
||||
map->setAttribute ("key", cm.keypresses.getReference (j).getTextDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultSet != nullptr)
|
||||
{
|
||||
for (int i = 0; i < defaultSet->mappings.size(); ++i)
|
||||
{
|
||||
const CommandMapping& cm = *defaultSet->mappings.getUnchecked(i);
|
||||
|
||||
for (int j = 0; j < cm.keypresses.size(); ++j)
|
||||
{
|
||||
if (! containsMapping (cm.commandID, cm.keypresses.getReference (j)))
|
||||
{
|
||||
XmlElement* const map = doc->createNewChildElement ("UNMAPPING");
|
||||
|
||||
map->setAttribute ("commandId", String::toHexString ((int) cm.commandID));
|
||||
map->setAttribute ("description", commandManager.getDescriptionOfCommand (cm.commandID));
|
||||
map->setAttribute ("key", cm.keypresses.getReference (j).getTextDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool KeyPressMappingSet::keyPressed (const KeyPress& key, Component* const originatingComponent)
|
||||
{
|
||||
bool commandWasDisabled = false;
|
||||
|
||||
for (int i = 0; i < mappings.size(); ++i)
|
||||
{
|
||||
CommandMapping& cm = *mappings.getUnchecked(i);
|
||||
|
||||
if (cm.keypresses.contains (key))
|
||||
{
|
||||
if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (cm.commandID))
|
||||
{
|
||||
if ((ci->flags & ApplicationCommandInfo::wantsKeyUpDownCallbacks) == 0)
|
||||
{
|
||||
ApplicationCommandInfo info (0);
|
||||
|
||||
if (commandManager.getTargetForCommand (cm.commandID, info) != nullptr)
|
||||
{
|
||||
if ((info.flags & ApplicationCommandInfo::isDisabled) == 0)
|
||||
{
|
||||
invokeCommand (cm.commandID, key, true, 0, originatingComponent);
|
||||
return true;
|
||||
}
|
||||
|
||||
commandWasDisabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (originatingComponent != nullptr && commandWasDisabled)
|
||||
originatingComponent->getLookAndFeel().playAlertSound();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool KeyPressMappingSet::keyStateChanged (const bool /*isKeyDown*/, Component* originatingComponent)
|
||||
{
|
||||
bool used = false;
|
||||
const uint32 now = Time::getMillisecondCounter();
|
||||
|
||||
for (int i = mappings.size(); --i >= 0;)
|
||||
{
|
||||
CommandMapping& cm = *mappings.getUnchecked(i);
|
||||
|
||||
if (cm.wantsKeyUpDownCallbacks)
|
||||
{
|
||||
for (int j = cm.keypresses.size(); --j >= 0;)
|
||||
{
|
||||
const KeyPress key (cm.keypresses.getReference (j));
|
||||
const bool isDown = key.isCurrentlyDown();
|
||||
|
||||
int keyPressEntryIndex = 0;
|
||||
bool wasDown = false;
|
||||
|
||||
for (int k = keysDown.size(); --k >= 0;)
|
||||
{
|
||||
if (key == keysDown.getUnchecked(k)->key)
|
||||
{
|
||||
keyPressEntryIndex = k;
|
||||
wasDown = true;
|
||||
used = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDown != wasDown)
|
||||
{
|
||||
int millisecs = 0;
|
||||
|
||||
if (isDown)
|
||||
{
|
||||
KeyPressTime* const k = new KeyPressTime();
|
||||
k->key = key;
|
||||
k->timeWhenPressed = now;
|
||||
|
||||
keysDown.add (k);
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint32 pressTime = keysDown.getUnchecked (keyPressEntryIndex)->timeWhenPressed;
|
||||
|
||||
if (now > pressTime)
|
||||
millisecs = (int) (now - pressTime);
|
||||
|
||||
keysDown.remove (keyPressEntryIndex);
|
||||
}
|
||||
|
||||
invokeCommand (cm.commandID, key, isDown, millisecs, originatingComponent);
|
||||
used = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return used;
|
||||
}
|
||||
|
||||
void KeyPressMappingSet::globalFocusChanged (Component* focusedComponent)
|
||||
{
|
||||
if (focusedComponent != nullptr)
|
||||
focusedComponent->keyStateChanged (false);
|
||||
}
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_KEYPRESSMAPPINGSET_H_INCLUDED
|
||||
#define JUCE_KEYPRESSMAPPINGSET_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Manages and edits a list of keypresses, which it uses to invoke the appropriate
|
||||
command in an ApplicationCommandManager.
|
||||
|
||||
Normally, you won't actually create a KeyPressMappingSet directly, because
|
||||
each ApplicationCommandManager contains its own KeyPressMappingSet, so typically
|
||||
you'd create yourself an ApplicationCommandManager, and call its
|
||||
ApplicationCommandManager::getKeyMappings() method to get a pointer to its
|
||||
KeyPressMappingSet.
|
||||
|
||||
For one of these to actually use keypresses, you'll need to add it as a KeyListener
|
||||
to the top-level component for which you want to handle keystrokes. So for example:
|
||||
|
||||
@code
|
||||
class MyMainWindow : public Component
|
||||
{
|
||||
ApplicationCommandManager* myCommandManager;
|
||||
|
||||
public:
|
||||
MyMainWindow()
|
||||
{
|
||||
myCommandManager = new ApplicationCommandManager();
|
||||
|
||||
// first, make sure the command manager has registered all the commands that its
|
||||
// targets can perform..
|
||||
myCommandManager->registerAllCommandsForTarget (myCommandTarget1);
|
||||
myCommandManager->registerAllCommandsForTarget (myCommandTarget2);
|
||||
|
||||
// this will use the command manager to initialise the KeyPressMappingSet with
|
||||
// the default keypresses that were specified when the targets added their commands
|
||||
// to the manager.
|
||||
myCommandManager->getKeyMappings()->resetToDefaultMappings();
|
||||
|
||||
// having set up the default key-mappings, you might now want to load the last set
|
||||
// of mappings that the user configured.
|
||||
myCommandManager->getKeyMappings()->restoreFromXml (lastSavedKeyMappingsXML);
|
||||
|
||||
// Now tell our top-level window to send any keypresses that arrive to the
|
||||
// KeyPressMappingSet, which will use them to invoke the appropriate commands.
|
||||
addKeyListener (myCommandManager->getKeyMappings());
|
||||
}
|
||||
|
||||
...
|
||||
}
|
||||
@endcode
|
||||
|
||||
KeyPressMappingSet derives from ChangeBroadcaster so that interested parties can
|
||||
register to be told when a command or mapping is added, removed, etc.
|
||||
|
||||
There's also a UI component called KeyMappingEditorComponent that can be used
|
||||
to easily edit the key mappings.
|
||||
|
||||
@see Component::addKeyListener(), KeyMappingEditorComponent, ApplicationCommandManager
|
||||
*/
|
||||
class JUCE_API KeyPressMappingSet : public KeyListener,
|
||||
public ChangeBroadcaster,
|
||||
private FocusChangeListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a KeyPressMappingSet for a given command manager.
|
||||
|
||||
Normally, you won't actually create a KeyPressMappingSet directly, because
|
||||
each ApplicationCommandManager contains its own KeyPressMappingSet, so the
|
||||
best thing to do is to create your ApplicationCommandManager, and use the
|
||||
ApplicationCommandManager::getKeyMappings() method to access its mappings.
|
||||
|
||||
When a suitable keypress happens, the manager's invoke() method will be
|
||||
used to invoke the appropriate command.
|
||||
|
||||
@see ApplicationCommandManager
|
||||
*/
|
||||
explicit KeyPressMappingSet (ApplicationCommandManager&);
|
||||
|
||||
/** Creates an copy of a KeyPressMappingSet. */
|
||||
KeyPressMappingSet (const KeyPressMappingSet&);
|
||||
|
||||
/** Destructor. */
|
||||
~KeyPressMappingSet();
|
||||
|
||||
//==============================================================================
|
||||
ApplicationCommandManager& getCommandManager() const noexcept { return commandManager; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a list of keypresses that are assigned to a particular command.
|
||||
|
||||
@param commandID the command's ID
|
||||
*/
|
||||
Array<KeyPress> getKeyPressesAssignedToCommand (CommandID commandID) const;
|
||||
|
||||
/** Assigns a keypress to a command.
|
||||
|
||||
If the keypress is already assigned to a different command, it will first be
|
||||
removed from that command, to avoid it triggering multiple functions.
|
||||
|
||||
@param commandID the ID of the command that you want to add a keypress to. If
|
||||
this is 0, the keypress will be removed from anything that it
|
||||
was previously assigned to, but not re-assigned
|
||||
@param newKeyPress the new key-press
|
||||
@param insertIndex if this is less than zero, the key will be appended to the
|
||||
end of the list of keypresses; otherwise the new keypress will
|
||||
be inserted into the existing list at this index
|
||||
*/
|
||||
void addKeyPress (CommandID commandID,
|
||||
const KeyPress& newKeyPress,
|
||||
int insertIndex = -1);
|
||||
|
||||
/** Reset all mappings to the defaults, as dictated by the ApplicationCommandManager.
|
||||
@see resetToDefaultMapping
|
||||
*/
|
||||
void resetToDefaultMappings();
|
||||
|
||||
/** Resets all key-mappings to the defaults for a particular command.
|
||||
@see resetToDefaultMappings
|
||||
*/
|
||||
void resetToDefaultMapping (CommandID commandID);
|
||||
|
||||
/** Removes all keypresses that are assigned to any commands. */
|
||||
void clearAllKeyPresses();
|
||||
|
||||
/** Removes all keypresses that are assigned to a particular command. */
|
||||
void clearAllKeyPresses (CommandID commandID);
|
||||
|
||||
/** Removes one of the keypresses that are assigned to a command.
|
||||
See the getKeyPressesAssignedToCommand() for the list of keypresses to
|
||||
which the keyPressIndex refers.
|
||||
*/
|
||||
void removeKeyPress (CommandID commandID, int keyPressIndex);
|
||||
|
||||
/** Removes a keypress from any command that it may be assigned to. */
|
||||
void removeKeyPress (const KeyPress& keypress);
|
||||
|
||||
/** Returns true if the given command is linked to this key. */
|
||||
bool containsMapping (CommandID commandID, const KeyPress& keyPress) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Looks for a command that corresponds to a keypress.
|
||||
@returns the UID of the command or 0 if none was found
|
||||
*/
|
||||
CommandID findCommandForKeyPress (const KeyPress& keyPress) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Tries to recreate the mappings from a previously stored state.
|
||||
|
||||
The XML passed in must have been created by the createXml() method.
|
||||
|
||||
If the stored state makes any reference to commands that aren't
|
||||
currently available, these will be ignored.
|
||||
|
||||
If the set of mappings being loaded was a set of differences (using createXml (true)),
|
||||
then this will call resetToDefaultMappings() and then merge the saved mappings
|
||||
on top. If the saved set was created with createXml (false), then this method
|
||||
will first clear all existing mappings and load the saved ones as a complete set.
|
||||
|
||||
@returns true if it manages to load the XML correctly
|
||||
@see createXml
|
||||
*/
|
||||
bool restoreFromXml (const XmlElement& xmlVersion);
|
||||
|
||||
/** Creates an XML representation of the current mappings.
|
||||
|
||||
This will produce a lump of XML that can be later reloaded using
|
||||
restoreFromXml() to recreate the current mapping state.
|
||||
|
||||
The object that is returned must be deleted by the caller.
|
||||
|
||||
@param saveDifferencesFromDefaultSet if this is false, then all keypresses
|
||||
will be saved into the XML. If it's true, then the XML will
|
||||
only store the differences between the current mappings and
|
||||
the default mappings you'd get from calling resetToDefaultMappings().
|
||||
The advantage of saving a set of differences from the default is that
|
||||
if you change the default mappings (in a new version of your app, for
|
||||
example), then these will be merged into a user's saved preferences.
|
||||
|
||||
@see restoreFromXml
|
||||
*/
|
||||
XmlElement* createXml (bool saveDifferencesFromDefaultSet) const;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&, Component*) override;
|
||||
/** @internal */
|
||||
bool keyStateChanged (bool isKeyDown, Component*) override;
|
||||
/** @internal */
|
||||
void globalFocusChanged (Component*) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
ApplicationCommandManager& commandManager;
|
||||
|
||||
struct CommandMapping
|
||||
{
|
||||
CommandID commandID;
|
||||
Array<KeyPress> keypresses;
|
||||
bool wantsKeyUpDownCallbacks;
|
||||
};
|
||||
|
||||
OwnedArray<CommandMapping> mappings;
|
||||
|
||||
struct KeyPressTime
|
||||
{
|
||||
KeyPress key;
|
||||
uint32 timeWhenPressed;
|
||||
};
|
||||
|
||||
OwnedArray<KeyPressTime> keysDown;
|
||||
|
||||
void invokeCommand (const CommandID, const KeyPress&, const bool isKeyDown,
|
||||
const int millisecsSinceKeyPressed, Component* originator) const;
|
||||
|
||||
KeyPressMappingSet& operator= (const KeyPressMappingSet&);
|
||||
JUCE_LEAK_DETECTOR (KeyPressMappingSet)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_KEYPRESSMAPPINGSET_H_INCLUDED
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_CACHEDCOMPONENTIMAGE_H_INCLUDED
|
||||
#define JUCE_CACHEDCOMPONENTIMAGE_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Base class used internally for structures that can store cached images of
|
||||
component state.
|
||||
|
||||
Most people are unlikely to ever need to know about this class - it's really
|
||||
only for power-users!
|
||||
|
||||
@see Component::setCachedComponentImage
|
||||
*/
|
||||
class JUCE_API CachedComponentImage
|
||||
{
|
||||
public:
|
||||
CachedComponentImage() noexcept {}
|
||||
virtual ~CachedComponentImage() {}
|
||||
|
||||
//==============================================================================
|
||||
/** Called as part of the parent component's paint method, this must draw
|
||||
the given component into the target graphics context, using the cached
|
||||
version where possible.
|
||||
*/
|
||||
virtual void paint (Graphics&) = 0;
|
||||
|
||||
/** Invalidates all cached image data.
|
||||
@returns true if the peer should also be repainted, or false if this object
|
||||
handles all repaint work internally.
|
||||
*/
|
||||
virtual bool invalidateAll() = 0;
|
||||
|
||||
/** Invalidates a section of the cached image data.
|
||||
@returns true if the peer should also be repainted, or false if this object
|
||||
handles all repaint work internally.
|
||||
*/
|
||||
virtual bool invalidate (const Rectangle<int>& area) = 0;
|
||||
|
||||
/** Called to indicate that the component is no longer active, so
|
||||
any cached data should be released if possible.
|
||||
*/
|
||||
virtual void releaseResources() = 0;
|
||||
};
|
||||
|
||||
#endif // JUCE_CACHEDCOMPONENTIMAGE_H_INCLUDED
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
void ComponentListener::componentMovedOrResized (Component&, bool, bool) {}
|
||||
void ComponentListener::componentBroughtToFront (Component&) {}
|
||||
void ComponentListener::componentVisibilityChanged (Component&) {}
|
||||
void ComponentListener::componentChildrenChanged (Component&) {}
|
||||
void ComponentListener::componentParentHierarchyChanged (Component&) {}
|
||||
void ComponentListener::componentNameChanged (Component&) {}
|
||||
void ComponentListener::componentBeingDeleted (Component&) {}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_COMPONENTLISTENER_H_INCLUDED
|
||||
#define JUCE_COMPONENTLISTENER_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Gets informed about changes to a component's hierarchy or position.
|
||||
|
||||
To monitor a component for changes, register a subclass of ComponentListener
|
||||
with the component using Component::addComponentListener().
|
||||
|
||||
Be sure to deregister listeners before you delete them!
|
||||
|
||||
@see Component::addComponentListener, Component::removeComponentListener
|
||||
*/
|
||||
class JUCE_API ComponentListener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~ComponentListener() {}
|
||||
|
||||
/** Called when the component's position or size changes.
|
||||
|
||||
@param component the component that was moved or resized
|
||||
@param wasMoved true if the component's top-left corner has just moved
|
||||
@param wasResized true if the component's width or height has just changed
|
||||
@see Component::setBounds, Component::resized, Component::moved
|
||||
*/
|
||||
virtual void componentMovedOrResized (Component& component,
|
||||
bool wasMoved,
|
||||
bool wasResized);
|
||||
|
||||
/** Called when the component is brought to the top of the z-order.
|
||||
|
||||
@param component the component that was moved
|
||||
@see Component::toFront, Component::broughtToFront
|
||||
*/
|
||||
virtual void componentBroughtToFront (Component& component);
|
||||
|
||||
/** Called when the component is made visible or invisible.
|
||||
|
||||
@param component the component that changed
|
||||
@see Component::setVisible
|
||||
*/
|
||||
virtual void componentVisibilityChanged (Component& component);
|
||||
|
||||
/** Called when the component has children added or removed, or their z-order
|
||||
changes.
|
||||
|
||||
@param component the component whose children have changed
|
||||
@see Component::childrenChanged, Component::addChildComponent,
|
||||
Component::removeChildComponent
|
||||
*/
|
||||
virtual void componentChildrenChanged (Component& component);
|
||||
|
||||
/** Called to indicate that the component's parents have changed.
|
||||
|
||||
When a component is added or removed from its parent, all of its children
|
||||
will produce this notification (recursively - so all children of its
|
||||
children will also be called as well).
|
||||
|
||||
@param component the component that this listener is registered with
|
||||
@see Component::parentHierarchyChanged
|
||||
*/
|
||||
virtual void componentParentHierarchyChanged (Component& component);
|
||||
|
||||
/** Called when the component's name is changed.
|
||||
|
||||
@see Component::setName, Component::getName
|
||||
*/
|
||||
virtual void componentNameChanged (Component& component);
|
||||
|
||||
/** Called when the component is in the process of being deleted.
|
||||
|
||||
This callback is made from inside the destructor, so be very, very cautious
|
||||
about what you do in here.
|
||||
|
||||
In particular, bear in mind that it's the Component base class's destructor that calls
|
||||
this - so if the object that's being deleted is a subclass of Component, then the
|
||||
subclass layers of the object will already have been destructed when it gets to this
|
||||
point!
|
||||
*/
|
||||
virtual void componentBeingDeleted (Component& component);
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_COMPONENTLISTENER_H_INCLUDED
|
||||
|
|
@ -0,0 +1,415 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
Desktop::Desktop()
|
||||
: mouseSources (new MouseInputSource::SourceList()),
|
||||
mouseClickCounter (0), mouseWheelCounter (0),
|
||||
kioskModeComponent (nullptr),
|
||||
kioskModeReentrant (false),
|
||||
allowedOrientations (allOrientations),
|
||||
masterScaleFactor ((float) getDefaultMasterScale())
|
||||
{
|
||||
displays = new Displays (*this);
|
||||
}
|
||||
|
||||
Desktop::~Desktop()
|
||||
{
|
||||
setScreenSaverEnabled (true);
|
||||
|
||||
jassert (instance == this);
|
||||
instance = nullptr;
|
||||
|
||||
// doh! If you don't delete all your windows before exiting, you're going to
|
||||
// be leaking memory!
|
||||
jassert (desktopComponents.size() == 0);
|
||||
}
|
||||
|
||||
Desktop& JUCE_CALLTYPE Desktop::getInstance()
|
||||
{
|
||||
if (instance == nullptr)
|
||||
instance = new Desktop();
|
||||
|
||||
return *instance;
|
||||
}
|
||||
|
||||
Desktop* Desktop::instance = nullptr;
|
||||
|
||||
//==============================================================================
|
||||
int Desktop::getNumComponents() const noexcept
|
||||
{
|
||||
return desktopComponents.size();
|
||||
}
|
||||
|
||||
Component* Desktop::getComponent (const int index) const noexcept
|
||||
{
|
||||
return desktopComponents [index];
|
||||
}
|
||||
|
||||
Component* Desktop::findComponentAt (Point<int> screenPosition) const
|
||||
{
|
||||
ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
|
||||
for (int i = desktopComponents.size(); --i >= 0;)
|
||||
{
|
||||
Component* const c = desktopComponents.getUnchecked(i);
|
||||
|
||||
if (c->isVisible())
|
||||
{
|
||||
const Point<int> relative (c->getLocalPoint (nullptr, screenPosition));
|
||||
|
||||
if (c->contains (relative))
|
||||
return c->getComponentAt (relative);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
LookAndFeel& Desktop::getDefaultLookAndFeel() noexcept
|
||||
{
|
||||
if (currentLookAndFeel == nullptr)
|
||||
{
|
||||
if (defaultLookAndFeel == nullptr)
|
||||
defaultLookAndFeel = new LookAndFeel_V2();
|
||||
|
||||
currentLookAndFeel = defaultLookAndFeel;
|
||||
}
|
||||
|
||||
return *currentLookAndFeel;
|
||||
}
|
||||
|
||||
void Desktop::setDefaultLookAndFeel (LookAndFeel* newDefaultLookAndFeel)
|
||||
{
|
||||
ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
currentLookAndFeel = newDefaultLookAndFeel;
|
||||
|
||||
for (int i = getNumComponents(); --i >= 0;)
|
||||
if (Component* const c = getComponent (i))
|
||||
c->sendLookAndFeelChange();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Desktop::addDesktopComponent (Component* const c)
|
||||
{
|
||||
jassert (c != nullptr);
|
||||
jassert (! desktopComponents.contains (c));
|
||||
desktopComponents.addIfNotAlreadyThere (c);
|
||||
}
|
||||
|
||||
void Desktop::removeDesktopComponent (Component* const c)
|
||||
{
|
||||
desktopComponents.removeFirstMatchingValue (c);
|
||||
}
|
||||
|
||||
void Desktop::componentBroughtToFront (Component* const c)
|
||||
{
|
||||
const int index = desktopComponents.indexOf (c);
|
||||
jassert (index >= 0);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
int newIndex = -1;
|
||||
|
||||
if (! c->isAlwaysOnTop())
|
||||
{
|
||||
newIndex = desktopComponents.size();
|
||||
|
||||
while (newIndex > 0 && desktopComponents.getUnchecked (newIndex - 1)->isAlwaysOnTop())
|
||||
--newIndex;
|
||||
|
||||
--newIndex;
|
||||
}
|
||||
|
||||
desktopComponents.move (index, newIndex);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Point<int> Desktop::getMousePosition()
|
||||
{
|
||||
return getMousePositionFloat().roundToInt();
|
||||
}
|
||||
|
||||
Point<float> Desktop::getMousePositionFloat()
|
||||
{
|
||||
return getInstance().getMainMouseSource().getScreenPosition();
|
||||
}
|
||||
|
||||
void Desktop::setMousePosition (Point<int> newPosition)
|
||||
{
|
||||
getInstance().getMainMouseSource().setScreenPosition (newPosition.toFloat());
|
||||
}
|
||||
|
||||
Point<int> Desktop::getLastMouseDownPosition()
|
||||
{
|
||||
return getInstance().getMainMouseSource().getLastMouseDownPosition().roundToInt();
|
||||
}
|
||||
|
||||
int Desktop::getMouseButtonClickCounter() const noexcept { return mouseClickCounter; }
|
||||
int Desktop::getMouseWheelMoveCounter() const noexcept { return mouseWheelCounter; }
|
||||
|
||||
void Desktop::incrementMouseClickCounter() noexcept { ++mouseClickCounter; }
|
||||
void Desktop::incrementMouseWheelCounter() noexcept { ++mouseWheelCounter; }
|
||||
|
||||
const Array<MouseInputSource>& Desktop::getMouseSources() const noexcept { return mouseSources->sourceArray; }
|
||||
int Desktop::getNumMouseSources() const noexcept { return mouseSources->sources.size(); }
|
||||
int Desktop::getNumDraggingMouseSources() const noexcept { return mouseSources->getNumDraggingMouseSources(); }
|
||||
MouseInputSource* Desktop::getMouseSource (int index) const noexcept { return mouseSources->getMouseSource (index); }
|
||||
MouseInputSource* Desktop::getDraggingMouseSource (int index) const noexcept { return mouseSources->getDraggingMouseSource (index); }
|
||||
MouseInputSource Desktop::getMainMouseSource() const noexcept { return MouseInputSource (mouseSources->sources.getUnchecked(0)); }
|
||||
void Desktop::beginDragAutoRepeat (int interval) { mouseSources->beginDragAutoRepeat (interval); }
|
||||
|
||||
//==============================================================================
|
||||
void Desktop::addFocusChangeListener (FocusChangeListener* const listener) { focusListeners.add (listener); }
|
||||
void Desktop::removeFocusChangeListener (FocusChangeListener* const listener) { focusListeners.remove (listener); }
|
||||
void Desktop::triggerFocusCallback() { triggerAsyncUpdate(); }
|
||||
|
||||
void Desktop::handleAsyncUpdate()
|
||||
{
|
||||
// The component may be deleted during this operation, but we'll use a SafePointer rather than a
|
||||
// BailOutChecker so that any remaining listeners will still get a callback (with a null pointer).
|
||||
WeakReference<Component> currentFocus (Component::getCurrentlyFocusedComponent());
|
||||
focusListeners.call (&FocusChangeListener::globalFocusChanged, currentFocus);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Desktop::resetTimer()
|
||||
{
|
||||
if (mouseListeners.size() == 0)
|
||||
stopTimer();
|
||||
else
|
||||
startTimer (100);
|
||||
|
||||
lastFakeMouseMove = getMousePositionFloat();
|
||||
}
|
||||
|
||||
ListenerList<MouseListener>& Desktop::getMouseListeners()
|
||||
{
|
||||
resetTimer();
|
||||
return mouseListeners;
|
||||
}
|
||||
|
||||
void Desktop::addGlobalMouseListener (MouseListener* const listener)
|
||||
{
|
||||
ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
mouseListeners.add (listener);
|
||||
resetTimer();
|
||||
}
|
||||
|
||||
void Desktop::removeGlobalMouseListener (MouseListener* const listener)
|
||||
{
|
||||
ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
mouseListeners.remove (listener);
|
||||
resetTimer();
|
||||
}
|
||||
|
||||
void Desktop::timerCallback()
|
||||
{
|
||||
if (lastFakeMouseMove != getMousePositionFloat())
|
||||
sendMouseMove();
|
||||
}
|
||||
|
||||
void Desktop::sendMouseMove()
|
||||
{
|
||||
if (! mouseListeners.isEmpty())
|
||||
{
|
||||
startTimer (20);
|
||||
|
||||
lastFakeMouseMove = getMousePositionFloat();
|
||||
|
||||
if (Component* const target = findComponentAt (lastFakeMouseMove.roundToInt()))
|
||||
{
|
||||
Component::BailOutChecker checker (target);
|
||||
const Point<float> pos (target->getLocalPoint (nullptr, lastFakeMouseMove));
|
||||
const Time now (Time::getCurrentTime());
|
||||
|
||||
const MouseEvent me (getMainMouseSource(), pos, ModifierKeys::getCurrentModifiers(),
|
||||
target, target, now, pos, now, 0, false);
|
||||
|
||||
if (me.mods.isAnyMouseButtonDown())
|
||||
mouseListeners.callChecked (checker, &MouseListener::mouseDrag, me);
|
||||
else
|
||||
mouseListeners.callChecked (checker, &MouseListener::mouseMove, me);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
Desktop::Displays::Displays (Desktop& desktop) { init (desktop); }
|
||||
Desktop::Displays::~Displays() {}
|
||||
|
||||
const Desktop::Displays::Display& Desktop::Displays::getMainDisplay() const noexcept
|
||||
{
|
||||
ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
jassert (displays.getReference(0).isMain);
|
||||
return displays.getReference(0);
|
||||
}
|
||||
|
||||
const Desktop::Displays::Display& Desktop::Displays::getDisplayContaining (Point<int> position) const noexcept
|
||||
{
|
||||
ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
const Display* best = &displays.getReference(0);
|
||||
double bestDistance = 1.0e10;
|
||||
|
||||
for (int i = displays.size(); --i >= 0;)
|
||||
{
|
||||
const Display& d = displays.getReference(i);
|
||||
|
||||
if (d.totalArea.contains (position))
|
||||
{
|
||||
best = &d;
|
||||
break;
|
||||
}
|
||||
|
||||
const double distance = d.totalArea.getCentre().getDistanceFrom (position);
|
||||
|
||||
if (distance < bestDistance)
|
||||
{
|
||||
bestDistance = distance;
|
||||
best = &d;
|
||||
}
|
||||
}
|
||||
|
||||
return *best;
|
||||
}
|
||||
|
||||
RectangleList<int> Desktop::Displays::getRectangleList (bool userAreasOnly) const
|
||||
{
|
||||
ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
RectangleList<int> rl;
|
||||
|
||||
for (int i = 0; i < displays.size(); ++i)
|
||||
{
|
||||
const Display& d = displays.getReference(i);
|
||||
rl.addWithoutMerging (userAreasOnly ? d.userArea : d.totalArea);
|
||||
}
|
||||
|
||||
return rl;
|
||||
}
|
||||
|
||||
Rectangle<int> Desktop::Displays::getTotalBounds (bool userAreasOnly) const
|
||||
{
|
||||
return getRectangleList (userAreasOnly).getBounds();
|
||||
}
|
||||
|
||||
bool operator== (const Desktop::Displays::Display& d1, const Desktop::Displays::Display& d2) noexcept;
|
||||
bool operator== (const Desktop::Displays::Display& d1, const Desktop::Displays::Display& d2) noexcept
|
||||
{
|
||||
return d1.userArea == d2.userArea
|
||||
&& d1.totalArea == d2.totalArea
|
||||
&& d1.scale == d2.scale
|
||||
&& d1.isMain == d2.isMain;
|
||||
}
|
||||
|
||||
bool operator!= (const Desktop::Displays::Display& d1, const Desktop::Displays::Display& d2) noexcept;
|
||||
bool operator!= (const Desktop::Displays::Display& d1, const Desktop::Displays::Display& d2) noexcept
|
||||
{
|
||||
return ! (d1 == d2);
|
||||
}
|
||||
|
||||
void Desktop::Displays::init (Desktop& desktop)
|
||||
{
|
||||
findDisplays (desktop.getGlobalScaleFactor());
|
||||
jassert (displays.size() > 0);
|
||||
}
|
||||
|
||||
void Desktop::Displays::refresh()
|
||||
{
|
||||
Array<Display> oldDisplays;
|
||||
oldDisplays.swapWith (displays);
|
||||
|
||||
init (Desktop::getInstance());
|
||||
jassert (displays.size() > 0);
|
||||
|
||||
if (oldDisplays != displays)
|
||||
{
|
||||
for (int i = ComponentPeer::getNumPeers(); --i >= 0;)
|
||||
if (ComponentPeer* const peer = ComponentPeer::getPeer (i))
|
||||
peer->handleScreenSizeChange();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Desktop::setKioskModeComponent (Component* componentToUse, const bool allowMenusAndBars)
|
||||
{
|
||||
if (kioskModeReentrant)
|
||||
return;
|
||||
|
||||
const ScopedValueSetter<bool> setter (kioskModeReentrant, true, false);
|
||||
|
||||
if (kioskModeComponent != componentToUse)
|
||||
{
|
||||
// agh! Don't delete or remove a component from the desktop while it's still the kiosk component!
|
||||
jassert (kioskModeComponent == nullptr || ComponentPeer::getPeerFor (kioskModeComponent) != nullptr);
|
||||
|
||||
if (Component* const oldKioskComp = kioskModeComponent)
|
||||
{
|
||||
kioskModeComponent = nullptr; // (to make sure that isKioskMode() returns false when resizing the old one)
|
||||
setKioskComponent (oldKioskComp, false, allowMenusAndBars);
|
||||
oldKioskComp->setBounds (kioskComponentOriginalBounds);
|
||||
}
|
||||
|
||||
kioskModeComponent = componentToUse;
|
||||
|
||||
if (kioskModeComponent != nullptr)
|
||||
{
|
||||
// Only components that are already on the desktop can be put into kiosk mode!
|
||||
jassert (ComponentPeer::getPeerFor (kioskModeComponent) != nullptr);
|
||||
|
||||
kioskComponentOriginalBounds = kioskModeComponent->getBounds();
|
||||
setKioskComponent (kioskModeComponent, true, allowMenusAndBars);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Desktop::setOrientationsEnabled (const int newOrientations)
|
||||
{
|
||||
// Dodgy set of flags being passed here! Make sure you specify at least one permitted orientation.
|
||||
jassert (newOrientations != 0 && (newOrientations & ~allOrientations) == 0);
|
||||
|
||||
allowedOrientations = newOrientations;
|
||||
}
|
||||
|
||||
bool Desktop::isOrientationEnabled (const DisplayOrientation orientation) const noexcept
|
||||
{
|
||||
// Make sure you only pass one valid flag in here...
|
||||
jassert (orientation == upright || orientation == upsideDown
|
||||
|| orientation == rotatedClockwise || orientation == rotatedAntiClockwise);
|
||||
|
||||
return (allowedOrientations & orientation) != 0;
|
||||
}
|
||||
|
||||
void Desktop::setGlobalScaleFactor (float newScaleFactor) noexcept
|
||||
{
|
||||
ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
|
||||
if (masterScaleFactor != newScaleFactor)
|
||||
{
|
||||
masterScaleFactor = newScaleFactor;
|
||||
displays->refresh();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,460 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_DESKTOP_H_INCLUDED
|
||||
#define JUCE_DESKTOP_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Classes can implement this interface and register themselves with the Desktop class
|
||||
to receive callbacks when the currently focused component changes.
|
||||
|
||||
@see Desktop::addFocusChangeListener, Desktop::removeFocusChangeListener
|
||||
*/
|
||||
class JUCE_API FocusChangeListener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~FocusChangeListener() {}
|
||||
|
||||
/** Callback to indicate that the currently focused component has changed. */
|
||||
virtual void globalFocusChanged (Component* focusedComponent) = 0;
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Describes and controls aspects of the computer's desktop.
|
||||
|
||||
*/
|
||||
class JUCE_API Desktop : private DeletedAtShutdown,
|
||||
private Timer,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** There's only one desktop object, and this method will return it. */
|
||||
static Desktop& JUCE_CALLTYPE getInstance();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the mouse position.
|
||||
|
||||
The coordinates are relative to the top-left of the main monitor.
|
||||
|
||||
Note that this is just a shortcut for calling getMainMouseSource().getScreenPosition(), and
|
||||
you should only resort to grabbing the global mouse position if there's really no
|
||||
way to get the coordinates via a mouse event callback instead.
|
||||
*/
|
||||
static Point<int> getMousePosition();
|
||||
|
||||
/** Makes the mouse pointer jump to a given location.
|
||||
The coordinates are relative to the top-left of the main monitor.
|
||||
*/
|
||||
static void setMousePosition (Point<int> newPosition);
|
||||
|
||||
/** Returns the last position at which a mouse button was pressed.
|
||||
|
||||
Note that this is just a shortcut for calling getMainMouseSource().getLastMouseDownPosition(),
|
||||
and in a multi-touch environment, it doesn't make much sense. ALWAYS prefer to
|
||||
get this information via other means, such as MouseEvent::getMouseDownScreenPosition()
|
||||
if possible, and only ever call this as a last resort.
|
||||
*/
|
||||
static Point<int> getLastMouseDownPosition();
|
||||
|
||||
/** Returns the number of times the mouse button has been clicked since the app started.
|
||||
Each mouse-down event increments this number by 1.
|
||||
@see getMouseWheelMoveCounter
|
||||
*/
|
||||
int getMouseButtonClickCounter() const noexcept;
|
||||
|
||||
/** Returns the number of times the mouse wheel has been moved since the app started.
|
||||
Each mouse-wheel event increments this number by 1.
|
||||
@see getMouseButtonClickCounter
|
||||
*/
|
||||
int getMouseWheelMoveCounter() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** This lets you prevent the screensaver from becoming active.
|
||||
|
||||
Handy if you're running some sort of presentation app where having a screensaver
|
||||
appear would be annoying.
|
||||
|
||||
Pass false to disable the screensaver, and true to re-enable it. (Note that this
|
||||
won't enable a screensaver unless the user has actually set one up).
|
||||
|
||||
The disablement will only happen while the Juce application is the foreground
|
||||
process - if another task is running in front of it, then the screensaver will
|
||||
be unaffected.
|
||||
|
||||
@see isScreenSaverEnabled
|
||||
*/
|
||||
static void setScreenSaverEnabled (bool isEnabled);
|
||||
|
||||
/** Returns true if the screensaver has not been turned off.
|
||||
|
||||
This will return the last value passed into setScreenSaverEnabled(). Note that
|
||||
it won't tell you whether the user is actually using a screen saver, just
|
||||
whether this app is deliberately preventing one from running.
|
||||
|
||||
@see setScreenSaverEnabled
|
||||
*/
|
||||
static bool isScreenSaverEnabled();
|
||||
|
||||
//==============================================================================
|
||||
/** Registers a MouseListener that will receive all mouse events that occur on
|
||||
any component.
|
||||
|
||||
@see removeGlobalMouseListener
|
||||
*/
|
||||
void addGlobalMouseListener (MouseListener* listener);
|
||||
|
||||
/** Unregisters a MouseListener that was added with the addGlobalMouseListener()
|
||||
method.
|
||||
|
||||
@see addGlobalMouseListener
|
||||
*/
|
||||
void removeGlobalMouseListener (MouseListener* listener);
|
||||
|
||||
//==============================================================================
|
||||
/** Registers a MouseListener that will receive a callback whenever the focused
|
||||
component changes.
|
||||
*/
|
||||
void addFocusChangeListener (FocusChangeListener* listener);
|
||||
|
||||
/** Unregisters a listener that was added with addFocusChangeListener(). */
|
||||
void removeFocusChangeListener (FocusChangeListener* listener);
|
||||
|
||||
//==============================================================================
|
||||
/** Takes a component and makes it full-screen, removing the taskbar, dock, etc.
|
||||
|
||||
The component must already be on the desktop for this method to work. It will
|
||||
be resized to completely fill the screen and any extraneous taskbars, menu bars,
|
||||
etc will be hidden.
|
||||
|
||||
To exit kiosk mode, just call setKioskModeComponent (nullptr). When this is called,
|
||||
the component that's currently being used will be resized back to the size
|
||||
and position it was in before being put into this mode.
|
||||
|
||||
If allowMenusAndBars is true, things like the menu and dock (on mac) are still
|
||||
allowed to pop up when the mouse moves onto them. If this is false, it'll try
|
||||
to hide as much on-screen paraphenalia as possible.
|
||||
*/
|
||||
void setKioskModeComponent (Component* componentToUse,
|
||||
bool allowMenusAndBars = true);
|
||||
|
||||
/** Returns the component that is currently being used in kiosk-mode.
|
||||
|
||||
This is the component that was last set by setKioskModeComponent(). If none
|
||||
has been set, this returns nullptr.
|
||||
*/
|
||||
Component* getKioskModeComponent() const noexcept { return kioskModeComponent; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of components that are currently active as top-level
|
||||
desktop windows.
|
||||
|
||||
@see getComponent, Component::addToDesktop
|
||||
*/
|
||||
int getNumComponents() const noexcept;
|
||||
|
||||
/** Returns one of the top-level desktop window components.
|
||||
|
||||
The index is from 0 to getNumComponents() - 1. This could return 0 if the
|
||||
index is out-of-range.
|
||||
|
||||
@see getNumComponents, Component::addToDesktop
|
||||
*/
|
||||
Component* getComponent (int index) const noexcept;
|
||||
|
||||
/** Finds the component at a given screen location.
|
||||
|
||||
This will drill down into top-level windows to find the child component at
|
||||
the given position.
|
||||
|
||||
Returns nullptr if the coordinates are inside a non-Juce window.
|
||||
*/
|
||||
Component* findComponentAt (Point<int> screenPosition) const;
|
||||
|
||||
/** The Desktop object has a ComponentAnimator instance which can be used for performing
|
||||
your animations.
|
||||
|
||||
Having a single shared ComponentAnimator object makes it more efficient when multiple
|
||||
components are being moved around simultaneously. It's also more convenient than having
|
||||
to manage your own instance of one.
|
||||
|
||||
@see ComponentAnimator
|
||||
*/
|
||||
ComponentAnimator& getAnimator() noexcept { return animator; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the current default look-and-feel for components which don't have one
|
||||
explicitly set.
|
||||
@see setDefaultLookAndFeel
|
||||
*/
|
||||
LookAndFeel& getDefaultLookAndFeel() noexcept;
|
||||
|
||||
/** Changes the default look-and-feel.
|
||||
@param newDefaultLookAndFeel the new look-and-feel object to use - if this is
|
||||
set to nullptr, it will revert to using the system's
|
||||
default one. The object passed-in must be deleted by the
|
||||
caller when it's no longer needed.
|
||||
@see getDefaultLookAndFeel
|
||||
*/
|
||||
void setDefaultLookAndFeel (LookAndFeel* newDefaultLookAndFeel);
|
||||
|
||||
//==============================================================================
|
||||
/** Provides access to the array of mouse sources, for iteration.
|
||||
In a traditional single-mouse system, there might be only one MouseInputSource. On a
|
||||
multi-touch system, there could be one input source per potential finger. The number
|
||||
of mouse sources returned here may increase dynamically as the program runs.
|
||||
To find out how many mouse events are currently happening, use getNumDraggingMouseSources().
|
||||
*/
|
||||
const Array<MouseInputSource>& getMouseSources() const noexcept;
|
||||
|
||||
/** Returns the number of MouseInputSource objects the system has at its disposal.
|
||||
In a traditional single-mouse system, there might be only one MouseInputSource. On a
|
||||
multi-touch system, there could be one input source per potential finger. The number
|
||||
of mouse sources returned here may increase dynamically as the program runs.
|
||||
To find out how many mouse events are currently happening, use getNumDraggingMouseSources().
|
||||
@see getMouseSource
|
||||
*/
|
||||
int getNumMouseSources() const noexcept;
|
||||
|
||||
/** Returns one of the system's MouseInputSource objects.
|
||||
The index should be from 0 to getNumMouseSources() - 1. Out-of-range indexes will return
|
||||
a null pointer.
|
||||
In a traditional single-mouse system, there might be only one object. On a multi-touch
|
||||
system, there could be one input source per potential finger.
|
||||
*/
|
||||
MouseInputSource* getMouseSource (int index) const noexcept;
|
||||
|
||||
/** Returns the main mouse input device that the system is using.
|
||||
@see getNumMouseSources()
|
||||
*/
|
||||
MouseInputSource getMainMouseSource() const noexcept;
|
||||
|
||||
/** Returns the number of mouse-sources that are currently being dragged.
|
||||
In a traditional single-mouse system, this will be 0 or 1, depending on whether a
|
||||
juce component has the button down on it. In a multi-touch system, this could
|
||||
be any number from 0 to the number of simultaneous touches that can be detected.
|
||||
*/
|
||||
int getNumDraggingMouseSources() const noexcept;
|
||||
|
||||
/** Returns one of the mouse sources that's currently being dragged.
|
||||
The index should be between 0 and getNumDraggingMouseSources() - 1. If the index is
|
||||
out of range, or if no mice or fingers are down, this will return a null pointer.
|
||||
*/
|
||||
MouseInputSource* getDraggingMouseSource (int index) const noexcept;
|
||||
|
||||
/** Ensures that a non-stop stream of mouse-drag events will be sent during the
|
||||
current mouse-drag operation.
|
||||
|
||||
This allows you to make sure that mouseDrag() events are sent continuously, even
|
||||
when the mouse isn't moving. This can be useful for things like auto-scrolling
|
||||
components when the mouse is near an edge.
|
||||
|
||||
Call this method during a mouseDown() or mouseDrag() callback, specifying the
|
||||
minimum interval between consecutive mouse drag callbacks. The callbacks
|
||||
will continue until the mouse is released, and then the interval will be reset,
|
||||
so you need to make sure it's called every time you begin a drag event.
|
||||
Passing an interval of 0 or less will cancel the auto-repeat.
|
||||
|
||||
@see mouseDrag
|
||||
*/
|
||||
void beginDragAutoRepeat (int millisecondsBetweenCallbacks);
|
||||
|
||||
//==============================================================================
|
||||
/** In a tablet device which can be turned around, this is used to inidicate the orientation. */
|
||||
enum DisplayOrientation
|
||||
{
|
||||
upright = 1, /**< Indicates that the display is the normal way up. */
|
||||
upsideDown = 2, /**< Indicates that the display is upside-down. */
|
||||
rotatedClockwise = 4, /**< Indicates that the display is turned 90 degrees clockwise from its upright position. */
|
||||
rotatedAntiClockwise = 8, /**< Indicates that the display is turned 90 degrees anti-clockwise from its upright position. */
|
||||
|
||||
allOrientations = 1 + 2 + 4 + 8 /**< A combination of all the orientation values */
|
||||
};
|
||||
|
||||
/** In a tablet device which can be turned around, this returns the current orientation. */
|
||||
DisplayOrientation getCurrentOrientation() const;
|
||||
|
||||
/** Sets which orientations the display is allowed to auto-rotate to.
|
||||
|
||||
For devices that support rotating desktops, this lets you specify which of the orientations your app can use.
|
||||
|
||||
The parameter is a bitwise or-ed combination of the values in DisplayOrientation, and must contain at least one
|
||||
set bit.
|
||||
*/
|
||||
void setOrientationsEnabled (int allowedOrientations);
|
||||
|
||||
/** Returns whether the display is allowed to auto-rotate to the given orientation.
|
||||
Each orientation can be enabled using setOrientationEnabled(). By default, all orientations are allowed.
|
||||
*/
|
||||
bool isOrientationEnabled (DisplayOrientation orientation) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
class JUCE_API Displays
|
||||
{
|
||||
public:
|
||||
/** Contains details about a display device. */
|
||||
struct Display
|
||||
{
|
||||
/** This is the bounds of the area of this display which isn't covered by
|
||||
OS-dependent objects like the taskbar, menu bar, etc. */
|
||||
Rectangle<int> userArea;
|
||||
|
||||
/** This is the total physical area of this display, including any taskbars, etc */
|
||||
Rectangle<int> totalArea;
|
||||
|
||||
/** This is the scale-factor of this display.
|
||||
If you create a component with size 1x1, this scale factor indicates the actual
|
||||
size of the component in terms of physical pixels.
|
||||
For higher-resolution displays, it may be a value greater than 1.0
|
||||
*/
|
||||
double scale;
|
||||
|
||||
/** The DPI of the display.
|
||||
This is the number of physical pixels per inch. To get the number of logical
|
||||
pixels per inch, divide this by the Display::scale value.
|
||||
*/
|
||||
double dpi;
|
||||
|
||||
/** This will be true if this is the user's main screen. */
|
||||
bool isMain;
|
||||
};
|
||||
|
||||
/** Returns the display which acts as user's main screen. */
|
||||
const Display& getMainDisplay() const noexcept;
|
||||
|
||||
/** Returns the display which contains a particular point.
|
||||
If the point lies outside all the displays, the nearest one will be returned.
|
||||
*/
|
||||
const Display& getDisplayContaining (Point<int> position) const noexcept;
|
||||
|
||||
/** Returns a RectangleList made up of all the displays. */
|
||||
RectangleList<int> getRectangleList (bool userAreasOnly) const;
|
||||
|
||||
/** Returns the smallest bounding box which contains all the displays. */
|
||||
Rectangle<int> getTotalBounds (bool userAreasOnly) const;
|
||||
|
||||
/** The list of displays. */
|
||||
Array<Display> displays;
|
||||
|
||||
#ifndef DOXYGEN
|
||||
/** @internal */
|
||||
void refresh();
|
||||
#endif
|
||||
|
||||
private:
|
||||
friend class Desktop;
|
||||
friend struct ContainerDeletePolicy<Displays>;
|
||||
Displays (Desktop&);
|
||||
~Displays();
|
||||
|
||||
void init (Desktop&);
|
||||
void findDisplays (float masterScale);
|
||||
};
|
||||
|
||||
const Displays& getDisplays() const noexcept { return *displays; }
|
||||
|
||||
//==============================================================================
|
||||
/** Sets a global scale factor to be used for all desktop windows.
|
||||
Setting this will also scale the monitor sizes that are returned by getDisplays().
|
||||
*/
|
||||
void setGlobalScaleFactor (float newScaleFactor) noexcept;
|
||||
|
||||
/** Returns the current global scale factor, as set by setGlobalScaleFactor().
|
||||
@see setGlobalScaleFactor
|
||||
*/
|
||||
float getGlobalScaleFactor() const noexcept { return masterScaleFactor; }
|
||||
|
||||
//==============================================================================
|
||||
/** True if the OS supports semitransparent windows */
|
||||
static bool canUseSemiTransparentWindows() noexcept;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
static Desktop* instance;
|
||||
|
||||
friend class Component;
|
||||
friend class ComponentPeer;
|
||||
friend class MouseInputSourceInternal;
|
||||
friend class DeletedAtShutdown;
|
||||
friend class TopLevelWindowManager;
|
||||
|
||||
ScopedPointer<MouseInputSource::SourceList> mouseSources;
|
||||
|
||||
ListenerList<MouseListener> mouseListeners;
|
||||
ListenerList<FocusChangeListener> focusListeners;
|
||||
|
||||
Array<Component*> desktopComponents;
|
||||
Array<ComponentPeer*> peers;
|
||||
|
||||
ScopedPointer<Displays> displays;
|
||||
|
||||
Point<float> lastFakeMouseMove;
|
||||
void sendMouseMove();
|
||||
|
||||
int mouseClickCounter, mouseWheelCounter;
|
||||
void incrementMouseClickCounter() noexcept;
|
||||
void incrementMouseWheelCounter() noexcept;
|
||||
|
||||
ScopedPointer<LookAndFeel> defaultLookAndFeel;
|
||||
WeakReference<LookAndFeel> currentLookAndFeel;
|
||||
|
||||
Component* kioskModeComponent;
|
||||
Rectangle<int> kioskComponentOriginalBounds;
|
||||
bool kioskModeReentrant;
|
||||
|
||||
int allowedOrientations;
|
||||
float masterScaleFactor;
|
||||
|
||||
ComponentAnimator animator;
|
||||
|
||||
void timerCallback() override;
|
||||
void resetTimer();
|
||||
ListenerList<MouseListener>& getMouseListeners();
|
||||
|
||||
void addDesktopComponent (Component*);
|
||||
void removeDesktopComponent (Component*);
|
||||
void componentBroughtToFront (Component*);
|
||||
|
||||
void setKioskComponent (Component*, bool shouldBeEnabled, bool allowMenusAndBars);
|
||||
|
||||
void triggerFocusCallback();
|
||||
void handleAsyncUpdate() override;
|
||||
|
||||
static Point<float> getMousePositionFloat();
|
||||
|
||||
static double getDefaultMasterScale();
|
||||
|
||||
Desktop();
|
||||
~Desktop();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Desktop)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_DESKTOP_H_INCLUDED
|
||||
|
|
@ -0,0 +1,294 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
class ModalComponentManager::ModalItem : public ComponentMovementWatcher
|
||||
{
|
||||
public:
|
||||
ModalItem (Component* const comp, const bool autoDelete_)
|
||||
: ComponentMovementWatcher (comp),
|
||||
component (comp), returnValue (0),
|
||||
isActive (true), autoDelete (autoDelete_)
|
||||
{
|
||||
jassert (comp != nullptr);
|
||||
}
|
||||
|
||||
void componentMovedOrResized (bool, bool) override {}
|
||||
|
||||
void componentPeerChanged() override
|
||||
{
|
||||
if (! component->isShowing())
|
||||
cancel();
|
||||
}
|
||||
|
||||
void componentVisibilityChanged() override
|
||||
{
|
||||
if (! component->isShowing())
|
||||
cancel();
|
||||
}
|
||||
|
||||
void componentBeingDeleted (Component& comp) override
|
||||
{
|
||||
ComponentMovementWatcher::componentBeingDeleted (comp);
|
||||
|
||||
if (component == &comp || comp.isParentOf (component))
|
||||
{
|
||||
autoDelete = false;
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void cancel()
|
||||
{
|
||||
if (isActive)
|
||||
{
|
||||
isActive = false;
|
||||
|
||||
if (ModalComponentManager* mcm = ModalComponentManager::getInstanceWithoutCreating())
|
||||
mcm->triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
Component* component;
|
||||
OwnedArray<Callback> callbacks;
|
||||
int returnValue;
|
||||
bool isActive, autoDelete;
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE (ModalItem)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ModalComponentManager::ModalComponentManager()
|
||||
{
|
||||
}
|
||||
|
||||
ModalComponentManager::~ModalComponentManager()
|
||||
{
|
||||
stack.clear();
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
juce_ImplementSingleton_SingleThreaded (ModalComponentManager);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
void ModalComponentManager::startModal (Component* component, bool autoDelete)
|
||||
{
|
||||
if (component != nullptr)
|
||||
stack.add (new ModalItem (component, autoDelete));
|
||||
}
|
||||
|
||||
void ModalComponentManager::attachCallback (Component* component, Callback* callback)
|
||||
{
|
||||
if (callback != nullptr)
|
||||
{
|
||||
ScopedPointer<Callback> callbackDeleter (callback);
|
||||
|
||||
for (int i = stack.size(); --i >= 0;)
|
||||
{
|
||||
ModalItem* const item = stack.getUnchecked(i);
|
||||
|
||||
if (item->component == component)
|
||||
{
|
||||
item->callbacks.add (callback);
|
||||
callbackDeleter.release();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModalComponentManager::endModal (Component* component)
|
||||
{
|
||||
for (int i = stack.size(); --i >= 0;)
|
||||
{
|
||||
ModalItem* const item = stack.getUnchecked(i);
|
||||
|
||||
if (item->component == component)
|
||||
item->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void ModalComponentManager::endModal (Component* component, int returnValue)
|
||||
{
|
||||
for (int i = stack.size(); --i >= 0;)
|
||||
{
|
||||
ModalItem* const item = stack.getUnchecked(i);
|
||||
|
||||
if (item->component == component)
|
||||
{
|
||||
item->returnValue = returnValue;
|
||||
item->cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ModalComponentManager::getNumModalComponents() const
|
||||
{
|
||||
int n = 0;
|
||||
for (int i = 0; i < stack.size(); ++i)
|
||||
if (stack.getUnchecked(i)->isActive)
|
||||
++n;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
Component* ModalComponentManager::getModalComponent (const int index) const
|
||||
{
|
||||
int n = 0;
|
||||
for (int i = stack.size(); --i >= 0;)
|
||||
{
|
||||
const ModalItem* const item = stack.getUnchecked(i);
|
||||
if (item->isActive)
|
||||
if (n++ == index)
|
||||
return item->component;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ModalComponentManager::isModal (Component* const comp) const
|
||||
{
|
||||
for (int i = stack.size(); --i >= 0;)
|
||||
{
|
||||
const ModalItem* const item = stack.getUnchecked(i);
|
||||
if (item->isActive && item->component == comp)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModalComponentManager::isFrontModalComponent (Component* const comp) const
|
||||
{
|
||||
return comp == getModalComponent (0);
|
||||
}
|
||||
|
||||
void ModalComponentManager::handleAsyncUpdate()
|
||||
{
|
||||
for (int i = stack.size(); --i >= 0;)
|
||||
{
|
||||
const ModalItem* const item = stack.getUnchecked(i);
|
||||
|
||||
if (! item->isActive)
|
||||
{
|
||||
ScopedPointer<ModalItem> deleter (stack.removeAndReturn (i));
|
||||
Component::SafePointer<Component> compToDelete (item->autoDelete ? item->component : nullptr);
|
||||
|
||||
for (int j = item->callbacks.size(); --j >= 0;)
|
||||
item->callbacks.getUnchecked(j)->modalStateFinished (item->returnValue);
|
||||
|
||||
compToDelete.deleteAndZero();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModalComponentManager::bringModalComponentsToFront (bool topOneShouldGrabFocus)
|
||||
{
|
||||
ComponentPeer* lastOne = nullptr;
|
||||
|
||||
for (int i = 0; i < getNumModalComponents(); ++i)
|
||||
{
|
||||
Component* const c = getModalComponent (i);
|
||||
|
||||
if (c == nullptr)
|
||||
break;
|
||||
|
||||
ComponentPeer* peer = c->getPeer();
|
||||
|
||||
if (peer != nullptr && peer != lastOne)
|
||||
{
|
||||
if (lastOne == nullptr)
|
||||
{
|
||||
peer->toFront (topOneShouldGrabFocus);
|
||||
|
||||
if (topOneShouldGrabFocus)
|
||||
peer->grabFocus();
|
||||
}
|
||||
else
|
||||
peer->toBehind (lastOne);
|
||||
|
||||
lastOne = peer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ModalComponentManager::cancelAllModalComponents()
|
||||
{
|
||||
const int numModal = getNumModalComponents();
|
||||
|
||||
for (int i = numModal; --i >= 0;)
|
||||
if (Component* const c = getModalComponent(i))
|
||||
c->exitModalState (0);
|
||||
|
||||
return numModal > 0;
|
||||
}
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
class ModalComponentManager::ReturnValueRetriever : public ModalComponentManager::Callback
|
||||
{
|
||||
public:
|
||||
ReturnValueRetriever (int& v, bool& done) : value (v), finished (done) {}
|
||||
|
||||
void modalStateFinished (int returnValue)
|
||||
{
|
||||
finished = true;
|
||||
value = returnValue;
|
||||
}
|
||||
|
||||
private:
|
||||
int& value;
|
||||
bool& finished;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (ReturnValueRetriever)
|
||||
};
|
||||
|
||||
int ModalComponentManager::runEventLoopForCurrentComponent()
|
||||
{
|
||||
// This can only be run from the message thread!
|
||||
jassert (MessageManager::getInstance()->isThisTheMessageThread());
|
||||
|
||||
int returnValue = 0;
|
||||
|
||||
if (Component* currentlyModal = getModalComponent (0))
|
||||
{
|
||||
FocusRestorer focusRestorer;
|
||||
|
||||
bool finished = false;
|
||||
attachCallback (currentlyModal, new ReturnValueRetriever (returnValue, finished));
|
||||
|
||||
JUCE_TRY
|
||||
{
|
||||
while (! finished)
|
||||
{
|
||||
if (! MessageManager::getInstance()->runDispatchLoopUntil (20))
|
||||
break;
|
||||
}
|
||||
}
|
||||
JUCE_CATCH_EXCEPTION
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,369 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_MODALCOMPONENTMANAGER_H_INCLUDED
|
||||
#define JUCE_MODALCOMPONENTMANAGER_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Manages the system's stack of modal components.
|
||||
|
||||
Normally you'll just use the Component methods to invoke modal states in components,
|
||||
and won't have to deal with this class directly, but this is the singleton object that's
|
||||
used internally to manage the stack.
|
||||
|
||||
@see Component::enterModalState, Component::exitModalState, Component::isCurrentlyModal,
|
||||
Component::getCurrentlyModalComponent, Component::isCurrentlyBlockedByAnotherModalComponent
|
||||
*/
|
||||
class JUCE_API ModalComponentManager : private AsyncUpdater,
|
||||
private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Receives callbacks when a modal component is dismissed.
|
||||
|
||||
You can register a callback using Component::enterModalState() or
|
||||
ModalComponentManager::attachCallback().
|
||||
|
||||
For some quick ways of creating callback objects, see the ModalCallbackFunction class.
|
||||
@see ModalCallbackFunction
|
||||
*/
|
||||
class Callback
|
||||
{
|
||||
public:
|
||||
/** */
|
||||
Callback() {}
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~Callback() {}
|
||||
|
||||
/** Called to indicate that a modal component has been dismissed.
|
||||
|
||||
You can register a callback using Component::enterModalState() or
|
||||
ModalComponentManager::attachCallback().
|
||||
|
||||
The returnValue parameter is the value that was passed to Component::exitModalState()
|
||||
when the component was dismissed.
|
||||
|
||||
The callback object will be deleted shortly after this method is called.
|
||||
*/
|
||||
virtual void modalStateFinished (int returnValue) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of components currently being shown modally.
|
||||
@see getModalComponent
|
||||
*/
|
||||
int getNumModalComponents() const;
|
||||
|
||||
/** Returns one of the components being shown modally.
|
||||
An index of 0 is the most recently-shown, topmost component.
|
||||
*/
|
||||
Component* getModalComponent (int index) const;
|
||||
|
||||
/** Returns true if the specified component is in a modal state. */
|
||||
bool isModal (Component* component) const;
|
||||
|
||||
/** Returns true if the specified component is currently the topmost modal component. */
|
||||
bool isFrontModalComponent (Component* component) const;
|
||||
|
||||
/** Adds a new callback that will be called when the specified modal component is dismissed.
|
||||
|
||||
If the component is modal, then when it is dismissed, either by being hidden, or by calling
|
||||
Component::exitModalState(), then the Callback::modalStateFinished() method will be
|
||||
called.
|
||||
|
||||
Each component can have any number of callbacks associated with it, and this one is added
|
||||
to that list.
|
||||
|
||||
The object that is passed in will be deleted by the manager when it's no longer needed. If
|
||||
the given component is not currently modal, the callback object is deleted immediately and
|
||||
no action is taken.
|
||||
*/
|
||||
void attachCallback (Component* component, Callback* callback);
|
||||
|
||||
/** Brings any modal components to the front. */
|
||||
void bringModalComponentsToFront (bool topOneShouldGrabFocus = true);
|
||||
|
||||
/** Calls exitModalState (0) on any components that are currently modal.
|
||||
@returns true if any components were modal; false if nothing needed cancelling
|
||||
*/
|
||||
bool cancelAllModalComponents();
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** Runs the event loop until the currently topmost modal component is dismissed, and
|
||||
returns the exit code for that component.
|
||||
*/
|
||||
int runEventLoopForCurrentComponent();
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
juce_DeclareSingleton_SingleThreaded_Minimal (ModalComponentManager);
|
||||
|
||||
protected:
|
||||
/** Creates a ModalComponentManager.
|
||||
You shouldn't ever call the constructor - it's a singleton, so use ModalComponentManager::getInstance()
|
||||
*/
|
||||
ModalComponentManager();
|
||||
|
||||
/** Destructor. */
|
||||
~ModalComponentManager();
|
||||
|
||||
/** @internal */
|
||||
void handleAsyncUpdate() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class ModalItem;
|
||||
class ReturnValueRetriever;
|
||||
|
||||
friend class Component;
|
||||
friend struct ContainerDeletePolicy<ModalItem>;
|
||||
OwnedArray<ModalItem> stack;
|
||||
|
||||
void startModal (Component*, bool autoDelete);
|
||||
void endModal (Component*, int returnValue);
|
||||
void endModal (Component*);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (ModalComponentManager)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This class provides some handy utility methods for creating ModalComponentManager::Callback
|
||||
objects that will invoke a static function with some parameters when a modal component is dismissed.
|
||||
*/
|
||||
class ModalCallbackFunction
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** This is a utility function to create a ModalComponentManager::Callback that will
|
||||
call a static function with a parameter.
|
||||
|
||||
The function that you supply must take two parameters - the first being an int, which is
|
||||
the result code that was used when the modal component was dismissed, and the second
|
||||
can be a custom type. Note that this custom value will be copied and stored, so it must
|
||||
be a primitive type or a class that provides copy-by-value semantics.
|
||||
|
||||
E.g. @code
|
||||
static void myCallbackFunction (int modalResult, double customValue)
|
||||
{
|
||||
if (modalResult == 1)
|
||||
doSomethingWith (customValue);
|
||||
}
|
||||
|
||||
Component* someKindOfComp;
|
||||
...
|
||||
someKindOfComp->enterModalState (ModalCallbackFunction::create (myCallbackFunction, 3.0));
|
||||
@endcode
|
||||
@see ModalComponentManager::Callback
|
||||
*/
|
||||
template <typename ParamType>
|
||||
static ModalComponentManager::Callback* create (void (*functionToCall) (int, ParamType),
|
||||
ParamType parameterValue)
|
||||
{
|
||||
return new FunctionCaller1 <ParamType> (functionToCall, parameterValue);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** This is a utility function to create a ModalComponentManager::Callback that will
|
||||
call a static function with two custom parameters.
|
||||
|
||||
The function that you supply must take three parameters - the first being an int, which is
|
||||
the result code that was used when the modal component was dismissed, and the next two are
|
||||
your custom types. Note that these custom values will be copied and stored, so they must
|
||||
be primitive types or classes that provide copy-by-value semantics.
|
||||
|
||||
E.g. @code
|
||||
static void myCallbackFunction (int modalResult, double customValue1, String customValue2)
|
||||
{
|
||||
if (modalResult == 1)
|
||||
doSomethingWith (customValue1, customValue2);
|
||||
}
|
||||
|
||||
Component* someKindOfComp;
|
||||
...
|
||||
someKindOfComp->enterModalState (ModalCallbackFunction::create (myCallbackFunction, 3.0, String ("xyz")));
|
||||
@endcode
|
||||
@see ModalComponentManager::Callback
|
||||
*/
|
||||
template <typename ParamType1, typename ParamType2>
|
||||
static ModalComponentManager::Callback* withParam (void (*functionToCall) (int, ParamType1, ParamType2),
|
||||
ParamType1 parameterValue1,
|
||||
ParamType2 parameterValue2)
|
||||
{
|
||||
return new FunctionCaller2 <ParamType1, ParamType2> (functionToCall, parameterValue1, parameterValue2);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** This is a utility function to create a ModalComponentManager::Callback that will
|
||||
call a static function with a component.
|
||||
|
||||
The function that you supply must take two parameters - the first being an int, which is
|
||||
the result code that was used when the modal component was dismissed, and the second
|
||||
can be a Component class. The component will be stored as a WeakReference, so that if it gets
|
||||
deleted before this callback is invoked, the pointer that is passed to the function will be null.
|
||||
|
||||
E.g. @code
|
||||
static void myCallbackFunction (int modalResult, Slider* mySlider)
|
||||
{
|
||||
if (modalResult == 1 && mySlider != nullptr) // (must check that mySlider isn't null in case it was deleted..)
|
||||
mySlider->setValue (0.0);
|
||||
}
|
||||
|
||||
Component* someKindOfComp;
|
||||
Slider* mySlider;
|
||||
...
|
||||
someKindOfComp->enterModalState (ModalCallbackFunction::forComponent (myCallbackFunction, mySlider));
|
||||
@endcode
|
||||
@see ModalComponentManager::Callback
|
||||
*/
|
||||
template <class ComponentType>
|
||||
static ModalComponentManager::Callback* forComponent (void (*functionToCall) (int, ComponentType*),
|
||||
ComponentType* component)
|
||||
{
|
||||
return new ComponentCaller1 <ComponentType> (functionToCall, component);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a ModalComponentManager::Callback that will call a static function with a component.
|
||||
|
||||
The function that you supply must take three parameters - the first being an int, which is
|
||||
the result code that was used when the modal component was dismissed, the second being a Component
|
||||
class, and the third being a custom type (which must be a primitive type or have copy-by-value semantics).
|
||||
The component will be stored as a WeakReference, so that if it gets deleted before this callback is
|
||||
invoked, the pointer that is passed into the function will be null.
|
||||
|
||||
E.g. @code
|
||||
static void myCallbackFunction (int modalResult, Slider* mySlider, String customParam)
|
||||
{
|
||||
if (modalResult == 1 && mySlider != nullptr) // (must check that mySlider isn't null in case it was deleted..)
|
||||
mySlider->setName (customParam);
|
||||
}
|
||||
|
||||
Component* someKindOfComp;
|
||||
Slider* mySlider;
|
||||
...
|
||||
someKindOfComp->enterModalState (ModalCallbackFunction::forComponent (myCallbackFunction, mySlider, String ("hello")));
|
||||
@endcode
|
||||
@see ModalComponentManager::Callback
|
||||
*/
|
||||
template <class ComponentType, typename ParamType>
|
||||
static ModalComponentManager::Callback* forComponent (void (*functionToCall) (int, ComponentType*, ParamType),
|
||||
ComponentType* component,
|
||||
ParamType param)
|
||||
{
|
||||
return new ComponentCaller2 <ComponentType, ParamType> (functionToCall, component, param);
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
template <typename ParamType>
|
||||
class FunctionCaller1 : public ModalComponentManager::Callback
|
||||
{
|
||||
public:
|
||||
typedef void (*FunctionType) (int, ParamType);
|
||||
|
||||
FunctionCaller1 (FunctionType& f, ParamType& p1)
|
||||
: function (f), param (p1) {}
|
||||
|
||||
void modalStateFinished (int returnValue) { function (returnValue, param); }
|
||||
|
||||
private:
|
||||
const FunctionType function;
|
||||
ParamType param;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FunctionCaller1)
|
||||
};
|
||||
|
||||
template <typename ParamType1, typename ParamType2>
|
||||
class FunctionCaller2 : public ModalComponentManager::Callback
|
||||
{
|
||||
public:
|
||||
typedef void (*FunctionType) (int, ParamType1, ParamType2);
|
||||
|
||||
FunctionCaller2 (FunctionType& f, ParamType1& p1, ParamType2& p2)
|
||||
: function (f), param1 (p1), param2 (p2) {}
|
||||
|
||||
void modalStateFinished (int returnValue) { function (returnValue, param1, param2); }
|
||||
|
||||
private:
|
||||
const FunctionType function;
|
||||
ParamType1 param1;
|
||||
ParamType2 param2;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FunctionCaller2)
|
||||
};
|
||||
|
||||
template <typename ComponentType>
|
||||
class ComponentCaller1 : public ModalComponentManager::Callback
|
||||
{
|
||||
public:
|
||||
typedef void (*FunctionType) (int, ComponentType*);
|
||||
|
||||
ComponentCaller1 (FunctionType& f, ComponentType* c)
|
||||
: function (f), comp (c) {}
|
||||
|
||||
void modalStateFinished (int returnValue)
|
||||
{
|
||||
function (returnValue, static_cast <ComponentType*> (comp.get()));
|
||||
}
|
||||
|
||||
private:
|
||||
const FunctionType function;
|
||||
WeakReference<Component> comp;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentCaller1)
|
||||
};
|
||||
|
||||
template <typename ComponentType, typename ParamType1>
|
||||
class ComponentCaller2 : public ModalComponentManager::Callback
|
||||
{
|
||||
public:
|
||||
typedef void (*FunctionType) (int, ComponentType*, ParamType1);
|
||||
|
||||
ComponentCaller2 (FunctionType& f, ComponentType* c, ParamType1 p1)
|
||||
: function (f), comp (c), param1 (p1) {}
|
||||
|
||||
void modalStateFinished (int returnValue)
|
||||
{
|
||||
function (returnValue, static_cast <ComponentType*> (comp.get()), param1);
|
||||
}
|
||||
|
||||
private:
|
||||
const FunctionType function;
|
||||
WeakReference<Component> comp;
|
||||
ParamType1 param1;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentCaller2)
|
||||
};
|
||||
|
||||
ModalCallbackFunction();
|
||||
~ModalCallbackFunction();
|
||||
JUCE_DECLARE_NON_COPYABLE (ModalCallbackFunction)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_MODALCOMPONENTMANAGER_H_INCLUDED
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
Drawable::Drawable()
|
||||
{
|
||||
setInterceptsMouseClicks (false, false);
|
||||
setPaintingIsUnclipped (true);
|
||||
}
|
||||
|
||||
Drawable::Drawable (const Drawable& other)
|
||||
: Component (other.getName())
|
||||
{
|
||||
setComponentID (other.getComponentID());
|
||||
}
|
||||
|
||||
Drawable::~Drawable()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Drawable::draw (Graphics& g, float opacity, const AffineTransform& transform) const
|
||||
{
|
||||
const_cast <Drawable*> (this)->nonConstDraw (g, opacity, transform);
|
||||
}
|
||||
|
||||
void Drawable::nonConstDraw (Graphics& g, float opacity, const AffineTransform& transform)
|
||||
{
|
||||
Graphics::ScopedSaveState ss (g);
|
||||
|
||||
g.addTransform (AffineTransform::translation ((float) -(originRelativeToComponent.x),
|
||||
(float) -(originRelativeToComponent.y))
|
||||
.followedBy (getTransform())
|
||||
.followedBy (transform));
|
||||
|
||||
if (! g.isClipEmpty())
|
||||
{
|
||||
if (opacity < 1.0f)
|
||||
{
|
||||
g.beginTransparencyLayer (opacity);
|
||||
paintEntireComponent (g, true);
|
||||
g.endTransparencyLayer();
|
||||
}
|
||||
else
|
||||
{
|
||||
paintEntireComponent (g, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Drawable::drawAt (Graphics& g, float x, float y, float opacity) const
|
||||
{
|
||||
draw (g, opacity, AffineTransform::translation (x, y));
|
||||
}
|
||||
|
||||
void Drawable::drawWithin (Graphics& g, const Rectangle<float>& destArea,
|
||||
RectanglePlacement placement, float opacity) const
|
||||
{
|
||||
draw (g, opacity, placement.getTransformToFit (getDrawableBounds(), destArea));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
DrawableComposite* Drawable::getParent() const
|
||||
{
|
||||
return dynamic_cast <DrawableComposite*> (getParentComponent());
|
||||
}
|
||||
|
||||
void Drawable::transformContextToCorrectOrigin (Graphics& g)
|
||||
{
|
||||
g.setOrigin (originRelativeToComponent);
|
||||
}
|
||||
|
||||
void Drawable::parentHierarchyChanged()
|
||||
{
|
||||
setBoundsToEnclose (getDrawableBounds());
|
||||
}
|
||||
|
||||
void Drawable::setBoundsToEnclose (const Rectangle<float>& area)
|
||||
{
|
||||
Drawable* const parent = getParent();
|
||||
Point<int> parentOrigin;
|
||||
if (parent != nullptr)
|
||||
parentOrigin = parent->originRelativeToComponent;
|
||||
|
||||
const Rectangle<int> newBounds (area.getSmallestIntegerContainer() + parentOrigin);
|
||||
originRelativeToComponent = parentOrigin - newBounds.getPosition();
|
||||
setBounds (newBounds);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool Drawable::replaceColour (Colour original, Colour replacement)
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
for (int i = getNumChildComponents(); --i >= 0;)
|
||||
if (Drawable* d = dynamic_cast<Drawable*> (getChildComponent(i)))
|
||||
changed = d->replaceColour (original, replacement) || changed;
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Drawable::setOriginWithOriginalSize (Point<float> originWithinParent)
|
||||
{
|
||||
setTransform (AffineTransform::translation (originWithinParent.x, originWithinParent.y));
|
||||
}
|
||||
|
||||
void Drawable::setTransformToFit (const Rectangle<float>& area, RectanglePlacement placement)
|
||||
{
|
||||
if (! area.isEmpty())
|
||||
setTransform (placement.getTransformToFit (getDrawableBounds(), area));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Drawable* Drawable::createFromImageData (const void* data, const size_t numBytes)
|
||||
{
|
||||
Drawable* result = nullptr;
|
||||
|
||||
Image image (ImageFileFormat::loadFrom (data, numBytes));
|
||||
|
||||
if (image.isValid())
|
||||
{
|
||||
DrawableImage* const di = new DrawableImage();
|
||||
di->setImage (image);
|
||||
result = di;
|
||||
}
|
||||
else
|
||||
{
|
||||
const String asString (String::createStringFromData (data, (int) numBytes));
|
||||
|
||||
XmlDocument doc (asString);
|
||||
ScopedPointer <XmlElement> outer (doc.getDocumentElement (true));
|
||||
|
||||
if (outer != nullptr && outer->hasTagName ("svg"))
|
||||
{
|
||||
ScopedPointer <XmlElement> svg (doc.getDocumentElement());
|
||||
|
||||
if (svg != nullptr)
|
||||
result = Drawable::createFromSVG (*svg);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Drawable* Drawable::createFromImageDataStream (InputStream& dataSource)
|
||||
{
|
||||
MemoryOutputStream mo;
|
||||
mo << dataSource;
|
||||
|
||||
return createFromImageData (mo.getData(), mo.getDataSize());
|
||||
}
|
||||
|
||||
Drawable* Drawable::createFromImageFile (const File& file)
|
||||
{
|
||||
FileInputStream fin (file);
|
||||
|
||||
return fin.openedOk() ? createFromImageDataStream (fin) : nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <class DrawableClass>
|
||||
class DrawableTypeHandler : public ComponentBuilder::TypeHandler
|
||||
{
|
||||
public:
|
||||
DrawableTypeHandler()
|
||||
: ComponentBuilder::TypeHandler (DrawableClass::valueTreeType)
|
||||
{
|
||||
}
|
||||
|
||||
Component* addNewComponentFromState (const ValueTree& state, Component* parent)
|
||||
{
|
||||
DrawableClass* const d = new DrawableClass();
|
||||
|
||||
if (parent != nullptr)
|
||||
parent->addAndMakeVisible (d);
|
||||
|
||||
updateComponentFromState (d, state);
|
||||
return d;
|
||||
}
|
||||
|
||||
void updateComponentFromState (Component* component, const ValueTree& state)
|
||||
{
|
||||
DrawableClass* const d = dynamic_cast <DrawableClass*> (component);
|
||||
jassert (d != nullptr);
|
||||
d->refreshFromValueTree (state, *this->getBuilder());
|
||||
}
|
||||
};
|
||||
|
||||
void Drawable::registerDrawableTypeHandlers (ComponentBuilder& builder)
|
||||
{
|
||||
builder.registerTypeHandler (new DrawableTypeHandler <DrawablePath>());
|
||||
builder.registerTypeHandler (new DrawableTypeHandler <DrawableComposite>());
|
||||
builder.registerTypeHandler (new DrawableTypeHandler <DrawableRectangle>());
|
||||
builder.registerTypeHandler (new DrawableTypeHandler <DrawableImage>());
|
||||
builder.registerTypeHandler (new DrawableTypeHandler <DrawableText>());
|
||||
}
|
||||
|
||||
Drawable* Drawable::createFromValueTree (const ValueTree& tree, ComponentBuilder::ImageProvider* imageProvider)
|
||||
{
|
||||
ComponentBuilder builder (tree);
|
||||
builder.setImageProvider (imageProvider);
|
||||
registerDrawableTypeHandlers (builder);
|
||||
|
||||
ScopedPointer<Component> comp (builder.createComponent());
|
||||
Drawable* const d = dynamic_cast<Drawable*> (static_cast <Component*> (comp));
|
||||
|
||||
if (d != nullptr)
|
||||
comp.release();
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Drawable::ValueTreeWrapperBase::ValueTreeWrapperBase (const ValueTree& state_)
|
||||
: state (state_)
|
||||
{
|
||||
}
|
||||
|
||||
String Drawable::ValueTreeWrapperBase::getID() const
|
||||
{
|
||||
return state [ComponentBuilder::idProperty];
|
||||
}
|
||||
|
||||
void Drawable::ValueTreeWrapperBase::setID (const String& newID)
|
||||
{
|
||||
if (newID.isEmpty())
|
||||
state.removeProperty (ComponentBuilder::idProperty, nullptr);
|
||||
else
|
||||
state.setProperty (ComponentBuilder::idProperty, newID, nullptr);
|
||||
}
|
||||
|
|
@ -0,0 +1,259 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_DRAWABLE_H_INCLUDED
|
||||
#define JUCE_DRAWABLE_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
The base class for objects which can draw themselves, e.g. polygons, images, etc.
|
||||
|
||||
@see DrawableComposite, DrawableImage, DrawablePath, DrawableText
|
||||
*/
|
||||
class JUCE_API Drawable : public Component
|
||||
{
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** The base class can't be instantiated directly.
|
||||
|
||||
@see DrawableComposite, DrawableImage, DrawablePath, DrawableText
|
||||
*/
|
||||
Drawable();
|
||||
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~Drawable();
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a deep copy of this Drawable object.
|
||||
|
||||
Use this to create a new copy of this and any sub-objects in the tree.
|
||||
*/
|
||||
virtual Drawable* createCopy() const = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** Renders this Drawable object.
|
||||
|
||||
Note that the preferred way to render a drawable in future is by using it
|
||||
as a component and adding it to a parent, so you might want to consider that
|
||||
before using this method.
|
||||
|
||||
@see drawWithin
|
||||
*/
|
||||
void draw (Graphics& g, float opacity,
|
||||
const AffineTransform& transform = AffineTransform::identity) const;
|
||||
|
||||
/** Renders the Drawable at a given offset within the Graphics context.
|
||||
|
||||
The coordinates passed-in are used to translate the object relative to its own
|
||||
origin before drawing it - this is basically a quick way of saying:
|
||||
|
||||
@code
|
||||
draw (g, AffineTransform::translation (x, y)).
|
||||
@endcode
|
||||
|
||||
Note that the preferred way to render a drawable in future is by using it
|
||||
as a component and adding it to a parent, so you might want to consider that
|
||||
before using this method.
|
||||
*/
|
||||
void drawAt (Graphics& g, float x, float y, float opacity) const;
|
||||
|
||||
/** Renders the Drawable within a rectangle, scaling it to fit neatly inside without
|
||||
changing its aspect-ratio.
|
||||
|
||||
The object can placed arbitrarily within the rectangle based on a Justification type,
|
||||
and can either be made as big as possible, or just reduced to fit.
|
||||
|
||||
Note that the preferred way to render a drawable in future is by using it
|
||||
as a component and adding it to a parent, so you might want to consider that
|
||||
before using this method.
|
||||
|
||||
@param g the graphics context to render onto
|
||||
@param destArea the target rectangle to fit the drawable into
|
||||
@param placement defines the alignment and rescaling to use to fit
|
||||
this object within the target rectangle.
|
||||
@param opacity the opacity to use, in the range 0 to 1.0
|
||||
*/
|
||||
void drawWithin (Graphics& g,
|
||||
const Rectangle<float>& destArea,
|
||||
RectanglePlacement placement,
|
||||
float opacity) const;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Resets any transformations on this drawable, and positions its origin within
|
||||
its parent component.
|
||||
*/
|
||||
void setOriginWithOriginalSize (Point<float> originWithinParent);
|
||||
|
||||
/** Sets a transform for this drawable that will position it within the specified
|
||||
area of its parent component.
|
||||
*/
|
||||
void setTransformToFit (const Rectangle<float>& areaInParent, RectanglePlacement placement);
|
||||
|
||||
/** Returns the DrawableComposite that contains this object, if there is one. */
|
||||
DrawableComposite* getParent() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Tries to turn some kind of image file into a drawable.
|
||||
|
||||
The data could be an image that the ImageFileFormat class understands, or it
|
||||
could be SVG.
|
||||
*/
|
||||
static Drawable* createFromImageData (const void* data, size_t numBytes);
|
||||
|
||||
/** Tries to turn a stream containing some kind of image data into a drawable.
|
||||
|
||||
The data could be an image that the ImageFileFormat class understands, or it
|
||||
could be SVG.
|
||||
*/
|
||||
static Drawable* createFromImageDataStream (InputStream& dataSource);
|
||||
|
||||
/** Tries to turn a file containing some kind of image data into a drawable.
|
||||
|
||||
The data could be an image that the ImageFileFormat class understands, or it
|
||||
could be SVG.
|
||||
*/
|
||||
static Drawable* createFromImageFile (const File& file);
|
||||
|
||||
/** Attempts to parse an SVG (Scalable Vector Graphics) document, and to turn this
|
||||
into a Drawable tree.
|
||||
|
||||
The object returned must be deleted by the caller. If something goes wrong
|
||||
while parsing, it may return nullptr.
|
||||
|
||||
SVG is a pretty large and complex spec, and this doesn't aim to be a full
|
||||
implementation, but it can return the basic vector objects.
|
||||
*/
|
||||
static Drawable* createFromSVG (const XmlElement& svgDocument);
|
||||
|
||||
/** Parses an SVG path string and returns it. */
|
||||
static Path parseSVGPath (const String& svgPath);
|
||||
|
||||
//==============================================================================
|
||||
/** Tries to create a Drawable from a previously-saved ValueTree.
|
||||
The ValueTree must have been created by the createValueTree() method.
|
||||
If there are any images used within the drawable, you'll need to provide a valid
|
||||
ImageProvider object that can be used to retrieve these images from whatever type
|
||||
of identifier is used to represent them.
|
||||
Internally, this uses a ComponentBuilder, and registerDrawableTypeHandlers().
|
||||
*/
|
||||
static Drawable* createFromValueTree (const ValueTree& tree, ComponentBuilder::ImageProvider* imageProvider);
|
||||
|
||||
/** Creates a ValueTree to represent this Drawable.
|
||||
The ValueTree that is returned can be turned back into a Drawable with createFromValueTree().
|
||||
If there are any images used in this drawable, you'll need to provide a valid ImageProvider
|
||||
object that can be used to create storable representations of them.
|
||||
*/
|
||||
virtual ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const = 0;
|
||||
|
||||
/** Returns the area that this drawble covers.
|
||||
The result is expressed in this drawable's own coordinate space, and does not take
|
||||
into account any transforms that may be applied to the component.
|
||||
*/
|
||||
virtual Rectangle<float> getDrawableBounds() const = 0;
|
||||
|
||||
/** Recursively replaces a colour that might be used for filling or stroking.
|
||||
return true if any instances of this colour were found.
|
||||
*/
|
||||
virtual bool replaceColour (Colour originalColour, Colour replacementColour);
|
||||
|
||||
//==============================================================================
|
||||
/** Internal class used to manage ValueTrees that represent Drawables. */
|
||||
class ValueTreeWrapperBase
|
||||
{
|
||||
public:
|
||||
ValueTreeWrapperBase (const ValueTree& state);
|
||||
|
||||
ValueTree& getState() noexcept { return state; }
|
||||
|
||||
String getID() const;
|
||||
void setID (const String& newID);
|
||||
|
||||
ValueTree state;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Registers a set of ComponentBuilder::TypeHandler objects that can be used to
|
||||
load all the different Drawable types from a saved state.
|
||||
@see ComponentBuilder::registerTypeHandler()
|
||||
*/
|
||||
static void registerDrawableTypeHandlers (ComponentBuilder& componentBuilder);
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
friend class DrawableComposite;
|
||||
friend class DrawableShape;
|
||||
|
||||
/** @internal */
|
||||
void transformContextToCorrectOrigin (Graphics&);
|
||||
/** @internal */
|
||||
void parentHierarchyChanged() override;
|
||||
/** @internal */
|
||||
void setBoundsToEnclose (const Rectangle<float>&);
|
||||
|
||||
Point<int> originRelativeToComponent;
|
||||
|
||||
#ifndef DOXYGEN
|
||||
/** Internal utility class used by Drawables. */
|
||||
template <class DrawableType>
|
||||
class Positioner : public RelativeCoordinatePositionerBase
|
||||
{
|
||||
public:
|
||||
Positioner (DrawableType& c)
|
||||
: RelativeCoordinatePositionerBase (c),
|
||||
owner (c)
|
||||
{}
|
||||
|
||||
bool registerCoordinates() { return owner.registerCoordinates (*this); }
|
||||
void applyToComponentBounds()
|
||||
{
|
||||
ComponentScope scope (getComponent());
|
||||
owner.recalculateCoordinates (&scope);
|
||||
}
|
||||
|
||||
void applyNewBounds (const Rectangle<int>&)
|
||||
{
|
||||
jassertfalse; // drawables can't be resized directly!
|
||||
}
|
||||
|
||||
private:
|
||||
DrawableType& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Positioner)
|
||||
};
|
||||
|
||||
Drawable (const Drawable&);
|
||||
#endif
|
||||
|
||||
private:
|
||||
void nonConstDraw (Graphics&, float opacity, const AffineTransform&);
|
||||
|
||||
Drawable& operator= (const Drawable&);
|
||||
JUCE_LEAK_DETECTOR (Drawable)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_DRAWABLE_H_INCLUDED
|
||||
|
|
@ -0,0 +1,327 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
DrawableComposite::DrawableComposite()
|
||||
: bounds (Point<float>(), Point<float> (100.0f, 0.0f), Point<float> (0.0f, 100.0f)),
|
||||
updateBoundsReentrant (false)
|
||||
{
|
||||
setContentArea (RelativeRectangle (RelativeCoordinate (0.0),
|
||||
RelativeCoordinate (100.0),
|
||||
RelativeCoordinate (0.0),
|
||||
RelativeCoordinate (100.0)));
|
||||
}
|
||||
|
||||
DrawableComposite::DrawableComposite (const DrawableComposite& other)
|
||||
: Drawable (other),
|
||||
bounds (other.bounds),
|
||||
markersX (other.markersX),
|
||||
markersY (other.markersY),
|
||||
updateBoundsReentrant (false)
|
||||
{
|
||||
for (int i = 0; i < other.getNumChildComponents(); ++i)
|
||||
if (const Drawable* const d = dynamic_cast <const Drawable*> (other.getChildComponent(i)))
|
||||
addAndMakeVisible (d->createCopy());
|
||||
}
|
||||
|
||||
DrawableComposite::~DrawableComposite()
|
||||
{
|
||||
deleteAllChildren();
|
||||
}
|
||||
|
||||
Drawable* DrawableComposite::createCopy() const
|
||||
{
|
||||
return new DrawableComposite (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Rectangle<float> DrawableComposite::getDrawableBounds() const
|
||||
{
|
||||
Rectangle<float> r;
|
||||
|
||||
for (int i = getNumChildComponents(); --i >= 0;)
|
||||
if (const Drawable* const d = dynamic_cast <const Drawable*> (getChildComponent(i)))
|
||||
r = r.getUnion (d->isTransformed() ? d->getDrawableBounds().transformedBy (d->getTransform())
|
||||
: d->getDrawableBounds());
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
MarkerList* DrawableComposite::getMarkers (bool xAxis)
|
||||
{
|
||||
return xAxis ? &markersX : &markersY;
|
||||
}
|
||||
|
||||
RelativeRectangle DrawableComposite::getContentArea() const
|
||||
{
|
||||
jassert (markersX.getNumMarkers() >= 2 && markersX.getMarker (0)->name == contentLeftMarkerName && markersX.getMarker (1)->name == contentRightMarkerName);
|
||||
jassert (markersY.getNumMarkers() >= 2 && markersY.getMarker (0)->name == contentTopMarkerName && markersY.getMarker (1)->name == contentBottomMarkerName);
|
||||
|
||||
return RelativeRectangle (markersX.getMarker(0)->position, markersX.getMarker(1)->position,
|
||||
markersY.getMarker(0)->position, markersY.getMarker(1)->position);
|
||||
}
|
||||
|
||||
void DrawableComposite::setContentArea (const RelativeRectangle& newArea)
|
||||
{
|
||||
markersX.setMarker (contentLeftMarkerName, newArea.left);
|
||||
markersX.setMarker (contentRightMarkerName, newArea.right);
|
||||
markersY.setMarker (contentTopMarkerName, newArea.top);
|
||||
markersY.setMarker (contentBottomMarkerName, newArea.bottom);
|
||||
}
|
||||
|
||||
void DrawableComposite::setBoundingBox (const RelativeParallelogram& newBounds)
|
||||
{
|
||||
if (bounds != newBounds)
|
||||
{
|
||||
bounds = newBounds;
|
||||
|
||||
if (bounds.isDynamic())
|
||||
{
|
||||
Drawable::Positioner<DrawableComposite>* const p = new Drawable::Positioner<DrawableComposite> (*this);
|
||||
setPositioner (p);
|
||||
p->apply();
|
||||
}
|
||||
else
|
||||
{
|
||||
setPositioner (nullptr);
|
||||
recalculateCoordinates (nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableComposite::resetBoundingBoxToContentArea()
|
||||
{
|
||||
const RelativeRectangle content (getContentArea());
|
||||
|
||||
setBoundingBox (RelativeParallelogram (RelativePoint (content.left, content.top),
|
||||
RelativePoint (content.right, content.top),
|
||||
RelativePoint (content.left, content.bottom)));
|
||||
}
|
||||
|
||||
void DrawableComposite::resetContentAreaAndBoundingBoxToFitChildren()
|
||||
{
|
||||
const Rectangle<float> activeArea (getDrawableBounds());
|
||||
|
||||
setContentArea (RelativeRectangle (RelativeCoordinate (activeArea.getX()),
|
||||
RelativeCoordinate (activeArea.getRight()),
|
||||
RelativeCoordinate (activeArea.getY()),
|
||||
RelativeCoordinate (activeArea.getBottom())));
|
||||
resetBoundingBoxToContentArea();
|
||||
}
|
||||
|
||||
bool DrawableComposite::registerCoordinates (RelativeCoordinatePositionerBase& pos)
|
||||
{
|
||||
bool ok = pos.addPoint (bounds.topLeft);
|
||||
ok = pos.addPoint (bounds.topRight) && ok;
|
||||
return pos.addPoint (bounds.bottomLeft) && ok;
|
||||
}
|
||||
|
||||
void DrawableComposite::recalculateCoordinates (Expression::Scope* scope)
|
||||
{
|
||||
Point<float> resolved[3];
|
||||
bounds.resolveThreePoints (resolved, scope);
|
||||
|
||||
const Rectangle<float> content (getContentArea().resolve (scope));
|
||||
|
||||
AffineTransform t (AffineTransform::fromTargetPoints (content.getX(), content.getY(), resolved[0].x, resolved[0].y,
|
||||
content.getRight(), content.getY(), resolved[1].x, resolved[1].y,
|
||||
content.getX(), content.getBottom(), resolved[2].x, resolved[2].y));
|
||||
|
||||
if (t.isSingularity())
|
||||
t = AffineTransform::identity;
|
||||
|
||||
setTransform (t);
|
||||
}
|
||||
|
||||
void DrawableComposite::parentHierarchyChanged()
|
||||
{
|
||||
DrawableComposite* parent = getParent();
|
||||
if (parent != nullptr)
|
||||
originRelativeToComponent = parent->originRelativeToComponent - getPosition();
|
||||
}
|
||||
|
||||
void DrawableComposite::childBoundsChanged (Component*)
|
||||
{
|
||||
updateBoundsToFitChildren();
|
||||
}
|
||||
|
||||
void DrawableComposite::childrenChanged()
|
||||
{
|
||||
updateBoundsToFitChildren();
|
||||
}
|
||||
|
||||
void DrawableComposite::updateBoundsToFitChildren()
|
||||
{
|
||||
if (! updateBoundsReentrant)
|
||||
{
|
||||
const ScopedValueSetter<bool> setter (updateBoundsReentrant, true, false);
|
||||
|
||||
Rectangle<int> childArea;
|
||||
|
||||
for (int i = getNumChildComponents(); --i >= 0;)
|
||||
childArea = childArea.getUnion (getChildComponent(i)->getBoundsInParent());
|
||||
|
||||
const Point<int> delta (childArea.getPosition());
|
||||
childArea += getPosition();
|
||||
|
||||
if (childArea != getBounds())
|
||||
{
|
||||
if (! delta.isOrigin())
|
||||
{
|
||||
originRelativeToComponent -= delta;
|
||||
|
||||
for (int i = getNumChildComponents(); --i >= 0;)
|
||||
if (Component* const c = getChildComponent(i))
|
||||
c->setBounds (c->getBounds() - delta);
|
||||
}
|
||||
|
||||
setBounds (childArea);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const char* const DrawableComposite::contentLeftMarkerName = "left";
|
||||
const char* const DrawableComposite::contentRightMarkerName = "right";
|
||||
const char* const DrawableComposite::contentTopMarkerName = "top";
|
||||
const char* const DrawableComposite::contentBottomMarkerName = "bottom";
|
||||
|
||||
//==============================================================================
|
||||
const Identifier DrawableComposite::valueTreeType ("Group");
|
||||
|
||||
const Identifier DrawableComposite::ValueTreeWrapper::topLeft ("topLeft");
|
||||
const Identifier DrawableComposite::ValueTreeWrapper::topRight ("topRight");
|
||||
const Identifier DrawableComposite::ValueTreeWrapper::bottomLeft ("bottomLeft");
|
||||
const Identifier DrawableComposite::ValueTreeWrapper::childGroupTag ("Drawables");
|
||||
const Identifier DrawableComposite::ValueTreeWrapper::markerGroupTagX ("MarkersX");
|
||||
const Identifier DrawableComposite::ValueTreeWrapper::markerGroupTagY ("MarkersY");
|
||||
|
||||
//==============================================================================
|
||||
DrawableComposite::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_)
|
||||
: ValueTreeWrapperBase (state_)
|
||||
{
|
||||
jassert (state.hasType (valueTreeType));
|
||||
}
|
||||
|
||||
ValueTree DrawableComposite::ValueTreeWrapper::getChildList() const
|
||||
{
|
||||
return state.getChildWithName (childGroupTag);
|
||||
}
|
||||
|
||||
ValueTree DrawableComposite::ValueTreeWrapper::getChildListCreating (UndoManager* undoManager)
|
||||
{
|
||||
return state.getOrCreateChildWithName (childGroupTag, undoManager);
|
||||
}
|
||||
|
||||
RelativeParallelogram DrawableComposite::ValueTreeWrapper::getBoundingBox() const
|
||||
{
|
||||
return RelativeParallelogram (state.getProperty (topLeft, "0, 0"),
|
||||
state.getProperty (topRight, "100, 0"),
|
||||
state.getProperty (bottomLeft, "0, 100"));
|
||||
}
|
||||
|
||||
void DrawableComposite::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager)
|
||||
{
|
||||
state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager);
|
||||
state.setProperty (topRight, newBounds.topRight.toString(), undoManager);
|
||||
state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager);
|
||||
}
|
||||
|
||||
void DrawableComposite::ValueTreeWrapper::resetBoundingBoxToContentArea (UndoManager* undoManager)
|
||||
{
|
||||
const RelativeRectangle content (getContentArea());
|
||||
|
||||
setBoundingBox (RelativeParallelogram (RelativePoint (content.left, content.top),
|
||||
RelativePoint (content.right, content.top),
|
||||
RelativePoint (content.left, content.bottom)), undoManager);
|
||||
}
|
||||
|
||||
RelativeRectangle DrawableComposite::ValueTreeWrapper::getContentArea() const
|
||||
{
|
||||
MarkerList::ValueTreeWrapper marksX (getMarkerList (true));
|
||||
MarkerList::ValueTreeWrapper marksY (getMarkerList (false));
|
||||
|
||||
return RelativeRectangle (marksX.getMarker (marksX.getMarkerState (0)).position,
|
||||
marksX.getMarker (marksX.getMarkerState (1)).position,
|
||||
marksY.getMarker (marksY.getMarkerState (0)).position,
|
||||
marksY.getMarker (marksY.getMarkerState (1)).position);
|
||||
}
|
||||
|
||||
void DrawableComposite::ValueTreeWrapper::setContentArea (const RelativeRectangle& newArea, UndoManager* undoManager)
|
||||
{
|
||||
MarkerList::ValueTreeWrapper marksX (getMarkerListCreating (true, nullptr));
|
||||
MarkerList::ValueTreeWrapper marksY (getMarkerListCreating (false, nullptr));
|
||||
|
||||
marksX.setMarker (MarkerList::Marker (contentLeftMarkerName, newArea.left), undoManager);
|
||||
marksX.setMarker (MarkerList::Marker (contentRightMarkerName, newArea.right), undoManager);
|
||||
marksY.setMarker (MarkerList::Marker (contentTopMarkerName, newArea.top), undoManager);
|
||||
marksY.setMarker (MarkerList::Marker (contentBottomMarkerName, newArea.bottom), undoManager);
|
||||
}
|
||||
|
||||
MarkerList::ValueTreeWrapper DrawableComposite::ValueTreeWrapper::getMarkerList (bool xAxis) const
|
||||
{
|
||||
return state.getChildWithName (xAxis ? markerGroupTagX : markerGroupTagY);
|
||||
}
|
||||
|
||||
MarkerList::ValueTreeWrapper DrawableComposite::ValueTreeWrapper::getMarkerListCreating (bool xAxis, UndoManager* undoManager)
|
||||
{
|
||||
return state.getOrCreateChildWithName (xAxis ? markerGroupTagX : markerGroupTagY, undoManager);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawableComposite::refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder)
|
||||
{
|
||||
const ValueTreeWrapper wrapper (tree);
|
||||
setComponentID (wrapper.getID());
|
||||
|
||||
wrapper.getMarkerList (true).applyTo (markersX);
|
||||
wrapper.getMarkerList (false).applyTo (markersY);
|
||||
|
||||
setBoundingBox (wrapper.getBoundingBox());
|
||||
|
||||
builder.updateChildComponents (*this, wrapper.getChildList());
|
||||
}
|
||||
|
||||
ValueTree DrawableComposite::createValueTree (ComponentBuilder::ImageProvider* imageProvider) const
|
||||
{
|
||||
ValueTree tree (valueTreeType);
|
||||
ValueTreeWrapper v (tree);
|
||||
|
||||
v.setID (getComponentID());
|
||||
v.setBoundingBox (bounds, nullptr);
|
||||
|
||||
ValueTree childList (v.getChildListCreating (nullptr));
|
||||
|
||||
for (int i = 0; i < getNumChildComponents(); ++i)
|
||||
{
|
||||
const Drawable* const d = dynamic_cast <const Drawable*> (getChildComponent(i));
|
||||
jassert (d != nullptr); // You can't save a mix of Drawables and normal components!
|
||||
|
||||
childList.addChild (d->createValueTree (imageProvider), -1, nullptr);
|
||||
}
|
||||
|
||||
v.getMarkerListCreating (true, nullptr).readFrom (markersX, nullptr);
|
||||
v.getMarkerListCreating (false, nullptr).readFrom (markersY, nullptr);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_DRAWABLECOMPOSITE_H_INCLUDED
|
||||
#define JUCE_DRAWABLECOMPOSITE_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A drawable object which acts as a container for a set of other Drawables.
|
||||
|
||||
@see Drawable
|
||||
*/
|
||||
class JUCE_API DrawableComposite : public Drawable
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a composite Drawable. */
|
||||
DrawableComposite();
|
||||
|
||||
/** Creates a copy of a DrawableComposite. */
|
||||
DrawableComposite (const DrawableComposite&);
|
||||
|
||||
/** Destructor. */
|
||||
~DrawableComposite();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the parallelogram that defines the target position of the content rectangle when the drawable is rendered.
|
||||
@see setContentArea
|
||||
*/
|
||||
void setBoundingBox (const RelativeParallelogram& newBoundingBox);
|
||||
|
||||
/** Returns the parallelogram that defines the target position of the content rectangle when the drawable is rendered.
|
||||
@see setBoundingBox
|
||||
*/
|
||||
const RelativeParallelogram& getBoundingBox() const noexcept { return bounds; }
|
||||
|
||||
/** Changes the bounding box transform to match the content area, so that any sub-items will
|
||||
be drawn at their untransformed positions.
|
||||
*/
|
||||
void resetBoundingBoxToContentArea();
|
||||
|
||||
/** Returns the main content rectangle.
|
||||
The content area is actually defined by the markers named "left", "right", "top" and
|
||||
"bottom", but this method is a shortcut that returns them all at once.
|
||||
@see contentLeftMarkerName, contentRightMarkerName, contentTopMarkerName, contentBottomMarkerName
|
||||
*/
|
||||
RelativeRectangle getContentArea() const;
|
||||
|
||||
/** Changes the main content area.
|
||||
The content area is actually defined by the markers named "left", "right", "top" and
|
||||
"bottom", but this method is a shortcut that sets them all at once.
|
||||
@see setBoundingBox, contentLeftMarkerName, contentRightMarkerName, contentTopMarkerName, contentBottomMarkerName
|
||||
*/
|
||||
void setContentArea (const RelativeRectangle& newArea);
|
||||
|
||||
/** Resets the content area and the bounding transform to fit around the area occupied
|
||||
by the child components (ignoring any markers).
|
||||
*/
|
||||
void resetContentAreaAndBoundingBoxToFitChildren();
|
||||
|
||||
//==============================================================================
|
||||
/** The name of the marker that defines the left edge of the content area. */
|
||||
static const char* const contentLeftMarkerName;
|
||||
/** The name of the marker that defines the right edge of the content area. */
|
||||
static const char* const contentRightMarkerName;
|
||||
/** The name of the marker that defines the top edge of the content area. */
|
||||
static const char* const contentTopMarkerName;
|
||||
/** The name of the marker that defines the bottom edge of the content area. */
|
||||
static const char* const contentBottomMarkerName;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
Drawable* createCopy() const;
|
||||
/** @internal */
|
||||
void refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder);
|
||||
/** @internal */
|
||||
ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const;
|
||||
/** @internal */
|
||||
static const Identifier valueTreeType;
|
||||
/** @internal */
|
||||
Rectangle<float> getDrawableBounds() const;
|
||||
/** @internal */
|
||||
void childBoundsChanged (Component*) override;
|
||||
/** @internal */
|
||||
void childrenChanged() override;
|
||||
/** @internal */
|
||||
void parentHierarchyChanged() override;
|
||||
/** @internal */
|
||||
MarkerList* getMarkers (bool xAxis) override;
|
||||
|
||||
//==============================================================================
|
||||
/** Internally-used class for wrapping a DrawableComposite's state into a ValueTree. */
|
||||
class ValueTreeWrapper : public Drawable::ValueTreeWrapperBase
|
||||
{
|
||||
public:
|
||||
ValueTreeWrapper (const ValueTree& state);
|
||||
|
||||
ValueTree getChildList() const;
|
||||
ValueTree getChildListCreating (UndoManager* undoManager);
|
||||
|
||||
RelativeParallelogram getBoundingBox() const;
|
||||
void setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager);
|
||||
void resetBoundingBoxToContentArea (UndoManager* undoManager);
|
||||
|
||||
RelativeRectangle getContentArea() const;
|
||||
void setContentArea (const RelativeRectangle& newArea, UndoManager* undoManager);
|
||||
|
||||
MarkerList::ValueTreeWrapper getMarkerList (bool xAxis) const;
|
||||
MarkerList::ValueTreeWrapper getMarkerListCreating (bool xAxis, UndoManager* undoManager);
|
||||
|
||||
static const Identifier topLeft, topRight, bottomLeft;
|
||||
|
||||
private:
|
||||
static const Identifier childGroupTag, markerGroupTagX, markerGroupTagY;
|
||||
};
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
RelativeParallelogram bounds;
|
||||
MarkerList markersX, markersY;
|
||||
bool updateBoundsReentrant;
|
||||
|
||||
friend class Drawable::Positioner<DrawableComposite>;
|
||||
bool registerCoordinates (RelativeCoordinatePositionerBase&);
|
||||
void recalculateCoordinates (Expression::Scope*);
|
||||
|
||||
void updateBoundsToFitChildren();
|
||||
|
||||
DrawableComposite& operator= (const DrawableComposite&);
|
||||
JUCE_LEAK_DETECTOR (DrawableComposite)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_DRAWABLECOMPOSITE_H_INCLUDED
|
||||
|
|
@ -0,0 +1,289 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
DrawableImage::DrawableImage()
|
||||
: opacity (1.0f),
|
||||
overlayColour (0x00000000)
|
||||
{
|
||||
bounds.topRight = RelativePoint (Point<float> (1.0f, 0.0f));
|
||||
bounds.bottomLeft = RelativePoint (Point<float> (0.0f, 1.0f));
|
||||
}
|
||||
|
||||
DrawableImage::DrawableImage (const DrawableImage& other)
|
||||
: Drawable (other),
|
||||
image (other.image),
|
||||
opacity (other.opacity),
|
||||
overlayColour (other.overlayColour),
|
||||
bounds (other.bounds)
|
||||
{
|
||||
}
|
||||
|
||||
DrawableImage::~DrawableImage()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawableImage::setImage (const Image& imageToUse)
|
||||
{
|
||||
image = imageToUse;
|
||||
setBounds (imageToUse.getBounds());
|
||||
|
||||
bounds.topLeft = RelativePoint (Point<float> (0.0f, 0.0f));
|
||||
bounds.topRight = RelativePoint (Point<float> ((float) image.getWidth(), 0.0f));
|
||||
bounds.bottomLeft = RelativePoint (Point<float> (0.0f, (float) image.getHeight()));
|
||||
recalculateCoordinates (nullptr);
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
void DrawableImage::setOpacity (const float newOpacity)
|
||||
{
|
||||
opacity = newOpacity;
|
||||
}
|
||||
|
||||
void DrawableImage::setOverlayColour (Colour newOverlayColour)
|
||||
{
|
||||
overlayColour = newOverlayColour;
|
||||
}
|
||||
|
||||
void DrawableImage::setBoundingBox (const RelativeParallelogram& newBounds)
|
||||
{
|
||||
if (bounds != newBounds)
|
||||
{
|
||||
bounds = newBounds;
|
||||
|
||||
if (bounds.isDynamic())
|
||||
{
|
||||
Drawable::Positioner<DrawableImage>* const p = new Drawable::Positioner<DrawableImage> (*this);
|
||||
setPositioner (p);
|
||||
p->apply();
|
||||
}
|
||||
else
|
||||
{
|
||||
setPositioner (nullptr);
|
||||
recalculateCoordinates (nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool DrawableImage::registerCoordinates (RelativeCoordinatePositionerBase& pos)
|
||||
{
|
||||
bool ok = pos.addPoint (bounds.topLeft);
|
||||
ok = pos.addPoint (bounds.topRight) && ok;
|
||||
return pos.addPoint (bounds.bottomLeft) && ok;
|
||||
}
|
||||
|
||||
void DrawableImage::recalculateCoordinates (Expression::Scope* scope)
|
||||
{
|
||||
if (image.isValid())
|
||||
{
|
||||
Point<float> resolved[3];
|
||||
bounds.resolveThreePoints (resolved, scope);
|
||||
|
||||
const Point<float> tr (resolved[0] + (resolved[1] - resolved[0]) / (float) image.getWidth());
|
||||
const Point<float> bl (resolved[0] + (resolved[2] - resolved[0]) / (float) image.getHeight());
|
||||
|
||||
AffineTransform t (AffineTransform::fromTargetPoints (resolved[0].x, resolved[0].y,
|
||||
tr.x, tr.y,
|
||||
bl.x, bl.y));
|
||||
|
||||
if (t.isSingularity())
|
||||
t = AffineTransform::identity;
|
||||
|
||||
setTransform (t);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawableImage::paint (Graphics& g)
|
||||
{
|
||||
if (image.isValid())
|
||||
{
|
||||
if (opacity > 0.0f && ! overlayColour.isOpaque())
|
||||
{
|
||||
g.setOpacity (opacity);
|
||||
g.drawImageAt (image, 0, 0, false);
|
||||
}
|
||||
|
||||
if (! overlayColour.isTransparent())
|
||||
{
|
||||
g.setColour (overlayColour.withMultipliedAlpha (opacity));
|
||||
g.drawImageAt (image, 0, 0, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle<float> DrawableImage::getDrawableBounds() const
|
||||
{
|
||||
return image.getBounds().toFloat();
|
||||
}
|
||||
|
||||
bool DrawableImage::hitTest (int x, int y)
|
||||
{
|
||||
return Drawable::hitTest (x, y) && image.isValid() && image.getPixelAt (x, y).getAlpha() >= 127;
|
||||
}
|
||||
|
||||
Drawable* DrawableImage::createCopy() const
|
||||
{
|
||||
return new DrawableImage (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const Identifier DrawableImage::valueTreeType ("Image");
|
||||
|
||||
const Identifier DrawableImage::ValueTreeWrapper::opacity ("opacity");
|
||||
const Identifier DrawableImage::ValueTreeWrapper::overlay ("overlay");
|
||||
const Identifier DrawableImage::ValueTreeWrapper::image ("image");
|
||||
const Identifier DrawableImage::ValueTreeWrapper::topLeft ("topLeft");
|
||||
const Identifier DrawableImage::ValueTreeWrapper::topRight ("topRight");
|
||||
const Identifier DrawableImage::ValueTreeWrapper::bottomLeft ("bottomLeft");
|
||||
|
||||
//==============================================================================
|
||||
DrawableImage::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_)
|
||||
: ValueTreeWrapperBase (state_)
|
||||
{
|
||||
jassert (state.hasType (valueTreeType));
|
||||
}
|
||||
|
||||
var DrawableImage::ValueTreeWrapper::getImageIdentifier() const
|
||||
{
|
||||
return state [image];
|
||||
}
|
||||
|
||||
Value DrawableImage::ValueTreeWrapper::getImageIdentifierValue (UndoManager* undoManager)
|
||||
{
|
||||
return state.getPropertyAsValue (image, undoManager);
|
||||
}
|
||||
|
||||
void DrawableImage::ValueTreeWrapper::setImageIdentifier (const var& newIdentifier, UndoManager* undoManager)
|
||||
{
|
||||
state.setProperty (image, newIdentifier, undoManager);
|
||||
}
|
||||
|
||||
float DrawableImage::ValueTreeWrapper::getOpacity() const
|
||||
{
|
||||
return (float) state.getProperty (opacity, 1.0);
|
||||
}
|
||||
|
||||
Value DrawableImage::ValueTreeWrapper::getOpacityValue (UndoManager* undoManager)
|
||||
{
|
||||
if (! state.hasProperty (opacity))
|
||||
state.setProperty (opacity, 1.0, undoManager);
|
||||
|
||||
return state.getPropertyAsValue (opacity, undoManager);
|
||||
}
|
||||
|
||||
void DrawableImage::ValueTreeWrapper::setOpacity (float newOpacity, UndoManager* undoManager)
|
||||
{
|
||||
state.setProperty (opacity, newOpacity, undoManager);
|
||||
}
|
||||
|
||||
Colour DrawableImage::ValueTreeWrapper::getOverlayColour() const
|
||||
{
|
||||
return Colour::fromString (state [overlay].toString());
|
||||
}
|
||||
|
||||
void DrawableImage::ValueTreeWrapper::setOverlayColour (Colour newColour, UndoManager* undoManager)
|
||||
{
|
||||
if (newColour.isTransparent())
|
||||
state.removeProperty (overlay, undoManager);
|
||||
else
|
||||
state.setProperty (overlay, String::toHexString ((int) newColour.getARGB()), undoManager);
|
||||
}
|
||||
|
||||
Value DrawableImage::ValueTreeWrapper::getOverlayColourValue (UndoManager* undoManager)
|
||||
{
|
||||
return state.getPropertyAsValue (overlay, undoManager);
|
||||
}
|
||||
|
||||
RelativeParallelogram DrawableImage::ValueTreeWrapper::getBoundingBox() const
|
||||
{
|
||||
return RelativeParallelogram (state.getProperty (topLeft, "0, 0"),
|
||||
state.getProperty (topRight, "100, 0"),
|
||||
state.getProperty (bottomLeft, "0, 100"));
|
||||
}
|
||||
|
||||
void DrawableImage::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager)
|
||||
{
|
||||
state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager);
|
||||
state.setProperty (topRight, newBounds.topRight.toString(), undoManager);
|
||||
state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager);
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
void DrawableImage::refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder)
|
||||
{
|
||||
const ValueTreeWrapper controller (tree);
|
||||
setComponentID (controller.getID());
|
||||
|
||||
const float newOpacity = controller.getOpacity();
|
||||
const Colour newOverlayColour (controller.getOverlayColour());
|
||||
|
||||
Image newImage;
|
||||
const var imageIdentifier (controller.getImageIdentifier());
|
||||
|
||||
|
||||
jassert (builder.getImageProvider() != 0 || imageIdentifier.isVoid()); // if you're using images, you need to provide something that can load and save them!
|
||||
|
||||
if (builder.getImageProvider() != nullptr)
|
||||
newImage = builder.getImageProvider()->getImageForIdentifier (imageIdentifier);
|
||||
|
||||
const RelativeParallelogram newBounds (controller.getBoundingBox());
|
||||
|
||||
if (bounds != newBounds || newOpacity != opacity
|
||||
|| overlayColour != newOverlayColour || image != newImage)
|
||||
{
|
||||
repaint();
|
||||
opacity = newOpacity;
|
||||
overlayColour = newOverlayColour;
|
||||
|
||||
if (image != newImage)
|
||||
setImage (newImage);
|
||||
|
||||
setBoundingBox (newBounds);
|
||||
}
|
||||
}
|
||||
|
||||
ValueTree DrawableImage::createValueTree (ComponentBuilder::ImageProvider* imageProvider) const
|
||||
{
|
||||
ValueTree tree (valueTreeType);
|
||||
ValueTreeWrapper v (tree);
|
||||
|
||||
v.setID (getComponentID());
|
||||
v.setOpacity (opacity, nullptr);
|
||||
v.setOverlayColour (overlayColour, nullptr);
|
||||
v.setBoundingBox (bounds, nullptr);
|
||||
|
||||
if (image.isValid())
|
||||
{
|
||||
jassert (imageProvider != nullptr); // if you're using images, you need to provide something that can load and save them!
|
||||
|
||||
if (imageProvider != nullptr)
|
||||
v.setImageIdentifier (imageProvider->getIdentifierForImage (image), nullptr);
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_DRAWABLEIMAGE_H_INCLUDED
|
||||
#define JUCE_DRAWABLEIMAGE_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A drawable object which is a bitmap image.
|
||||
|
||||
@see Drawable
|
||||
*/
|
||||
class JUCE_API DrawableImage : public Drawable
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
DrawableImage();
|
||||
DrawableImage (const DrawableImage&);
|
||||
|
||||
/** Destructor. */
|
||||
~DrawableImage();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the image that this drawable will render. */
|
||||
void setImage (const Image& imageToUse);
|
||||
|
||||
/** Returns the current image. */
|
||||
const Image& getImage() const noexcept { return image; }
|
||||
|
||||
/** Sets the opacity to use when drawing the image. */
|
||||
void setOpacity (float newOpacity);
|
||||
|
||||
/** Returns the image's opacity. */
|
||||
float getOpacity() const noexcept { return opacity; }
|
||||
|
||||
/** Sets a colour to draw over the image's alpha channel.
|
||||
|
||||
By default this is transparent so isn't drawn, but if you set a non-transparent
|
||||
colour here, then it will be overlaid on the image, using the image's alpha
|
||||
channel as a mask.
|
||||
|
||||
This is handy for doing things like darkening or lightening an image by overlaying
|
||||
it with semi-transparent black or white.
|
||||
*/
|
||||
void setOverlayColour (Colour newOverlayColour);
|
||||
|
||||
/** Returns the overlay colour. */
|
||||
Colour getOverlayColour() const noexcept { return overlayColour; }
|
||||
|
||||
/** Sets the bounding box within which the image should be displayed. */
|
||||
void setBoundingBox (const RelativeParallelogram& newBounds);
|
||||
|
||||
/** Returns the position to which the image's top-left corner should be remapped in the target
|
||||
coordinate space when rendering this object.
|
||||
@see setTransform
|
||||
*/
|
||||
const RelativeParallelogram& getBoundingBox() const noexcept { return bounds; }
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
bool hitTest (int x, int y) override;
|
||||
/** @internal */
|
||||
Drawable* createCopy() const override;
|
||||
/** @internal */
|
||||
Rectangle<float> getDrawableBounds() const override;
|
||||
/** @internal */
|
||||
void refreshFromValueTree (const ValueTree& tree, ComponentBuilder&);
|
||||
/** @internal */
|
||||
ValueTree createValueTree (ComponentBuilder::ImageProvider*) const override;
|
||||
/** @internal */
|
||||
static const Identifier valueTreeType;
|
||||
|
||||
//==============================================================================
|
||||
/** Internally-used class for wrapping a DrawableImage's state into a ValueTree. */
|
||||
class ValueTreeWrapper : public Drawable::ValueTreeWrapperBase
|
||||
{
|
||||
public:
|
||||
ValueTreeWrapper (const ValueTree& state);
|
||||
|
||||
var getImageIdentifier() const;
|
||||
void setImageIdentifier (const var&, UndoManager*);
|
||||
Value getImageIdentifierValue (UndoManager*);
|
||||
|
||||
float getOpacity() const;
|
||||
void setOpacity (float newOpacity, UndoManager*);
|
||||
Value getOpacityValue (UndoManager*);
|
||||
|
||||
Colour getOverlayColour() const;
|
||||
void setOverlayColour (Colour newColour, UndoManager*);
|
||||
Value getOverlayColourValue (UndoManager*);
|
||||
|
||||
RelativeParallelogram getBoundingBox() const;
|
||||
void setBoundingBox (const RelativeParallelogram&, UndoManager*);
|
||||
|
||||
static const Identifier opacity, overlay, image, topLeft, topRight, bottomLeft;
|
||||
};
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Image image;
|
||||
float opacity;
|
||||
Colour overlayColour;
|
||||
RelativeParallelogram bounds;
|
||||
|
||||
friend class Drawable::Positioner<DrawableImage>;
|
||||
bool registerCoordinates (RelativeCoordinatePositionerBase&);
|
||||
void recalculateCoordinates (Expression::Scope*);
|
||||
|
||||
DrawableImage& operator= (const DrawableImage&);
|
||||
JUCE_LEAK_DETECTOR (DrawableImage)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_DRAWABLEIMAGE_H_INCLUDED
|
||||
|
|
@ -0,0 +1,570 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
DrawablePath::DrawablePath()
|
||||
{
|
||||
}
|
||||
|
||||
DrawablePath::DrawablePath (const DrawablePath& other)
|
||||
: DrawableShape (other)
|
||||
{
|
||||
if (other.relativePath != nullptr)
|
||||
setPath (*other.relativePath);
|
||||
else
|
||||
setPath (other.path);
|
||||
}
|
||||
|
||||
DrawablePath::~DrawablePath()
|
||||
{
|
||||
}
|
||||
|
||||
Drawable* DrawablePath::createCopy() const
|
||||
{
|
||||
return new DrawablePath (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawablePath::setPath (const Path& newPath)
|
||||
{
|
||||
path = newPath;
|
||||
pathChanged();
|
||||
}
|
||||
|
||||
const Path& DrawablePath::getPath() const
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
const Path& DrawablePath::getStrokePath() const
|
||||
{
|
||||
return strokePath;
|
||||
}
|
||||
|
||||
void DrawablePath::applyRelativePath (const RelativePointPath& newRelativePath, Expression::Scope* scope)
|
||||
{
|
||||
Path newPath;
|
||||
newRelativePath.createPath (newPath, scope);
|
||||
|
||||
if (path != newPath)
|
||||
{
|
||||
path.swapWithPath (newPath);
|
||||
pathChanged();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class DrawablePath::RelativePositioner : public RelativeCoordinatePositionerBase
|
||||
{
|
||||
public:
|
||||
RelativePositioner (DrawablePath& comp)
|
||||
: RelativeCoordinatePositionerBase (comp),
|
||||
owner (comp)
|
||||
{
|
||||
}
|
||||
|
||||
bool registerCoordinates()
|
||||
{
|
||||
bool ok = true;
|
||||
|
||||
jassert (owner.relativePath != nullptr);
|
||||
const RelativePointPath& path = *owner.relativePath;
|
||||
|
||||
for (int i = 0; i < path.elements.size(); ++i)
|
||||
{
|
||||
RelativePointPath::ElementBase* const e = path.elements.getUnchecked(i);
|
||||
|
||||
int numPoints;
|
||||
RelativePoint* const points = e->getControlPoints (numPoints);
|
||||
|
||||
for (int j = numPoints; --j >= 0;)
|
||||
ok = addPoint (points[j]) && ok;
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
void applyToComponentBounds()
|
||||
{
|
||||
jassert (owner.relativePath != nullptr);
|
||||
|
||||
ComponentScope scope (getComponent());
|
||||
owner.applyRelativePath (*owner.relativePath, &scope);
|
||||
}
|
||||
|
||||
void applyNewBounds (const Rectangle<int>&)
|
||||
{
|
||||
jassertfalse; // drawables can't be resized directly!
|
||||
}
|
||||
|
||||
private:
|
||||
DrawablePath& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RelativePositioner)
|
||||
};
|
||||
|
||||
void DrawablePath::setPath (const RelativePointPath& newRelativePath)
|
||||
{
|
||||
if (newRelativePath.containsAnyDynamicPoints())
|
||||
{
|
||||
if (relativePath == nullptr || newRelativePath != *relativePath)
|
||||
{
|
||||
relativePath = new RelativePointPath (newRelativePath);
|
||||
|
||||
RelativePositioner* const p = new RelativePositioner (*this);
|
||||
setPositioner (p);
|
||||
p->apply();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
relativePath = nullptr;
|
||||
applyRelativePath (newRelativePath, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const Identifier DrawablePath::valueTreeType ("Path");
|
||||
|
||||
const Identifier DrawablePath::ValueTreeWrapper::nonZeroWinding ("nonZeroWinding");
|
||||
const Identifier DrawablePath::ValueTreeWrapper::point1 ("p1");
|
||||
const Identifier DrawablePath::ValueTreeWrapper::point2 ("p2");
|
||||
const Identifier DrawablePath::ValueTreeWrapper::point3 ("p3");
|
||||
|
||||
//==============================================================================
|
||||
DrawablePath::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_)
|
||||
: FillAndStrokeState (state_)
|
||||
{
|
||||
jassert (state.hasType (valueTreeType));
|
||||
}
|
||||
|
||||
ValueTree DrawablePath::ValueTreeWrapper::getPathState()
|
||||
{
|
||||
return state.getOrCreateChildWithName (path, nullptr);
|
||||
}
|
||||
|
||||
bool DrawablePath::ValueTreeWrapper::usesNonZeroWinding() const
|
||||
{
|
||||
return state [nonZeroWinding];
|
||||
}
|
||||
|
||||
void DrawablePath::ValueTreeWrapper::setUsesNonZeroWinding (bool b, UndoManager* undoManager)
|
||||
{
|
||||
state.setProperty (nonZeroWinding, b, undoManager);
|
||||
}
|
||||
|
||||
void DrawablePath::ValueTreeWrapper::readFrom (const RelativePointPath& p, UndoManager* undoManager)
|
||||
{
|
||||
setUsesNonZeroWinding (p.usesNonZeroWinding, undoManager);
|
||||
|
||||
ValueTree pathTree (getPathState());
|
||||
pathTree.removeAllChildren (undoManager);
|
||||
|
||||
for (int i = 0; i < p.elements.size(); ++i)
|
||||
pathTree.addChild (p.elements.getUnchecked(i)->createTree(), -1, undoManager);
|
||||
}
|
||||
|
||||
void DrawablePath::ValueTreeWrapper::writeTo (RelativePointPath& p) const
|
||||
{
|
||||
p.usesNonZeroWinding = usesNonZeroWinding();
|
||||
RelativePoint points[3];
|
||||
|
||||
const ValueTree pathTree (state.getChildWithName (path));
|
||||
const int num = pathTree.getNumChildren();
|
||||
for (int i = 0; i < num; ++i)
|
||||
{
|
||||
const Element e (pathTree.getChild(i));
|
||||
|
||||
const int numCps = e.getNumControlPoints();
|
||||
for (int j = 0; j < numCps; ++j)
|
||||
points[j] = e.getControlPoint (j);
|
||||
|
||||
RelativePointPath::ElementBase* newElement = nullptr;
|
||||
const Identifier t (e.getType());
|
||||
|
||||
if (t == Element::startSubPathElement) newElement = new RelativePointPath::StartSubPath (points[0]);
|
||||
else if (t == Element::closeSubPathElement) newElement = new RelativePointPath::CloseSubPath();
|
||||
else if (t == Element::lineToElement) newElement = new RelativePointPath::LineTo (points[0]);
|
||||
else if (t == Element::quadraticToElement) newElement = new RelativePointPath::QuadraticTo (points[0], points[1]);
|
||||
else if (t == Element::cubicToElement) newElement = new RelativePointPath::CubicTo (points[0], points[1], points[2]);
|
||||
else jassertfalse;
|
||||
|
||||
p.addElement (newElement);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const Identifier DrawablePath::ValueTreeWrapper::Element::mode ("mode");
|
||||
const Identifier DrawablePath::ValueTreeWrapper::Element::startSubPathElement ("Move");
|
||||
const Identifier DrawablePath::ValueTreeWrapper::Element::closeSubPathElement ("Close");
|
||||
const Identifier DrawablePath::ValueTreeWrapper::Element::lineToElement ("Line");
|
||||
const Identifier DrawablePath::ValueTreeWrapper::Element::quadraticToElement ("Quad");
|
||||
const Identifier DrawablePath::ValueTreeWrapper::Element::cubicToElement ("Cubic");
|
||||
|
||||
const char* DrawablePath::ValueTreeWrapper::Element::cornerMode = "corner";
|
||||
const char* DrawablePath::ValueTreeWrapper::Element::roundedMode = "round";
|
||||
const char* DrawablePath::ValueTreeWrapper::Element::symmetricMode = "symm";
|
||||
|
||||
DrawablePath::ValueTreeWrapper::Element::Element (const ValueTree& state_)
|
||||
: state (state_)
|
||||
{
|
||||
}
|
||||
|
||||
DrawablePath::ValueTreeWrapper::Element::~Element()
|
||||
{
|
||||
}
|
||||
|
||||
DrawablePath::ValueTreeWrapper DrawablePath::ValueTreeWrapper::Element::getParent() const
|
||||
{
|
||||
return ValueTreeWrapper (state.getParent().getParent());
|
||||
}
|
||||
|
||||
DrawablePath::ValueTreeWrapper::Element DrawablePath::ValueTreeWrapper::Element::getPreviousElement() const
|
||||
{
|
||||
return Element (state.getSibling (-1));
|
||||
}
|
||||
|
||||
int DrawablePath::ValueTreeWrapper::Element::getNumControlPoints() const noexcept
|
||||
{
|
||||
const Identifier i (state.getType());
|
||||
if (i == startSubPathElement || i == lineToElement) return 1;
|
||||
if (i == quadraticToElement) return 2;
|
||||
if (i == cubicToElement) return 3;
|
||||
return 0;
|
||||
}
|
||||
|
||||
RelativePoint DrawablePath::ValueTreeWrapper::Element::getControlPoint (const int index) const
|
||||
{
|
||||
jassert (index >= 0 && index < getNumControlPoints());
|
||||
return RelativePoint (state [index == 0 ? point1 : (index == 1 ? point2 : point3)].toString());
|
||||
}
|
||||
|
||||
Value DrawablePath::ValueTreeWrapper::Element::getControlPointValue (int index, UndoManager* undoManager)
|
||||
{
|
||||
jassert (index >= 0 && index < getNumControlPoints());
|
||||
return state.getPropertyAsValue (index == 0 ? point1 : (index == 1 ? point2 : point3), undoManager);
|
||||
}
|
||||
|
||||
void DrawablePath::ValueTreeWrapper::Element::setControlPoint (const int index, const RelativePoint& point, UndoManager* undoManager)
|
||||
{
|
||||
jassert (index >= 0 && index < getNumControlPoints());
|
||||
state.setProperty (index == 0 ? point1 : (index == 1 ? point2 : point3), point.toString(), undoManager);
|
||||
}
|
||||
|
||||
RelativePoint DrawablePath::ValueTreeWrapper::Element::getStartPoint() const
|
||||
{
|
||||
const Identifier i (state.getType());
|
||||
|
||||
if (i == startSubPathElement)
|
||||
return getControlPoint (0);
|
||||
|
||||
jassert (i == lineToElement || i == quadraticToElement || i == cubicToElement || i == closeSubPathElement);
|
||||
|
||||
return getPreviousElement().getEndPoint();
|
||||
}
|
||||
|
||||
RelativePoint DrawablePath::ValueTreeWrapper::Element::getEndPoint() const
|
||||
{
|
||||
const Identifier i (state.getType());
|
||||
if (i == startSubPathElement || i == lineToElement) return getControlPoint (0);
|
||||
if (i == quadraticToElement) return getControlPoint (1);
|
||||
if (i == cubicToElement) return getControlPoint (2);
|
||||
|
||||
jassert (i == closeSubPathElement);
|
||||
return RelativePoint();
|
||||
}
|
||||
|
||||
float DrawablePath::ValueTreeWrapper::Element::getLength (Expression::Scope* scope) const
|
||||
{
|
||||
const Identifier i (state.getType());
|
||||
|
||||
if (i == lineToElement || i == closeSubPathElement)
|
||||
return getEndPoint().resolve (scope).getDistanceFrom (getStartPoint().resolve (scope));
|
||||
|
||||
if (i == cubicToElement)
|
||||
{
|
||||
Path p;
|
||||
p.startNewSubPath (getStartPoint().resolve (scope));
|
||||
p.cubicTo (getControlPoint (0).resolve (scope), getControlPoint (1).resolve (scope), getControlPoint (2).resolve (scope));
|
||||
return p.getLength();
|
||||
}
|
||||
|
||||
if (i == quadraticToElement)
|
||||
{
|
||||
Path p;
|
||||
p.startNewSubPath (getStartPoint().resolve (scope));
|
||||
p.quadraticTo (getControlPoint (0).resolve (scope), getControlPoint (1).resolve (scope));
|
||||
return p.getLength();
|
||||
}
|
||||
|
||||
jassert (i == startSubPathElement);
|
||||
return 0;
|
||||
}
|
||||
|
||||
String DrawablePath::ValueTreeWrapper::Element::getModeOfEndPoint() const
|
||||
{
|
||||
return state [mode].toString();
|
||||
}
|
||||
|
||||
void DrawablePath::ValueTreeWrapper::Element::setModeOfEndPoint (const String& newMode, UndoManager* undoManager)
|
||||
{
|
||||
if (state.hasType (cubicToElement))
|
||||
state.setProperty (mode, newMode, undoManager);
|
||||
}
|
||||
|
||||
void DrawablePath::ValueTreeWrapper::Element::convertToLine (UndoManager* undoManager)
|
||||
{
|
||||
const Identifier i (state.getType());
|
||||
|
||||
if (i == quadraticToElement || i == cubicToElement)
|
||||
{
|
||||
ValueTree newState (lineToElement);
|
||||
Element e (newState);
|
||||
e.setControlPoint (0, getEndPoint(), undoManager);
|
||||
state = newState;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawablePath::ValueTreeWrapper::Element::convertToCubic (Expression::Scope* scope, UndoManager* undoManager)
|
||||
{
|
||||
const Identifier i (state.getType());
|
||||
|
||||
if (i == lineToElement || i == quadraticToElement)
|
||||
{
|
||||
ValueTree newState (cubicToElement);
|
||||
Element e (newState);
|
||||
|
||||
const RelativePoint start (getStartPoint());
|
||||
const RelativePoint end (getEndPoint());
|
||||
const Point<float> startResolved (start.resolve (scope));
|
||||
const Point<float> endResolved (end.resolve (scope));
|
||||
e.setControlPoint (0, startResolved + (endResolved - startResolved) * 0.3f, undoManager);
|
||||
e.setControlPoint (1, startResolved + (endResolved - startResolved) * 0.7f, undoManager);
|
||||
e.setControlPoint (2, end, undoManager);
|
||||
|
||||
state = newState;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawablePath::ValueTreeWrapper::Element::convertToPathBreak (UndoManager* undoManager)
|
||||
{
|
||||
const Identifier i (state.getType());
|
||||
|
||||
if (i != startSubPathElement)
|
||||
{
|
||||
ValueTree newState (startSubPathElement);
|
||||
Element e (newState);
|
||||
e.setControlPoint (0, getEndPoint(), undoManager);
|
||||
state = newState;
|
||||
}
|
||||
}
|
||||
|
||||
namespace DrawablePathHelpers
|
||||
{
|
||||
static Point<float> findCubicSubdivisionPoint (float proportion, const Point<float> points[4])
|
||||
{
|
||||
const Point<float> mid1 (points[0] + (points[1] - points[0]) * proportion),
|
||||
mid2 (points[1] + (points[2] - points[1]) * proportion),
|
||||
mid3 (points[2] + (points[3] - points[2]) * proportion);
|
||||
|
||||
const Point<float> newCp1 (mid1 + (mid2 - mid1) * proportion),
|
||||
newCp2 (mid2 + (mid3 - mid2) * proportion);
|
||||
|
||||
return newCp1 + (newCp2 - newCp1) * proportion;
|
||||
}
|
||||
|
||||
static Point<float> findQuadraticSubdivisionPoint (float proportion, const Point<float> points[3])
|
||||
{
|
||||
const Point<float> mid1 (points[0] + (points[1] - points[0]) * proportion),
|
||||
mid2 (points[1] + (points[2] - points[1]) * proportion);
|
||||
|
||||
return mid1 + (mid2 - mid1) * proportion;
|
||||
}
|
||||
}
|
||||
|
||||
float DrawablePath::ValueTreeWrapper::Element::findProportionAlongLine (Point<float> targetPoint, Expression::Scope* scope) const
|
||||
{
|
||||
using namespace DrawablePathHelpers;
|
||||
const Identifier pointType (state.getType());
|
||||
float bestProp = 0;
|
||||
|
||||
if (pointType == cubicToElement)
|
||||
{
|
||||
RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getControlPoint (1)), rp4 (getEndPoint());
|
||||
|
||||
const Point<float> points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope), rp4.resolve (scope) };
|
||||
|
||||
float bestDistance = std::numeric_limits<float>::max();
|
||||
|
||||
for (int i = 110; --i >= 0;)
|
||||
{
|
||||
float prop = i > 10 ? ((i - 10) / 100.0f) : (bestProp + ((i - 5) / 1000.0f));
|
||||
const Point<float> centre (findCubicSubdivisionPoint (prop, points));
|
||||
const float distance = centre.getDistanceFrom (targetPoint);
|
||||
|
||||
if (distance < bestDistance)
|
||||
{
|
||||
bestProp = prop;
|
||||
bestDistance = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (pointType == quadraticToElement)
|
||||
{
|
||||
RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getEndPoint());
|
||||
const Point<float> points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope) };
|
||||
|
||||
float bestDistance = std::numeric_limits<float>::max();
|
||||
|
||||
for (int i = 110; --i >= 0;)
|
||||
{
|
||||
float prop = i > 10 ? ((i - 10) / 100.0f) : (bestProp + ((i - 5) / 1000.0f));
|
||||
const Point<float> centre (findQuadraticSubdivisionPoint ((float) prop, points));
|
||||
const float distance = centre.getDistanceFrom (targetPoint);
|
||||
|
||||
if (distance < bestDistance)
|
||||
{
|
||||
bestProp = prop;
|
||||
bestDistance = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (pointType == lineToElement)
|
||||
{
|
||||
RelativePoint rp1 (getStartPoint()), rp2 (getEndPoint());
|
||||
const Line<float> line (rp1.resolve (scope), rp2.resolve (scope));
|
||||
bestProp = line.findNearestProportionalPositionTo (targetPoint);
|
||||
}
|
||||
|
||||
return bestProp;
|
||||
}
|
||||
|
||||
ValueTree DrawablePath::ValueTreeWrapper::Element::insertPoint (Point<float> targetPoint, Expression::Scope* scope, UndoManager* undoManager)
|
||||
{
|
||||
ValueTree newTree;
|
||||
const Identifier pointType (state.getType());
|
||||
|
||||
if (pointType == cubicToElement)
|
||||
{
|
||||
float bestProp = findProportionAlongLine (targetPoint, scope);
|
||||
|
||||
RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getControlPoint (1)), rp4 (getEndPoint());
|
||||
const Point<float> points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope), rp4.resolve (scope) };
|
||||
|
||||
const Point<float> mid1 (points[0] + (points[1] - points[0]) * bestProp),
|
||||
mid2 (points[1] + (points[2] - points[1]) * bestProp),
|
||||
mid3 (points[2] + (points[3] - points[2]) * bestProp);
|
||||
|
||||
const Point<float> newCp1 (mid1 + (mid2 - mid1) * bestProp),
|
||||
newCp2 (mid2 + (mid3 - mid2) * bestProp);
|
||||
|
||||
const Point<float> newCentre (newCp1 + (newCp2 - newCp1) * bestProp);
|
||||
|
||||
setControlPoint (0, mid1, undoManager);
|
||||
setControlPoint (1, newCp1, undoManager);
|
||||
setControlPoint (2, newCentre, undoManager);
|
||||
setModeOfEndPoint (roundedMode, undoManager);
|
||||
|
||||
Element newElement (newTree = ValueTree (cubicToElement));
|
||||
newElement.setControlPoint (0, newCp2, nullptr);
|
||||
newElement.setControlPoint (1, mid3, nullptr);
|
||||
newElement.setControlPoint (2, rp4, nullptr);
|
||||
|
||||
state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager);
|
||||
}
|
||||
else if (pointType == quadraticToElement)
|
||||
{
|
||||
float bestProp = findProportionAlongLine (targetPoint, scope);
|
||||
|
||||
RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getEndPoint());
|
||||
const Point<float> points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope) };
|
||||
|
||||
const Point<float> mid1 (points[0] + (points[1] - points[0]) * bestProp),
|
||||
mid2 (points[1] + (points[2] - points[1]) * bestProp);
|
||||
|
||||
const Point<float> newCentre (mid1 + (mid2 - mid1) * bestProp);
|
||||
|
||||
setControlPoint (0, mid1, undoManager);
|
||||
setControlPoint (1, newCentre, undoManager);
|
||||
setModeOfEndPoint (roundedMode, undoManager);
|
||||
|
||||
Element newElement (newTree = ValueTree (quadraticToElement));
|
||||
newElement.setControlPoint (0, mid2, nullptr);
|
||||
newElement.setControlPoint (1, rp3, nullptr);
|
||||
|
||||
state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager);
|
||||
}
|
||||
else if (pointType == lineToElement)
|
||||
{
|
||||
RelativePoint rp1 (getStartPoint()), rp2 (getEndPoint());
|
||||
const Line<float> line (rp1.resolve (scope), rp2.resolve (scope));
|
||||
const Point<float> newPoint (line.findNearestPointTo (targetPoint));
|
||||
|
||||
setControlPoint (0, newPoint, undoManager);
|
||||
|
||||
Element newElement (newTree = ValueTree (lineToElement));
|
||||
newElement.setControlPoint (0, rp2, nullptr);
|
||||
|
||||
state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager);
|
||||
}
|
||||
else if (pointType == closeSubPathElement)
|
||||
{
|
||||
}
|
||||
|
||||
return newTree;
|
||||
}
|
||||
|
||||
void DrawablePath::ValueTreeWrapper::Element::removePoint (UndoManager* undoManager)
|
||||
{
|
||||
state.getParent().removeChild (state, undoManager);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawablePath::refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder)
|
||||
{
|
||||
ValueTreeWrapper v (tree);
|
||||
setComponentID (v.getID());
|
||||
|
||||
refreshFillTypes (v, builder.getImageProvider());
|
||||
setStrokeType (v.getStrokeType());
|
||||
|
||||
RelativePointPath newRelativePath;
|
||||
v.writeTo (newRelativePath);
|
||||
setPath (newRelativePath);
|
||||
}
|
||||
|
||||
ValueTree DrawablePath::createValueTree (ComponentBuilder::ImageProvider* imageProvider) const
|
||||
{
|
||||
ValueTree tree (valueTreeType);
|
||||
ValueTreeWrapper v (tree);
|
||||
|
||||
v.setID (getComponentID());
|
||||
writeTo (v, imageProvider, nullptr);
|
||||
|
||||
if (relativePath != nullptr)
|
||||
v.readFrom (*relativePath, nullptr);
|
||||
else
|
||||
v.readFrom (RelativePointPath (path), nullptr);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_DRAWABLEPATH_H_INCLUDED
|
||||
#define JUCE_DRAWABLEPATH_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A drawable object which renders a filled or outlined shape.
|
||||
|
||||
For details on how to change the fill and stroke, see the DrawableShape class.
|
||||
|
||||
@see Drawable, DrawableShape
|
||||
*/
|
||||
class JUCE_API DrawablePath : public DrawableShape
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a DrawablePath. */
|
||||
DrawablePath();
|
||||
DrawablePath (const DrawablePath&);
|
||||
|
||||
/** Destructor. */
|
||||
~DrawablePath();
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the path that will be drawn.
|
||||
@see setFillColour, setStrokeType
|
||||
*/
|
||||
void setPath (const Path& newPath);
|
||||
|
||||
/** Sets the path using a RelativePointPath.
|
||||
Calling this will set up a Component::Positioner to automatically update the path
|
||||
if any of the points in the source path are dynamic.
|
||||
*/
|
||||
void setPath (const RelativePointPath& newPath);
|
||||
|
||||
/** Returns the current path. */
|
||||
const Path& getPath() const;
|
||||
|
||||
/** Returns the current path for the outline. */
|
||||
const Path& getStrokePath() const;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
Drawable* createCopy() const;
|
||||
/** @internal */
|
||||
void refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder);
|
||||
/** @internal */
|
||||
ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const;
|
||||
/** @internal */
|
||||
static const Identifier valueTreeType;
|
||||
|
||||
//==============================================================================
|
||||
/** Internally-used class for wrapping a DrawablePath's state into a ValueTree. */
|
||||
class ValueTreeWrapper : public DrawableShape::FillAndStrokeState
|
||||
{
|
||||
public:
|
||||
ValueTreeWrapper (const ValueTree& state);
|
||||
|
||||
bool usesNonZeroWinding() const;
|
||||
void setUsesNonZeroWinding (bool b, UndoManager* undoManager);
|
||||
|
||||
class Element
|
||||
{
|
||||
public:
|
||||
explicit Element (const ValueTree& state);
|
||||
~Element();
|
||||
|
||||
const Identifier getType() const noexcept { return state.getType(); }
|
||||
int getNumControlPoints() const noexcept;
|
||||
|
||||
RelativePoint getControlPoint (int index) const;
|
||||
Value getControlPointValue (int index, UndoManager*);
|
||||
RelativePoint getStartPoint() const;
|
||||
RelativePoint getEndPoint() const;
|
||||
void setControlPoint (int index, const RelativePoint& point, UndoManager*);
|
||||
float getLength (Expression::Scope*) const;
|
||||
|
||||
ValueTreeWrapper getParent() const;
|
||||
Element getPreviousElement() const;
|
||||
|
||||
String getModeOfEndPoint() const;
|
||||
void setModeOfEndPoint (const String& newMode, UndoManager*);
|
||||
|
||||
void convertToLine (UndoManager*);
|
||||
void convertToCubic (Expression::Scope*, UndoManager*);
|
||||
void convertToPathBreak (UndoManager* undoManager);
|
||||
ValueTree insertPoint (Point<float> targetPoint, Expression::Scope*, UndoManager*);
|
||||
void removePoint (UndoManager* undoManager);
|
||||
float findProportionAlongLine (Point<float> targetPoint, Expression::Scope*) const;
|
||||
|
||||
static const Identifier mode, startSubPathElement, closeSubPathElement,
|
||||
lineToElement, quadraticToElement, cubicToElement;
|
||||
static const char* cornerMode;
|
||||
static const char* roundedMode;
|
||||
static const char* symmetricMode;
|
||||
|
||||
ValueTree state;
|
||||
};
|
||||
|
||||
ValueTree getPathState();
|
||||
|
||||
void readFrom (const RelativePointPath& path, UndoManager* undoManager);
|
||||
void writeTo (RelativePointPath& path) const;
|
||||
|
||||
static const Identifier nonZeroWinding, point1, point2, point3;
|
||||
};
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
ScopedPointer<RelativePointPath> relativePath;
|
||||
|
||||
class RelativePositioner;
|
||||
friend class RelativePositioner;
|
||||
void applyRelativePath (const RelativePointPath&, Expression::Scope*);
|
||||
|
||||
DrawablePath& operator= (const DrawablePath&);
|
||||
JUCE_LEAK_DETECTOR (DrawablePath)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_DRAWABLEPATH_H_INCLUDED
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
DrawableRectangle::DrawableRectangle()
|
||||
{
|
||||
}
|
||||
|
||||
DrawableRectangle::DrawableRectangle (const DrawableRectangle& other)
|
||||
: DrawableShape (other),
|
||||
bounds (other.bounds),
|
||||
cornerSize (other.cornerSize)
|
||||
{
|
||||
}
|
||||
|
||||
DrawableRectangle::~DrawableRectangle()
|
||||
{
|
||||
}
|
||||
|
||||
Drawable* DrawableRectangle::createCopy() const
|
||||
{
|
||||
return new DrawableRectangle (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawableRectangle::setRectangle (const RelativeParallelogram& newBounds)
|
||||
{
|
||||
if (bounds != newBounds)
|
||||
{
|
||||
bounds = newBounds;
|
||||
rebuildPath();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableRectangle::setCornerSize (const RelativePoint& newSize)
|
||||
{
|
||||
if (cornerSize != newSize)
|
||||
{
|
||||
cornerSize = newSize;
|
||||
rebuildPath();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableRectangle::rebuildPath()
|
||||
{
|
||||
if (bounds.isDynamic() || cornerSize.isDynamic())
|
||||
{
|
||||
Drawable::Positioner<DrawableRectangle>* const p = new Drawable::Positioner<DrawableRectangle> (*this);
|
||||
setPositioner (p);
|
||||
p->apply();
|
||||
}
|
||||
else
|
||||
{
|
||||
setPositioner (nullptr);
|
||||
recalculateCoordinates (nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool DrawableRectangle::registerCoordinates (RelativeCoordinatePositionerBase& pos)
|
||||
{
|
||||
bool ok = pos.addPoint (bounds.topLeft);
|
||||
ok = pos.addPoint (bounds.topRight) && ok;
|
||||
ok = pos.addPoint (bounds.bottomLeft) && ok;
|
||||
return pos.addPoint (cornerSize) && ok;
|
||||
}
|
||||
|
||||
void DrawableRectangle::recalculateCoordinates (Expression::Scope* scope)
|
||||
{
|
||||
Point<float> points[3];
|
||||
bounds.resolveThreePoints (points, scope);
|
||||
|
||||
const float cornerSizeX = (float) cornerSize.x.resolve (scope);
|
||||
const float cornerSizeY = (float) cornerSize.y.resolve (scope);
|
||||
|
||||
const float w = Line<float> (points[0], points[1]).getLength();
|
||||
const float h = Line<float> (points[0], points[2]).getLength();
|
||||
|
||||
Path newPath;
|
||||
|
||||
if (cornerSizeX > 0 && cornerSizeY > 0)
|
||||
newPath.addRoundedRectangle (0, 0, w, h, cornerSizeX, cornerSizeY);
|
||||
else
|
||||
newPath.addRectangle (0, 0, w, h);
|
||||
|
||||
newPath.applyTransform (AffineTransform::fromTargetPoints (0, 0, points[0].x, points[0].y,
|
||||
w, 0, points[1].x, points[1].y,
|
||||
0, h, points[2].x, points[2].y));
|
||||
|
||||
if (path != newPath)
|
||||
{
|
||||
path.swapWithPath (newPath);
|
||||
pathChanged();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const Identifier DrawableRectangle::valueTreeType ("Rectangle");
|
||||
const Identifier DrawableRectangle::ValueTreeWrapper::topLeft ("topLeft");
|
||||
const Identifier DrawableRectangle::ValueTreeWrapper::topRight ("topRight");
|
||||
const Identifier DrawableRectangle::ValueTreeWrapper::bottomLeft ("bottomLeft");
|
||||
const Identifier DrawableRectangle::ValueTreeWrapper::cornerSize ("cornerSize");
|
||||
|
||||
//==============================================================================
|
||||
DrawableRectangle::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_)
|
||||
: FillAndStrokeState (state_)
|
||||
{
|
||||
jassert (state.hasType (valueTreeType));
|
||||
}
|
||||
|
||||
RelativeParallelogram DrawableRectangle::ValueTreeWrapper::getRectangle() const
|
||||
{
|
||||
return RelativeParallelogram (state.getProperty (topLeft, "0, 0"),
|
||||
state.getProperty (topRight, "100, 0"),
|
||||
state.getProperty (bottomLeft, "0, 100"));
|
||||
}
|
||||
|
||||
void DrawableRectangle::ValueTreeWrapper::setRectangle (const RelativeParallelogram& newBounds, UndoManager* undoManager)
|
||||
{
|
||||
state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager);
|
||||
state.setProperty (topRight, newBounds.topRight.toString(), undoManager);
|
||||
state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager);
|
||||
}
|
||||
|
||||
void DrawableRectangle::ValueTreeWrapper::setCornerSize (const RelativePoint& newSize, UndoManager* undoManager)
|
||||
{
|
||||
state.setProperty (cornerSize, newSize.toString(), undoManager);
|
||||
}
|
||||
|
||||
RelativePoint DrawableRectangle::ValueTreeWrapper::getCornerSize() const
|
||||
{
|
||||
return RelativePoint (state [cornerSize]);
|
||||
}
|
||||
|
||||
Value DrawableRectangle::ValueTreeWrapper::getCornerSizeValue (UndoManager* undoManager)
|
||||
{
|
||||
return state.getPropertyAsValue (cornerSize, undoManager);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawableRectangle::refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder)
|
||||
{
|
||||
ValueTreeWrapper v (tree);
|
||||
setComponentID (v.getID());
|
||||
|
||||
refreshFillTypes (v, builder.getImageProvider());
|
||||
setStrokeType (v.getStrokeType());
|
||||
setRectangle (v.getRectangle());
|
||||
setCornerSize (v.getCornerSize());
|
||||
}
|
||||
|
||||
ValueTree DrawableRectangle::createValueTree (ComponentBuilder::ImageProvider* imageProvider) const
|
||||
{
|
||||
ValueTree tree (valueTreeType);
|
||||
ValueTreeWrapper v (tree);
|
||||
|
||||
v.setID (getComponentID());
|
||||
writeTo (v, imageProvider, nullptr);
|
||||
v.setRectangle (bounds, nullptr);
|
||||
v.setCornerSize (cornerSize, nullptr);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_DRAWABLERECTANGLE_H_INCLUDED
|
||||
#define JUCE_DRAWABLERECTANGLE_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A Drawable object which draws a rectangle.
|
||||
|
||||
For details on how to change the fill and stroke, see the DrawableShape class.
|
||||
|
||||
@see Drawable, DrawableShape
|
||||
*/
|
||||
class JUCE_API DrawableRectangle : public DrawableShape
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
DrawableRectangle();
|
||||
DrawableRectangle (const DrawableRectangle&);
|
||||
|
||||
/** Destructor. */
|
||||
~DrawableRectangle();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the rectangle's bounds. */
|
||||
void setRectangle (const RelativeParallelogram& newBounds);
|
||||
|
||||
/** Returns the rectangle's bounds. */
|
||||
const RelativeParallelogram& getRectangle() const noexcept { return bounds; }
|
||||
|
||||
/** Returns the corner size to be used. */
|
||||
const RelativePoint& getCornerSize() const noexcept { return cornerSize; }
|
||||
|
||||
/** Sets a new corner size for the rectangle */
|
||||
void setCornerSize (const RelativePoint& newSize);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
Drawable* createCopy() const;
|
||||
/** @internal */
|
||||
void refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder);
|
||||
/** @internal */
|
||||
ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const;
|
||||
/** @internal */
|
||||
static const Identifier valueTreeType;
|
||||
|
||||
//==============================================================================
|
||||
/** Internally-used class for wrapping a DrawableRectangle's state into a ValueTree. */
|
||||
class ValueTreeWrapper : public DrawableShape::FillAndStrokeState
|
||||
{
|
||||
public:
|
||||
ValueTreeWrapper (const ValueTree& state);
|
||||
|
||||
RelativeParallelogram getRectangle() const;
|
||||
void setRectangle (const RelativeParallelogram& newBounds, UndoManager*);
|
||||
|
||||
void setCornerSize (const RelativePoint& cornerSize, UndoManager*);
|
||||
RelativePoint getCornerSize() const;
|
||||
Value getCornerSizeValue (UndoManager*);
|
||||
|
||||
static const Identifier topLeft, topRight, bottomLeft, cornerSize;
|
||||
};
|
||||
|
||||
|
||||
private:
|
||||
friend class Drawable::Positioner<DrawableRectangle>;
|
||||
|
||||
RelativeParallelogram bounds;
|
||||
RelativePoint cornerSize;
|
||||
|
||||
void rebuildPath();
|
||||
bool registerCoordinates (RelativeCoordinatePositionerBase&);
|
||||
void recalculateCoordinates (Expression::Scope*);
|
||||
|
||||
DrawableRectangle& operator= (const DrawableRectangle&);
|
||||
JUCE_LEAK_DETECTOR (DrawableRectangle)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_DRAWABLERECTANGLE_H_INCLUDED
|
||||
|
|
@ -0,0 +1,472 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
DrawableShape::DrawableShape()
|
||||
: strokeType (0.0f),
|
||||
mainFill (Colours::black),
|
||||
strokeFill (Colours::black)
|
||||
{
|
||||
}
|
||||
|
||||
DrawableShape::DrawableShape (const DrawableShape& other)
|
||||
: Drawable (other),
|
||||
strokeType (other.strokeType),
|
||||
mainFill (other.mainFill),
|
||||
strokeFill (other.strokeFill)
|
||||
{
|
||||
}
|
||||
|
||||
DrawableShape::~DrawableShape()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class DrawableShape::RelativePositioner : public RelativeCoordinatePositionerBase
|
||||
{
|
||||
public:
|
||||
RelativePositioner (DrawableShape& comp, const DrawableShape::RelativeFillType& f, bool isMain)
|
||||
: RelativeCoordinatePositionerBase (comp),
|
||||
owner (comp),
|
||||
fill (f),
|
||||
isMainFill (isMain)
|
||||
{
|
||||
}
|
||||
|
||||
bool registerCoordinates()
|
||||
{
|
||||
bool ok = addPoint (fill.gradientPoint1);
|
||||
ok = addPoint (fill.gradientPoint2) && ok;
|
||||
return addPoint (fill.gradientPoint3) && ok;
|
||||
}
|
||||
|
||||
void applyToComponentBounds()
|
||||
{
|
||||
ComponentScope scope (owner);
|
||||
if (isMainFill ? owner.mainFill.recalculateCoords (&scope)
|
||||
: owner.strokeFill.recalculateCoords (&scope))
|
||||
owner.repaint();
|
||||
}
|
||||
|
||||
void applyNewBounds (const Rectangle<int>&)
|
||||
{
|
||||
jassertfalse; // drawables can't be resized directly!
|
||||
}
|
||||
|
||||
private:
|
||||
DrawableShape& owner;
|
||||
const DrawableShape::RelativeFillType fill;
|
||||
const bool isMainFill;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RelativePositioner)
|
||||
};
|
||||
|
||||
void DrawableShape::setFill (const FillType& newFill)
|
||||
{
|
||||
setFill (RelativeFillType (newFill));
|
||||
}
|
||||
|
||||
void DrawableShape::setStrokeFill (const FillType& newFill)
|
||||
{
|
||||
setStrokeFill (RelativeFillType (newFill));
|
||||
}
|
||||
|
||||
void DrawableShape::setFillInternal (RelativeFillType& fill, const RelativeFillType& newFill,
|
||||
ScopedPointer<RelativeCoordinatePositionerBase>& pos)
|
||||
{
|
||||
if (fill != newFill)
|
||||
{
|
||||
fill = newFill;
|
||||
pos = nullptr;
|
||||
|
||||
if (fill.isDynamic())
|
||||
{
|
||||
pos = new RelativePositioner (*this, fill, true);
|
||||
pos->apply();
|
||||
}
|
||||
else
|
||||
{
|
||||
fill.recalculateCoords (nullptr);
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableShape::setFill (const RelativeFillType& newFill)
|
||||
{
|
||||
setFillInternal (mainFill, newFill, mainFillPositioner);
|
||||
}
|
||||
|
||||
void DrawableShape::setStrokeFill (const RelativeFillType& newFill)
|
||||
{
|
||||
setFillInternal (strokeFill, newFill, strokeFillPositioner);
|
||||
}
|
||||
|
||||
void DrawableShape::setStrokeType (const PathStrokeType& newStrokeType)
|
||||
{
|
||||
if (strokeType != newStrokeType)
|
||||
{
|
||||
strokeType = newStrokeType;
|
||||
strokeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableShape::setStrokeThickness (const float newThickness)
|
||||
{
|
||||
setStrokeType (PathStrokeType (newThickness, strokeType.getJointStyle(), strokeType.getEndStyle()));
|
||||
}
|
||||
|
||||
bool DrawableShape::isStrokeVisible() const noexcept
|
||||
{
|
||||
return strokeType.getStrokeThickness() > 0.0f && ! strokeFill.fill.isInvisible();
|
||||
}
|
||||
|
||||
void DrawableShape::refreshFillTypes (const FillAndStrokeState& newState, ComponentBuilder::ImageProvider* imageProvider)
|
||||
{
|
||||
setFill (newState.getFill (FillAndStrokeState::fill, imageProvider));
|
||||
setStrokeFill (newState.getFill (FillAndStrokeState::stroke, imageProvider));
|
||||
}
|
||||
|
||||
void DrawableShape::writeTo (FillAndStrokeState& state, ComponentBuilder::ImageProvider* imageProvider, UndoManager* undoManager) const
|
||||
{
|
||||
state.setFill (FillAndStrokeState::fill, mainFill, imageProvider, undoManager);
|
||||
state.setFill (FillAndStrokeState::stroke, strokeFill, imageProvider, undoManager);
|
||||
state.setStrokeType (strokeType, undoManager);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawableShape::paint (Graphics& g)
|
||||
{
|
||||
transformContextToCorrectOrigin (g);
|
||||
|
||||
g.setFillType (mainFill.fill);
|
||||
g.fillPath (path);
|
||||
|
||||
if (isStrokeVisible())
|
||||
{
|
||||
g.setFillType (strokeFill.fill);
|
||||
g.fillPath (strokePath);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableShape::pathChanged()
|
||||
{
|
||||
strokeChanged();
|
||||
}
|
||||
|
||||
void DrawableShape::strokeChanged()
|
||||
{
|
||||
strokePath.clear();
|
||||
strokeType.createStrokedPath (strokePath, path, AffineTransform::identity, 4.0f);
|
||||
|
||||
setBoundsToEnclose (getDrawableBounds());
|
||||
repaint();
|
||||
}
|
||||
|
||||
Rectangle<float> DrawableShape::getDrawableBounds() const
|
||||
{
|
||||
if (isStrokeVisible())
|
||||
return strokePath.getBounds();
|
||||
|
||||
return path.getBounds();
|
||||
}
|
||||
|
||||
bool DrawableShape::hitTest (int x, int y)
|
||||
{
|
||||
bool allowsClicksOnThisComponent, allowsClicksOnChildComponents;
|
||||
getInterceptsMouseClicks (allowsClicksOnThisComponent, allowsClicksOnChildComponents);
|
||||
|
||||
if (! allowsClicksOnThisComponent)
|
||||
return false;
|
||||
|
||||
const float globalX = (float) (x - originRelativeToComponent.x);
|
||||
const float globalY = (float) (y - originRelativeToComponent.y);
|
||||
|
||||
return path.contains (globalX, globalY)
|
||||
|| (isStrokeVisible() && strokePath.contains (globalX, globalY));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
DrawableShape::RelativeFillType::RelativeFillType()
|
||||
{
|
||||
}
|
||||
|
||||
DrawableShape::RelativeFillType::RelativeFillType (const FillType& fill_)
|
||||
: fill (fill_)
|
||||
{
|
||||
if (fill.isGradient())
|
||||
{
|
||||
const ColourGradient& g = *fill.gradient;
|
||||
|
||||
gradientPoint1 = g.point1.transformedBy (fill.transform);
|
||||
gradientPoint2 = g.point2.transformedBy (fill.transform);
|
||||
gradientPoint3 = Point<float> (g.point1.x + g.point2.y - g.point1.y,
|
||||
g.point1.y + g.point1.x - g.point2.x)
|
||||
.transformedBy (fill.transform);
|
||||
fill.transform = AffineTransform::identity;
|
||||
}
|
||||
}
|
||||
|
||||
DrawableShape::RelativeFillType::RelativeFillType (const RelativeFillType& other)
|
||||
: fill (other.fill),
|
||||
gradientPoint1 (other.gradientPoint1),
|
||||
gradientPoint2 (other.gradientPoint2),
|
||||
gradientPoint3 (other.gradientPoint3)
|
||||
{
|
||||
}
|
||||
|
||||
DrawableShape::RelativeFillType& DrawableShape::RelativeFillType::operator= (const RelativeFillType& other)
|
||||
{
|
||||
fill = other.fill;
|
||||
gradientPoint1 = other.gradientPoint1;
|
||||
gradientPoint2 = other.gradientPoint2;
|
||||
gradientPoint3 = other.gradientPoint3;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool DrawableShape::RelativeFillType::operator== (const RelativeFillType& other) const
|
||||
{
|
||||
return fill == other.fill
|
||||
&& ((! fill.isGradient())
|
||||
|| (gradientPoint1 == other.gradientPoint1
|
||||
&& gradientPoint2 == other.gradientPoint2
|
||||
&& gradientPoint3 == other.gradientPoint3));
|
||||
}
|
||||
|
||||
bool DrawableShape::RelativeFillType::operator!= (const RelativeFillType& other) const
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
bool DrawableShape::RelativeFillType::recalculateCoords (Expression::Scope* scope)
|
||||
{
|
||||
if (fill.isGradient())
|
||||
{
|
||||
const Point<float> g1 (gradientPoint1.resolve (scope));
|
||||
const Point<float> g2 (gradientPoint2.resolve (scope));
|
||||
AffineTransform t;
|
||||
|
||||
ColourGradient& g = *fill.gradient;
|
||||
|
||||
if (g.isRadial)
|
||||
{
|
||||
const Point<float> g3 (gradientPoint3.resolve (scope));
|
||||
const Point<float> g3Source (g1.x + g2.y - g1.y,
|
||||
g1.y + g1.x - g2.x);
|
||||
|
||||
t = AffineTransform::fromTargetPoints (g1.x, g1.y, g1.x, g1.y,
|
||||
g2.x, g2.y, g2.x, g2.y,
|
||||
g3Source.x, g3Source.y, g3.x, g3.y);
|
||||
}
|
||||
|
||||
if (g.point1 != g1 || g.point2 != g2 || fill.transform != t)
|
||||
{
|
||||
g.point1 = g1;
|
||||
g.point2 = g2;
|
||||
fill.transform = t;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DrawableShape::RelativeFillType::isDynamic() const
|
||||
{
|
||||
return gradientPoint1.isDynamic() || gradientPoint2.isDynamic() || gradientPoint3.isDynamic();
|
||||
}
|
||||
|
||||
void DrawableShape::RelativeFillType::writeTo (ValueTree& v, ComponentBuilder::ImageProvider* imageProvider, UndoManager* undoManager) const
|
||||
{
|
||||
if (fill.isColour())
|
||||
{
|
||||
v.setProperty (FillAndStrokeState::type, "solid", undoManager);
|
||||
v.setProperty (FillAndStrokeState::colour, String::toHexString ((int) fill.colour.getARGB()), undoManager);
|
||||
}
|
||||
else if (fill.isGradient())
|
||||
{
|
||||
v.setProperty (FillAndStrokeState::type, "gradient", undoManager);
|
||||
v.setProperty (FillAndStrokeState::gradientPoint1, gradientPoint1.toString(), undoManager);
|
||||
v.setProperty (FillAndStrokeState::gradientPoint2, gradientPoint2.toString(), undoManager);
|
||||
v.setProperty (FillAndStrokeState::gradientPoint3, gradientPoint3.toString(), undoManager);
|
||||
|
||||
const ColourGradient& cg = *fill.gradient;
|
||||
v.setProperty (FillAndStrokeState::radial, cg.isRadial, undoManager);
|
||||
|
||||
String s;
|
||||
for (int i = 0; i < cg.getNumColours(); ++i)
|
||||
s << ' ' << cg.getColourPosition (i)
|
||||
<< ' ' << String::toHexString ((int) cg.getColour(i).getARGB());
|
||||
|
||||
v.setProperty (FillAndStrokeState::colours, s.trimStart(), undoManager);
|
||||
}
|
||||
else if (fill.isTiledImage())
|
||||
{
|
||||
v.setProperty (FillAndStrokeState::type, "image", undoManager);
|
||||
|
||||
if (imageProvider != nullptr)
|
||||
v.setProperty (FillAndStrokeState::imageId, imageProvider->getIdentifierForImage (fill.image), undoManager);
|
||||
|
||||
if (fill.getOpacity() < 1.0f)
|
||||
v.setProperty (FillAndStrokeState::imageOpacity, fill.getOpacity(), undoManager);
|
||||
else
|
||||
v.removeProperty (FillAndStrokeState::imageOpacity, undoManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
jassertfalse;
|
||||
}
|
||||
}
|
||||
|
||||
bool DrawableShape::RelativeFillType::readFrom (const ValueTree& v, ComponentBuilder::ImageProvider* imageProvider)
|
||||
{
|
||||
const String newType (v [FillAndStrokeState::type].toString());
|
||||
|
||||
if (newType == "solid")
|
||||
{
|
||||
const String colourString (v [FillAndStrokeState::colour].toString());
|
||||
fill.setColour (colourString.isEmpty() ? Colours::black
|
||||
: Colour::fromString (colourString));
|
||||
return true;
|
||||
}
|
||||
else if (newType == "gradient")
|
||||
{
|
||||
ColourGradient g;
|
||||
g.isRadial = v [FillAndStrokeState::radial];
|
||||
|
||||
StringArray colourSteps;
|
||||
colourSteps.addTokens (v [FillAndStrokeState::colours].toString(), false);
|
||||
|
||||
for (int i = 0; i < colourSteps.size() / 2; ++i)
|
||||
g.addColour (colourSteps[i * 2].getDoubleValue(),
|
||||
Colour::fromString (colourSteps[i * 2 + 1]));
|
||||
|
||||
fill.setGradient (g);
|
||||
|
||||
gradientPoint1 = RelativePoint (v [FillAndStrokeState::gradientPoint1]);
|
||||
gradientPoint2 = RelativePoint (v [FillAndStrokeState::gradientPoint2]);
|
||||
gradientPoint3 = RelativePoint (v [FillAndStrokeState::gradientPoint3]);
|
||||
return true;
|
||||
}
|
||||
else if (newType == "image")
|
||||
{
|
||||
Image im;
|
||||
if (imageProvider != nullptr)
|
||||
im = imageProvider->getImageForIdentifier (v [FillAndStrokeState::imageId]);
|
||||
|
||||
fill.setTiledImage (im, AffineTransform::identity);
|
||||
fill.setOpacity ((float) v.getProperty (FillAndStrokeState::imageOpacity, 1.0f));
|
||||
return true;
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const Identifier DrawableShape::FillAndStrokeState::type ("type");
|
||||
const Identifier DrawableShape::FillAndStrokeState::colour ("colour");
|
||||
const Identifier DrawableShape::FillAndStrokeState::colours ("colours");
|
||||
const Identifier DrawableShape::FillAndStrokeState::fill ("Fill");
|
||||
const Identifier DrawableShape::FillAndStrokeState::stroke ("Stroke");
|
||||
const Identifier DrawableShape::FillAndStrokeState::path ("Path");
|
||||
const Identifier DrawableShape::FillAndStrokeState::jointStyle ("jointStyle");
|
||||
const Identifier DrawableShape::FillAndStrokeState::capStyle ("capStyle");
|
||||
const Identifier DrawableShape::FillAndStrokeState::strokeWidth ("strokeWidth");
|
||||
const Identifier DrawableShape::FillAndStrokeState::gradientPoint1 ("point1");
|
||||
const Identifier DrawableShape::FillAndStrokeState::gradientPoint2 ("point2");
|
||||
const Identifier DrawableShape::FillAndStrokeState::gradientPoint3 ("point3");
|
||||
const Identifier DrawableShape::FillAndStrokeState::radial ("radial");
|
||||
const Identifier DrawableShape::FillAndStrokeState::imageId ("imageId");
|
||||
const Identifier DrawableShape::FillAndStrokeState::imageOpacity ("imageOpacity");
|
||||
|
||||
DrawableShape::FillAndStrokeState::FillAndStrokeState (const ValueTree& state_)
|
||||
: Drawable::ValueTreeWrapperBase (state_)
|
||||
{
|
||||
}
|
||||
|
||||
DrawableShape::RelativeFillType DrawableShape::FillAndStrokeState::getFill (const Identifier& fillOrStrokeType, ComponentBuilder::ImageProvider* imageProvider) const
|
||||
{
|
||||
DrawableShape::RelativeFillType f;
|
||||
f.readFrom (state.getChildWithName (fillOrStrokeType), imageProvider);
|
||||
return f;
|
||||
}
|
||||
|
||||
ValueTree DrawableShape::FillAndStrokeState::getFillState (const Identifier& fillOrStrokeType)
|
||||
{
|
||||
ValueTree v (state.getChildWithName (fillOrStrokeType));
|
||||
if (v.isValid())
|
||||
return v;
|
||||
|
||||
setFill (fillOrStrokeType, FillType (Colours::black), nullptr, nullptr);
|
||||
return getFillState (fillOrStrokeType);
|
||||
}
|
||||
|
||||
void DrawableShape::FillAndStrokeState::setFill (const Identifier& fillOrStrokeType, const RelativeFillType& newFill,
|
||||
ComponentBuilder::ImageProvider* imageProvider, UndoManager* undoManager)
|
||||
{
|
||||
ValueTree v (state.getOrCreateChildWithName (fillOrStrokeType, undoManager));
|
||||
newFill.writeTo (v, imageProvider, undoManager);
|
||||
}
|
||||
|
||||
PathStrokeType DrawableShape::FillAndStrokeState::getStrokeType() const
|
||||
{
|
||||
const String jointStyleString (state [jointStyle].toString());
|
||||
const String capStyleString (state [capStyle].toString());
|
||||
|
||||
return PathStrokeType (state [strokeWidth],
|
||||
jointStyleString == "curved" ? PathStrokeType::curved
|
||||
: (jointStyleString == "bevel" ? PathStrokeType::beveled
|
||||
: PathStrokeType::mitered),
|
||||
capStyleString == "square" ? PathStrokeType::square
|
||||
: (capStyleString == "round" ? PathStrokeType::rounded
|
||||
: PathStrokeType::butt));
|
||||
}
|
||||
|
||||
void DrawableShape::FillAndStrokeState::setStrokeType (const PathStrokeType& newStrokeType, UndoManager* undoManager)
|
||||
{
|
||||
state.setProperty (strokeWidth, (double) newStrokeType.getStrokeThickness(), undoManager);
|
||||
state.setProperty (jointStyle, newStrokeType.getJointStyle() == PathStrokeType::mitered
|
||||
? "miter" : (newStrokeType.getJointStyle() == PathStrokeType::curved ? "curved" : "bevel"), undoManager);
|
||||
state.setProperty (capStyle, newStrokeType.getEndStyle() == PathStrokeType::butt
|
||||
? "butt" : (newStrokeType.getEndStyle() == PathStrokeType::square ? "square" : "round"), undoManager);
|
||||
}
|
||||
|
||||
static bool replaceColourInFill (DrawableShape::RelativeFillType& fill, Colour original, Colour replacement)
|
||||
{
|
||||
if (fill.fill.colour == original && fill.fill.isColour())
|
||||
{
|
||||
fill = FillType (replacement);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DrawableShape::replaceColour (Colour original, Colour replacement)
|
||||
{
|
||||
bool changed1 = replaceColourInFill (mainFill, original, replacement);
|
||||
bool changed2 = replaceColourInFill (strokeFill, original, replacement);
|
||||
return changed1 || changed2;
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_DRAWABLESHAPE_H_INCLUDED
|
||||
#define JUCE_DRAWABLESHAPE_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A base class implementing common functionality for Drawable classes which
|
||||
consist of some kind of filled and stroked outline.
|
||||
|
||||
@see DrawablePath, DrawableRectangle
|
||||
*/
|
||||
class JUCE_API DrawableShape : public Drawable
|
||||
{
|
||||
protected:
|
||||
//==============================================================================
|
||||
DrawableShape();
|
||||
DrawableShape (const DrawableShape&);
|
||||
|
||||
public:
|
||||
/** Destructor. */
|
||||
~DrawableShape();
|
||||
|
||||
//==============================================================================
|
||||
/** A FillType wrapper that allows the gradient coordinates to be implemented using RelativePoint.
|
||||
*/
|
||||
class RelativeFillType
|
||||
{
|
||||
public:
|
||||
RelativeFillType();
|
||||
RelativeFillType (const FillType& fill);
|
||||
RelativeFillType (const RelativeFillType&);
|
||||
RelativeFillType& operator= (const RelativeFillType&);
|
||||
|
||||
bool operator== (const RelativeFillType&) const;
|
||||
bool operator!= (const RelativeFillType&) const;
|
||||
|
||||
bool isDynamic() const;
|
||||
bool recalculateCoords (Expression::Scope* scope);
|
||||
|
||||
void writeTo (ValueTree& v, ComponentBuilder::ImageProvider*, UndoManager*) const;
|
||||
bool readFrom (const ValueTree& v, ComponentBuilder::ImageProvider*);
|
||||
|
||||
//==============================================================================
|
||||
FillType fill;
|
||||
RelativePoint gradientPoint1, gradientPoint2, gradientPoint3;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Sets a fill type for the path.
|
||||
This colour is used to fill the path - if you don't want the path to be
|
||||
filled (e.g. if you're just drawing an outline), set this to a transparent
|
||||
colour.
|
||||
|
||||
@see setPath, setStrokeFill
|
||||
*/
|
||||
void setFill (const FillType& newFill);
|
||||
|
||||
/** Sets a fill type for the path.
|
||||
This colour is used to fill the path - if you don't want the path to be
|
||||
filled (e.g. if you're just drawing an outline), set this to a transparent
|
||||
colour.
|
||||
|
||||
@see setPath, setStrokeFill
|
||||
*/
|
||||
void setFill (const RelativeFillType& newFill);
|
||||
|
||||
/** Returns the current fill type.
|
||||
@see setFill
|
||||
*/
|
||||
const RelativeFillType& getFill() const noexcept { return mainFill; }
|
||||
|
||||
/** Sets the fill type with which the outline will be drawn.
|
||||
@see setFill
|
||||
*/
|
||||
void setStrokeFill (const FillType& newStrokeFill);
|
||||
|
||||
/** Sets the fill type with which the outline will be drawn.
|
||||
@see setFill
|
||||
*/
|
||||
void setStrokeFill (const RelativeFillType& newStrokeFill);
|
||||
|
||||
/** Returns the current stroke fill.
|
||||
@see setStrokeFill
|
||||
*/
|
||||
const RelativeFillType& getStrokeFill() const noexcept { return strokeFill; }
|
||||
|
||||
/** Changes the properties of the outline that will be drawn around the path.
|
||||
If the stroke has 0 thickness, no stroke will be drawn.
|
||||
@see setStrokeThickness, setStrokeColour
|
||||
*/
|
||||
void setStrokeType (const PathStrokeType& newStrokeType);
|
||||
|
||||
/** Changes the stroke thickness.
|
||||
This is a shortcut for calling setStrokeType.
|
||||
*/
|
||||
void setStrokeThickness (float newThickness);
|
||||
|
||||
/** Returns the current outline style. */
|
||||
const PathStrokeType& getStrokeType() const noexcept { return strokeType; }
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
class FillAndStrokeState : public Drawable::ValueTreeWrapperBase
|
||||
{
|
||||
public:
|
||||
FillAndStrokeState (const ValueTree& state);
|
||||
|
||||
ValueTree getFillState (const Identifier& fillOrStrokeType);
|
||||
RelativeFillType getFill (const Identifier& fillOrStrokeType, ComponentBuilder::ImageProvider*) const;
|
||||
void setFill (const Identifier& fillOrStrokeType, const RelativeFillType& newFill,
|
||||
ComponentBuilder::ImageProvider*, UndoManager*);
|
||||
|
||||
PathStrokeType getStrokeType() const;
|
||||
void setStrokeType (const PathStrokeType& newStrokeType, UndoManager*);
|
||||
|
||||
static const Identifier type, colour, colours, fill, stroke, path, jointStyle, capStyle, strokeWidth,
|
||||
gradientPoint1, gradientPoint2, gradientPoint3, radial, imageId, imageOpacity;
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
Rectangle<float> getDrawableBounds() const override;
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
bool hitTest (int x, int y) override;
|
||||
/** @internal */
|
||||
bool replaceColour (Colour originalColour, Colour replacementColour) override;
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** Called when the cached path should be updated. */
|
||||
void pathChanged();
|
||||
/** Called when the cached stroke should be updated. */
|
||||
void strokeChanged();
|
||||
/** True if there's a stroke with a non-zero thickness and non-transparent colour. */
|
||||
bool isStrokeVisible() const noexcept;
|
||||
/** Updates the details from a FillAndStrokeState object, returning true if something changed. */
|
||||
void refreshFillTypes (const FillAndStrokeState& newState, ComponentBuilder::ImageProvider*);
|
||||
/** Writes the stroke and fill details to a FillAndStrokeState object. */
|
||||
void writeTo (FillAndStrokeState& state, ComponentBuilder::ImageProvider*, UndoManager*) const;
|
||||
|
||||
//==============================================================================
|
||||
PathStrokeType strokeType;
|
||||
Path path, strokePath;
|
||||
|
||||
private:
|
||||
class RelativePositioner;
|
||||
RelativeFillType mainFill, strokeFill;
|
||||
ScopedPointer<RelativeCoordinatePositionerBase> mainFillPositioner, strokeFillPositioner;
|
||||
|
||||
void setFillInternal (RelativeFillType& fill, const RelativeFillType& newFill,
|
||||
ScopedPointer<RelativeCoordinatePositionerBase>& positioner);
|
||||
|
||||
DrawableShape& operator= (const DrawableShape&);
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_DRAWABLESHAPE_H_INCLUDED
|
||||
|
|
@ -0,0 +1,334 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
DrawableText::DrawableText()
|
||||
: colour (Colours::black),
|
||||
justification (Justification::centredLeft)
|
||||
{
|
||||
setBoundingBox (RelativeParallelogram (RelativePoint (0.0f, 0.0f),
|
||||
RelativePoint (50.0f, 0.0f),
|
||||
RelativePoint (0.0f, 20.0f)));
|
||||
setFont (Font (15.0f), true);
|
||||
}
|
||||
|
||||
DrawableText::DrawableText (const DrawableText& other)
|
||||
: Drawable (other),
|
||||
bounds (other.bounds),
|
||||
fontHeight (other.fontHeight),
|
||||
fontHScale (other.fontHScale),
|
||||
font (other.font),
|
||||
text (other.text),
|
||||
colour (other.colour),
|
||||
justification (other.justification)
|
||||
{
|
||||
refreshBounds();
|
||||
}
|
||||
|
||||
DrawableText::~DrawableText()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawableText::setText (const String& newText)
|
||||
{
|
||||
if (text != newText)
|
||||
{
|
||||
text = newText;
|
||||
refreshBounds();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableText::setColour (Colour newColour)
|
||||
{
|
||||
if (colour != newColour)
|
||||
{
|
||||
colour = newColour;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableText::setFont (const Font& newFont, bool applySizeAndScale)
|
||||
{
|
||||
if (font != newFont)
|
||||
{
|
||||
font = newFont;
|
||||
|
||||
if (applySizeAndScale)
|
||||
{
|
||||
fontHeight = font.getHeight();
|
||||
fontHScale = font.getHorizontalScale();
|
||||
}
|
||||
|
||||
refreshBounds();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableText::setJustification (Justification newJustification)
|
||||
{
|
||||
justification = newJustification;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void DrawableText::setBoundingBox (const RelativeParallelogram& newBounds)
|
||||
{
|
||||
if (bounds != newBounds)
|
||||
{
|
||||
bounds = newBounds;
|
||||
refreshBounds();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableText::setFontHeight (const RelativeCoordinate& newHeight)
|
||||
{
|
||||
if (fontHeight != newHeight)
|
||||
{
|
||||
fontHeight = newHeight;
|
||||
refreshBounds();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableText::setFontHorizontalScale (const RelativeCoordinate& newScale)
|
||||
{
|
||||
if (fontHScale != newScale)
|
||||
{
|
||||
fontHScale = newScale;
|
||||
refreshBounds();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableText::refreshBounds()
|
||||
{
|
||||
if (bounds.isDynamic() || fontHeight.isDynamic() || fontHScale.isDynamic())
|
||||
{
|
||||
Drawable::Positioner<DrawableText>* const p = new Drawable::Positioner<DrawableText> (*this);
|
||||
setPositioner (p);
|
||||
p->apply();
|
||||
}
|
||||
else
|
||||
{
|
||||
setPositioner (0);
|
||||
recalculateCoordinates (0);
|
||||
}
|
||||
}
|
||||
|
||||
bool DrawableText::registerCoordinates (RelativeCoordinatePositionerBase& pos)
|
||||
{
|
||||
bool ok = pos.addPoint (bounds.topLeft);
|
||||
ok = pos.addPoint (bounds.topRight) && ok;
|
||||
ok = pos.addPoint (bounds.bottomLeft) && ok;
|
||||
ok = pos.addCoordinate (fontHeight) && ok;
|
||||
return pos.addCoordinate (fontHScale) && ok;
|
||||
}
|
||||
|
||||
void DrawableText::recalculateCoordinates (Expression::Scope* scope)
|
||||
{
|
||||
bounds.resolveThreePoints (resolvedPoints, scope);
|
||||
|
||||
const float w = Line<float> (resolvedPoints[0], resolvedPoints[1]).getLength();
|
||||
const float h = Line<float> (resolvedPoints[0], resolvedPoints[2]).getLength();
|
||||
|
||||
const float height = jlimit (0.01f, jmax (0.01f, h), (float) fontHeight.resolve (scope));
|
||||
const float hscale = jlimit (0.01f, jmax (0.01f, w), (float) fontHScale.resolve (scope));
|
||||
|
||||
scaledFont = font;
|
||||
scaledFont.setHeight (height);
|
||||
scaledFont.setHorizontalScale (hscale);
|
||||
|
||||
setBoundsToEnclose (getDrawableBounds());
|
||||
repaint();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawableText::paint (Graphics& g)
|
||||
{
|
||||
transformContextToCorrectOrigin (g);
|
||||
|
||||
const float w = Line<float> (resolvedPoints[0], resolvedPoints[1]).getLength();
|
||||
const float h = Line<float> (resolvedPoints[0], resolvedPoints[2]).getLength();
|
||||
|
||||
g.addTransform (AffineTransform::fromTargetPoints (0, 0, resolvedPoints[0].x, resolvedPoints[0].y,
|
||||
w, 0, resolvedPoints[1].x, resolvedPoints[1].y,
|
||||
0, h, resolvedPoints[2].x, resolvedPoints[2].y));
|
||||
g.setFont (scaledFont);
|
||||
g.setColour (colour);
|
||||
|
||||
g.drawFittedText (text, Rectangle<float> (w, h).getSmallestIntegerContainer(), justification, 0x100000);
|
||||
}
|
||||
|
||||
Rectangle<float> DrawableText::getDrawableBounds() const
|
||||
{
|
||||
return RelativeParallelogram::getBoundingBox (resolvedPoints);
|
||||
}
|
||||
|
||||
Drawable* DrawableText::createCopy() const
|
||||
{
|
||||
return new DrawableText (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const Identifier DrawableText::valueTreeType ("Text");
|
||||
|
||||
const Identifier DrawableText::ValueTreeWrapper::text ("text");
|
||||
const Identifier DrawableText::ValueTreeWrapper::colour ("colour");
|
||||
const Identifier DrawableText::ValueTreeWrapper::font ("font");
|
||||
const Identifier DrawableText::ValueTreeWrapper::justification ("justification");
|
||||
const Identifier DrawableText::ValueTreeWrapper::topLeft ("topLeft");
|
||||
const Identifier DrawableText::ValueTreeWrapper::topRight ("topRight");
|
||||
const Identifier DrawableText::ValueTreeWrapper::bottomLeft ("bottomLeft");
|
||||
const Identifier DrawableText::ValueTreeWrapper::fontHeight ("fontHeight");
|
||||
const Identifier DrawableText::ValueTreeWrapper::fontHScale ("fontHScale");
|
||||
|
||||
//==============================================================================
|
||||
DrawableText::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_)
|
||||
: ValueTreeWrapperBase (state_)
|
||||
{
|
||||
jassert (state.hasType (valueTreeType));
|
||||
}
|
||||
|
||||
String DrawableText::ValueTreeWrapper::getText() const
|
||||
{
|
||||
return state [text].toString();
|
||||
}
|
||||
|
||||
void DrawableText::ValueTreeWrapper::setText (const String& newText, UndoManager* undoManager)
|
||||
{
|
||||
state.setProperty (text, newText, undoManager);
|
||||
}
|
||||
|
||||
Value DrawableText::ValueTreeWrapper::getTextValue (UndoManager* undoManager)
|
||||
{
|
||||
return state.getPropertyAsValue (text, undoManager);
|
||||
}
|
||||
|
||||
Colour DrawableText::ValueTreeWrapper::getColour() const
|
||||
{
|
||||
return Colour::fromString (state [colour].toString());
|
||||
}
|
||||
|
||||
void DrawableText::ValueTreeWrapper::setColour (Colour newColour, UndoManager* undoManager)
|
||||
{
|
||||
state.setProperty (colour, newColour.toString(), undoManager);
|
||||
}
|
||||
|
||||
Justification DrawableText::ValueTreeWrapper::getJustification() const
|
||||
{
|
||||
return Justification ((int) state [justification]);
|
||||
}
|
||||
|
||||
void DrawableText::ValueTreeWrapper::setJustification (Justification newJustification, UndoManager* undoManager)
|
||||
{
|
||||
state.setProperty (justification, newJustification.getFlags(), undoManager);
|
||||
}
|
||||
|
||||
Font DrawableText::ValueTreeWrapper::getFont() const
|
||||
{
|
||||
return Font::fromString (state [font]);
|
||||
}
|
||||
|
||||
void DrawableText::ValueTreeWrapper::setFont (const Font& newFont, UndoManager* undoManager)
|
||||
{
|
||||
state.setProperty (font, newFont.toString(), undoManager);
|
||||
}
|
||||
|
||||
Value DrawableText::ValueTreeWrapper::getFontValue (UndoManager* undoManager)
|
||||
{
|
||||
return state.getPropertyAsValue (font, undoManager);
|
||||
}
|
||||
|
||||
RelativeParallelogram DrawableText::ValueTreeWrapper::getBoundingBox() const
|
||||
{
|
||||
return RelativeParallelogram (state [topLeft].toString(), state [topRight].toString(), state [bottomLeft].toString());
|
||||
}
|
||||
|
||||
void DrawableText::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager)
|
||||
{
|
||||
state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager);
|
||||
state.setProperty (topRight, newBounds.topRight.toString(), undoManager);
|
||||
state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager);
|
||||
}
|
||||
|
||||
RelativeCoordinate DrawableText::ValueTreeWrapper::getFontHeight() const
|
||||
{
|
||||
return state [fontHeight].toString();
|
||||
}
|
||||
|
||||
void DrawableText::ValueTreeWrapper::setFontHeight (const RelativeCoordinate& coord, UndoManager* undoManager)
|
||||
{
|
||||
state.setProperty (fontHeight, coord.toString(), undoManager);
|
||||
}
|
||||
|
||||
RelativeCoordinate DrawableText::ValueTreeWrapper::getFontHorizontalScale() const
|
||||
{
|
||||
return state [fontHScale].toString();
|
||||
}
|
||||
|
||||
void DrawableText::ValueTreeWrapper::setFontHorizontalScale (const RelativeCoordinate& coord, UndoManager* undoManager)
|
||||
{
|
||||
state.setProperty (fontHScale, coord.toString(), undoManager);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawableText::refreshFromValueTree (const ValueTree& tree, ComponentBuilder&)
|
||||
{
|
||||
ValueTreeWrapper v (tree);
|
||||
setComponentID (v.getID());
|
||||
|
||||
const RelativeParallelogram newBounds (v.getBoundingBox());
|
||||
const RelativeCoordinate newFontHeight (v.getFontHeight());
|
||||
const RelativeCoordinate newFontHScale (v.getFontHorizontalScale());
|
||||
const Colour newColour (v.getColour());
|
||||
const Justification newJustification (v.getJustification());
|
||||
const String newText (v.getText());
|
||||
const Font newFont (v.getFont());
|
||||
|
||||
if (text != newText || font != newFont || justification != newJustification
|
||||
|| colour != newColour || bounds != newBounds
|
||||
|| newFontHeight != fontHeight || newFontHScale != fontHScale)
|
||||
{
|
||||
setBoundingBox (newBounds);
|
||||
setFontHeight (newFontHeight);
|
||||
setFontHorizontalScale (newFontHScale);
|
||||
setColour (newColour);
|
||||
setFont (newFont, false);
|
||||
setJustification (newJustification);
|
||||
setText (newText);
|
||||
}
|
||||
}
|
||||
|
||||
ValueTree DrawableText::createValueTree (ComponentBuilder::ImageProvider*) const
|
||||
{
|
||||
ValueTree tree (valueTreeType);
|
||||
ValueTreeWrapper v (tree);
|
||||
|
||||
v.setID (getComponentID());
|
||||
v.setText (text, nullptr);
|
||||
v.setFont (font, nullptr);
|
||||
v.setJustification (justification, nullptr);
|
||||
v.setColour (colour, nullptr);
|
||||
v.setBoundingBox (bounds, nullptr);
|
||||
v.setFontHeight (fontHeight, nullptr);
|
||||
v.setFontHorizontalScale (fontHScale, nullptr);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_DRAWABLETEXT_H_INCLUDED
|
||||
#define JUCE_DRAWABLETEXT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A drawable object which renders a line of text.
|
||||
|
||||
@see Drawable
|
||||
*/
|
||||
class JUCE_API DrawableText : public Drawable
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a DrawableText object. */
|
||||
DrawableText();
|
||||
DrawableText (const DrawableText&);
|
||||
|
||||
/** Destructor. */
|
||||
~DrawableText();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the text to display.*/
|
||||
void setText (const String& newText);
|
||||
|
||||
/** Returns the currently displayed text */
|
||||
const String& getText() const noexcept { return text;}
|
||||
|
||||
/** Sets the colour of the text. */
|
||||
void setColour (Colour newColour);
|
||||
|
||||
/** Returns the current text colour. */
|
||||
Colour getColour() const noexcept { return colour; }
|
||||
|
||||
/** Sets the font to use.
|
||||
Note that the font height and horizontal scale are set as RelativeCoordinates using
|
||||
setFontHeight and setFontHorizontalScale. If applySizeAndScale is true, then these height
|
||||
and scale values will be changed to match the dimensions of the font supplied;
|
||||
if it is false, then the new font object's height and scale are ignored.
|
||||
*/
|
||||
void setFont (const Font& newFont, bool applySizeAndScale);
|
||||
|
||||
/** Returns the current font. */
|
||||
const Font& getFont() const noexcept { return font; }
|
||||
|
||||
/** Changes the justification of the text within the bounding box. */
|
||||
void setJustification (Justification newJustification);
|
||||
|
||||
/** Returns the current justification. */
|
||||
Justification getJustification() const noexcept { return justification; }
|
||||
|
||||
/** Returns the parallelogram that defines the text bounding box. */
|
||||
const RelativeParallelogram& getBoundingBox() const noexcept { return bounds; }
|
||||
|
||||
/** Sets the bounding box that contains the text. */
|
||||
void setBoundingBox (const RelativeParallelogram& newBounds);
|
||||
|
||||
const RelativeCoordinate& getFontHeight() const { return fontHeight; }
|
||||
void setFontHeight (const RelativeCoordinate& newHeight);
|
||||
|
||||
const RelativeCoordinate& getFontHorizontalScale() const { return fontHScale; }
|
||||
void setFontHorizontalScale (const RelativeCoordinate& newScale);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
Drawable* createCopy() const override;
|
||||
/** @internal */
|
||||
void refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder);
|
||||
/** @internal */
|
||||
ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const override;
|
||||
/** @internal */
|
||||
static const Identifier valueTreeType;
|
||||
/** @internal */
|
||||
Rectangle<float> getDrawableBounds() const override;
|
||||
|
||||
//==============================================================================
|
||||
/** Internally-used class for wrapping a DrawableText's state into a ValueTree. */
|
||||
class ValueTreeWrapper : public Drawable::ValueTreeWrapperBase
|
||||
{
|
||||
public:
|
||||
ValueTreeWrapper (const ValueTree& state);
|
||||
|
||||
String getText() const;
|
||||
void setText (const String& newText, UndoManager* undoManager);
|
||||
Value getTextValue (UndoManager* undoManager);
|
||||
|
||||
Colour getColour() const;
|
||||
void setColour (Colour newColour, UndoManager* undoManager);
|
||||
|
||||
Justification getJustification() const;
|
||||
void setJustification (Justification newJustification, UndoManager* undoManager);
|
||||
|
||||
Font getFont() const;
|
||||
void setFont (const Font& newFont, UndoManager* undoManager);
|
||||
Value getFontValue (UndoManager* undoManager);
|
||||
|
||||
RelativeParallelogram getBoundingBox() const;
|
||||
void setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager);
|
||||
|
||||
RelativeCoordinate getFontHeight() const;
|
||||
void setFontHeight (const RelativeCoordinate& newHeight, UndoManager* undoManager);
|
||||
|
||||
RelativeCoordinate getFontHorizontalScale() const;
|
||||
void setFontHorizontalScale (const RelativeCoordinate& newScale, UndoManager* undoManager);
|
||||
|
||||
static const Identifier text, colour, font, justification, topLeft, topRight, bottomLeft, fontHeight, fontHScale;
|
||||
};
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
RelativeParallelogram bounds;
|
||||
RelativeCoordinate fontHeight, fontHScale;
|
||||
Point<float> resolvedPoints[3];
|
||||
Font font, scaledFont;
|
||||
String text;
|
||||
Colour colour;
|
||||
Justification justification;
|
||||
|
||||
friend class Drawable::Positioner<DrawableText>;
|
||||
bool registerCoordinates (RelativeCoordinatePositionerBase&);
|
||||
void recalculateCoordinates (Expression::Scope*);
|
||||
void refreshBounds();
|
||||
|
||||
DrawableText& operator= (const DrawableText&);
|
||||
JUCE_LEAK_DETECTOR (DrawableText)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_DRAWABLETEXT_H_INCLUDED
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
DirectoryContentsDisplayComponent::DirectoryContentsDisplayComponent (DirectoryContentsList& listToShow)
|
||||
: fileList (listToShow)
|
||||
{
|
||||
}
|
||||
|
||||
DirectoryContentsDisplayComponent::~DirectoryContentsDisplayComponent()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
FileBrowserListener::~FileBrowserListener()
|
||||
{
|
||||
}
|
||||
|
||||
void DirectoryContentsDisplayComponent::addListener (FileBrowserListener* const listener)
|
||||
{
|
||||
listeners.add (listener);
|
||||
}
|
||||
|
||||
void DirectoryContentsDisplayComponent::removeListener (FileBrowserListener* const listener)
|
||||
{
|
||||
listeners.remove (listener);
|
||||
}
|
||||
|
||||
void DirectoryContentsDisplayComponent::sendSelectionChangeMessage()
|
||||
{
|
||||
Component::BailOutChecker checker (dynamic_cast <Component*> (this));
|
||||
listeners.callChecked (checker, &FileBrowserListener::selectionChanged);
|
||||
}
|
||||
|
||||
void DirectoryContentsDisplayComponent::sendMouseClickMessage (const File& file, const MouseEvent& e)
|
||||
{
|
||||
if (fileList.getDirectory().exists())
|
||||
{
|
||||
Component::BailOutChecker checker (dynamic_cast <Component*> (this));
|
||||
listeners.callChecked (checker, &FileBrowserListener::fileClicked, file, e);
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryContentsDisplayComponent::sendDoubleClickMessage (const File& file)
|
||||
{
|
||||
if (fileList.getDirectory().exists())
|
||||
{
|
||||
Component::BailOutChecker checker (dynamic_cast <Component*> (this));
|
||||
listeners.callChecked (checker, &FileBrowserListener::fileDoubleClicked, file);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_DIRECTORYCONTENTSDISPLAYCOMPONENT_H_INCLUDED
|
||||
#define JUCE_DIRECTORYCONTENTSDISPLAYCOMPONENT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A base class for components that display a list of the files in a directory.
|
||||
|
||||
@see DirectoryContentsList
|
||||
*/
|
||||
class JUCE_API DirectoryContentsDisplayComponent
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a DirectoryContentsDisplayComponent for a given list of files. */
|
||||
DirectoryContentsDisplayComponent (DirectoryContentsList& listToShow);
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~DirectoryContentsDisplayComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of files the user has got selected.
|
||||
@see getSelectedFile
|
||||
*/
|
||||
virtual int getNumSelectedFiles() const = 0;
|
||||
|
||||
/** Returns one of the files that the user has currently selected.
|
||||
The index should be in the range 0 to (getNumSelectedFiles() - 1).
|
||||
@see getNumSelectedFiles
|
||||
*/
|
||||
virtual File getSelectedFile (int index) const = 0;
|
||||
|
||||
/** Deselects any selected files. */
|
||||
virtual void deselectAllFiles() = 0;
|
||||
|
||||
/** Scrolls this view to the top. */
|
||||
virtual void scrollToTop() = 0;
|
||||
|
||||
/** If the specified file is in the list, it will become the only selected item
|
||||
(and if the file isn't in the list, all other items will be deselected). */
|
||||
virtual void setSelectedFile (const File&) = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a listener to be told when files are selected or clicked.
|
||||
@see removeListener
|
||||
*/
|
||||
void addListener (FileBrowserListener* listener);
|
||||
|
||||
/** Removes a listener.
|
||||
@see addListener
|
||||
*/
|
||||
void removeListener (FileBrowserListener* listener);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the list.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
highlightColourId = 0x1000540, /**< The colour to use to fill a highlighted row of the list. */
|
||||
textColourId = 0x1000541, /**< The colour for the text. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void sendSelectionChangeMessage();
|
||||
/** @internal */
|
||||
void sendDoubleClickMessage (const File& file);
|
||||
/** @internal */
|
||||
void sendMouseClickMessage (const File& file, const MouseEvent& e);
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
DirectoryContentsList& fileList;
|
||||
ListenerList <FileBrowserListener> listeners;
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectoryContentsDisplayComponent)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_DIRECTORYCONTENTSDISPLAYCOMPONENT_H_INCLUDED
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
DirectoryContentsList::DirectoryContentsList (const FileFilter* f, TimeSliceThread& t)
|
||||
: fileFilter (f), thread (t),
|
||||
fileTypeFlags (File::ignoreHiddenFiles | File::findFiles),
|
||||
shouldStop (true)
|
||||
{
|
||||
}
|
||||
|
||||
DirectoryContentsList::~DirectoryContentsList()
|
||||
{
|
||||
stopSearching();
|
||||
}
|
||||
|
||||
void DirectoryContentsList::setIgnoresHiddenFiles (const bool shouldIgnoreHiddenFiles)
|
||||
{
|
||||
setTypeFlags (shouldIgnoreHiddenFiles ? (fileTypeFlags | File::ignoreHiddenFiles)
|
||||
: (fileTypeFlags & ~File::ignoreHiddenFiles));
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::ignoresHiddenFiles() const
|
||||
{
|
||||
return (fileTypeFlags & File::ignoreHiddenFiles) != 0;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DirectoryContentsList::setDirectory (const File& directory,
|
||||
const bool includeDirectories,
|
||||
const bool includeFiles)
|
||||
{
|
||||
jassert (includeDirectories || includeFiles); // you have to speciify at least one of these!
|
||||
|
||||
if (directory != root)
|
||||
{
|
||||
clear();
|
||||
root = directory;
|
||||
changed();
|
||||
|
||||
// (this forces a refresh when setTypeFlags() is called, rather than triggering two refreshes)
|
||||
fileTypeFlags &= ~(File::findDirectories | File::findFiles);
|
||||
}
|
||||
|
||||
int newFlags = fileTypeFlags;
|
||||
if (includeDirectories) newFlags |= File::findDirectories; else newFlags &= ~File::findDirectories;
|
||||
if (includeFiles) newFlags |= File::findFiles; else newFlags &= ~File::findFiles;
|
||||
|
||||
setTypeFlags (newFlags);
|
||||
}
|
||||
|
||||
void DirectoryContentsList::setTypeFlags (const int newFlags)
|
||||
{
|
||||
if (fileTypeFlags != newFlags)
|
||||
{
|
||||
fileTypeFlags = newFlags;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryContentsList::stopSearching()
|
||||
{
|
||||
shouldStop = true;
|
||||
thread.removeTimeSliceClient (this);
|
||||
fileFindHandle = nullptr;
|
||||
}
|
||||
|
||||
void DirectoryContentsList::clear()
|
||||
{
|
||||
stopSearching();
|
||||
|
||||
if (files.size() > 0)
|
||||
{
|
||||
files.clear();
|
||||
changed();
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryContentsList::refresh()
|
||||
{
|
||||
clear();
|
||||
|
||||
if (root.isDirectory())
|
||||
{
|
||||
fileFindHandle = new DirectoryIterator (root, false, "*", fileTypeFlags);
|
||||
shouldStop = false;
|
||||
thread.addTimeSliceClient (this);
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryContentsList::setFileFilter (const FileFilter* newFileFilter)
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
fileFilter = newFileFilter;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool DirectoryContentsList::getFileInfo (const int index, FileInfo& result) const
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
|
||||
if (const FileInfo* const info = files [index])
|
||||
{
|
||||
result = *info;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
File DirectoryContentsList::getFile (const int index) const
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
|
||||
if (const FileInfo* const info = files [index])
|
||||
return root.getChildFile (info->filename);
|
||||
|
||||
return File();
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::contains (const File& targetFile) const
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
|
||||
for (int i = files.size(); --i >= 0;)
|
||||
if (root.getChildFile (files.getUnchecked(i)->filename) == targetFile)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::isStillLoading() const
|
||||
{
|
||||
return fileFindHandle != nullptr;
|
||||
}
|
||||
|
||||
void DirectoryContentsList::changed()
|
||||
{
|
||||
sendChangeMessage();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int DirectoryContentsList::useTimeSlice()
|
||||
{
|
||||
const uint32 startTime = Time::getApproximateMillisecondCounter();
|
||||
bool hasChanged = false;
|
||||
|
||||
for (int i = 100; --i >= 0;)
|
||||
{
|
||||
if (! checkNextFile (hasChanged))
|
||||
{
|
||||
if (hasChanged)
|
||||
changed();
|
||||
|
||||
return 500;
|
||||
}
|
||||
|
||||
if (shouldStop || (Time::getApproximateMillisecondCounter() > startTime + 150))
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasChanged)
|
||||
changed();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::checkNextFile (bool& hasChanged)
|
||||
{
|
||||
if (fileFindHandle != nullptr)
|
||||
{
|
||||
bool fileFoundIsDir, isHidden, isReadOnly;
|
||||
int64 fileSize;
|
||||
Time modTime, creationTime;
|
||||
|
||||
if (fileFindHandle->next (&fileFoundIsDir, &isHidden, &fileSize,
|
||||
&modTime, &creationTime, &isReadOnly))
|
||||
{
|
||||
if (addFile (fileFindHandle->getFile(), fileFoundIsDir,
|
||||
fileSize, modTime, creationTime, isReadOnly))
|
||||
{
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fileFindHandle = nullptr;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct FileInfoComparator
|
||||
{
|
||||
static int compareElements (const DirectoryContentsList::FileInfo* const first,
|
||||
const DirectoryContentsList::FileInfo* const second)
|
||||
{
|
||||
#if JUCE_WINDOWS
|
||||
if (first->isDirectory != second->isDirectory)
|
||||
return first->isDirectory ? -1 : 1;
|
||||
#endif
|
||||
|
||||
return first->filename.compareNatural (second->filename);
|
||||
}
|
||||
};
|
||||
|
||||
bool DirectoryContentsList::addFile (const File& file, const bool isDir,
|
||||
const int64 fileSize,
|
||||
Time modTime, Time creationTime,
|
||||
const bool isReadOnly)
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
|
||||
if (fileFilter == nullptr
|
||||
|| ((! isDir) && fileFilter->isFileSuitable (file))
|
||||
|| (isDir && fileFilter->isDirectorySuitable (file)))
|
||||
{
|
||||
ScopedPointer<FileInfo> info (new FileInfo());
|
||||
|
||||
info->filename = file.getFileName();
|
||||
info->fileSize = fileSize;
|
||||
info->modificationTime = modTime;
|
||||
info->creationTime = creationTime;
|
||||
info->isDirectory = isDir;
|
||||
info->isReadOnly = isReadOnly;
|
||||
|
||||
for (int i = files.size(); --i >= 0;)
|
||||
if (files.getUnchecked(i)->filename == info->filename)
|
||||
return false;
|
||||
|
||||
FileInfoComparator comp;
|
||||
files.addSorted (comp, info.release());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_DIRECTORYCONTENTSLIST_H_INCLUDED
|
||||
#define JUCE_DIRECTORYCONTENTSLIST_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A class to asynchronously scan for details about the files in a directory.
|
||||
|
||||
This keeps a list of files and some information about them, using a background
|
||||
thread to scan for more files. As files are found, it broadcasts change messages
|
||||
to tell any listeners.
|
||||
|
||||
@see FileListComponent, FileBrowserComponent
|
||||
*/
|
||||
class JUCE_API DirectoryContentsList : public ChangeBroadcaster,
|
||||
private TimeSliceClient
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a directory list.
|
||||
|
||||
To set the directory it should point to, use setDirectory(), which will
|
||||
also start it scanning for files on the background thread.
|
||||
|
||||
When the background thread finds and adds new files to this list, the
|
||||
ChangeBroadcaster class will send a change message, so you can register
|
||||
listeners and update them when the list changes.
|
||||
|
||||
@param fileFilter an optional filter to select which files are
|
||||
included in the list. If this is nullptr, then all files
|
||||
and directories are included. Make sure that the filter
|
||||
doesn't get deleted during the lifetime of this object
|
||||
@param threadToUse a thread object that this list can use
|
||||
to scan for files as a background task. Make sure
|
||||
that the thread you give it has been started, or you
|
||||
won't get any files!
|
||||
*/
|
||||
DirectoryContentsList (const FileFilter* fileFilter,
|
||||
TimeSliceThread& threadToUse);
|
||||
|
||||
/** Destructor. */
|
||||
~DirectoryContentsList();
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the directory that's currently being used. */
|
||||
const File& getDirectory() const noexcept { return root; }
|
||||
|
||||
/** Sets the directory to look in for files.
|
||||
|
||||
If the directory that's passed in is different to the current one, this will
|
||||
also start the background thread scanning it for files.
|
||||
*/
|
||||
void setDirectory (const File& directory,
|
||||
bool includeDirectories,
|
||||
bool includeFiles);
|
||||
|
||||
/** Returns true if this list contains directories.
|
||||
@see setDirectory
|
||||
*/
|
||||
bool isFindingDirectories() const noexcept { return (fileTypeFlags & File::findDirectories) != 0; }
|
||||
|
||||
/** Returns true if this list contains files.
|
||||
@see setDirectory
|
||||
*/
|
||||
bool isFindingFiles() const noexcept { return (fileTypeFlags & File::findFiles) != 0; }
|
||||
|
||||
/** Clears the list, and stops the thread scanning for files. */
|
||||
void clear();
|
||||
|
||||
/** Clears the list and restarts scanning the directory for files. */
|
||||
void refresh();
|
||||
|
||||
/** True if the background thread hasn't yet finished scanning for files. */
|
||||
bool isStillLoading() const;
|
||||
|
||||
/** Tells the list whether or not to ignore hidden files.
|
||||
By default these are ignored.
|
||||
*/
|
||||
void setIgnoresHiddenFiles (bool shouldIgnoreHiddenFiles);
|
||||
|
||||
/** Returns true if hidden files are ignored.
|
||||
@see setIgnoresHiddenFiles
|
||||
*/
|
||||
bool ignoresHiddenFiles() const;
|
||||
|
||||
/** Replaces the current FileFilter.
|
||||
This can be nullptr to have no filter. The DirectoryContentList does not take
|
||||
ownership of this object - it just keeps a pointer to it, so you must manage its
|
||||
lifetime.
|
||||
Note that this only replaces the filter, it doesn't refresh the list - you'll
|
||||
probably want to call refresh() after calling this.
|
||||
*/
|
||||
void setFileFilter (const FileFilter* newFileFilter);
|
||||
|
||||
//==============================================================================
|
||||
/** Contains cached information about one of the files in a DirectoryContentsList.
|
||||
*/
|
||||
struct FileInfo
|
||||
{
|
||||
//==============================================================================
|
||||
/** The filename.
|
||||
|
||||
This isn't a full pathname, it's just the last part of the path, same as you'd
|
||||
get from File::getFileName().
|
||||
|
||||
To get the full pathname, use DirectoryContentsList::getDirectory().getChildFile (filename).
|
||||
*/
|
||||
String filename;
|
||||
|
||||
/** File size in bytes. */
|
||||
int64 fileSize;
|
||||
|
||||
/** File modification time.
|
||||
As supplied by File::getLastModificationTime().
|
||||
*/
|
||||
Time modificationTime;
|
||||
|
||||
/** File creation time.
|
||||
As supplied by File::getCreationTime().
|
||||
*/
|
||||
Time creationTime;
|
||||
|
||||
/** True if the file is a directory. */
|
||||
bool isDirectory;
|
||||
|
||||
/** True if the file is read-only. */
|
||||
bool isReadOnly;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of files currently available in the list.
|
||||
|
||||
The info about one of these files can be retrieved with getFileInfo() or getFile().
|
||||
|
||||
Obviously as the background thread runs and scans the directory for files, this
|
||||
number will change.
|
||||
|
||||
@see getFileInfo, getFile
|
||||
*/
|
||||
int getNumFiles() const noexcept { return files.size(); }
|
||||
|
||||
/** Returns the cached information about one of the files in the list.
|
||||
|
||||
If the index is in-range, this will return true and will copy the file's details
|
||||
to the structure that is passed-in.
|
||||
|
||||
If it returns false, then the index wasn't in range, and the structure won't
|
||||
be affected.
|
||||
|
||||
@see getNumFiles, getFile
|
||||
*/
|
||||
bool getFileInfo (int index, FileInfo& resultInfo) const;
|
||||
|
||||
/** Returns one of the files in the list.
|
||||
|
||||
@param index should be less than getNumFiles(). If this is out-of-range, the
|
||||
return value will be File::nonexistent
|
||||
@see getNumFiles, getFileInfo
|
||||
*/
|
||||
File getFile (int index) const;
|
||||
|
||||
/** Returns the file filter being used.
|
||||
The filter is specified in the constructor.
|
||||
*/
|
||||
const FileFilter* getFilter() const noexcept { return fileFilter; }
|
||||
|
||||
/** Returns true if the list contains the specified file. */
|
||||
bool contains (const File&) const;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
TimeSliceThread& getTimeSliceThread() const noexcept { return thread; }
|
||||
|
||||
private:
|
||||
File root;
|
||||
const FileFilter* fileFilter;
|
||||
TimeSliceThread& thread;
|
||||
int fileTypeFlags;
|
||||
|
||||
CriticalSection fileListLock;
|
||||
OwnedArray<FileInfo> files;
|
||||
|
||||
ScopedPointer<DirectoryIterator> fileFindHandle;
|
||||
bool volatile shouldStop;
|
||||
|
||||
int useTimeSlice() override;
|
||||
void stopSearching();
|
||||
void changed();
|
||||
bool checkNextFile (bool& hasChanged);
|
||||
bool addFile (const File&, bool isDir, int64 fileSize, Time modTime,
|
||||
Time creationTime, bool isReadOnly);
|
||||
void setTypeFlags (int);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectoryContentsList)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_DIRECTORYCONTENTSLIST_H_INCLUDED
|
||||
|
|
@ -0,0 +1,586 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
FileBrowserComponent::FileBrowserComponent (int flags_,
|
||||
const File& initialFileOrDirectory,
|
||||
const FileFilter* fileFilter_,
|
||||
FilePreviewComponent* previewComp_)
|
||||
: FileFilter (String::empty),
|
||||
fileFilter (fileFilter_),
|
||||
flags (flags_),
|
||||
previewComp (previewComp_),
|
||||
currentPathBox ("path"),
|
||||
fileLabel ("f", TRANS ("file:")),
|
||||
thread ("Juce FileBrowser")
|
||||
{
|
||||
// You need to specify one or other of the open/save flags..
|
||||
jassert ((flags & (saveMode | openMode)) != 0);
|
||||
jassert ((flags & (saveMode | openMode)) != (saveMode | openMode));
|
||||
|
||||
// You need to specify at least one of these flags..
|
||||
jassert ((flags & (canSelectFiles | canSelectDirectories)) != 0);
|
||||
|
||||
String filename;
|
||||
|
||||
if (initialFileOrDirectory == File::nonexistent)
|
||||
{
|
||||
currentRoot = File::getCurrentWorkingDirectory();
|
||||
}
|
||||
else if (initialFileOrDirectory.isDirectory())
|
||||
{
|
||||
currentRoot = initialFileOrDirectory;
|
||||
}
|
||||
else
|
||||
{
|
||||
chosenFiles.add (initialFileOrDirectory);
|
||||
currentRoot = initialFileOrDirectory.getParentDirectory();
|
||||
filename = initialFileOrDirectory.getFileName();
|
||||
}
|
||||
|
||||
fileList = new DirectoryContentsList (this, thread);
|
||||
|
||||
if ((flags & useTreeView) != 0)
|
||||
{
|
||||
FileTreeComponent* const tree = new FileTreeComponent (*fileList);
|
||||
fileListComponent = tree;
|
||||
|
||||
if ((flags & canSelectMultipleItems) != 0)
|
||||
tree->setMultiSelectEnabled (true);
|
||||
|
||||
addAndMakeVisible (tree);
|
||||
}
|
||||
else
|
||||
{
|
||||
FileListComponent* const list = new FileListComponent (*fileList);
|
||||
fileListComponent = list;
|
||||
list->setOutlineThickness (1);
|
||||
|
||||
if ((flags & canSelectMultipleItems) != 0)
|
||||
list->setMultipleSelectionEnabled (true);
|
||||
|
||||
addAndMakeVisible (list);
|
||||
}
|
||||
|
||||
fileListComponent->addListener (this);
|
||||
|
||||
addAndMakeVisible (currentPathBox);
|
||||
currentPathBox.setEditableText (true);
|
||||
resetRecentPaths();
|
||||
currentPathBox.addListener (this);
|
||||
|
||||
addAndMakeVisible (filenameBox);
|
||||
filenameBox.setMultiLine (false);
|
||||
filenameBox.setSelectAllWhenFocused (true);
|
||||
filenameBox.setText (filename, false);
|
||||
filenameBox.addListener (this);
|
||||
filenameBox.setReadOnly ((flags & (filenameBoxIsReadOnly | canSelectMultipleItems)) != 0);
|
||||
|
||||
addAndMakeVisible (fileLabel);
|
||||
fileLabel.attachToComponent (&filenameBox, true);
|
||||
|
||||
addAndMakeVisible (goUpButton = getLookAndFeel().createFileBrowserGoUpButton());
|
||||
goUpButton->addListener (this);
|
||||
goUpButton->setTooltip (TRANS ("Go up to parent directory"));
|
||||
|
||||
if (previewComp != nullptr)
|
||||
addAndMakeVisible (previewComp);
|
||||
|
||||
setRoot (currentRoot);
|
||||
|
||||
thread.startThread (4);
|
||||
}
|
||||
|
||||
FileBrowserComponent::~FileBrowserComponent()
|
||||
{
|
||||
fileListComponent = nullptr;
|
||||
fileList = nullptr;
|
||||
thread.stopThread (10000);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileBrowserComponent::addListener (FileBrowserListener* const newListener)
|
||||
{
|
||||
listeners.add (newListener);
|
||||
}
|
||||
|
||||
void FileBrowserComponent::removeListener (FileBrowserListener* const listener)
|
||||
{
|
||||
listeners.remove (listener);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool FileBrowserComponent::isSaveMode() const noexcept
|
||||
{
|
||||
return (flags & saveMode) != 0;
|
||||
}
|
||||
|
||||
int FileBrowserComponent::getNumSelectedFiles() const noexcept
|
||||
{
|
||||
if (chosenFiles.size() == 0 && currentFileIsValid())
|
||||
return 1;
|
||||
|
||||
return chosenFiles.size();
|
||||
}
|
||||
|
||||
File FileBrowserComponent::getSelectedFile (int index) const noexcept
|
||||
{
|
||||
if ((flags & canSelectDirectories) != 0 && filenameBox.getText().isEmpty())
|
||||
return currentRoot;
|
||||
|
||||
if (! filenameBox.isReadOnly())
|
||||
return currentRoot.getChildFile (filenameBox.getText());
|
||||
|
||||
return chosenFiles[index];
|
||||
}
|
||||
|
||||
bool FileBrowserComponent::currentFileIsValid() const
|
||||
{
|
||||
const File f (getSelectedFile (0));
|
||||
|
||||
if (isSaveMode())
|
||||
return (flags & canSelectDirectories) != 0 || ! f.isDirectory();
|
||||
|
||||
return f.exists();
|
||||
}
|
||||
|
||||
File FileBrowserComponent::getHighlightedFile() const noexcept
|
||||
{
|
||||
return fileListComponent->getSelectedFile (0);
|
||||
}
|
||||
|
||||
void FileBrowserComponent::deselectAllFiles()
|
||||
{
|
||||
fileListComponent->deselectAllFiles();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool FileBrowserComponent::isFileSuitable (const File& file) const
|
||||
{
|
||||
return (flags & canSelectFiles) != 0
|
||||
&& (fileFilter == nullptr || fileFilter->isFileSuitable (file));
|
||||
}
|
||||
|
||||
bool FileBrowserComponent::isDirectorySuitable (const File&) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileBrowserComponent::isFileOrDirSuitable (const File& f) const
|
||||
{
|
||||
if (f.isDirectory())
|
||||
return (flags & canSelectDirectories) != 0
|
||||
&& (fileFilter == nullptr || fileFilter->isDirectorySuitable (f));
|
||||
|
||||
return (flags & canSelectFiles) != 0 && f.exists()
|
||||
&& (fileFilter == nullptr || fileFilter->isFileSuitable (f));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const File& FileBrowserComponent::getRoot() const
|
||||
{
|
||||
return currentRoot;
|
||||
}
|
||||
|
||||
void FileBrowserComponent::setRoot (const File& newRootDirectory)
|
||||
{
|
||||
bool callListeners = false;
|
||||
|
||||
if (currentRoot != newRootDirectory)
|
||||
{
|
||||
callListeners = true;
|
||||
fileListComponent->scrollToTop();
|
||||
|
||||
String path (newRootDirectory.getFullPathName());
|
||||
|
||||
if (path.isEmpty())
|
||||
path = File::separatorString;
|
||||
|
||||
StringArray rootNames, rootPaths;
|
||||
getRoots (rootNames, rootPaths);
|
||||
|
||||
if (! rootPaths.contains (path, true))
|
||||
{
|
||||
bool alreadyListed = false;
|
||||
|
||||
for (int i = currentPathBox.getNumItems(); --i >= 0;)
|
||||
{
|
||||
if (currentPathBox.getItemText (i).equalsIgnoreCase (path))
|
||||
{
|
||||
alreadyListed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! alreadyListed)
|
||||
currentPathBox.addItem (path, currentPathBox.getNumItems() + 2);
|
||||
}
|
||||
}
|
||||
|
||||
currentRoot = newRootDirectory;
|
||||
fileList->setDirectory (currentRoot, true, true);
|
||||
|
||||
String currentRootName (currentRoot.getFullPathName());
|
||||
if (currentRootName.isEmpty())
|
||||
currentRootName = File::separatorString;
|
||||
|
||||
currentPathBox.setText (currentRootName, dontSendNotification);
|
||||
|
||||
goUpButton->setEnabled (currentRoot.getParentDirectory().isDirectory()
|
||||
&& currentRoot.getParentDirectory() != currentRoot);
|
||||
|
||||
if (callListeners)
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
listeners.callChecked (checker, &FileBrowserListener::browserRootChanged, currentRoot);
|
||||
}
|
||||
}
|
||||
|
||||
void FileBrowserComponent::setFileName (const String& newName)
|
||||
{
|
||||
filenameBox.setText (newName, true);
|
||||
|
||||
fileListComponent->setSelectedFile (currentRoot.getChildFile (newName));
|
||||
}
|
||||
|
||||
void FileBrowserComponent::resetRecentPaths()
|
||||
{
|
||||
currentPathBox.clear();
|
||||
|
||||
StringArray rootNames, rootPaths;
|
||||
getRoots (rootNames, rootPaths);
|
||||
|
||||
for (int i = 0; i < rootNames.size(); ++i)
|
||||
{
|
||||
if (rootNames[i].isEmpty())
|
||||
currentPathBox.addSeparator();
|
||||
else
|
||||
currentPathBox.addItem (rootNames[i], i + 1);
|
||||
}
|
||||
|
||||
currentPathBox.addSeparator();
|
||||
}
|
||||
|
||||
void FileBrowserComponent::goUp()
|
||||
{
|
||||
setRoot (getRoot().getParentDirectory());
|
||||
}
|
||||
|
||||
void FileBrowserComponent::refresh()
|
||||
{
|
||||
fileList->refresh();
|
||||
}
|
||||
|
||||
void FileBrowserComponent::setFileFilter (const FileFilter* const newFileFilter)
|
||||
{
|
||||
if (fileFilter != newFileFilter)
|
||||
{
|
||||
fileFilter = newFileFilter;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
String FileBrowserComponent::getActionVerb() const
|
||||
{
|
||||
return isSaveMode() ? ((flags & canSelectDirectories) != 0 ? TRANS("Choose")
|
||||
: TRANS("Save"))
|
||||
: TRANS("Open");
|
||||
}
|
||||
|
||||
void FileBrowserComponent::setFilenameBoxLabel (const String& name)
|
||||
{
|
||||
fileLabel.setText (name, dontSendNotification);
|
||||
}
|
||||
|
||||
FilePreviewComponent* FileBrowserComponent::getPreviewComponent() const noexcept
|
||||
{
|
||||
return previewComp;
|
||||
}
|
||||
|
||||
DirectoryContentsDisplayComponent* FileBrowserComponent::getDisplayComponent() const noexcept
|
||||
{
|
||||
return fileListComponent;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileBrowserComponent::resized()
|
||||
{
|
||||
getLookAndFeel()
|
||||
.layoutFileBrowserComponent (*this, fileListComponent, previewComp,
|
||||
¤tPathBox, &filenameBox, goUpButton);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileBrowserComponent::sendListenerChangeMessage()
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
|
||||
if (previewComp != nullptr)
|
||||
previewComp->selectedFileChanged (getSelectedFile (0));
|
||||
|
||||
// You shouldn't delete the browser when the file gets changed!
|
||||
jassert (! checker.shouldBailOut());
|
||||
|
||||
listeners.callChecked (checker, &FileBrowserListener::selectionChanged);
|
||||
}
|
||||
|
||||
void FileBrowserComponent::selectionChanged()
|
||||
{
|
||||
StringArray newFilenames;
|
||||
bool resetChosenFiles = true;
|
||||
|
||||
for (int i = 0; i < fileListComponent->getNumSelectedFiles(); ++i)
|
||||
{
|
||||
const File f (fileListComponent->getSelectedFile (i));
|
||||
|
||||
if (isFileOrDirSuitable (f))
|
||||
{
|
||||
if (resetChosenFiles)
|
||||
{
|
||||
chosenFiles.clear();
|
||||
resetChosenFiles = false;
|
||||
}
|
||||
|
||||
chosenFiles.add (f);
|
||||
newFilenames.add (f.getRelativePathFrom (getRoot()));
|
||||
}
|
||||
}
|
||||
|
||||
if (newFilenames.size() > 0)
|
||||
filenameBox.setText (newFilenames.joinIntoString (", "), false);
|
||||
|
||||
sendListenerChangeMessage();
|
||||
}
|
||||
|
||||
void FileBrowserComponent::fileClicked (const File& f, const MouseEvent& e)
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
listeners.callChecked (checker, &FileBrowserListener::fileClicked, f, e);
|
||||
}
|
||||
|
||||
void FileBrowserComponent::fileDoubleClicked (const File& f)
|
||||
{
|
||||
if (f.isDirectory())
|
||||
{
|
||||
setRoot (f);
|
||||
|
||||
if ((flags & canSelectDirectories) != 0)
|
||||
filenameBox.setText (String::empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
listeners.callChecked (checker, &FileBrowserListener::fileDoubleClicked, f);
|
||||
}
|
||||
}
|
||||
|
||||
void FileBrowserComponent::browserRootChanged (const File&) {}
|
||||
|
||||
bool FileBrowserComponent::keyPressed (const KeyPress& key)
|
||||
{
|
||||
(void) key;
|
||||
|
||||
#if JUCE_LINUX || JUCE_WINDOWS
|
||||
if (key.getModifiers().isCommandDown()
|
||||
&& (key.getKeyCode() == 'H' || key.getKeyCode() == 'h'))
|
||||
{
|
||||
fileList->setIgnoresHiddenFiles (! fileList->ignoresHiddenFiles());
|
||||
fileList->refresh();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileBrowserComponent::textEditorTextChanged (TextEditor&)
|
||||
{
|
||||
sendListenerChangeMessage();
|
||||
}
|
||||
|
||||
void FileBrowserComponent::textEditorReturnKeyPressed (TextEditor&)
|
||||
{
|
||||
if (filenameBox.getText().containsChar (File::separator))
|
||||
{
|
||||
const File f (currentRoot.getChildFile (filenameBox.getText()));
|
||||
|
||||
if (f.isDirectory())
|
||||
{
|
||||
setRoot (f);
|
||||
chosenFiles.clear();
|
||||
filenameBox.setText (String::empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
setRoot (f.getParentDirectory());
|
||||
chosenFiles.clear();
|
||||
chosenFiles.add (f);
|
||||
filenameBox.setText (f.getFileName());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fileDoubleClicked (getSelectedFile (0));
|
||||
}
|
||||
}
|
||||
|
||||
void FileBrowserComponent::textEditorEscapeKeyPressed (TextEditor&)
|
||||
{
|
||||
}
|
||||
|
||||
void FileBrowserComponent::textEditorFocusLost (TextEditor&)
|
||||
{
|
||||
if (! isSaveMode())
|
||||
selectionChanged();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileBrowserComponent::buttonClicked (Button*)
|
||||
{
|
||||
goUp();
|
||||
}
|
||||
|
||||
void FileBrowserComponent::comboBoxChanged (ComboBox*)
|
||||
{
|
||||
const String newText (currentPathBox.getText().trim().unquoted());
|
||||
|
||||
if (newText.isNotEmpty())
|
||||
{
|
||||
const int index = currentPathBox.getSelectedId() - 1;
|
||||
|
||||
StringArray rootNames, rootPaths;
|
||||
getRoots (rootNames, rootPaths);
|
||||
|
||||
if (rootPaths [index].isNotEmpty())
|
||||
{
|
||||
setRoot (File (rootPaths [index]));
|
||||
}
|
||||
else
|
||||
{
|
||||
File f (newText);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (f.isDirectory())
|
||||
{
|
||||
setRoot (f);
|
||||
break;
|
||||
}
|
||||
|
||||
if (f.getParentDirectory() == f)
|
||||
break;
|
||||
|
||||
f = f.getParentDirectory();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileBrowserComponent::getDefaultRoots (StringArray& rootNames, StringArray& rootPaths)
|
||||
{
|
||||
#if JUCE_WINDOWS
|
||||
Array<File> roots;
|
||||
File::findFileSystemRoots (roots);
|
||||
rootPaths.clear();
|
||||
|
||||
for (int i = 0; i < roots.size(); ++i)
|
||||
{
|
||||
const File& drive = roots.getReference(i);
|
||||
|
||||
String name (drive.getFullPathName());
|
||||
rootPaths.add (name);
|
||||
|
||||
if (drive.isOnHardDisk())
|
||||
{
|
||||
String volume (drive.getVolumeLabel());
|
||||
|
||||
if (volume.isEmpty())
|
||||
volume = TRANS("Hard Drive");
|
||||
|
||||
name << " [" << volume << ']';
|
||||
}
|
||||
else if (drive.isOnCDRomDrive())
|
||||
{
|
||||
name << " [" << TRANS("CD/DVD drive") << ']';
|
||||
}
|
||||
|
||||
rootNames.add (name);
|
||||
}
|
||||
|
||||
rootPaths.add (String::empty);
|
||||
rootNames.add (String::empty);
|
||||
|
||||
rootPaths.add (File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Documents"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userMusicDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Music"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userPicturesDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Pictures"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Desktop"));
|
||||
|
||||
#elif JUCE_MAC
|
||||
rootPaths.add (File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Home folder"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Documents"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userMusicDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Music"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userPicturesDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Pictures"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Desktop"));
|
||||
|
||||
rootPaths.add (String::empty);
|
||||
rootNames.add (String::empty);
|
||||
|
||||
Array <File> volumes;
|
||||
File vol ("/Volumes");
|
||||
vol.findChildFiles (volumes, File::findDirectories, false);
|
||||
|
||||
for (int i = 0; i < volumes.size(); ++i)
|
||||
{
|
||||
const File& volume = volumes.getReference(i);
|
||||
|
||||
if (volume.isDirectory() && ! volume.getFileName().startsWithChar ('.'))
|
||||
{
|
||||
rootPaths.add (volume.getFullPathName());
|
||||
rootNames.add (volume.getFileName());
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
rootPaths.add ("/");
|
||||
rootNames.add ("/");
|
||||
rootPaths.add (File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Home folder"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName());
|
||||
rootNames.add (TRANS("Desktop"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void FileBrowserComponent::getRoots (StringArray& rootNames, StringArray& rootPaths)
|
||||
{
|
||||
getDefaultRoots (rootNames, rootPaths);
|
||||
}
|
||||
|
|
@ -0,0 +1,290 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_FILEBROWSERCOMPONENT_H_INCLUDED
|
||||
#define JUCE_FILEBROWSERCOMPONENT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component for browsing and selecting a file or directory to open or save.
|
||||
|
||||
This contains a FileListComponent and adds various boxes and controls for
|
||||
navigating and selecting a file. It can work in different modes so that it can
|
||||
be used for loading or saving a file, or for choosing a directory.
|
||||
|
||||
@see FileChooserDialogBox, FileChooser, FileListComponent
|
||||
*/
|
||||
class JUCE_API FileBrowserComponent : public Component,
|
||||
private FileBrowserListener,
|
||||
private TextEditorListener,
|
||||
private ButtonListener,
|
||||
private ComboBoxListener, // (can't use ComboBox::Listener due to idiotic VC2005 bug)
|
||||
private FileFilter
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Various options for the browser.
|
||||
|
||||
A combination of these is passed into the FileBrowserComponent constructor.
|
||||
*/
|
||||
enum FileChooserFlags
|
||||
{
|
||||
openMode = 1, /**< specifies that the component should allow the user to
|
||||
choose an existing file with the intention of opening it. */
|
||||
saveMode = 2, /**< specifies that the component should allow the user to specify
|
||||
the name of a file that will be used to save something. */
|
||||
canSelectFiles = 4, /**< specifies that the user can select files (can be used in
|
||||
conjunction with canSelectDirectories). */
|
||||
canSelectDirectories = 8, /**< specifies that the user can select directories (can be used in
|
||||
conjuction with canSelectFiles). */
|
||||
canSelectMultipleItems = 16, /**< specifies that the user can select multiple items. */
|
||||
useTreeView = 32, /**< specifies that a tree-view should be shown instead of a file list. */
|
||||
filenameBoxIsReadOnly = 64, /**< specifies that the user can't type directly into the filename box. */
|
||||
warnAboutOverwriting = 128 /**< specifies that the dialog should warn about overwriting existing files (if possible). */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a FileBrowserComponent.
|
||||
|
||||
@param flags A combination of flags from the FileChooserFlags enumeration, used to
|
||||
specify the component's behaviour. The flags must contain either openMode
|
||||
or saveMode, and canSelectFiles and/or canSelectDirectories.
|
||||
@param initialFileOrDirectory The file or directory that should be selected when the component begins.
|
||||
If this is File::nonexistent, a default directory will be chosen.
|
||||
@param fileFilter an optional filter to use to determine which files are shown.
|
||||
If this is nullptr then all files are displayed. Note that a pointer
|
||||
is kept internally to this object, so make sure that it is not deleted
|
||||
before the FileBrowserComponent object is deleted.
|
||||
@param previewComp an optional preview component that will be used to show previews of
|
||||
files that the user selects
|
||||
*/
|
||||
FileBrowserComponent (int flags,
|
||||
const File& initialFileOrDirectory,
|
||||
const FileFilter* fileFilter,
|
||||
FilePreviewComponent* previewComp);
|
||||
|
||||
/** Destructor. */
|
||||
~FileBrowserComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of files that the user has got selected.
|
||||
If multiple select isn't active, this will only be 0 or 1. To get the complete
|
||||
list of files they've chosen, pass an index to getCurrentFile().
|
||||
*/
|
||||
int getNumSelectedFiles() const noexcept;
|
||||
|
||||
/** Returns one of the files that the user has chosen.
|
||||
If the box has multi-select enabled, the index lets you specify which of the files
|
||||
to get - see getNumSelectedFiles() to find out how many files were chosen.
|
||||
@see getHighlightedFile
|
||||
*/
|
||||
File getSelectedFile (int index) const noexcept;
|
||||
|
||||
/** Deselects any files that are currently selected.
|
||||
*/
|
||||
void deselectAllFiles();
|
||||
|
||||
/** Returns true if the currently selected file(s) are usable.
|
||||
|
||||
This can be used to decide whether the user can press "ok" for the
|
||||
current file. What it does depends on the mode, so for example in an "open"
|
||||
mode, this only returns true if a file has been selected and if it exists.
|
||||
In a "save" mode, a non-existent file would also be valid.
|
||||
*/
|
||||
bool currentFileIsValid() const;
|
||||
|
||||
/** This returns the last item in the view that the user has highlighted.
|
||||
This may be different from getCurrentFile(), which returns the value
|
||||
that is shown in the filename box, and if there are multiple selections,
|
||||
this will only return one of them.
|
||||
@see getSelectedFile
|
||||
*/
|
||||
File getHighlightedFile() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the directory whose contents are currently being shown in the listbox. */
|
||||
const File& getRoot() const;
|
||||
|
||||
/** Changes the directory that's being shown in the listbox. */
|
||||
void setRoot (const File& newRootDirectory);
|
||||
|
||||
/** Changes the name that is currently shown in the filename box. */
|
||||
void setFileName (const String& newName);
|
||||
|
||||
/** Equivalent to pressing the "up" button to browse the parent directory. */
|
||||
void goUp();
|
||||
|
||||
/** Refreshes the directory that's currently being listed. */
|
||||
void refresh();
|
||||
|
||||
/** Changes the filter that's being used to sift the files. */
|
||||
void setFileFilter (const FileFilter* newFileFilter);
|
||||
|
||||
/** Returns a verb to describe what should happen when the file is accepted.
|
||||
|
||||
E.g. if browsing in "load file" mode, this will be "Open", if in "save file"
|
||||
mode, it'll be "Save", etc.
|
||||
*/
|
||||
virtual String getActionVerb() const;
|
||||
|
||||
/** Returns true if the saveMode flag was set when this component was created.
|
||||
*/
|
||||
bool isSaveMode() const noexcept;
|
||||
|
||||
/** Sets the label that will be displayed next to the filename entry box.
|
||||
By default this is just "file", but you might want to change it to something more
|
||||
appropriate for your app.
|
||||
*/
|
||||
void setFilenameBoxLabel (const String& name);
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a listener to be told when the user selects and clicks on files.
|
||||
@see removeListener
|
||||
*/
|
||||
void addListener (FileBrowserListener* listener);
|
||||
|
||||
/** Removes a listener.
|
||||
@see addListener
|
||||
*/
|
||||
void removeListener (FileBrowserListener* listener);
|
||||
|
||||
/** Returns a platform-specific list of names and paths for some suggested places the user
|
||||
might want to use as root folders.
|
||||
The list returned contains empty strings to indicate section breaks.
|
||||
@see getRoots()
|
||||
*/
|
||||
static void getDefaultRoots (StringArray& rootNames, StringArray& rootPaths);
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
various file-browser layout and drawing methods.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
// These return a pointer to an internally cached drawable - make sure you don't keep
|
||||
// a copy of this pointer anywhere, as it may become invalid in the future.
|
||||
virtual const Drawable* getDefaultFolderImage() = 0;
|
||||
virtual const Drawable* getDefaultDocumentFileImage() = 0;
|
||||
|
||||
virtual AttributedString createFileChooserHeaderText (const String& title,
|
||||
const String& instructions) = 0;
|
||||
|
||||
virtual void drawFileBrowserRow (Graphics&, int width, int height,
|
||||
const String& filename,
|
||||
Image* optionalIcon,
|
||||
const String& fileSizeDescription,
|
||||
const String& fileTimeDescription,
|
||||
bool isDirectory,
|
||||
bool isItemSelected,
|
||||
int itemIndex,
|
||||
DirectoryContentsDisplayComponent&) = 0;
|
||||
|
||||
virtual Button* createFileBrowserGoUpButton() = 0;
|
||||
|
||||
virtual void layoutFileBrowserComponent (FileBrowserComponent& browserComp,
|
||||
DirectoryContentsDisplayComponent* fileListComponent,
|
||||
FilePreviewComponent* previewComp,
|
||||
ComboBox* currentPathBox,
|
||||
TextEditor* filenameBox,
|
||||
Button* goUpButton) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void buttonClicked (Button*) override;
|
||||
/** @internal */
|
||||
void comboBoxChanged (ComboBox*) override;
|
||||
/** @internal */
|
||||
void textEditorTextChanged (TextEditor&) override;
|
||||
/** @internal */
|
||||
void textEditorReturnKeyPressed (TextEditor&) override;
|
||||
/** @internal */
|
||||
void textEditorEscapeKeyPressed (TextEditor&) override;
|
||||
/** @internal */
|
||||
void textEditorFocusLost (TextEditor&) override;
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
void selectionChanged() override;
|
||||
/** @internal */
|
||||
void fileClicked (const File&, const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void fileDoubleClicked (const File&) override;
|
||||
/** @internal */
|
||||
void browserRootChanged (const File&) override;
|
||||
/** @internal */
|
||||
bool isFileSuitable (const File&) const override;
|
||||
/** @internal */
|
||||
bool isDirectorySuitable (const File&) const override;
|
||||
|
||||
/** @internal */
|
||||
FilePreviewComponent* getPreviewComponent() const noexcept;
|
||||
|
||||
/** @internal */
|
||||
DirectoryContentsDisplayComponent* getDisplayComponent() const noexcept;
|
||||
|
||||
protected:
|
||||
/** Returns a list of names and paths for the default places the user might want to look.
|
||||
|
||||
By default this just calls getDefaultRoots(), but you may want to override it to
|
||||
return a custom list.
|
||||
*/
|
||||
virtual void getRoots (StringArray& rootNames, StringArray& rootPaths);
|
||||
|
||||
/** Updates the items in the dropdown list of recent paths with the values from getRoots(). */
|
||||
void resetRecentPaths();
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
ScopedPointer <DirectoryContentsList> fileList;
|
||||
const FileFilter* fileFilter;
|
||||
|
||||
int flags;
|
||||
File currentRoot;
|
||||
Array<File> chosenFiles;
|
||||
ListenerList <FileBrowserListener> listeners;
|
||||
|
||||
ScopedPointer<DirectoryContentsDisplayComponent> fileListComponent;
|
||||
FilePreviewComponent* previewComp;
|
||||
ComboBox currentPathBox;
|
||||
TextEditor filenameBox;
|
||||
Label fileLabel;
|
||||
ScopedPointer<Button> goUpButton;
|
||||
|
||||
TimeSliceThread thread;
|
||||
|
||||
void sendListenerChangeMessage();
|
||||
bool isFileOrDirSuitable (const File& f) const;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileBrowserComponent)
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // JUCE_FILEBROWSERCOMPONENT_H_INCLUDED
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_FILEBROWSERLISTENER_H_INCLUDED
|
||||
#define JUCE_FILEBROWSERLISTENER_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A listener for user selection events in a file browser.
|
||||
|
||||
This is used by a FileBrowserComponent or FileListComponent.
|
||||
*/
|
||||
class JUCE_API FileBrowserListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Destructor. */
|
||||
virtual ~FileBrowserListener();
|
||||
|
||||
//==============================================================================
|
||||
/** Callback when the user selects a different file in the browser. */
|
||||
virtual void selectionChanged() = 0;
|
||||
|
||||
/** Callback when the user clicks on a file in the browser. */
|
||||
virtual void fileClicked (const File& file, const MouseEvent& e) = 0;
|
||||
|
||||
/** Callback when the user double-clicks on a file in the browser. */
|
||||
virtual void fileDoubleClicked (const File& file) = 0;
|
||||
|
||||
/** Callback when the browser's root folder changes. */
|
||||
virtual void browserRootChanged (const File& newRoot) = 0;
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_FILEBROWSERLISTENER_H_INCLUDED
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
FileChooser::FileChooser (const String& chooserBoxTitle,
|
||||
const File& currentFileOrDirectory,
|
||||
const String& fileFilters,
|
||||
const bool useNativeBox)
|
||||
: title (chooserBoxTitle),
|
||||
filters (fileFilters),
|
||||
startingFile (currentFileOrDirectory),
|
||||
useNativeDialogBox (useNativeBox && isPlatformDialogAvailable())
|
||||
{
|
||||
if (! fileFilters.containsNonWhitespaceChars())
|
||||
filters = "*";
|
||||
}
|
||||
|
||||
FileChooser::~FileChooser() {}
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
bool FileChooser::browseForFileToOpen (FilePreviewComponent* previewComp)
|
||||
{
|
||||
return showDialog (FileBrowserComponent::openMode
|
||||
| FileBrowserComponent::canSelectFiles,
|
||||
previewComp);
|
||||
}
|
||||
|
||||
bool FileChooser::browseForMultipleFilesToOpen (FilePreviewComponent* previewComp)
|
||||
{
|
||||
return showDialog (FileBrowserComponent::openMode
|
||||
| FileBrowserComponent::canSelectFiles
|
||||
| FileBrowserComponent::canSelectMultipleItems,
|
||||
previewComp);
|
||||
}
|
||||
|
||||
bool FileChooser::browseForMultipleFilesOrDirectories (FilePreviewComponent* previewComp)
|
||||
{
|
||||
return showDialog (FileBrowserComponent::openMode
|
||||
| FileBrowserComponent::canSelectFiles
|
||||
| FileBrowserComponent::canSelectDirectories
|
||||
| FileBrowserComponent::canSelectMultipleItems,
|
||||
previewComp);
|
||||
}
|
||||
|
||||
bool FileChooser::browseForFileToSave (const bool warnAboutOverwrite)
|
||||
{
|
||||
return showDialog (FileBrowserComponent::saveMode
|
||||
| FileBrowserComponent::canSelectFiles
|
||||
| (warnAboutOverwrite ? FileBrowserComponent::warnAboutOverwriting : 0),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
bool FileChooser::browseForDirectory()
|
||||
{
|
||||
return showDialog (FileBrowserComponent::openMode
|
||||
| FileBrowserComponent::canSelectDirectories,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
bool FileChooser::showDialog (const int flags, FilePreviewComponent* const previewComp)
|
||||
{
|
||||
FocusRestorer focusRestorer;
|
||||
|
||||
results.clear();
|
||||
|
||||
// the preview component needs to be the right size before you pass it in here..
|
||||
jassert (previewComp == nullptr || (previewComp->getWidth() > 10
|
||||
&& previewComp->getHeight() > 10));
|
||||
|
||||
const bool selectsDirectories = (flags & FileBrowserComponent::canSelectDirectories) != 0;
|
||||
const bool selectsFiles = (flags & FileBrowserComponent::canSelectFiles) != 0;
|
||||
const bool isSave = (flags & FileBrowserComponent::saveMode) != 0;
|
||||
const bool warnAboutOverwrite = (flags & FileBrowserComponent::warnAboutOverwriting) != 0;
|
||||
const bool selectMultiple = (flags & FileBrowserComponent::canSelectMultipleItems) != 0;
|
||||
|
||||
// You've set the flags for both saveMode and openMode!
|
||||
jassert (! (isSave && (flags & FileBrowserComponent::openMode) != 0));
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
if (useNativeDialogBox && ! (selectsFiles && selectsDirectories))
|
||||
#elif JUCE_MAC || JUCE_LINUX
|
||||
if (useNativeDialogBox && (previewComp == nullptr))
|
||||
#else
|
||||
if (false)
|
||||
#endif
|
||||
{
|
||||
showPlatformDialog (results, title, startingFile, filters,
|
||||
selectsDirectories, selectsFiles, isSave,
|
||||
warnAboutOverwrite, selectMultiple, previewComp);
|
||||
}
|
||||
else
|
||||
{
|
||||
WildcardFileFilter wildcard (selectsFiles ? filters : String::empty,
|
||||
selectsDirectories ? "*" : String::empty,
|
||||
String::empty);
|
||||
|
||||
FileBrowserComponent browserComponent (flags, startingFile, &wildcard, previewComp);
|
||||
|
||||
FileChooserDialogBox box (title, String::empty,
|
||||
browserComponent, warnAboutOverwrite,
|
||||
browserComponent.findColour (AlertWindow::backgroundColourId));
|
||||
|
||||
if (box.show())
|
||||
{
|
||||
for (int i = 0; i < browserComponent.getNumSelectedFiles(); ++i)
|
||||
results.add (browserComponent.getSelectedFile (i));
|
||||
}
|
||||
}
|
||||
|
||||
return results.size() > 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
File FileChooser::getResult() const
|
||||
{
|
||||
// if you've used a multiple-file select, you should use the getResults() method
|
||||
// to retrieve all the files that were chosen.
|
||||
jassert (results.size() <= 1);
|
||||
|
||||
return results.getFirst();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
FilePreviewComponent::FilePreviewComponent() {}
|
||||
FilePreviewComponent::~FilePreviewComponent() {}
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_FILECHOOSER_H_INCLUDED
|
||||
#define JUCE_FILECHOOSER_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Creates a dialog box to choose a file or directory to load or save.
|
||||
|
||||
To use a FileChooser:
|
||||
- create one (as a local stack variable is the neatest way)
|
||||
- call one of its browseFor.. methods
|
||||
- if this returns true, the user has selected a file, so you can retrieve it
|
||||
with the getResult() method.
|
||||
|
||||
e.g. @code
|
||||
void loadMooseFile()
|
||||
{
|
||||
FileChooser myChooser ("Please select the moose you want to load...",
|
||||
File::getSpecialLocation (File::userHomeDirectory),
|
||||
"*.moose");
|
||||
|
||||
if (myChooser.browseForFileToOpen())
|
||||
{
|
||||
File mooseFile (myChooser.getResult());
|
||||
|
||||
loadMoose (mooseFile);
|
||||
}
|
||||
}
|
||||
@endcode
|
||||
*/
|
||||
class JUCE_API FileChooser
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a FileChooser.
|
||||
|
||||
After creating one of these, use one of the browseFor... methods to display it.
|
||||
|
||||
@param dialogBoxTitle a text string to display in the dialog box to
|
||||
tell the user what's going on
|
||||
@param initialFileOrDirectory the file or directory that should be selected when
|
||||
the dialog box opens. If this parameter is set to
|
||||
File::nonexistent, a sensible default directory
|
||||
will be used instead.
|
||||
@param filePatternsAllowed a set of file patterns to specify which files can be
|
||||
selected - each pattern should be separated by a
|
||||
comma or semi-colon, e.g. "*" or "*.jpg;*.gif". An
|
||||
empty string means that all files are allowed
|
||||
@param useOSNativeDialogBox if true, then a native dialog box will be used if
|
||||
possible; if false, then a Juce-based browser dialog
|
||||
box will always be used
|
||||
@see browseForFileToOpen, browseForFileToSave, browseForDirectory
|
||||
*/
|
||||
FileChooser (const String& dialogBoxTitle,
|
||||
const File& initialFileOrDirectory = File::nonexistent,
|
||||
const String& filePatternsAllowed = String::empty,
|
||||
bool useOSNativeDialogBox = true);
|
||||
|
||||
/** Destructor. */
|
||||
~FileChooser();
|
||||
|
||||
//==============================================================================
|
||||
/** Shows a dialog box to choose a file to open.
|
||||
|
||||
This will display the dialog box modally, using an "open file" mode, so that
|
||||
it won't allow non-existent files or directories to be chosen.
|
||||
|
||||
@param previewComponent an optional component to display inside the dialog
|
||||
box to show special info about the files that the user
|
||||
is browsing. The component will not be deleted by this
|
||||
object, so the caller must take care of it.
|
||||
@returns true if the user selected a file, in which case, use the getResult()
|
||||
method to find out what it was. Returns false if they cancelled instead.
|
||||
@see browseForFileToSave, browseForDirectory
|
||||
*/
|
||||
bool browseForFileToOpen (FilePreviewComponent* previewComponent = nullptr);
|
||||
|
||||
/** Same as browseForFileToOpen, but allows the user to select multiple files.
|
||||
|
||||
The files that are returned can be obtained by calling getResults(). See
|
||||
browseForFileToOpen() for more info about the behaviour of this method.
|
||||
*/
|
||||
bool browseForMultipleFilesToOpen (FilePreviewComponent* previewComponent = nullptr);
|
||||
|
||||
/** Shows a dialog box to choose a file to save.
|
||||
|
||||
This will display the dialog box modally, using an "save file" mode, so it
|
||||
will allow non-existent files to be chosen, but not directories.
|
||||
|
||||
@param warnAboutOverwritingExistingFiles if true, the dialog box will ask
|
||||
the user if they're sure they want to overwrite a file that already
|
||||
exists
|
||||
@returns true if the user chose a file and pressed 'ok', in which case, use
|
||||
the getResult() method to find out what the file was. Returns false
|
||||
if they cancelled instead.
|
||||
@see browseForFileToOpen, browseForDirectory
|
||||
*/
|
||||
bool browseForFileToSave (bool warnAboutOverwritingExistingFiles);
|
||||
|
||||
/** Shows a dialog box to choose a directory.
|
||||
|
||||
This will display the dialog box modally, using an "open directory" mode, so it
|
||||
will only allow directories to be returned, not files.
|
||||
|
||||
@returns true if the user chose a directory and pressed 'ok', in which case, use
|
||||
the getResult() method to find out what they chose. Returns false
|
||||
if they cancelled instead.
|
||||
@see browseForFileToOpen, browseForFileToSave
|
||||
*/
|
||||
bool browseForDirectory();
|
||||
|
||||
/** Same as browseForFileToOpen, but allows the user to select multiple files and directories.
|
||||
|
||||
The files that are returned can be obtained by calling getResults(). See
|
||||
browseForFileToOpen() for more info about the behaviour of this method.
|
||||
*/
|
||||
bool browseForMultipleFilesOrDirectories (FilePreviewComponent* previewComponent = nullptr);
|
||||
|
||||
//==============================================================================
|
||||
/** Runs a dialog box for the given set of option flags.
|
||||
The flag values used are those in FileBrowserComponent::FileChooserFlags.
|
||||
|
||||
@returns true if the user chose a directory and pressed 'ok', in which case, use
|
||||
the getResult() method to find out what they chose. Returns false
|
||||
if they cancelled instead.
|
||||
@see FileBrowserComponent::FileChooserFlags
|
||||
*/
|
||||
bool showDialog (int flags, FilePreviewComponent* previewComponent);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the last file that was chosen by one of the browseFor methods.
|
||||
|
||||
After calling the appropriate browseFor... method, this method lets you
|
||||
find out what file or directory they chose.
|
||||
|
||||
Note that the file returned is only valid if the browse method returned true (i.e.
|
||||
if the user pressed 'ok' rather than cancelling).
|
||||
|
||||
If you're using a multiple-file select, then use the getResults() method instead,
|
||||
to obtain the list of all files chosen.
|
||||
|
||||
@see getResults
|
||||
*/
|
||||
File getResult() const;
|
||||
|
||||
/** Returns a list of all the files that were chosen during the last call to a
|
||||
browse method.
|
||||
|
||||
This array may be empty if no files were chosen, or can contain multiple entries
|
||||
if multiple files were chosen.
|
||||
|
||||
@see getResult
|
||||
*/
|
||||
const Array<File>& getResults() const noexcept { return results; }
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
String title, filters;
|
||||
const File startingFile;
|
||||
Array<File> results;
|
||||
const bool useNativeDialogBox;
|
||||
|
||||
static void showPlatformDialog (Array<File>& results, const String& title, const File& file,
|
||||
const String& filters, bool selectsDirectories, bool selectsFiles,
|
||||
bool isSave, bool warnAboutOverwritingExistingFiles, bool selectMultipleFiles,
|
||||
FilePreviewComponent* previewComponent);
|
||||
static bool isPlatformDialogAvailable();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileChooser)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_FILECHOOSER_H_INCLUDED
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
class FileChooserDialogBox::ContentComponent : public Component
|
||||
{
|
||||
public:
|
||||
ContentComponent (const String& name, const String& desc, FileBrowserComponent& chooser)
|
||||
: Component (name),
|
||||
chooserComponent (chooser),
|
||||
okButton (chooser.getActionVerb()),
|
||||
cancelButton (TRANS ("Cancel")),
|
||||
newFolderButton (TRANS ("New Folder")),
|
||||
instructions (desc)
|
||||
{
|
||||
addAndMakeVisible (chooserComponent);
|
||||
|
||||
addAndMakeVisible (okButton);
|
||||
okButton.addShortcut (KeyPress (KeyPress::returnKey));
|
||||
|
||||
addAndMakeVisible (cancelButton);
|
||||
cancelButton.addShortcut (KeyPress (KeyPress::escapeKey));
|
||||
|
||||
addChildComponent (newFolderButton);
|
||||
|
||||
setInterceptsMouseClicks (false, true);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
text.draw (g, getLocalBounds().reduced (6)
|
||||
.removeFromTop ((int) text.getHeight()).toFloat());
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
const int buttonHeight = 26;
|
||||
|
||||
Rectangle<int> area (getLocalBounds());
|
||||
|
||||
text.createLayout (getLookAndFeel().createFileChooserHeaderText (getName(), instructions),
|
||||
getWidth() - 12.0f);
|
||||
|
||||
area.removeFromTop (roundToInt (text.getHeight()) + 10);
|
||||
|
||||
chooserComponent.setBounds (area.removeFromTop (area.getHeight() - buttonHeight - 20));
|
||||
Rectangle<int> buttonArea (area.reduced (16, 10));
|
||||
|
||||
okButton.changeWidthToFitText (buttonHeight);
|
||||
okButton.setBounds (buttonArea.removeFromRight (okButton.getWidth() + 16));
|
||||
|
||||
buttonArea.removeFromRight (16);
|
||||
|
||||
cancelButton.changeWidthToFitText (buttonHeight);
|
||||
cancelButton.setBounds (buttonArea.removeFromRight (cancelButton.getWidth()));
|
||||
|
||||
newFolderButton.changeWidthToFitText (buttonHeight);
|
||||
newFolderButton.setBounds (buttonArea.removeFromLeft (newFolderButton.getWidth()));
|
||||
}
|
||||
|
||||
FileBrowserComponent& chooserComponent;
|
||||
TextButton okButton, cancelButton, newFolderButton;
|
||||
|
||||
private:
|
||||
String instructions;
|
||||
TextLayout text;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
FileChooserDialogBox::FileChooserDialogBox (const String& name,
|
||||
const String& instructions,
|
||||
FileBrowserComponent& chooserComponent,
|
||||
const bool warnAboutOverwritingExistingFiles_,
|
||||
Colour backgroundColour)
|
||||
: ResizableWindow (name, backgroundColour, true),
|
||||
warnAboutOverwritingExistingFiles (warnAboutOverwritingExistingFiles_)
|
||||
{
|
||||
content = new ContentComponent (name, instructions, chooserComponent);
|
||||
setContentOwned (content, false);
|
||||
|
||||
setResizable (true, true);
|
||||
setResizeLimits (300, 300, 1200, 1000);
|
||||
|
||||
content->okButton.addListener (this);
|
||||
content->cancelButton.addListener (this);
|
||||
content->newFolderButton.addListener (this);
|
||||
content->chooserComponent.addListener (this);
|
||||
|
||||
FileChooserDialogBox::selectionChanged();
|
||||
}
|
||||
|
||||
FileChooserDialogBox::~FileChooserDialogBox()
|
||||
{
|
||||
content->chooserComponent.removeListener (this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
bool FileChooserDialogBox::show (int w, int h)
|
||||
{
|
||||
return showAt (-1, -1, w, h);
|
||||
}
|
||||
|
||||
bool FileChooserDialogBox::showAt (int x, int y, int w, int h)
|
||||
{
|
||||
if (w <= 0) w = getDefaultWidth();
|
||||
if (h <= 0) h = 500;
|
||||
|
||||
if (x < 0 || y < 0)
|
||||
centreWithSize (w, h);
|
||||
else
|
||||
setBounds (x, y, w, h);
|
||||
|
||||
const bool ok = (runModalLoop() != 0);
|
||||
setVisible (false);
|
||||
return ok;
|
||||
}
|
||||
#endif
|
||||
|
||||
void FileChooserDialogBox::centreWithDefaultSize (Component* componentToCentreAround)
|
||||
{
|
||||
centreAroundComponent (componentToCentreAround, getDefaultWidth(), 500);
|
||||
}
|
||||
|
||||
int FileChooserDialogBox::getDefaultWidth() const
|
||||
{
|
||||
if (Component* const previewComp = content->chooserComponent.getPreviewComponent())
|
||||
return 400 + previewComp->getWidth();
|
||||
|
||||
return 600;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileChooserDialogBox::buttonClicked (Button* button)
|
||||
{
|
||||
if (button == &(content->okButton))
|
||||
{
|
||||
okButtonPressed();
|
||||
}
|
||||
else if (button == &(content->cancelButton))
|
||||
{
|
||||
closeButtonPressed();
|
||||
}
|
||||
else if (button == &(content->newFolderButton))
|
||||
{
|
||||
createNewFolder();
|
||||
}
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::closeButtonPressed()
|
||||
{
|
||||
setVisible (false);
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::selectionChanged()
|
||||
{
|
||||
content->okButton.setEnabled (content->chooserComponent.currentFileIsValid());
|
||||
|
||||
content->newFolderButton.setVisible (content->chooserComponent.isSaveMode()
|
||||
&& content->chooserComponent.getRoot().isDirectory());
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::fileDoubleClicked (const File&)
|
||||
{
|
||||
selectionChanged();
|
||||
content->okButton.triggerClick();
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::fileClicked (const File&, const MouseEvent&) {}
|
||||
void FileChooserDialogBox::browserRootChanged (const File&) {}
|
||||
|
||||
void FileChooserDialogBox::okToOverwriteFileCallback (int result, FileChooserDialogBox* box)
|
||||
{
|
||||
if (result != 0 && box != nullptr)
|
||||
box->exitModalState (1);
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::okButtonPressed()
|
||||
{
|
||||
if (warnAboutOverwritingExistingFiles
|
||||
&& content->chooserComponent.isSaveMode()
|
||||
&& content->chooserComponent.getSelectedFile(0).exists())
|
||||
{
|
||||
AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
|
||||
TRANS("File already exists"),
|
||||
TRANS("There's already a file called: FLNM")
|
||||
.replace ("FLNM", content->chooserComponent.getSelectedFile(0).getFullPathName())
|
||||
+ "\n\n"
|
||||
+ TRANS("Are you sure you want to overwrite it?"),
|
||||
TRANS("Overwrite"),
|
||||
TRANS("Cancel"),
|
||||
this,
|
||||
ModalCallbackFunction::forComponent (okToOverwriteFileCallback, this));
|
||||
}
|
||||
else
|
||||
{
|
||||
exitModalState (1);
|
||||
}
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::createNewFolderCallback (int result, FileChooserDialogBox* box,
|
||||
Component::SafePointer<AlertWindow> alert)
|
||||
{
|
||||
if (result != 0 && alert != nullptr && box != nullptr)
|
||||
{
|
||||
alert->setVisible (false);
|
||||
box->createNewFolderConfirmed (alert->getTextEditorContents ("Folder Name"));
|
||||
}
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::createNewFolder()
|
||||
{
|
||||
File parent (content->chooserComponent.getRoot());
|
||||
|
||||
if (parent.isDirectory())
|
||||
{
|
||||
AlertWindow* aw = new AlertWindow (TRANS("New Folder"),
|
||||
TRANS("Please enter the name for the folder"),
|
||||
AlertWindow::NoIcon, this);
|
||||
|
||||
aw->addTextEditor ("Folder Name", String::empty, String::empty, false);
|
||||
aw->addButton (TRANS("Create Folder"), 1, KeyPress (KeyPress::returnKey));
|
||||
aw->addButton (TRANS("Cancel"), 0, KeyPress (KeyPress::escapeKey));
|
||||
|
||||
aw->enterModalState (true,
|
||||
ModalCallbackFunction::forComponent (createNewFolderCallback, this,
|
||||
Component::SafePointer<AlertWindow> (aw)),
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
void FileChooserDialogBox::createNewFolderConfirmed (const String& nameFromDialog)
|
||||
{
|
||||
const String name (File::createLegalFileName (nameFromDialog));
|
||||
|
||||
if (! name.isEmpty())
|
||||
{
|
||||
const File parent (content->chooserComponent.getRoot());
|
||||
|
||||
if (! parent.getChildFile (name).createDirectory())
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
|
||||
TRANS ("New Folder"),
|
||||
TRANS ("Couldn't create the folder!"));
|
||||
}
|
||||
|
||||
content->chooserComponent.refresh();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_FILECHOOSERDIALOGBOX_H_INCLUDED
|
||||
#define JUCE_FILECHOOSERDIALOGBOX_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A file open/save dialog box.
|
||||
|
||||
This is a Juce-based file dialog box; to use a native file chooser, see the
|
||||
FileChooser class.
|
||||
|
||||
To use one of these, create it and call its show() method. e.g.
|
||||
|
||||
@code
|
||||
{
|
||||
WildcardFileFilter wildcardFilter ("*.foo", String::empty, "Foo files");
|
||||
|
||||
FileBrowserComponent browser (FileBrowserComponent::canSelectFiles,
|
||||
File::nonexistent,
|
||||
&wildcardFilter,
|
||||
nullptr);
|
||||
|
||||
FileChooserDialogBox dialogBox ("Open some kind of file",
|
||||
"Please choose some kind of file that you want to open...",
|
||||
browser,
|
||||
false,
|
||||
Colours::lightgrey);
|
||||
|
||||
if (dialogBox.show())
|
||||
{
|
||||
File selectedFile = browser.getSelectedFile (0);
|
||||
|
||||
...etc..
|
||||
}
|
||||
}
|
||||
@endcode
|
||||
|
||||
@see FileChooser
|
||||
*/
|
||||
class JUCE_API FileChooserDialogBox : public ResizableWindow,
|
||||
private ButtonListener, // (can't use Button::Listener due to idiotic VC2005 bug)
|
||||
private FileBrowserListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a file chooser box.
|
||||
|
||||
@param title the main title to show at the top of the box
|
||||
@param instructions an optional longer piece of text to show below the title in
|
||||
a smaller font, describing in more detail what's required.
|
||||
@param browserComponent a FileBrowserComponent that will be shown inside this dialog
|
||||
box. Make sure you delete this after (but not before!) the
|
||||
dialog box has been deleted.
|
||||
@param warnAboutOverwritingExistingFiles if true, then the user will be asked to confirm
|
||||
if they try to select a file that already exists. (This
|
||||
flag is only used when saving files)
|
||||
@param backgroundColour the background colour for the top level window
|
||||
|
||||
@see FileBrowserComponent, FilePreviewComponent
|
||||
*/
|
||||
FileChooserDialogBox (const String& title,
|
||||
const String& instructions,
|
||||
FileBrowserComponent& browserComponent,
|
||||
bool warnAboutOverwritingExistingFiles,
|
||||
Colour backgroundColour);
|
||||
|
||||
/** Destructor. */
|
||||
~FileChooserDialogBox();
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** Displays and runs the dialog box modally.
|
||||
|
||||
This will show the box with the specified size, returning true if the user
|
||||
pressed 'ok', or false if they cancelled.
|
||||
|
||||
Leave the width or height as 0 to use the default size
|
||||
*/
|
||||
bool show (int width = 0, int height = 0);
|
||||
|
||||
/** Displays and runs the dialog box modally.
|
||||
|
||||
This will show the box with the specified size at the specified location,
|
||||
returning true if the user pressed 'ok', or false if they cancelled.
|
||||
|
||||
Leave the width or height as 0 to use the default size.
|
||||
*/
|
||||
bool showAt (int x, int y, int width, int height);
|
||||
#endif
|
||||
|
||||
/** Sets the size of this dialog box to its default and positions it either in the
|
||||
centre of the screen, or centred around a component that is provided.
|
||||
*/
|
||||
void centreWithDefaultSize (Component* componentToCentreAround = nullptr);
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the box.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
titleTextColourId = 0x1000850, /**< The colour to use to draw the box's title. */
|
||||
};
|
||||
|
||||
private:
|
||||
class ContentComponent;
|
||||
ContentComponent* content;
|
||||
const bool warnAboutOverwritingExistingFiles;
|
||||
|
||||
void buttonClicked (Button*) override;
|
||||
void closeButtonPressed();
|
||||
void selectionChanged() override;
|
||||
void fileClicked (const File&, const MouseEvent&) override;
|
||||
void fileDoubleClicked (const File&) override;
|
||||
void browserRootChanged (const File&) override;
|
||||
int getDefaultWidth() const;
|
||||
|
||||
void okButtonPressed();
|
||||
void createNewFolder();
|
||||
void createNewFolderConfirmed (const String& name);
|
||||
|
||||
static void okToOverwriteFileCallback (int result, FileChooserDialogBox*);
|
||||
static void createNewFolderCallback (int result, FileChooserDialogBox*, Component::SafePointer<AlertWindow>);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileChooserDialogBox)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_FILECHOOSERDIALOGBOX_H_INCLUDED
|
||||
|
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
Image juce_createIconForFile (const File& file);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
FileListComponent::FileListComponent (DirectoryContentsList& listToShow)
|
||||
: ListBox (String::empty, nullptr),
|
||||
DirectoryContentsDisplayComponent (listToShow)
|
||||
{
|
||||
setModel (this);
|
||||
fileList.addChangeListener (this);
|
||||
}
|
||||
|
||||
FileListComponent::~FileListComponent()
|
||||
{
|
||||
fileList.removeChangeListener (this);
|
||||
}
|
||||
|
||||
int FileListComponent::getNumSelectedFiles() const
|
||||
{
|
||||
return getNumSelectedRows();
|
||||
}
|
||||
|
||||
File FileListComponent::getSelectedFile (int index) const
|
||||
{
|
||||
return fileList.getFile (getSelectedRow (index));
|
||||
}
|
||||
|
||||
void FileListComponent::deselectAllFiles()
|
||||
{
|
||||
deselectAllRows();
|
||||
}
|
||||
|
||||
void FileListComponent::scrollToTop()
|
||||
{
|
||||
getVerticalScrollBar()->setCurrentRangeStart (0);
|
||||
}
|
||||
|
||||
void FileListComponent::setSelectedFile (const File& f)
|
||||
{
|
||||
for (int i = fileList.getNumFiles(); --i >= 0;)
|
||||
{
|
||||
if (fileList.getFile(i) == f)
|
||||
{
|
||||
selectRow (i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
deselectAllRows();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileListComponent::changeListenerCallback (ChangeBroadcaster*)
|
||||
{
|
||||
updateContent();
|
||||
|
||||
if (lastDirectory != fileList.getDirectory())
|
||||
{
|
||||
lastDirectory = fileList.getDirectory();
|
||||
deselectAllRows();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class FileListComponent::ItemComponent : public Component,
|
||||
private TimeSliceClient,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
ItemComponent (FileListComponent& fc, TimeSliceThread& t)
|
||||
: owner (fc), thread (t), index (0), highlighted (false)
|
||||
{
|
||||
}
|
||||
|
||||
~ItemComponent()
|
||||
{
|
||||
thread.removeTimeSliceClient (this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
getLookAndFeel().drawFileBrowserRow (g, getWidth(), getHeight(),
|
||||
file.getFileName(),
|
||||
&icon, fileSize, modTime,
|
||||
isDirectory, highlighted,
|
||||
index, owner);
|
||||
}
|
||||
|
||||
void mouseDown (const MouseEvent& e) override
|
||||
{
|
||||
owner.selectRowsBasedOnModifierKeys (index, e.mods, true);
|
||||
owner.sendMouseClickMessage (file, e);
|
||||
}
|
||||
|
||||
void mouseDoubleClick (const MouseEvent&) override
|
||||
{
|
||||
owner.sendDoubleClickMessage (file);
|
||||
}
|
||||
|
||||
void update (const File& root,
|
||||
const DirectoryContentsList::FileInfo* const fileInfo,
|
||||
const int index_,
|
||||
const bool highlighted_)
|
||||
{
|
||||
thread.removeTimeSliceClient (this);
|
||||
|
||||
if (highlighted_ != highlighted || index_ != index)
|
||||
{
|
||||
index = index_;
|
||||
highlighted = highlighted_;
|
||||
repaint();
|
||||
}
|
||||
|
||||
File newFile;
|
||||
String newFileSize, newModTime;
|
||||
|
||||
if (fileInfo != nullptr)
|
||||
{
|
||||
newFile = root.getChildFile (fileInfo->filename);
|
||||
newFileSize = File::descriptionOfSizeInBytes (fileInfo->fileSize);
|
||||
newModTime = fileInfo->modificationTime.formatted ("%d %b '%y %H:%M");
|
||||
}
|
||||
|
||||
if (newFile != file
|
||||
|| fileSize != newFileSize
|
||||
|| modTime != newModTime)
|
||||
{
|
||||
file = newFile;
|
||||
fileSize = newFileSize;
|
||||
modTime = newModTime;
|
||||
icon = Image::null;
|
||||
isDirectory = fileInfo != nullptr && fileInfo->isDirectory;
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
if (file != File::nonexistent && icon.isNull() && ! isDirectory)
|
||||
{
|
||||
updateIcon (true);
|
||||
|
||||
if (! icon.isValid())
|
||||
thread.addTimeSliceClient (this);
|
||||
}
|
||||
}
|
||||
|
||||
int useTimeSlice() override
|
||||
{
|
||||
updateIcon (false);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
FileListComponent& owner;
|
||||
TimeSliceThread& thread;
|
||||
File file;
|
||||
String fileSize, modTime;
|
||||
Image icon;
|
||||
int index;
|
||||
bool highlighted, isDirectory;
|
||||
|
||||
void updateIcon (const bool onlyUpdateIfCached)
|
||||
{
|
||||
if (icon.isNull())
|
||||
{
|
||||
const int hashCode = (file.getFullPathName() + "_iconCacheSalt").hashCode();
|
||||
Image im (ImageCache::getFromHashCode (hashCode));
|
||||
|
||||
if (im.isNull() && ! onlyUpdateIfCached)
|
||||
{
|
||||
im = juce_createIconForFile (file);
|
||||
|
||||
if (im.isValid())
|
||||
ImageCache::addImageToCache (im, hashCode);
|
||||
}
|
||||
|
||||
if (im.isValid())
|
||||
{
|
||||
icon = im;
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ItemComponent)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
int FileListComponent::getNumRows()
|
||||
{
|
||||
return fileList.getNumFiles();
|
||||
}
|
||||
|
||||
void FileListComponent::paintListBoxItem (int, Graphics&, int, int, bool)
|
||||
{
|
||||
}
|
||||
|
||||
Component* FileListComponent::refreshComponentForRow (int row, bool isSelected, Component* existingComponentToUpdate)
|
||||
{
|
||||
jassert (existingComponentToUpdate == nullptr || dynamic_cast<ItemComponent*> (existingComponentToUpdate) != nullptr);
|
||||
|
||||
ItemComponent* comp = static_cast<ItemComponent*> (existingComponentToUpdate);
|
||||
|
||||
if (comp == nullptr)
|
||||
comp = new ItemComponent (*this, fileList.getTimeSliceThread());
|
||||
|
||||
DirectoryContentsList::FileInfo fileInfo;
|
||||
comp->update (fileList.getDirectory(),
|
||||
fileList.getFileInfo (row, fileInfo) ? &fileInfo : nullptr,
|
||||
row, isSelected);
|
||||
|
||||
return comp;
|
||||
}
|
||||
|
||||
void FileListComponent::selectedRowsChanged (int /*lastRowSelected*/)
|
||||
{
|
||||
sendSelectionChangeMessage();
|
||||
}
|
||||
|
||||
void FileListComponent::deleteKeyPressed (int /*currentSelectedRow*/)
|
||||
{
|
||||
}
|
||||
|
||||
void FileListComponent::returnKeyPressed (int currentSelectedRow)
|
||||
{
|
||||
sendDoubleClickMessage (fileList.getFile (currentSelectedRow));
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_FILELISTCOMPONENT_H_INCLUDED
|
||||
#define JUCE_FILELISTCOMPONENT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that displays the files in a directory as a listbox.
|
||||
|
||||
This implements the DirectoryContentsDisplayComponent base class so that
|
||||
it can be used in a FileBrowserComponent.
|
||||
|
||||
To attach a listener to it, use its DirectoryContentsDisplayComponent base
|
||||
class and the FileBrowserListener class.
|
||||
|
||||
@see DirectoryContentsList, FileTreeComponent
|
||||
*/
|
||||
class JUCE_API FileListComponent : public ListBox,
|
||||
public DirectoryContentsDisplayComponent,
|
||||
private ListBoxModel,
|
||||
private ChangeListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a listbox to show the contents of a specified directory.
|
||||
*/
|
||||
FileListComponent (DirectoryContentsList& listToShow);
|
||||
|
||||
/** Destructor. */
|
||||
~FileListComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of files the user has got selected.
|
||||
@see getSelectedFile
|
||||
*/
|
||||
int getNumSelectedFiles() const;
|
||||
|
||||
/** Returns one of the files that the user has currently selected.
|
||||
The index should be in the range 0 to (getNumSelectedFiles() - 1).
|
||||
@see getNumSelectedFiles
|
||||
*/
|
||||
File getSelectedFile (int index = 0) const;
|
||||
|
||||
/** Deselects any files that are currently selected. */
|
||||
void deselectAllFiles();
|
||||
|
||||
/** Scrolls to the top of the list. */
|
||||
void scrollToTop();
|
||||
|
||||
/** If the specified file is in the list, it will become the only selected item
|
||||
(and if the file isn't in the list, all other items will be deselected). */
|
||||
void setSelectedFile (const File&);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
File lastDirectory;
|
||||
|
||||
class ItemComponent;
|
||||
|
||||
void changeListenerCallback (ChangeBroadcaster*) override;
|
||||
|
||||
int getNumRows() override;
|
||||
void paintListBoxItem (int, Graphics&, int, int, bool) override;
|
||||
Component* refreshComponentForRow (int rowNumber, bool isRowSelected, Component* existingComponentToUpdate) override;
|
||||
void selectedRowsChanged (int lastRowSelected) override;
|
||||
void deleteKeyPressed (int currentSelectedRow) override;
|
||||
void returnKeyPressed (int currentSelectedRow) override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileListComponent)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_FILELISTCOMPONENT_H_INCLUDED
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_FILEPREVIEWCOMPONENT_H_INCLUDED
|
||||
#define JUCE_FILEPREVIEWCOMPONENT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Base class for components that live inside a file chooser dialog box and
|
||||
show previews of the files that get selected.
|
||||
|
||||
One of these allows special extra information to be displayed for files
|
||||
in a dialog box as the user selects them. Each time the current file or
|
||||
directory is changed, the selectedFileChanged() method will be called
|
||||
to allow it to update itself appropriately.
|
||||
|
||||
@see FileChooser, ImagePreviewComponent
|
||||
*/
|
||||
class JUCE_API FilePreviewComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a FilePreviewComponent. */
|
||||
FilePreviewComponent();
|
||||
|
||||
/** Destructor. */
|
||||
~FilePreviewComponent();
|
||||
|
||||
/** Called to indicate that the user's currently selected file has changed.
|
||||
|
||||
@param newSelectedFile the newly selected file or directory, which may be
|
||||
File::nonexistent if none is selected.
|
||||
*/
|
||||
virtual void selectedFileChanged (const File& newSelectedFile) = 0;
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilePreviewComponent)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_FILEPREVIEWCOMPONENT_H_INCLUDED
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
FileSearchPathListComponent::FileSearchPathListComponent()
|
||||
: addButton ("+"),
|
||||
removeButton ("-"),
|
||||
changeButton (TRANS ("change...")),
|
||||
upButton (String::empty, DrawableButton::ImageOnButtonBackground),
|
||||
downButton (String::empty, DrawableButton::ImageOnButtonBackground)
|
||||
{
|
||||
listBox.setModel (this);
|
||||
addAndMakeVisible (listBox);
|
||||
listBox.setColour (ListBox::backgroundColourId, Colours::black.withAlpha (0.02f));
|
||||
listBox.setColour (ListBox::outlineColourId, Colours::black.withAlpha (0.1f));
|
||||
listBox.setOutlineThickness (1);
|
||||
|
||||
addAndMakeVisible (addButton);
|
||||
addButton.addListener (this);
|
||||
addButton.setConnectedEdges (Button::ConnectedOnLeft | Button::ConnectedOnRight | Button::ConnectedOnBottom | Button::ConnectedOnTop);
|
||||
|
||||
addAndMakeVisible (removeButton);
|
||||
removeButton.addListener (this);
|
||||
removeButton.setConnectedEdges (Button::ConnectedOnLeft | Button::ConnectedOnRight | Button::ConnectedOnBottom | Button::ConnectedOnTop);
|
||||
|
||||
addAndMakeVisible (changeButton);
|
||||
changeButton.addListener (this);
|
||||
|
||||
addAndMakeVisible (upButton);
|
||||
upButton.addListener (this);
|
||||
|
||||
{
|
||||
Path arrowPath;
|
||||
arrowPath.addArrow (Line<float> (50.0f, 100.0f, 50.0f, 0.0f), 40.0f, 100.0f, 50.0f);
|
||||
DrawablePath arrowImage;
|
||||
arrowImage.setFill (Colours::black.withAlpha (0.4f));
|
||||
arrowImage.setPath (arrowPath);
|
||||
|
||||
upButton.setImages (&arrowImage);
|
||||
}
|
||||
|
||||
addAndMakeVisible (downButton);
|
||||
downButton.addListener (this);
|
||||
|
||||
{
|
||||
Path arrowPath;
|
||||
arrowPath.addArrow (Line<float> (50.0f, 0.0f, 50.0f, 100.0f), 40.0f, 100.0f, 50.0f);
|
||||
DrawablePath arrowImage;
|
||||
arrowImage.setFill (Colours::black.withAlpha (0.4f));
|
||||
arrowImage.setPath (arrowPath);
|
||||
|
||||
downButton.setImages (&arrowImage);
|
||||
}
|
||||
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
FileSearchPathListComponent::~FileSearchPathListComponent()
|
||||
{
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::updateButtons()
|
||||
{
|
||||
const bool anythingSelected = listBox.getNumSelectedRows() > 0;
|
||||
|
||||
removeButton.setEnabled (anythingSelected);
|
||||
changeButton.setEnabled (anythingSelected);
|
||||
upButton.setEnabled (anythingSelected);
|
||||
downButton.setEnabled (anythingSelected);
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::changed()
|
||||
{
|
||||
listBox.updateContent();
|
||||
listBox.repaint();
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileSearchPathListComponent::setPath (const FileSearchPath& newPath)
|
||||
{
|
||||
if (newPath.toString() != path.toString())
|
||||
{
|
||||
path = newPath;
|
||||
changed();
|
||||
}
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::setDefaultBrowseTarget (const File& newDefaultDirectory)
|
||||
{
|
||||
defaultBrowseTarget = newDefaultDirectory;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int FileSearchPathListComponent::getNumRows()
|
||||
{
|
||||
return path.getNumPaths();
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected)
|
||||
{
|
||||
if (rowIsSelected)
|
||||
g.fillAll (findColour (TextEditor::highlightColourId));
|
||||
|
||||
g.setColour (findColour (ListBox::textColourId));
|
||||
Font f (height * 0.7f);
|
||||
f.setHorizontalScale (0.9f);
|
||||
g.setFont (f);
|
||||
|
||||
g.drawText (path [rowNumber].getFullPathName(),
|
||||
4, 0, width - 6, height,
|
||||
Justification::centredLeft, true);
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::deleteKeyPressed (int row)
|
||||
{
|
||||
if (isPositiveAndBelow (row, path.getNumPaths()))
|
||||
{
|
||||
path.remove (row);
|
||||
changed();
|
||||
}
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::returnKeyPressed (int row)
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
FileChooser chooser (TRANS("Change folder..."), path [row], "*");
|
||||
|
||||
if (chooser.browseForDirectory())
|
||||
{
|
||||
path.remove (row);
|
||||
path.add (chooser.getResult(), row);
|
||||
changed();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::listBoxItemDoubleClicked (int row, const MouseEvent&)
|
||||
{
|
||||
returnKeyPressed (row);
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::selectedRowsChanged (int)
|
||||
{
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::paint (Graphics& g)
|
||||
{
|
||||
g.fillAll (findColour (backgroundColourId));
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::resized()
|
||||
{
|
||||
const int buttonH = 22;
|
||||
const int buttonY = getHeight() - buttonH - 4;
|
||||
listBox.setBounds (2, 2, getWidth() - 4, buttonY - 5);
|
||||
|
||||
addButton.setBounds (2, buttonY, buttonH, buttonH);
|
||||
removeButton.setBounds (addButton.getRight(), buttonY, buttonH, buttonH);
|
||||
|
||||
changeButton.changeWidthToFitText (buttonH);
|
||||
downButton.setSize (buttonH * 2, buttonH);
|
||||
upButton.setSize (buttonH * 2, buttonH);
|
||||
|
||||
downButton.setTopRightPosition (getWidth() - 2, buttonY);
|
||||
upButton.setTopRightPosition (downButton.getX() - 4, buttonY);
|
||||
changeButton.setTopRightPosition (upButton.getX() - 8, buttonY);
|
||||
}
|
||||
|
||||
bool FileSearchPathListComponent::isInterestedInFileDrag (const StringArray&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::filesDropped (const StringArray& filenames, int, int mouseY)
|
||||
{
|
||||
for (int i = filenames.size(); --i >= 0;)
|
||||
{
|
||||
const File f (filenames[i]);
|
||||
|
||||
if (f.isDirectory())
|
||||
{
|
||||
const int row = listBox.getRowContainingPosition (0, mouseY - listBox.getY());
|
||||
path.add (f, row);
|
||||
changed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileSearchPathListComponent::buttonClicked (Button* button)
|
||||
{
|
||||
const int currentRow = listBox.getSelectedRow();
|
||||
|
||||
if (button == &removeButton)
|
||||
{
|
||||
deleteKeyPressed (currentRow);
|
||||
}
|
||||
else if (button == &addButton)
|
||||
{
|
||||
File start (defaultBrowseTarget);
|
||||
|
||||
if (start == File::nonexistent)
|
||||
start = path [0];
|
||||
|
||||
if (start == File::nonexistent)
|
||||
start = File::getCurrentWorkingDirectory();
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
FileChooser chooser (TRANS("Add a folder..."), start, "*");
|
||||
|
||||
if (chooser.browseForDirectory())
|
||||
path.add (chooser.getResult(), currentRow);
|
||||
#else
|
||||
jassertfalse; // needs rewriting to deal with non-modal environments
|
||||
#endif
|
||||
}
|
||||
else if (button == &changeButton)
|
||||
{
|
||||
returnKeyPressed (currentRow);
|
||||
}
|
||||
else if (button == &upButton)
|
||||
{
|
||||
if (currentRow > 0 && currentRow < path.getNumPaths())
|
||||
{
|
||||
const File f (path[currentRow]);
|
||||
path.remove (currentRow);
|
||||
path.add (f, currentRow - 1);
|
||||
listBox.selectRow (currentRow - 1);
|
||||
}
|
||||
}
|
||||
else if (button == &downButton)
|
||||
{
|
||||
if (currentRow >= 0 && currentRow < path.getNumPaths() - 1)
|
||||
{
|
||||
const File f (path[currentRow]);
|
||||
path.remove (currentRow);
|
||||
path.add (f, currentRow + 1);
|
||||
listBox.selectRow (currentRow + 1);
|
||||
}
|
||||
}
|
||||
|
||||
changed();
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_FILESEARCHPATHLISTCOMPONENT_H_INCLUDED
|
||||
#define JUCE_FILESEARCHPATHLISTCOMPONENT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Shows a set of file paths in a list, allowing them to be added, removed or
|
||||
re-ordered.
|
||||
|
||||
@see FileSearchPath
|
||||
*/
|
||||
class JUCE_API FileSearchPathListComponent : public Component,
|
||||
public SettableTooltipClient,
|
||||
public FileDragAndDropTarget,
|
||||
private ButtonListener, // (can't use Button::Listener due to idiotic VC2005 bug)
|
||||
private ListBoxModel
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty FileSearchPathListComponent. */
|
||||
FileSearchPathListComponent();
|
||||
|
||||
/** Destructor. */
|
||||
~FileSearchPathListComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the path as it is currently shown. */
|
||||
const FileSearchPath& getPath() const noexcept { return path; }
|
||||
|
||||
/** Changes the current path. */
|
||||
void setPath (const FileSearchPath& newPath);
|
||||
|
||||
/** Sets a file or directory to be the default starting point for the browser to show.
|
||||
|
||||
This is only used if the current file hasn't been set.
|
||||
*/
|
||||
void setDefaultBrowseTarget (const File& newDefaultDirectory);
|
||||
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the label.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1004100, /**< The background colour to fill the component with.
|
||||
Make this transparent if you don't want the background to be filled. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
int getNumRows() override;
|
||||
/** @internal */
|
||||
void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected) override;
|
||||
/** @internal */
|
||||
void deleteKeyPressed (int lastRowSelected) override;
|
||||
/** @internal */
|
||||
void returnKeyPressed (int lastRowSelected) override;
|
||||
/** @internal */
|
||||
void listBoxItemDoubleClicked (int row, const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void selectedRowsChanged (int lastRowSelected) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
bool isInterestedInFileDrag (const StringArray&) override;
|
||||
/** @internal */
|
||||
void filesDropped (const StringArray& files, int, int) override;
|
||||
/** @internal */
|
||||
void buttonClicked (Button*) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
FileSearchPath path;
|
||||
File defaultBrowseTarget;
|
||||
|
||||
ListBox listBox;
|
||||
TextButton addButton, removeButton, changeButton;
|
||||
DrawableButton upButton, downButton;
|
||||
|
||||
void changed();
|
||||
void updateButtons();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileSearchPathListComponent)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_FILESEARCHPATHLISTCOMPONENT_H_INCLUDED
|
||||
|
|
@ -0,0 +1,322 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
Image juce_createIconForFile (const File&);
|
||||
|
||||
//==============================================================================
|
||||
class FileListTreeItem : public TreeViewItem,
|
||||
private TimeSliceClient,
|
||||
private AsyncUpdater,
|
||||
private ChangeListener
|
||||
{
|
||||
public:
|
||||
FileListTreeItem (FileTreeComponent& treeComp,
|
||||
DirectoryContentsList* const parentContents,
|
||||
const int indexInContents,
|
||||
const File& f,
|
||||
TimeSliceThread& t)
|
||||
: file (f),
|
||||
owner (treeComp),
|
||||
parentContentsList (parentContents),
|
||||
indexInContentsList (indexInContents),
|
||||
subContentsList (nullptr, false),
|
||||
thread (t)
|
||||
{
|
||||
DirectoryContentsList::FileInfo fileInfo;
|
||||
|
||||
if (parentContents != nullptr
|
||||
&& parentContents->getFileInfo (indexInContents, fileInfo))
|
||||
{
|
||||
fileSize = File::descriptionOfSizeInBytes (fileInfo.fileSize);
|
||||
modTime = fileInfo.modificationTime.formatted ("%d %b '%y %H:%M");
|
||||
isDirectory = fileInfo.isDirectory;
|
||||
}
|
||||
else
|
||||
{
|
||||
isDirectory = true;
|
||||
}
|
||||
}
|
||||
|
||||
~FileListTreeItem()
|
||||
{
|
||||
thread.removeTimeSliceClient (this);
|
||||
clearSubItems();
|
||||
removeSubContentsList();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool mightContainSubItems() override { return isDirectory; }
|
||||
String getUniqueName() const override { return file.getFullPathName(); }
|
||||
int getItemHeight() const override { return owner.getItemHeight(); }
|
||||
|
||||
var getDragSourceDescription() override { return owner.getDragAndDropDescription(); }
|
||||
|
||||
void itemOpennessChanged (bool isNowOpen) override
|
||||
{
|
||||
if (isNowOpen)
|
||||
{
|
||||
clearSubItems();
|
||||
|
||||
isDirectory = file.isDirectory();
|
||||
|
||||
if (isDirectory)
|
||||
{
|
||||
if (subContentsList == nullptr)
|
||||
{
|
||||
jassert (parentContentsList != nullptr);
|
||||
|
||||
DirectoryContentsList* const l = new DirectoryContentsList (parentContentsList->getFilter(), thread);
|
||||
|
||||
l->setDirectory (file,
|
||||
parentContentsList->isFindingDirectories(),
|
||||
parentContentsList->isFindingFiles());
|
||||
|
||||
setSubContentsList (l, true);
|
||||
}
|
||||
|
||||
changeListenerCallback (nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void removeSubContentsList()
|
||||
{
|
||||
if (subContentsList != nullptr)
|
||||
{
|
||||
subContentsList->removeChangeListener (this);
|
||||
subContentsList.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void setSubContentsList (DirectoryContentsList* newList, const bool canDeleteList)
|
||||
{
|
||||
removeSubContentsList();
|
||||
|
||||
OptionalScopedPointer<DirectoryContentsList> newPointer (newList, canDeleteList);
|
||||
subContentsList = newPointer;
|
||||
newList->addChangeListener (this);
|
||||
}
|
||||
|
||||
bool selectFile (const File& target)
|
||||
{
|
||||
if (file == target)
|
||||
{
|
||||
setSelected (true, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (target.isAChildOf (file))
|
||||
{
|
||||
setOpen (true);
|
||||
|
||||
for (int maxRetries = 500; --maxRetries > 0;)
|
||||
{
|
||||
for (int i = 0; i < getNumSubItems(); ++i)
|
||||
if (FileListTreeItem* f = dynamic_cast <FileListTreeItem*> (getSubItem (i)))
|
||||
if (f->selectFile (target))
|
||||
return true;
|
||||
|
||||
// if we've just opened and the contents are still loading, wait for it..
|
||||
if (subContentsList != nullptr && subContentsList->isStillLoading())
|
||||
{
|
||||
Thread::sleep (10);
|
||||
rebuildItemsFromContentList();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void changeListenerCallback (ChangeBroadcaster*) override
|
||||
{
|
||||
rebuildItemsFromContentList();
|
||||
}
|
||||
|
||||
void rebuildItemsFromContentList()
|
||||
{
|
||||
clearSubItems();
|
||||
|
||||
if (isOpen() && subContentsList != nullptr)
|
||||
{
|
||||
for (int i = 0; i < subContentsList->getNumFiles(); ++i)
|
||||
addSubItem (new FileListTreeItem (owner, subContentsList, i,
|
||||
subContentsList->getFile(i), thread));
|
||||
}
|
||||
}
|
||||
|
||||
void paintItem (Graphics& g, int width, int height) override
|
||||
{
|
||||
if (file != File::nonexistent)
|
||||
{
|
||||
updateIcon (true);
|
||||
|
||||
if (icon.isNull())
|
||||
thread.addTimeSliceClient (this);
|
||||
}
|
||||
|
||||
owner.getLookAndFeel().drawFileBrowserRow (g, width, height,
|
||||
file.getFileName(),
|
||||
&icon, fileSize, modTime,
|
||||
isDirectory, isSelected(),
|
||||
indexInContentsList, owner);
|
||||
}
|
||||
|
||||
void itemClicked (const MouseEvent& e) override
|
||||
{
|
||||
owner.sendMouseClickMessage (file, e);
|
||||
}
|
||||
|
||||
void itemDoubleClicked (const MouseEvent& e) override
|
||||
{
|
||||
TreeViewItem::itemDoubleClicked (e);
|
||||
|
||||
owner.sendDoubleClickMessage (file);
|
||||
}
|
||||
|
||||
void itemSelectionChanged (bool) override
|
||||
{
|
||||
owner.sendSelectionChangeMessage();
|
||||
}
|
||||
|
||||
int useTimeSlice() override
|
||||
{
|
||||
updateIcon (false);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
owner.repaint();
|
||||
}
|
||||
|
||||
const File file;
|
||||
|
||||
private:
|
||||
FileTreeComponent& owner;
|
||||
DirectoryContentsList* parentContentsList;
|
||||
int indexInContentsList;
|
||||
OptionalScopedPointer<DirectoryContentsList> subContentsList;
|
||||
bool isDirectory;
|
||||
TimeSliceThread& thread;
|
||||
Image icon;
|
||||
String fileSize, modTime;
|
||||
|
||||
void updateIcon (const bool onlyUpdateIfCached)
|
||||
{
|
||||
if (icon.isNull())
|
||||
{
|
||||
const int hashCode = (file.getFullPathName() + "_iconCacheSalt").hashCode();
|
||||
Image im (ImageCache::getFromHashCode (hashCode));
|
||||
|
||||
if (im.isNull() && ! onlyUpdateIfCached)
|
||||
{
|
||||
im = juce_createIconForFile (file);
|
||||
|
||||
if (im.isValid())
|
||||
ImageCache::addImageToCache (im, hashCode);
|
||||
}
|
||||
|
||||
if (im.isValid())
|
||||
{
|
||||
icon = im;
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileListTreeItem)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
FileTreeComponent::FileTreeComponent (DirectoryContentsList& listToShow)
|
||||
: DirectoryContentsDisplayComponent (listToShow),
|
||||
itemHeight (22)
|
||||
{
|
||||
setRootItemVisible (false);
|
||||
refresh();
|
||||
}
|
||||
|
||||
FileTreeComponent::~FileTreeComponent()
|
||||
{
|
||||
deleteRootItem();
|
||||
}
|
||||
|
||||
void FileTreeComponent::refresh()
|
||||
{
|
||||
deleteRootItem();
|
||||
|
||||
FileListTreeItem* const root
|
||||
= new FileListTreeItem (*this, nullptr, 0, fileList.getDirectory(),
|
||||
fileList.getTimeSliceThread());
|
||||
|
||||
root->setSubContentsList (&fileList, false);
|
||||
setRootItem (root);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
File FileTreeComponent::getSelectedFile (const int index) const
|
||||
{
|
||||
if (const FileListTreeItem* const item = dynamic_cast <const FileListTreeItem*> (getSelectedItem (index)))
|
||||
return item->file;
|
||||
|
||||
return File::nonexistent;
|
||||
}
|
||||
|
||||
void FileTreeComponent::deselectAllFiles()
|
||||
{
|
||||
clearSelectedItems();
|
||||
}
|
||||
|
||||
void FileTreeComponent::scrollToTop()
|
||||
{
|
||||
getViewport()->getVerticalScrollBar()->setCurrentRangeStart (0);
|
||||
}
|
||||
|
||||
void FileTreeComponent::setDragAndDropDescription (const String& description)
|
||||
{
|
||||
dragAndDropDescription = description;
|
||||
}
|
||||
|
||||
void FileTreeComponent::setSelectedFile (const File& target)
|
||||
{
|
||||
if (FileListTreeItem* t = dynamic_cast <FileListTreeItem*> (getRootItem()))
|
||||
if (! t->selectFile (target))
|
||||
clearSelectedItems();
|
||||
}
|
||||
|
||||
void FileTreeComponent::setItemHeight (int newHeight)
|
||||
{
|
||||
if (itemHeight != newHeight)
|
||||
{
|
||||
itemHeight = newHeight;
|
||||
|
||||
if (TreeViewItem* root = getRootItem())
|
||||
root->treeHasChanged();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_FILETREECOMPONENT_H_INCLUDED
|
||||
#define JUCE_FILETREECOMPONENT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that displays the files in a directory as a treeview.
|
||||
|
||||
This implements the DirectoryContentsDisplayComponent base class so that
|
||||
it can be used in a FileBrowserComponent.
|
||||
|
||||
To attach a listener to it, use its DirectoryContentsDisplayComponent base
|
||||
class and the FileBrowserListener class.
|
||||
|
||||
@see DirectoryContentsList, FileListComponent
|
||||
*/
|
||||
class JUCE_API FileTreeComponent : public TreeView,
|
||||
public DirectoryContentsDisplayComponent
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a listbox to show the contents of a specified directory.
|
||||
*/
|
||||
FileTreeComponent (DirectoryContentsList& listToShow);
|
||||
|
||||
/** Destructor. */
|
||||
~FileTreeComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of files the user has got selected.
|
||||
@see getSelectedFile
|
||||
*/
|
||||
int getNumSelectedFiles() const { return TreeView::getNumSelectedItems(); }
|
||||
|
||||
/** Returns one of the files that the user has currently selected.
|
||||
The index should be in the range 0 to (getNumSelectedFiles() - 1).
|
||||
@see getNumSelectedFiles
|
||||
*/
|
||||
File getSelectedFile (int index = 0) const;
|
||||
|
||||
/** Deselects any files that are currently selected. */
|
||||
void deselectAllFiles();
|
||||
|
||||
/** Scrolls the list to the top. */
|
||||
void scrollToTop();
|
||||
|
||||
/** If the specified file is in the list, it will become the only selected item
|
||||
(and if the file isn't in the list, all other items will be deselected). */
|
||||
void setSelectedFile (const File&);
|
||||
|
||||
/** Updates the files in the list. */
|
||||
void refresh();
|
||||
|
||||
/** Setting a name for this allows tree items to be dragged.
|
||||
|
||||
The string that you pass in here will be returned by the getDragSourceDescription()
|
||||
of the items in the tree. For more info, see TreeViewItem::getDragSourceDescription().
|
||||
*/
|
||||
void setDragAndDropDescription (const String& description);
|
||||
|
||||
/** Returns the last value that was set by setDragAndDropDescription().
|
||||
*/
|
||||
const String& getDragAndDropDescription() const noexcept { return dragAndDropDescription; }
|
||||
|
||||
/** Changes the height of the treeview items. */
|
||||
void setItemHeight (int newHeight);
|
||||
|
||||
/** Returns the height of the treeview items. */
|
||||
int getItemHeight() const noexcept { return itemHeight; }
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
String dragAndDropDescription;
|
||||
int itemHeight;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileTreeComponent)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_FILETREECOMPONENT_H_INCLUDED
|
||||
|
|
@ -0,0 +1,267 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
FilenameComponent::FilenameComponent (const String& name,
|
||||
const File& currentFile,
|
||||
const bool canEditFilename,
|
||||
const bool isDirectory,
|
||||
const bool isForSaving,
|
||||
const String& fileBrowserWildcard,
|
||||
const String& suffix,
|
||||
const String& textWhenNothingSelected)
|
||||
: Component (name),
|
||||
maxRecentFiles (30),
|
||||
isDir (isDirectory),
|
||||
isSaving (isForSaving),
|
||||
isFileDragOver (false),
|
||||
wildcard (fileBrowserWildcard),
|
||||
enforcedSuffix (suffix)
|
||||
{
|
||||
addAndMakeVisible (filenameBox);
|
||||
filenameBox.setEditableText (canEditFilename);
|
||||
filenameBox.addListener (this);
|
||||
filenameBox.setTextWhenNothingSelected (textWhenNothingSelected);
|
||||
filenameBox.setTextWhenNoChoicesAvailable (TRANS ("(no recently selected files)"));
|
||||
|
||||
setBrowseButtonText ("...");
|
||||
|
||||
setCurrentFile (currentFile, true, dontSendNotification);
|
||||
}
|
||||
|
||||
FilenameComponent::~FilenameComponent()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FilenameComponent::paintOverChildren (Graphics& g)
|
||||
{
|
||||
if (isFileDragOver)
|
||||
{
|
||||
g.setColour (Colours::red.withAlpha (0.2f));
|
||||
g.drawRect (getLocalBounds(), 3);
|
||||
}
|
||||
}
|
||||
|
||||
void FilenameComponent::resized()
|
||||
{
|
||||
getLookAndFeel().layoutFilenameComponent (*this, &filenameBox, browseButton);
|
||||
}
|
||||
|
||||
KeyboardFocusTraverser* FilenameComponent::createFocusTraverser()
|
||||
{
|
||||
// This prevents the sub-components from grabbing focus if the
|
||||
// FilenameComponent has been set to refuse focus.
|
||||
return getWantsKeyboardFocus() ? Component::createFocusTraverser() : nullptr;
|
||||
}
|
||||
|
||||
void FilenameComponent::setBrowseButtonText (const String& newBrowseButtonText)
|
||||
{
|
||||
browseButtonText = newBrowseButtonText;
|
||||
lookAndFeelChanged();
|
||||
}
|
||||
|
||||
void FilenameComponent::lookAndFeelChanged()
|
||||
{
|
||||
browseButton = nullptr;
|
||||
|
||||
addAndMakeVisible (browseButton = getLookAndFeel().createFilenameComponentBrowseButton (browseButtonText));
|
||||
browseButton->setConnectedEdges (Button::ConnectedOnLeft);
|
||||
resized();
|
||||
|
||||
browseButton->addListener (this);
|
||||
}
|
||||
|
||||
void FilenameComponent::setTooltip (const String& newTooltip)
|
||||
{
|
||||
SettableTooltipClient::setTooltip (newTooltip);
|
||||
filenameBox.setTooltip (newTooltip);
|
||||
}
|
||||
|
||||
void FilenameComponent::setDefaultBrowseTarget (const File& newDefaultDirectory)
|
||||
{
|
||||
defaultBrowseFile = newDefaultDirectory;
|
||||
}
|
||||
|
||||
File FilenameComponent::getLocationToBrowse()
|
||||
{
|
||||
return getCurrentFile() == File::nonexistent ? defaultBrowseFile
|
||||
: getCurrentFile();
|
||||
}
|
||||
|
||||
void FilenameComponent::buttonClicked (Button*)
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
FileChooser fc (isDir ? TRANS ("Choose a new directory")
|
||||
: TRANS ("Choose a new file"),
|
||||
getLocationToBrowse(),
|
||||
wildcard);
|
||||
|
||||
if (isDir ? fc.browseForDirectory()
|
||||
: (isSaving ? fc.browseForFileToSave (false)
|
||||
: fc.browseForFileToOpen()))
|
||||
{
|
||||
setCurrentFile (fc.getResult(), true);
|
||||
}
|
||||
#else
|
||||
jassertfalse; // needs rewriting to deal with non-modal environments
|
||||
#endif
|
||||
}
|
||||
|
||||
void FilenameComponent::comboBoxChanged (ComboBox*)
|
||||
{
|
||||
setCurrentFile (getCurrentFile(), true);
|
||||
}
|
||||
|
||||
bool FilenameComponent::isInterestedInFileDrag (const StringArray&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void FilenameComponent::filesDropped (const StringArray& filenames, int, int)
|
||||
{
|
||||
isFileDragOver = false;
|
||||
repaint();
|
||||
|
||||
const File f (filenames[0]);
|
||||
|
||||
if (f.exists() && (f.isDirectory() == isDir))
|
||||
setCurrentFile (f, true);
|
||||
}
|
||||
|
||||
void FilenameComponent::fileDragEnter (const StringArray&, int, int)
|
||||
{
|
||||
isFileDragOver = true;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void FilenameComponent::fileDragExit (const StringArray&)
|
||||
{
|
||||
isFileDragOver = false;
|
||||
repaint();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
String FilenameComponent::getCurrentFileText() const
|
||||
{
|
||||
return filenameBox.getText();
|
||||
}
|
||||
|
||||
File FilenameComponent::getCurrentFile() const
|
||||
{
|
||||
File f (File::getCurrentWorkingDirectory().getChildFile (getCurrentFileText()));
|
||||
|
||||
if (enforcedSuffix.isNotEmpty())
|
||||
f = f.withFileExtension (enforcedSuffix);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
void FilenameComponent::setCurrentFile (File newFile,
|
||||
const bool addToRecentlyUsedList,
|
||||
NotificationType notification)
|
||||
{
|
||||
if (enforcedSuffix.isNotEmpty())
|
||||
newFile = newFile.withFileExtension (enforcedSuffix);
|
||||
|
||||
if (newFile.getFullPathName() != lastFilename)
|
||||
{
|
||||
lastFilename = newFile.getFullPathName();
|
||||
|
||||
if (addToRecentlyUsedList)
|
||||
addRecentlyUsedFile (newFile);
|
||||
|
||||
filenameBox.setText (lastFilename, dontSendNotification);
|
||||
|
||||
if (notification != dontSendNotification)
|
||||
{
|
||||
triggerAsyncUpdate();
|
||||
|
||||
if (notification == sendNotificationSync)
|
||||
handleUpdateNowIfNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FilenameComponent::setFilenameIsEditable (const bool shouldBeEditable)
|
||||
{
|
||||
filenameBox.setEditableText (shouldBeEditable);
|
||||
}
|
||||
|
||||
StringArray FilenameComponent::getRecentlyUsedFilenames() const
|
||||
{
|
||||
StringArray names;
|
||||
|
||||
for (int i = 0; i < filenameBox.getNumItems(); ++i)
|
||||
names.add (filenameBox.getItemText (i));
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
void FilenameComponent::setRecentlyUsedFilenames (const StringArray& filenames)
|
||||
{
|
||||
if (filenames != getRecentlyUsedFilenames())
|
||||
{
|
||||
filenameBox.clear();
|
||||
|
||||
for (int i = 0; i < jmin (filenames.size(), maxRecentFiles); ++i)
|
||||
filenameBox.addItem (filenames[i], i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void FilenameComponent::setMaxNumberOfRecentFiles (const int newMaximum)
|
||||
{
|
||||
maxRecentFiles = jmax (1, newMaximum);
|
||||
|
||||
setRecentlyUsedFilenames (getRecentlyUsedFilenames());
|
||||
}
|
||||
|
||||
void FilenameComponent::addRecentlyUsedFile (const File& file)
|
||||
{
|
||||
StringArray files (getRecentlyUsedFilenames());
|
||||
|
||||
if (file.getFullPathName().isNotEmpty())
|
||||
{
|
||||
files.removeString (file.getFullPathName(), true);
|
||||
files.insert (0, file.getFullPathName());
|
||||
|
||||
setRecentlyUsedFilenames (files);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FilenameComponent::addListener (FilenameComponentListener* const listener)
|
||||
{
|
||||
listeners.add (listener);
|
||||
}
|
||||
|
||||
void FilenameComponent::removeListener (FilenameComponentListener* const listener)
|
||||
{
|
||||
listeners.remove (listener);
|
||||
}
|
||||
|
||||
void FilenameComponent::handleAsyncUpdate()
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
listeners.callChecked (checker, &FilenameComponentListener::filenameComponentChanged, this);
|
||||
}
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_FILENAMECOMPONENT_H_INCLUDED
|
||||
#define JUCE_FILENAMECOMPONENT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Listens for events happening to a FilenameComponent.
|
||||
|
||||
Use FilenameComponent::addListener() and FilenameComponent::removeListener() to
|
||||
register one of these objects for event callbacks when the filename is changed.
|
||||
|
||||
@see FilenameComponent
|
||||
*/
|
||||
class JUCE_API FilenameComponentListener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~FilenameComponentListener() {}
|
||||
|
||||
/** This method is called after the FilenameComponent's file has been changed. */
|
||||
virtual void filenameComponentChanged (FilenameComponent* fileComponentThatHasChanged) = 0;
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Shows a filename as an editable text box, with a 'browse' button and a
|
||||
drop-down list for recently selected files.
|
||||
|
||||
A handy component for dialogue boxes where you want the user to be able to
|
||||
select a file or directory.
|
||||
|
||||
Attach an FilenameComponentListener using the addListener() method, and it will
|
||||
get called each time the user changes the filename, either by browsing for a file
|
||||
and clicking 'ok', or by typing a new filename into the box and pressing return.
|
||||
|
||||
@see FileChooser, ComboBox
|
||||
*/
|
||||
class JUCE_API FilenameComponent : public Component,
|
||||
public SettableTooltipClient,
|
||||
public FileDragAndDropTarget,
|
||||
private AsyncUpdater,
|
||||
private ButtonListener, // (can't use Button::Listener due to idiotic VC2005 bug)
|
||||
private ComboBoxListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a FilenameComponent.
|
||||
|
||||
@param name the name for this component.
|
||||
@param currentFile the file to initially show in the box
|
||||
@param canEditFilename if true, the user can manually edit the filename; if false,
|
||||
they can only change it by browsing for a new file
|
||||
@param isDirectory if true, the file will be treated as a directory, and
|
||||
an appropriate directory browser used
|
||||
@param isForSaving if true, the file browser will allow non-existent files to
|
||||
be picked, as the file is assumed to be used for saving rather
|
||||
than loading
|
||||
@param fileBrowserWildcard a wildcard pattern to use in the file browser - e.g. "*.txt;*.foo".
|
||||
If an empty string is passed in, then the pattern is assumed to be "*"
|
||||
@param enforcedSuffix if this is non-empty, it is treated as a suffix that will be added
|
||||
to any filenames that are entered or chosen
|
||||
@param textWhenNothingSelected the message to display in the box before any filename is entered. (This
|
||||
will only appear if the initial file isn't valid)
|
||||
*/
|
||||
FilenameComponent (const String& name,
|
||||
const File& currentFile,
|
||||
bool canEditFilename,
|
||||
bool isDirectory,
|
||||
bool isForSaving,
|
||||
const String& fileBrowserWildcard,
|
||||
const String& enforcedSuffix,
|
||||
const String& textWhenNothingSelected);
|
||||
|
||||
/** Destructor. */
|
||||
~FilenameComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the currently displayed filename. */
|
||||
File getCurrentFile() const;
|
||||
|
||||
/** Returns the raw text that the user has entered. */
|
||||
String getCurrentFileText() const;
|
||||
|
||||
/** Changes the current filename.
|
||||
|
||||
@param newFile the new filename to use
|
||||
@param addToRecentlyUsedList if true, the filename will also be added to the
|
||||
drop-down list of recent files.
|
||||
@param notification whether to send a notification of the change to listeners
|
||||
*/
|
||||
void setCurrentFile (File newFile,
|
||||
bool addToRecentlyUsedList,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Changes whether the use can type into the filename box.
|
||||
*/
|
||||
void setFilenameIsEditable (bool shouldBeEditable);
|
||||
|
||||
/** Sets a file or directory to be the default starting point for the browser to show.
|
||||
|
||||
This is only used if the current file hasn't been set.
|
||||
*/
|
||||
void setDefaultBrowseTarget (const File& newDefaultDirectory);
|
||||
|
||||
/** This can be overridden to return a custom location that you want the dialog box
|
||||
to show when the browse button is pushed.
|
||||
The default implementation of this method will return either the current file
|
||||
(if one has been chosen) or the location that was set by setDefaultBrowseTarget().
|
||||
*/
|
||||
virtual File getLocationToBrowse();
|
||||
|
||||
/** Returns all the entries on the recent files list.
|
||||
|
||||
This can be used in conjunction with setRecentlyUsedFilenames() for saving the
|
||||
state of this list.
|
||||
|
||||
@see setRecentlyUsedFilenames
|
||||
*/
|
||||
StringArray getRecentlyUsedFilenames() const;
|
||||
|
||||
/** Sets all the entries on the recent files list.
|
||||
|
||||
This can be used in conjunction with getRecentlyUsedFilenames() for saving the
|
||||
state of this list.
|
||||
|
||||
@see getRecentlyUsedFilenames, addRecentlyUsedFile
|
||||
*/
|
||||
void setRecentlyUsedFilenames (const StringArray& filenames);
|
||||
|
||||
/** Adds an entry to the recently-used files dropdown list.
|
||||
|
||||
If the file is already in the list, it will be moved to the top. A limit
|
||||
is also placed on the number of items that are kept in the list.
|
||||
|
||||
@see getRecentlyUsedFilenames, setRecentlyUsedFilenames, setMaxNumberOfRecentFiles
|
||||
*/
|
||||
void addRecentlyUsedFile (const File& file);
|
||||
|
||||
/** Changes the limit for the number of files that will be stored in the recent-file list.
|
||||
*/
|
||||
void setMaxNumberOfRecentFiles (int newMaximum);
|
||||
|
||||
/** Changes the text shown on the 'browse' button.
|
||||
|
||||
By default this button just says "..." but you can change it. The button itself
|
||||
can be changed using the look-and-feel classes, so it might not actually have any
|
||||
text on it.
|
||||
*/
|
||||
void setBrowseButtonText (const String& browseButtonText);
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a listener that will be called when the selected file is changed. */
|
||||
void addListener (FilenameComponentListener* listener);
|
||||
|
||||
/** Removes a previously-registered listener. */
|
||||
void removeListener (FilenameComponentListener* listener);
|
||||
|
||||
/** Gives the component a tooltip. */
|
||||
void setTooltip (const String& newTooltip) override;
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes. */
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() {}
|
||||
|
||||
virtual Button* createFilenameComponentBrowseButton (const String& text) = 0;
|
||||
virtual void layoutFilenameComponent (FilenameComponent&, ComboBox* filenameBox, Button* browseButton) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paintOverChildren (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
bool isInterestedInFileDrag (const StringArray&) override;
|
||||
/** @internal */
|
||||
void filesDropped (const StringArray&, int, int) override;
|
||||
/** @internal */
|
||||
void fileDragEnter (const StringArray&, int, int) override;
|
||||
/** @internal */
|
||||
void fileDragExit (const StringArray&) override;
|
||||
/** @internal */
|
||||
KeyboardFocusTraverser* createFocusTraverser() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
ComboBox filenameBox;
|
||||
String lastFilename;
|
||||
ScopedPointer<Button> browseButton;
|
||||
int maxRecentFiles;
|
||||
bool isDir, isSaving, isFileDragOver;
|
||||
String wildcard, enforcedSuffix, browseButtonText;
|
||||
ListenerList <FilenameComponentListener> listeners;
|
||||
File defaultBrowseFile;
|
||||
|
||||
void comboBoxChanged (ComboBox*) override;
|
||||
void buttonClicked (Button*) override;
|
||||
void handleAsyncUpdate() override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilenameComponent)
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // JUCE_FILENAMECOMPONENT_H_INCLUDED
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
ImagePreviewComponent::ImagePreviewComponent()
|
||||
{
|
||||
}
|
||||
|
||||
ImagePreviewComponent::~ImagePreviewComponent()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ImagePreviewComponent::getThumbSize (int& w, int& h) const
|
||||
{
|
||||
const int availableW = proportionOfWidth (0.97f);
|
||||
const int availableH = getHeight() - 13 * 4;
|
||||
|
||||
const double scale = jmin (1.0,
|
||||
availableW / (double) w,
|
||||
availableH / (double) h);
|
||||
|
||||
w = roundToInt (scale * w);
|
||||
h = roundToInt (scale * h);
|
||||
}
|
||||
|
||||
void ImagePreviewComponent::selectedFileChanged (const File& file)
|
||||
{
|
||||
if (fileToLoad != file)
|
||||
{
|
||||
fileToLoad = file;
|
||||
startTimer (100);
|
||||
}
|
||||
}
|
||||
|
||||
void ImagePreviewComponent::timerCallback()
|
||||
{
|
||||
stopTimer();
|
||||
|
||||
currentThumbnail = Image::null;
|
||||
currentDetails.clear();
|
||||
repaint();
|
||||
|
||||
ScopedPointer<FileInputStream> in (fileToLoad.createInputStream());
|
||||
|
||||
if (in != nullptr)
|
||||
{
|
||||
if (ImageFileFormat* const format = ImageFileFormat::findImageFormatForStream (*in))
|
||||
{
|
||||
currentThumbnail = format->decodeImage (*in);
|
||||
|
||||
if (currentThumbnail.isValid())
|
||||
{
|
||||
int w = currentThumbnail.getWidth();
|
||||
int h = currentThumbnail.getHeight();
|
||||
|
||||
currentDetails
|
||||
<< fileToLoad.getFileName() << "\n"
|
||||
<< format->getFormatName() << "\n"
|
||||
<< w << " x " << h << " pixels\n"
|
||||
<< File::descriptionOfSizeInBytes (fileToLoad.getSize());
|
||||
|
||||
getThumbSize (w, h);
|
||||
|
||||
currentThumbnail = currentThumbnail.rescaled (w, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImagePreviewComponent::paint (Graphics& g)
|
||||
{
|
||||
if (currentThumbnail.isValid())
|
||||
{
|
||||
g.setFont (13.0f);
|
||||
|
||||
int w = currentThumbnail.getWidth();
|
||||
int h = currentThumbnail.getHeight();
|
||||
getThumbSize (w, h);
|
||||
|
||||
const int numLines = 4;
|
||||
const int totalH = 13 * numLines + h + 4;
|
||||
const int y = (getHeight() - totalH) / 2;
|
||||
|
||||
g.drawImageWithin (currentThumbnail,
|
||||
(getWidth() - w) / 2, y, w, h,
|
||||
RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize,
|
||||
false);
|
||||
|
||||
g.drawFittedText (currentDetails,
|
||||
0, y + h + 4, getWidth(), 100,
|
||||
Justification::centredTop, numLines);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_IMAGEPREVIEWCOMPONENT_H_INCLUDED
|
||||
#define JUCE_IMAGEPREVIEWCOMPONENT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A simple preview component that shows thumbnails of image files.
|
||||
|
||||
@see FileChooserDialogBox, FilePreviewComponent
|
||||
*/
|
||||
class JUCE_API ImagePreviewComponent : public FilePreviewComponent,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an ImagePreviewComponent. */
|
||||
ImagePreviewComponent();
|
||||
|
||||
/** Destructor. */
|
||||
~ImagePreviewComponent();
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void selectedFileChanged (const File& newSelectedFile) override;
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void timerCallback() override;
|
||||
|
||||
private:
|
||||
File fileToLoad;
|
||||
Image currentThumbnail;
|
||||
String currentDetails;
|
||||
|
||||
void getThumbSize (int& w, int& h) const;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImagePreviewComponent)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_IMAGEPREVIEWCOMPONENT_H_INCLUDED
|
||||
|
|
@ -0,0 +1,297 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if defined (JUCE_GUI_BASICS_H_INCLUDED) && ! JUCE_AMALGAMATED_INCLUDE
|
||||
/* When you add this cpp file to your project, you mustn't include it in a file where you've
|
||||
already included any other headers - just put it inside a file on its own, possibly with your config
|
||||
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix
|
||||
header files that the compiler may be using.
|
||||
*/
|
||||
#error "Incorrect use of JUCE cpp file"
|
||||
#endif
|
||||
|
||||
// Your project must contain an AppConfig.h file with your project-specific settings in it,
|
||||
// and your header search path must make it accessible to the module's files.
|
||||
#include "AppConfig.h"
|
||||
|
||||
#define NS_FORMAT_FUNCTION(F,A) // To avoid spurious warnings from GCC
|
||||
|
||||
#include "../juce_core/native/juce_BasicNativeHeaders.h"
|
||||
#include "juce_gui_basics.h"
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_opengl
|
||||
#include "../juce_opengl/juce_opengl.h"
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MAC
|
||||
#import <WebKit/WebKit.h>
|
||||
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||
|
||||
#if JUCE_SUPPORT_CARBON && ! (defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6)
|
||||
#define Point CarbonDummyPointName
|
||||
#define Component CarbonDummyCompName
|
||||
#import <Carbon/Carbon.h> // still needed for SetSystemUIMode()
|
||||
#undef Point
|
||||
#undef Component
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
#elif JUCE_WINDOWS
|
||||
#include <windowsx.h>
|
||||
#include <vfw.h>
|
||||
#include <commdlg.h>
|
||||
|
||||
#if JUCE_WEB_BROWSER
|
||||
#include <exdisp.h>
|
||||
#include <exdispid.h>
|
||||
#endif
|
||||
|
||||
#if JUCE_MSVC && ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES
|
||||
#pragma comment(lib, "vfw32.lib")
|
||||
#pragma comment(lib, "imm32.lib")
|
||||
#endif
|
||||
|
||||
#if JUCE_OPENGL
|
||||
#if JUCE_MSVC && ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES
|
||||
#pragma comment(lib, "OpenGL32.Lib")
|
||||
#pragma comment(lib, "GlU32.Lib")
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if JUCE_QUICKTIME && JUCE_MSVC && ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES
|
||||
#pragma comment (lib, "QTMLClient.lib")
|
||||
#endif
|
||||
|
||||
#if JUCE_DIRECT2D && JUCE_MSVC && ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES
|
||||
#pragma comment (lib, "Dwrite.lib")
|
||||
#pragma comment (lib, "D2d1.lib")
|
||||
#endif
|
||||
|
||||
#if JUCE_MINGW
|
||||
#include <imm.h>
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
#elif JUCE_LINUX
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xresource.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/Xmd.h>
|
||||
#include <X11/keysym.h>
|
||||
#include <X11/XKBlib.h>
|
||||
#include <X11/cursorfont.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if JUCE_USE_XINERAMA
|
||||
/* If you're trying to use Xinerama, you'll need to install the "libxinerama-dev" package.. */
|
||||
#include <X11/extensions/Xinerama.h>
|
||||
#endif
|
||||
|
||||
#if JUCE_USE_XSHM
|
||||
#include <X11/extensions/XShm.h>
|
||||
#include <sys/shm.h>
|
||||
#include <sys/ipc.h>
|
||||
#endif
|
||||
|
||||
#if JUCE_USE_XRENDER
|
||||
// If you're missing these headers, try installing the libxrender-dev and libxcomposite-dev
|
||||
#include <X11/extensions/Xrender.h>
|
||||
#include <X11/extensions/Xcomposite.h>
|
||||
#endif
|
||||
|
||||
#if JUCE_USE_XCURSOR
|
||||
// If you're missing this header, try installing the libxcursor-dev package
|
||||
#include <X11/Xcursor/Xcursor.h>
|
||||
#endif
|
||||
|
||||
#undef SIZEOF
|
||||
#undef KeyPress
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#define ASSERT_MESSAGE_MANAGER_IS_LOCKED \
|
||||
jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager());
|
||||
|
||||
#define ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN \
|
||||
jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager() || getPeer() == nullptr);
|
||||
|
||||
extern bool juce_areThereAnyAlwaysOnTopWindows();
|
||||
|
||||
#include "components/juce_Component.cpp"
|
||||
#include "components/juce_ComponentListener.cpp"
|
||||
#include "mouse/juce_MouseInputSource.cpp"
|
||||
#include "components/juce_Desktop.cpp"
|
||||
#include "components/juce_ModalComponentManager.cpp"
|
||||
#include "mouse/juce_ComponentDragger.cpp"
|
||||
#include "mouse/juce_DragAndDropContainer.cpp"
|
||||
#include "mouse/juce_MouseCursor.cpp"
|
||||
#include "mouse/juce_MouseEvent.cpp"
|
||||
#include "mouse/juce_MouseInactivityDetector.cpp"
|
||||
#include "mouse/juce_MouseListener.cpp"
|
||||
#include "keyboard/juce_CaretComponent.cpp"
|
||||
#include "keyboard/juce_KeyboardFocusTraverser.cpp"
|
||||
#include "keyboard/juce_KeyListener.cpp"
|
||||
#include "keyboard/juce_KeyPress.cpp"
|
||||
#include "keyboard/juce_ModifierKeys.cpp"
|
||||
#include "buttons/juce_ArrowButton.cpp"
|
||||
#include "buttons/juce_Button.cpp"
|
||||
#include "buttons/juce_DrawableButton.cpp"
|
||||
#include "buttons/juce_HyperlinkButton.cpp"
|
||||
#include "buttons/juce_ImageButton.cpp"
|
||||
#include "buttons/juce_ShapeButton.cpp"
|
||||
#include "buttons/juce_TextButton.cpp"
|
||||
#include "buttons/juce_ToggleButton.cpp"
|
||||
#include "buttons/juce_ToolbarButton.cpp"
|
||||
#include "drawables/juce_Drawable.cpp"
|
||||
#include "drawables/juce_DrawableComposite.cpp"
|
||||
#include "drawables/juce_DrawableImage.cpp"
|
||||
#include "drawables/juce_DrawablePath.cpp"
|
||||
#include "drawables/juce_DrawableRectangle.cpp"
|
||||
#include "drawables/juce_DrawableShape.cpp"
|
||||
#include "drawables/juce_DrawableText.cpp"
|
||||
#include "drawables/juce_SVGParser.cpp"
|
||||
#include "filebrowser/juce_DirectoryContentsDisplayComponent.cpp"
|
||||
#include "filebrowser/juce_DirectoryContentsList.cpp"
|
||||
#include "filebrowser/juce_FileBrowserComponent.cpp"
|
||||
#include "filebrowser/juce_FileChooser.cpp"
|
||||
#include "filebrowser/juce_FileChooserDialogBox.cpp"
|
||||
#include "filebrowser/juce_FileListComponent.cpp"
|
||||
#include "filebrowser/juce_FilenameComponent.cpp"
|
||||
#include "filebrowser/juce_FileSearchPathListComponent.cpp"
|
||||
#include "filebrowser/juce_FileTreeComponent.cpp"
|
||||
#include "filebrowser/juce_ImagePreviewComponent.cpp"
|
||||
#include "layout/juce_ComponentAnimator.cpp"
|
||||
#include "layout/juce_ComponentBoundsConstrainer.cpp"
|
||||
#include "layout/juce_ComponentBuilder.cpp"
|
||||
#include "layout/juce_ComponentMovementWatcher.cpp"
|
||||
#include "layout/juce_ConcertinaPanel.cpp"
|
||||
#include "layout/juce_GroupComponent.cpp"
|
||||
#include "layout/juce_MultiDocumentPanel.cpp"
|
||||
#include "layout/juce_ResizableBorderComponent.cpp"
|
||||
#include "layout/juce_ResizableCornerComponent.cpp"
|
||||
#include "layout/juce_ResizableEdgeComponent.cpp"
|
||||
#include "layout/juce_ScrollBar.cpp"
|
||||
#include "layout/juce_StretchableLayoutManager.cpp"
|
||||
#include "layout/juce_StretchableLayoutResizerBar.cpp"
|
||||
#include "layout/juce_StretchableObjectResizer.cpp"
|
||||
#include "layout/juce_TabbedButtonBar.cpp"
|
||||
#include "layout/juce_TabbedComponent.cpp"
|
||||
#include "layout/juce_Viewport.cpp"
|
||||
#include "lookandfeel/juce_LookAndFeel.cpp"
|
||||
#include "lookandfeel/juce_LookAndFeel_V2.cpp"
|
||||
#include "lookandfeel/juce_LookAndFeel_V1.cpp"
|
||||
#include "lookandfeel/juce_LookAndFeel_V3.cpp"
|
||||
#include "menus/juce_MenuBarComponent.cpp"
|
||||
#include "menus/juce_MenuBarModel.cpp"
|
||||
#include "menus/juce_PopupMenu.cpp"
|
||||
#include "positioning/juce_MarkerList.cpp"
|
||||
#include "positioning/juce_RelativeCoordinate.cpp"
|
||||
#include "positioning/juce_RelativeCoordinatePositioner.cpp"
|
||||
#include "positioning/juce_RelativeParallelogram.cpp"
|
||||
#include "positioning/juce_RelativePoint.cpp"
|
||||
#include "positioning/juce_RelativePointPath.cpp"
|
||||
#include "positioning/juce_RelativeRectangle.cpp"
|
||||
#include "properties/juce_BooleanPropertyComponent.cpp"
|
||||
#include "properties/juce_ButtonPropertyComponent.cpp"
|
||||
#include "properties/juce_ChoicePropertyComponent.cpp"
|
||||
#include "properties/juce_PropertyComponent.cpp"
|
||||
#include "properties/juce_PropertyPanel.cpp"
|
||||
#include "properties/juce_SliderPropertyComponent.cpp"
|
||||
#include "properties/juce_TextPropertyComponent.cpp"
|
||||
#include "widgets/juce_ComboBox.cpp"
|
||||
#include "widgets/juce_ImageComponent.cpp"
|
||||
#include "widgets/juce_Label.cpp"
|
||||
#include "widgets/juce_ListBox.cpp"
|
||||
#include "widgets/juce_ProgressBar.cpp"
|
||||
#include "widgets/juce_Slider.cpp"
|
||||
#include "widgets/juce_TableHeaderComponent.cpp"
|
||||
#include "widgets/juce_TableListBox.cpp"
|
||||
#include "widgets/juce_TextEditor.cpp"
|
||||
#include "widgets/juce_ToolbarItemComponent.cpp"
|
||||
#include "widgets/juce_Toolbar.cpp"
|
||||
#include "widgets/juce_ToolbarItemPalette.cpp"
|
||||
#include "widgets/juce_TreeView.cpp"
|
||||
#include "windows/juce_AlertWindow.cpp"
|
||||
#include "windows/juce_CallOutBox.cpp"
|
||||
#include "windows/juce_ComponentPeer.cpp"
|
||||
#include "windows/juce_DialogWindow.cpp"
|
||||
#include "windows/juce_DocumentWindow.cpp"
|
||||
#include "windows/juce_ResizableWindow.cpp"
|
||||
#include "windows/juce_ThreadWithProgressWindow.cpp"
|
||||
#include "windows/juce_TooltipWindow.cpp"
|
||||
#include "windows/juce_TopLevelWindow.cpp"
|
||||
#include "commands/juce_ApplicationCommandInfo.cpp"
|
||||
#include "commands/juce_ApplicationCommandManager.cpp"
|
||||
#include "commands/juce_ApplicationCommandTarget.cpp"
|
||||
#include "commands/juce_KeyPressMappingSet.cpp"
|
||||
#include "application/juce_Application.cpp"
|
||||
#include "misc/juce_BubbleComponent.cpp"
|
||||
#include "misc/juce_DropShadower.cpp"
|
||||
|
||||
#if JUCE_IOS || JUCE_WINDOWS
|
||||
#include "native/juce_MultiTouchMapper.h"
|
||||
#endif
|
||||
|
||||
#if JUCE_MAC || JUCE_IOS
|
||||
#include "../juce_core/native/juce_osx_ObjCHelpers.h"
|
||||
#include "../juce_graphics/native/juce_mac_CoreGraphicsHelpers.h"
|
||||
#include "../juce_graphics/native/juce_mac_CoreGraphicsContext.h"
|
||||
|
||||
#if JUCE_IOS
|
||||
#include "native/juce_ios_UIViewComponentPeer.mm"
|
||||
#include "native/juce_ios_Windowing.mm"
|
||||
#else
|
||||
#include "native/juce_mac_NSViewComponentPeer.mm"
|
||||
#include "native/juce_mac_Windowing.mm"
|
||||
#include "native/juce_mac_MainMenu.mm"
|
||||
#endif
|
||||
|
||||
#include "native/juce_mac_MouseCursor.mm"
|
||||
#include "native/juce_mac_FileChooser.mm"
|
||||
|
||||
#elif JUCE_WINDOWS
|
||||
#include "../juce_core/native/juce_win32_ComSmartPtr.h"
|
||||
#include "../juce_events/native/juce_win32_HiddenMessageWindow.h"
|
||||
#include "native/juce_win32_Windowing.cpp"
|
||||
#include "native/juce_win32_DragAndDrop.cpp"
|
||||
#include "native/juce_win32_FileChooser.cpp"
|
||||
|
||||
#elif JUCE_LINUX
|
||||
#include "native/juce_linux_Clipboard.cpp"
|
||||
#include "native/juce_linux_Windowing.cpp"
|
||||
#include "native/juce_linux_FileChooser.cpp"
|
||||
|
||||
#elif JUCE_ANDROID
|
||||
#include "../juce_core/native/juce_android_JNIHelpers.h"
|
||||
#include "native/juce_android_Windowing.cpp"
|
||||
#include "native/juce_android_FileChooser.cpp"
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_GUI_BASICS_H_INCLUDED
|
||||
#define JUCE_GUI_BASICS_H_INCLUDED
|
||||
|
||||
#include "../juce_graphics/juce_graphics.h"
|
||||
#include "../juce_data_structures/juce_data_structures.h"
|
||||
|
||||
//=============================================================================
|
||||
/** Config: JUCE_ENABLE_REPAINT_DEBUGGING
|
||||
If this option is turned on, each area of the screen that gets repainted will
|
||||
flash in a random colour, so that you can see exactly which bits of your
|
||||
components are being drawn.
|
||||
*/
|
||||
#ifndef JUCE_ENABLE_REPAINT_DEBUGGING
|
||||
#define JUCE_ENABLE_REPAINT_DEBUGGING 0
|
||||
#endif
|
||||
|
||||
/** JUCE_USE_XINERAMA: Enables Xinerama multi-monitor support (Linux only).
|
||||
Unless you specifically want to disable this, it's best to leave this option turned on.
|
||||
*/
|
||||
#ifndef JUCE_USE_XINERAMA
|
||||
#define JUCE_USE_XINERAMA 1
|
||||
#endif
|
||||
|
||||
/** Config: JUCE_USE_XSHM
|
||||
Enables X shared memory for faster rendering on Linux. This is best left turned on
|
||||
unless you have a good reason to disable it.
|
||||
*/
|
||||
#ifndef JUCE_USE_XSHM
|
||||
#define JUCE_USE_XSHM 1
|
||||
#endif
|
||||
|
||||
/** Config: JUCE_USE_XRENDER
|
||||
Enables XRender to allow semi-transparent windowing on Linux.
|
||||
*/
|
||||
#ifndef JUCE_USE_XRENDER
|
||||
#define JUCE_USE_XRENDER 0
|
||||
#endif
|
||||
|
||||
/** Config: JUCE_USE_XCURSOR
|
||||
Uses XCursor to allow ARGB cursor on Linux. This is best left turned on unless you have
|
||||
a good reason to disable it.
|
||||
*/
|
||||
#ifndef JUCE_USE_XCURSOR
|
||||
#define JUCE_USE_XCURSOR 1
|
||||
#endif
|
||||
|
||||
//=============================================================================
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class Component;
|
||||
class LookAndFeel;
|
||||
class MouseInputSource;
|
||||
class MouseInputSourceInternal;
|
||||
class ComponentPeer;
|
||||
class MarkerList;
|
||||
class RelativeRectangle;
|
||||
class MouseEvent;
|
||||
struct MouseWheelDetails;
|
||||
class ToggleButton;
|
||||
class TextButton;
|
||||
class AlertWindow;
|
||||
class TextLayout;
|
||||
class ScrollBar;
|
||||
class ComboBox;
|
||||
class Button;
|
||||
class FilenameComponent;
|
||||
class DocumentWindow;
|
||||
class ResizableWindow;
|
||||
class GroupComponent;
|
||||
class MenuBarComponent;
|
||||
class DropShadower;
|
||||
class GlyphArrangement;
|
||||
class PropertyComponent;
|
||||
class TableHeaderComponent;
|
||||
class Toolbar;
|
||||
class ToolbarItemComponent;
|
||||
class PopupMenu;
|
||||
class ProgressBar;
|
||||
class FileBrowserComponent;
|
||||
class DirectoryContentsDisplayComponent;
|
||||
class FilePreviewComponent;
|
||||
class ImageButton;
|
||||
class CallOutBox;
|
||||
class Drawable;
|
||||
class DrawablePath;
|
||||
class DrawableComposite;
|
||||
class CaretComponent;
|
||||
class BubbleComponent;
|
||||
class KeyPressMappingSet;
|
||||
class ApplicationCommandManagerListener;
|
||||
class DrawableButton;
|
||||
|
||||
#include "mouse/juce_MouseCursor.h"
|
||||
#include "mouse/juce_MouseListener.h"
|
||||
#include "keyboard/juce_ModifierKeys.h"
|
||||
#include "mouse/juce_MouseInputSource.h"
|
||||
#include "mouse/juce_MouseEvent.h"
|
||||
#include "keyboard/juce_KeyPress.h"
|
||||
#include "keyboard/juce_KeyListener.h"
|
||||
#include "keyboard/juce_KeyboardFocusTraverser.h"
|
||||
#include "components/juce_ModalComponentManager.h"
|
||||
#include "components/juce_ComponentListener.h"
|
||||
#include "components/juce_CachedComponentImage.h"
|
||||
#include "components/juce_Component.h"
|
||||
#include "layout/juce_ComponentAnimator.h"
|
||||
#include "components/juce_Desktop.h"
|
||||
#include "layout/juce_ComponentBoundsConstrainer.h"
|
||||
#include "mouse/juce_ComponentDragger.h"
|
||||
#include "mouse/juce_DragAndDropTarget.h"
|
||||
#include "mouse/juce_DragAndDropContainer.h"
|
||||
#include "mouse/juce_FileDragAndDropTarget.h"
|
||||
#include "mouse/juce_SelectedItemSet.h"
|
||||
#include "mouse/juce_LassoComponent.h"
|
||||
#include "mouse/juce_MouseInactivityDetector.h"
|
||||
#include "mouse/juce_TextDragAndDropTarget.h"
|
||||
#include "mouse/juce_TooltipClient.h"
|
||||
#include "keyboard/juce_CaretComponent.h"
|
||||
#include "keyboard/juce_SystemClipboard.h"
|
||||
#include "keyboard/juce_TextEditorKeyMapper.h"
|
||||
#include "keyboard/juce_TextInputTarget.h"
|
||||
#include "commands/juce_ApplicationCommandID.h"
|
||||
#include "commands/juce_ApplicationCommandInfo.h"
|
||||
#include "commands/juce_ApplicationCommandTarget.h"
|
||||
#include "commands/juce_ApplicationCommandManager.h"
|
||||
#include "commands/juce_KeyPressMappingSet.h"
|
||||
#include "buttons/juce_Button.h"
|
||||
#include "buttons/juce_ArrowButton.h"
|
||||
#include "buttons/juce_DrawableButton.h"
|
||||
#include "buttons/juce_HyperlinkButton.h"
|
||||
#include "buttons/juce_ImageButton.h"
|
||||
#include "buttons/juce_ShapeButton.h"
|
||||
#include "buttons/juce_TextButton.h"
|
||||
#include "buttons/juce_ToggleButton.h"
|
||||
#include "layout/juce_AnimatedPosition.h"
|
||||
#include "layout/juce_AnimatedPositionBehaviours.h"
|
||||
#include "layout/juce_ComponentBuilder.h"
|
||||
#include "layout/juce_ComponentMovementWatcher.h"
|
||||
#include "layout/juce_ConcertinaPanel.h"
|
||||
#include "layout/juce_GroupComponent.h"
|
||||
#include "layout/juce_ResizableBorderComponent.h"
|
||||
#include "layout/juce_ResizableCornerComponent.h"
|
||||
#include "layout/juce_ResizableEdgeComponent.h"
|
||||
#include "layout/juce_ScrollBar.h"
|
||||
#include "layout/juce_StretchableLayoutManager.h"
|
||||
#include "layout/juce_StretchableLayoutResizerBar.h"
|
||||
#include "layout/juce_StretchableObjectResizer.h"
|
||||
#include "layout/juce_TabbedButtonBar.h"
|
||||
#include "layout/juce_TabbedComponent.h"
|
||||
#include "layout/juce_Viewport.h"
|
||||
#include "menus/juce_PopupMenu.h"
|
||||
#include "menus/juce_MenuBarModel.h"
|
||||
#include "menus/juce_MenuBarComponent.h"
|
||||
#include "positioning/juce_RelativeCoordinate.h"
|
||||
#include "positioning/juce_MarkerList.h"
|
||||
#include "positioning/juce_RelativePoint.h"
|
||||
#include "positioning/juce_RelativeRectangle.h"
|
||||
#include "positioning/juce_RelativeCoordinatePositioner.h"
|
||||
#include "positioning/juce_RelativeParallelogram.h"
|
||||
#include "positioning/juce_RelativePointPath.h"
|
||||
#include "drawables/juce_Drawable.h"
|
||||
#include "drawables/juce_DrawableShape.h"
|
||||
#include "drawables/juce_DrawableComposite.h"
|
||||
#include "drawables/juce_DrawableImage.h"
|
||||
#include "drawables/juce_DrawablePath.h"
|
||||
#include "drawables/juce_DrawableRectangle.h"
|
||||
#include "drawables/juce_DrawableText.h"
|
||||
#include "widgets/juce_TextEditor.h"
|
||||
#include "widgets/juce_Label.h"
|
||||
#include "widgets/juce_ComboBox.h"
|
||||
#include "widgets/juce_ImageComponent.h"
|
||||
#include "widgets/juce_ListBox.h"
|
||||
#include "widgets/juce_ProgressBar.h"
|
||||
#include "widgets/juce_Slider.h"
|
||||
#include "widgets/juce_TableHeaderComponent.h"
|
||||
#include "widgets/juce_TableListBox.h"
|
||||
#include "widgets/juce_Toolbar.h"
|
||||
#include "widgets/juce_ToolbarItemComponent.h"
|
||||
#include "widgets/juce_ToolbarItemFactory.h"
|
||||
#include "widgets/juce_ToolbarItemPalette.h"
|
||||
#include "buttons/juce_ToolbarButton.h"
|
||||
#include "misc/juce_DropShadower.h"
|
||||
#include "widgets/juce_TreeView.h"
|
||||
#include "windows/juce_TopLevelWindow.h"
|
||||
#include "windows/juce_AlertWindow.h"
|
||||
#include "windows/juce_CallOutBox.h"
|
||||
#include "windows/juce_ComponentPeer.h"
|
||||
#include "windows/juce_ResizableWindow.h"
|
||||
#include "windows/juce_DocumentWindow.h"
|
||||
#include "windows/juce_DialogWindow.h"
|
||||
#include "windows/juce_NativeMessageBox.h"
|
||||
#include "windows/juce_ThreadWithProgressWindow.h"
|
||||
#include "windows/juce_TooltipWindow.h"
|
||||
#include "layout/juce_MultiDocumentPanel.h"
|
||||
#include "filebrowser/juce_FileBrowserListener.h"
|
||||
#include "filebrowser/juce_DirectoryContentsList.h"
|
||||
#include "filebrowser/juce_DirectoryContentsDisplayComponent.h"
|
||||
#include "filebrowser/juce_FileBrowserComponent.h"
|
||||
#include "filebrowser/juce_FileChooser.h"
|
||||
#include "filebrowser/juce_FileChooserDialogBox.h"
|
||||
#include "filebrowser/juce_FileListComponent.h"
|
||||
#include "filebrowser/juce_FilenameComponent.h"
|
||||
#include "filebrowser/juce_FilePreviewComponent.h"
|
||||
#include "filebrowser/juce_FileSearchPathListComponent.h"
|
||||
#include "filebrowser/juce_FileTreeComponent.h"
|
||||
#include "filebrowser/juce_ImagePreviewComponent.h"
|
||||
#include "properties/juce_PropertyComponent.h"
|
||||
#include "properties/juce_BooleanPropertyComponent.h"
|
||||
#include "properties/juce_ButtonPropertyComponent.h"
|
||||
#include "properties/juce_ChoicePropertyComponent.h"
|
||||
#include "properties/juce_PropertyPanel.h"
|
||||
#include "properties/juce_SliderPropertyComponent.h"
|
||||
#include "properties/juce_TextPropertyComponent.h"
|
||||
#include "application/juce_Application.h"
|
||||
#include "misc/juce_BubbleComponent.h"
|
||||
#include "lookandfeel/juce_LookAndFeel.h"
|
||||
#include "lookandfeel/juce_LookAndFeel_V2.h"
|
||||
#include "lookandfeel/juce_LookAndFeel_V1.h"
|
||||
#include "lookandfeel/juce_LookAndFeel_V3.h"
|
||||
|
||||
}
|
||||
|
||||
#endif // JUCE_GUI_BASICS_H_INCLUDED
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 "juce_gui_basics.cpp"
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"id": "juce_gui_basics",
|
||||
"name": "JUCE GUI core classes",
|
||||
"version": "3.0.8",
|
||||
"description": "Basic user-interface components and related classes.",
|
||||
"website": "http://www.juce.com/juce",
|
||||
"license": "GPL/Commercial",
|
||||
|
||||
"dependencies": [ { "id": "juce_core", "version": "matching" },
|
||||
{ "id": "juce_events", "version": "matching" },
|
||||
{ "id": "juce_graphics", "version": "matching" },
|
||||
{ "id": "juce_data_structures", "version": "matching" } ],
|
||||
|
||||
"include": "juce_gui_basics.h",
|
||||
|
||||
"compile": [ { "file": "juce_gui_basics.cpp", "target": "! xcode" },
|
||||
{ "file": "juce_gui_basics.mm", "target": "xcode" } ],
|
||||
|
||||
"browse": [ "components/*",
|
||||
"mouse/*",
|
||||
"keyboard/*",
|
||||
"widgets/*",
|
||||
"windows/*",
|
||||
"menus/*",
|
||||
"layout/*",
|
||||
"buttons/*",
|
||||
"positioning/*",
|
||||
"drawables/*",
|
||||
"properties/*",
|
||||
"lookandfeel/*",
|
||||
"filebrowser/*",
|
||||
"commands/*",
|
||||
"misc/*",
|
||||
"application/*",
|
||||
"native/*" ],
|
||||
|
||||
"OSXFrameworks": "Cocoa Carbon QuartzCore",
|
||||
"iOSFrameworks": "UIKit",
|
||||
"LinuxLibs": "X11 Xinerama Xext"
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
CaretComponent::CaretComponent (Component* const keyFocusOwner)
|
||||
: owner (keyFocusOwner)
|
||||
{
|
||||
setPaintingIsUnclipped (true);
|
||||
setInterceptsMouseClicks (false, false);
|
||||
}
|
||||
|
||||
CaretComponent::~CaretComponent()
|
||||
{
|
||||
}
|
||||
|
||||
void CaretComponent::paint (Graphics& g)
|
||||
{
|
||||
g.setColour (findColour (caretColourId, true));
|
||||
g.fillRect (getLocalBounds());
|
||||
}
|
||||
|
||||
void CaretComponent::timerCallback()
|
||||
{
|
||||
setVisible (shouldBeShown() && ! isVisible());
|
||||
}
|
||||
|
||||
void CaretComponent::setCaretPosition (const Rectangle<int>& characterArea)
|
||||
{
|
||||
startTimer (380);
|
||||
setVisible (shouldBeShown());
|
||||
setBounds (characterArea.withWidth (2));
|
||||
}
|
||||
|
||||
bool CaretComponent::shouldBeShown() const
|
||||
{
|
||||
return owner == nullptr || (owner->hasKeyboardFocus (false)
|
||||
&& ! owner->isCurrentlyBlockedByAnotherModalComponent());
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_CARETCOMPONENT_H_INCLUDED
|
||||
#define JUCE_CARETCOMPONENT_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
*/
|
||||
class JUCE_API CaretComponent : public Component,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates the caret component.
|
||||
The keyFocusOwner is an optional component which the caret will check, making
|
||||
itself visible only when the keyFocusOwner has keyboard focus.
|
||||
*/
|
||||
CaretComponent (Component* keyFocusOwner);
|
||||
|
||||
/** Destructor. */
|
||||
~CaretComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the caret's position to place it next to the given character.
|
||||
The area is the rectangle containing the entire character that the caret is
|
||||
positioned on, so by default a vertical-line caret may choose to just show itself
|
||||
at the left of this area. You can override this method to customise its size.
|
||||
This method will also force the caret to reset its timer and become visible (if
|
||||
appropriate), so that as it moves, you can see where it is.
|
||||
*/
|
||||
virtual void setCaretPosition (const Rectangle<int>& characterArea);
|
||||
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the caret.
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
caretColourId = 0x1000204, /**< The colour with which to draw the caret. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
|
||||
private:
|
||||
Component* owner;
|
||||
|
||||
bool shouldBeShown() const;
|
||||
void timerCallback() override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (CaretComponent)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_CARETCOMPONENT_H_INCLUDED
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
bool KeyListener::keyStateChanged (const bool, Component*)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_KEYLISTENER_H_INCLUDED
|
||||
#define JUCE_KEYLISTENER_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Receives callbacks when keys are pressed.
|
||||
|
||||
You can add a key listener to a component to be informed when that component
|
||||
gets key events. See the Component::addListener method for more details.
|
||||
|
||||
@see KeyPress, Component::addKeyListener, KeyPressMappingSet
|
||||
*/
|
||||
class JUCE_API KeyListener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~KeyListener() {}
|
||||
|
||||
//==============================================================================
|
||||
/** Called to indicate that a key has been pressed.
|
||||
|
||||
If your implementation returns true, then the key event is considered to have
|
||||
been consumed, and will not be passed on to any other components. If it returns
|
||||
false, then the key will be passed to other components that might want to use it.
|
||||
|
||||
@param key the keystroke, including modifier keys
|
||||
@param originatingComponent the component that received the key event
|
||||
@see keyStateChanged, Component::keyPressed
|
||||
*/
|
||||
virtual bool keyPressed (const KeyPress& key,
|
||||
Component* originatingComponent) = 0;
|
||||
|
||||
/** Called when any key is pressed or released.
|
||||
|
||||
When this is called, classes that might be interested in
|
||||
the state of one or more keys can use KeyPress::isKeyCurrentlyDown() to
|
||||
check whether their key has changed.
|
||||
|
||||
If your implementation returns true, then the key event is considered to have
|
||||
been consumed, and will not be passed on to any other components. If it returns
|
||||
false, then the key will be passed to other components that might want to use it.
|
||||
|
||||
@param originatingComponent the component that received the key event
|
||||
@param isKeyDown true if a key is being pressed, false if one is being released
|
||||
@see KeyPress, Component::keyStateChanged
|
||||
*/
|
||||
virtual bool keyStateChanged (bool isKeyDown, Component* originatingComponent);
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_KEYLISTENER_H_INCLUDED
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
KeyPress::KeyPress() noexcept
|
||||
: keyCode (0), textCharacter (0)
|
||||
{
|
||||
}
|
||||
|
||||
KeyPress::KeyPress (int code, ModifierKeys m, juce_wchar textChar) noexcept
|
||||
: keyCode (code), mods (m), textCharacter (textChar)
|
||||
{
|
||||
}
|
||||
|
||||
KeyPress::KeyPress (const int code) noexcept
|
||||
: keyCode (code), textCharacter (0)
|
||||
{
|
||||
}
|
||||
|
||||
KeyPress::KeyPress (const KeyPress& other) noexcept
|
||||
: keyCode (other.keyCode), mods (other.mods),
|
||||
textCharacter (other.textCharacter)
|
||||
{
|
||||
}
|
||||
|
||||
KeyPress& KeyPress::operator= (const KeyPress& other) noexcept
|
||||
{
|
||||
keyCode = other.keyCode;
|
||||
mods = other.mods;
|
||||
textCharacter = other.textCharacter;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool KeyPress::operator== (int otherKeyCode) const noexcept
|
||||
{
|
||||
return keyCode == otherKeyCode && ! mods.isAnyModifierKeyDown();
|
||||
}
|
||||
|
||||
bool KeyPress::operator== (const KeyPress& other) const noexcept
|
||||
{
|
||||
return mods.getRawFlags() == other.mods.getRawFlags()
|
||||
&& (textCharacter == other.textCharacter
|
||||
|| textCharacter == 0
|
||||
|| other.textCharacter == 0)
|
||||
&& (keyCode == other.keyCode
|
||||
|| (keyCode < 256
|
||||
&& other.keyCode < 256
|
||||
&& CharacterFunctions::toLowerCase ((juce_wchar) keyCode)
|
||||
== CharacterFunctions::toLowerCase ((juce_wchar) other.keyCode)));
|
||||
}
|
||||
|
||||
bool KeyPress::operator!= (const KeyPress& other) const noexcept
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
bool KeyPress::operator!= (int otherKeyCode) const noexcept
|
||||
{
|
||||
return ! operator== (otherKeyCode);
|
||||
}
|
||||
|
||||
bool KeyPress::isCurrentlyDown() const
|
||||
{
|
||||
return isKeyCurrentlyDown (keyCode)
|
||||
&& (ModifierKeys::getCurrentModifiers().getRawFlags() & ModifierKeys::allKeyboardModifiers)
|
||||
== (mods.getRawFlags() & ModifierKeys::allKeyboardModifiers);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
namespace KeyPressHelpers
|
||||
{
|
||||
struct KeyNameAndCode
|
||||
{
|
||||
const char* name;
|
||||
int code;
|
||||
};
|
||||
|
||||
const KeyNameAndCode translations[] =
|
||||
{
|
||||
{ "spacebar", KeyPress::spaceKey },
|
||||
{ "return", KeyPress::returnKey },
|
||||
{ "escape", KeyPress::escapeKey },
|
||||
{ "backspace", KeyPress::backspaceKey },
|
||||
{ "cursor left", KeyPress::leftKey },
|
||||
{ "cursor right", KeyPress::rightKey },
|
||||
{ "cursor up", KeyPress::upKey },
|
||||
{ "cursor down", KeyPress::downKey },
|
||||
{ "page up", KeyPress::pageUpKey },
|
||||
{ "page down", KeyPress::pageDownKey },
|
||||
{ "home", KeyPress::homeKey },
|
||||
{ "end", KeyPress::endKey },
|
||||
{ "delete", KeyPress::deleteKey },
|
||||
{ "insert", KeyPress::insertKey },
|
||||
{ "tab", KeyPress::tabKey },
|
||||
{ "play", KeyPress::playKey },
|
||||
{ "stop", KeyPress::stopKey },
|
||||
{ "fast forward", KeyPress::fastForwardKey },
|
||||
{ "rewind", KeyPress::rewindKey }
|
||||
};
|
||||
|
||||
struct ModifierDescription
|
||||
{
|
||||
const char* name;
|
||||
int flag;
|
||||
};
|
||||
|
||||
static const ModifierDescription modifierNames[] =
|
||||
{
|
||||
{ "ctrl", ModifierKeys::ctrlModifier },
|
||||
{ "control", ModifierKeys::ctrlModifier },
|
||||
{ "ctl", ModifierKeys::ctrlModifier },
|
||||
{ "shift", ModifierKeys::shiftModifier },
|
||||
{ "shft", ModifierKeys::shiftModifier },
|
||||
{ "alt", ModifierKeys::altModifier },
|
||||
{ "option", ModifierKeys::altModifier },
|
||||
{ "command", ModifierKeys::commandModifier },
|
||||
{ "cmd", ModifierKeys::commandModifier }
|
||||
};
|
||||
|
||||
static const char* numberPadPrefix() noexcept { return "numpad "; }
|
||||
|
||||
static int getNumpadKeyCode (const String& desc)
|
||||
{
|
||||
if (desc.containsIgnoreCase (numberPadPrefix()))
|
||||
{
|
||||
const juce_wchar lastChar = desc.trimEnd().getLastCharacter();
|
||||
|
||||
switch (lastChar)
|
||||
{
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
return (int) (KeyPress::numberPad0 + lastChar - '0');
|
||||
|
||||
case '+': return KeyPress::numberPadAdd;
|
||||
case '-': return KeyPress::numberPadSubtract;
|
||||
case '*': return KeyPress::numberPadMultiply;
|
||||
case '/': return KeyPress::numberPadDivide;
|
||||
case '.': return KeyPress::numberPadDecimalPoint;
|
||||
case '=': return KeyPress::numberPadEquals;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (desc.endsWith ("separator")) return KeyPress::numberPadSeparator;
|
||||
if (desc.endsWith ("delete")) return KeyPress::numberPadDelete;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if JUCE_MAC
|
||||
struct OSXSymbolReplacement
|
||||
{
|
||||
const char* text;
|
||||
juce_wchar symbol;
|
||||
};
|
||||
|
||||
const OSXSymbolReplacement osxSymbols[] =
|
||||
{
|
||||
{ "shift + ", 0x21e7 },
|
||||
{ "command + ", 0x2318 },
|
||||
{ "option + ", 0x2325 },
|
||||
{ "ctrl + ", 0x2303 },
|
||||
{ "return", 0x21b5 },
|
||||
{ "cursor left", 0x2190 },
|
||||
{ "cursor right", 0x2192 },
|
||||
{ "cursor up", 0x2191 },
|
||||
{ "cursor down", 0x2193 },
|
||||
{ "backspace", 0x232b },
|
||||
{ "delete", 0x2326 },
|
||||
{ "spacebar", 0x2423 }
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
KeyPress KeyPress::createFromDescription (const String& desc)
|
||||
{
|
||||
int modifiers = 0;
|
||||
|
||||
for (int i = 0; i < numElementsInArray (KeyPressHelpers::modifierNames); ++i)
|
||||
if (desc.containsWholeWordIgnoreCase (KeyPressHelpers::modifierNames[i].name))
|
||||
modifiers |= KeyPressHelpers::modifierNames[i].flag;
|
||||
|
||||
int key = 0;
|
||||
|
||||
for (int i = 0; i < numElementsInArray (KeyPressHelpers::translations); ++i)
|
||||
{
|
||||
if (desc.containsWholeWordIgnoreCase (String (KeyPressHelpers::translations[i].name)))
|
||||
{
|
||||
key = KeyPressHelpers::translations[i].code;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (key == 0)
|
||||
key = KeyPressHelpers::getNumpadKeyCode (desc);
|
||||
|
||||
if (key == 0)
|
||||
{
|
||||
// see if it's a function key..
|
||||
if (! desc.containsChar ('#')) // avoid mistaking hex-codes like "#f1"
|
||||
for (int i = 1; i <= 12; ++i)
|
||||
if (desc.containsWholeWordIgnoreCase ("f" + String (i)))
|
||||
key = F1Key + i - 1;
|
||||
|
||||
if (key == 0)
|
||||
{
|
||||
// give up and use the hex code..
|
||||
const int hexCode = desc.fromFirstOccurrenceOf ("#", false, false)
|
||||
.retainCharacters ("0123456789abcdefABCDEF")
|
||||
.getHexValue32();
|
||||
|
||||
if (hexCode > 0)
|
||||
key = hexCode;
|
||||
else
|
||||
key = (int) CharacterFunctions::toUpperCase (desc.getLastCharacter());
|
||||
}
|
||||
}
|
||||
|
||||
return KeyPress (key, ModifierKeys (modifiers), 0);
|
||||
}
|
||||
|
||||
String KeyPress::getTextDescription() const
|
||||
{
|
||||
String desc;
|
||||
|
||||
if (keyCode > 0)
|
||||
{
|
||||
// some keyboard layouts use a shift-key to get the slash, but in those cases, we
|
||||
// want to store it as being a slash, not shift+whatever.
|
||||
if (textCharacter == '/')
|
||||
return "/";
|
||||
|
||||
if (mods.isCtrlDown()) desc << "ctrl + ";
|
||||
if (mods.isShiftDown()) desc << "shift + ";
|
||||
|
||||
#if JUCE_MAC
|
||||
if (mods.isAltDown()) desc << "option + ";
|
||||
if (mods.isCommandDown()) desc << "command + ";
|
||||
#else
|
||||
if (mods.isAltDown()) desc << "alt + ";
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < numElementsInArray (KeyPressHelpers::translations); ++i)
|
||||
if (keyCode == KeyPressHelpers::translations[i].code)
|
||||
return desc + KeyPressHelpers::translations[i].name;
|
||||
|
||||
if (keyCode >= F1Key && keyCode <= F16Key) desc << 'F' << (1 + keyCode - F1Key);
|
||||
else if (keyCode >= numberPad0 && keyCode <= numberPad9) desc << KeyPressHelpers::numberPadPrefix() << (keyCode - numberPad0);
|
||||
else if (keyCode >= 33 && keyCode < 176) desc += CharacterFunctions::toUpperCase ((juce_wchar) keyCode);
|
||||
else if (keyCode == numberPadAdd) desc << KeyPressHelpers::numberPadPrefix() << '+';
|
||||
else if (keyCode == numberPadSubtract) desc << KeyPressHelpers::numberPadPrefix() << '-';
|
||||
else if (keyCode == numberPadMultiply) desc << KeyPressHelpers::numberPadPrefix() << '*';
|
||||
else if (keyCode == numberPadDivide) desc << KeyPressHelpers::numberPadPrefix() << '/';
|
||||
else if (keyCode == numberPadSeparator) desc << KeyPressHelpers::numberPadPrefix() << "separator";
|
||||
else if (keyCode == numberPadDecimalPoint) desc << KeyPressHelpers::numberPadPrefix() << '.';
|
||||
else if (keyCode == numberPadDelete) desc << KeyPressHelpers::numberPadPrefix() << "delete";
|
||||
else desc << '#' << String::toHexString (keyCode);
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
String KeyPress::getTextDescriptionWithIcons() const
|
||||
{
|
||||
#if JUCE_MAC
|
||||
String s (getTextDescription());
|
||||
|
||||
for (int i = 0; i < numElementsInArray (KeyPressHelpers::osxSymbols); ++i)
|
||||
s = s.replace (KeyPressHelpers::osxSymbols[i].text,
|
||||
String::charToString (KeyPressHelpers::osxSymbols[i].symbol));
|
||||
|
||||
return s;
|
||||
#else
|
||||
return getTextDescription();
|
||||
#endif
|
||||
}
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_KEYPRESS_H_INCLUDED
|
||||
#define JUCE_KEYPRESS_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents a key press, including any modifier keys that are needed.
|
||||
|
||||
E.g. a KeyPress might represent CTRL+C, SHIFT+ALT+H, Spacebar, Escape, etc.
|
||||
|
||||
@see Component, KeyListener, KeyPressMappingSet, Button::addShortcut
|
||||
*/
|
||||
class JUCE_API KeyPress
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an (invalid) KeyPress.
|
||||
|
||||
@see isValid
|
||||
*/
|
||||
KeyPress() noexcept;
|
||||
|
||||
/** Creates a KeyPress for a key and some modifiers.
|
||||
|
||||
e.g.
|
||||
CTRL+C would be: KeyPress ('c', ModifierKeys::ctrlModifier)
|
||||
SHIFT+Escape would be: KeyPress (KeyPress::escapeKey, ModifierKeys::shiftModifier)
|
||||
|
||||
@param keyCode a code that represents the key - this value must be
|
||||
one of special constants listed in this class, or an
|
||||
8-bit character code such as a letter (case is ignored),
|
||||
digit or a simple key like "," or ".". Note that this
|
||||
isn't the same as the textCharacter parameter, so for example
|
||||
a keyCode of 'a' and a shift-key modifier should have a
|
||||
textCharacter value of 'A'.
|
||||
@param modifiers the modifiers to associate with the keystroke
|
||||
@param textCharacter the character that would be printed if someone typed
|
||||
this keypress into a text editor. This value may be
|
||||
null if the keypress is a non-printing character
|
||||
@see getKeyCode, isKeyCode, getModifiers
|
||||
*/
|
||||
KeyPress (int keyCode,
|
||||
ModifierKeys modifiers,
|
||||
juce_wchar textCharacter) noexcept;
|
||||
|
||||
/** Creates a keypress with a keyCode but no modifiers or text character. */
|
||||
explicit KeyPress (int keyCode) noexcept;
|
||||
|
||||
/** Creates a copy of another KeyPress. */
|
||||
KeyPress (const KeyPress& other) noexcept;
|
||||
|
||||
/** Copies this KeyPress from another one. */
|
||||
KeyPress& operator= (const KeyPress& other) noexcept;
|
||||
|
||||
/** Compares two KeyPress objects. */
|
||||
bool operator== (const KeyPress& other) const noexcept;
|
||||
|
||||
/** Compares two KeyPress objects. */
|
||||
bool operator!= (const KeyPress& other) const noexcept;
|
||||
|
||||
/** Returns true if this keypress is for the given keycode without any modifiers. */
|
||||
bool operator== (int keyCode) const noexcept;
|
||||
|
||||
/** Returns true if this keypress is not for the given keycode without any modifiers. */
|
||||
bool operator!= (int keyCode) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if this is a valid KeyPress.
|
||||
|
||||
A null keypress can be created by the default constructor, in case it's
|
||||
needed.
|
||||
*/
|
||||
bool isValid() const noexcept { return keyCode != 0; }
|
||||
|
||||
/** Returns the key code itself.
|
||||
|
||||
This will either be one of the special constants defined in this class,
|
||||
or an 8-bit character code.
|
||||
*/
|
||||
int getKeyCode() const noexcept { return keyCode; }
|
||||
|
||||
/** Returns the key modifiers.
|
||||
|
||||
@see ModifierKeys
|
||||
*/
|
||||
ModifierKeys getModifiers() const noexcept { return mods; }
|
||||
|
||||
/** Returns the character that is associated with this keypress.
|
||||
|
||||
This is the character that you'd expect to see printed if you press this
|
||||
keypress in a text editor or similar component.
|
||||
*/
|
||||
juce_wchar getTextCharacter() const noexcept { return textCharacter; }
|
||||
|
||||
/** Checks whether the KeyPress's key is the same as the one provided, without checking
|
||||
the modifiers.
|
||||
|
||||
The values for key codes can either be one of the special constants defined in
|
||||
this class, or an 8-bit character code.
|
||||
|
||||
@see getKeyCode
|
||||
*/
|
||||
bool isKeyCode (int keyCodeToCompare) const noexcept { return keyCode == keyCodeToCompare; }
|
||||
|
||||
//==============================================================================
|
||||
/** Converts a textual key description to a KeyPress.
|
||||
|
||||
This attempts to decode a textual version of a keypress, e.g. "CTRL + C" or "SPACE".
|
||||
|
||||
This isn't designed to cope with any kind of input, but should be given the
|
||||
strings that are created by the getTextDescription() method.
|
||||
|
||||
If the string can't be parsed, the object returned will be invalid.
|
||||
|
||||
@see getTextDescription
|
||||
*/
|
||||
static KeyPress createFromDescription (const String& textVersion);
|
||||
|
||||
/** Creates a textual description of the key combination.
|
||||
|
||||
e.g. "CTRL + C" or "DELETE".
|
||||
|
||||
To store a keypress in a file, use this method, along with createFromDescription()
|
||||
to retrieve it later.
|
||||
*/
|
||||
String getTextDescription() const;
|
||||
|
||||
/** Creates a textual description of the key combination, using unicode icon symbols if possible.
|
||||
|
||||
On OSX, this uses the Apple symbols for command, option, shift, etc, instead of the textual
|
||||
modifier key descriptions that are returned by getTextDescription()
|
||||
*/
|
||||
String getTextDescriptionWithIcons() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Checks whether the user is currently holding down the keys that make up this
|
||||
KeyPress.
|
||||
|
||||
Note that this will return false if any extra modifier keys are
|
||||
down - e.g. if the keypress is CTRL+X and the user is actually holding CTRL+ALT+x
|
||||
then it will be false.
|
||||
*/
|
||||
bool isCurrentlyDown() const;
|
||||
|
||||
/** Checks whether a particular key is held down, irrespective of modifiers.
|
||||
|
||||
The values for key codes can either be one of the special constants defined in
|
||||
this class, or an 8-bit character code.
|
||||
*/
|
||||
static bool isKeyCurrentlyDown (int keyCode);
|
||||
|
||||
//==============================================================================
|
||||
// Key codes
|
||||
//
|
||||
// Note that the actual values of these are platform-specific and may change
|
||||
// without warning, so don't store them anywhere as constants. For persisting/retrieving
|
||||
// KeyPress objects, use getTextDescription() and createFromDescription() instead.
|
||||
//
|
||||
|
||||
static const int spaceKey; /**< key-code for the space bar */
|
||||
static const int escapeKey; /**< key-code for the escape key */
|
||||
static const int returnKey; /**< key-code for the return key*/
|
||||
static const int tabKey; /**< key-code for the tab key*/
|
||||
|
||||
static const int deleteKey; /**< key-code for the delete key (not backspace) */
|
||||
static const int backspaceKey; /**< key-code for the backspace key */
|
||||
static const int insertKey; /**< key-code for the insert key */
|
||||
|
||||
static const int upKey; /**< key-code for the cursor-up key */
|
||||
static const int downKey; /**< key-code for the cursor-down key */
|
||||
static const int leftKey; /**< key-code for the cursor-left key */
|
||||
static const int rightKey; /**< key-code for the cursor-right key */
|
||||
static const int pageUpKey; /**< key-code for the page-up key */
|
||||
static const int pageDownKey; /**< key-code for the page-down key */
|
||||
static const int homeKey; /**< key-code for the home key */
|
||||
static const int endKey; /**< key-code for the end key */
|
||||
|
||||
static const int F1Key; /**< key-code for the F1 key */
|
||||
static const int F2Key; /**< key-code for the F2 key */
|
||||
static const int F3Key; /**< key-code for the F3 key */
|
||||
static const int F4Key; /**< key-code for the F4 key */
|
||||
static const int F5Key; /**< key-code for the F5 key */
|
||||
static const int F6Key; /**< key-code for the F6 key */
|
||||
static const int F7Key; /**< key-code for the F7 key */
|
||||
static const int F8Key; /**< key-code for the F8 key */
|
||||
static const int F9Key; /**< key-code for the F9 key */
|
||||
static const int F10Key; /**< key-code for the F10 key */
|
||||
static const int F11Key; /**< key-code for the F11 key */
|
||||
static const int F12Key; /**< key-code for the F12 key */
|
||||
static const int F13Key; /**< key-code for the F13 key */
|
||||
static const int F14Key; /**< key-code for the F14 key */
|
||||
static const int F15Key; /**< key-code for the F15 key */
|
||||
static const int F16Key; /**< key-code for the F16 key */
|
||||
|
||||
static const int numberPad0; /**< key-code for the 0 on the numeric keypad. */
|
||||
static const int numberPad1; /**< key-code for the 1 on the numeric keypad. */
|
||||
static const int numberPad2; /**< key-code for the 2 on the numeric keypad. */
|
||||
static const int numberPad3; /**< key-code for the 3 on the numeric keypad. */
|
||||
static const int numberPad4; /**< key-code for the 4 on the numeric keypad. */
|
||||
static const int numberPad5; /**< key-code for the 5 on the numeric keypad. */
|
||||
static const int numberPad6; /**< key-code for the 6 on the numeric keypad. */
|
||||
static const int numberPad7; /**< key-code for the 7 on the numeric keypad. */
|
||||
static const int numberPad8; /**< key-code for the 8 on the numeric keypad. */
|
||||
static const int numberPad9; /**< key-code for the 9 on the numeric keypad. */
|
||||
|
||||
static const int numberPadAdd; /**< key-code for the add sign on the numeric keypad. */
|
||||
static const int numberPadSubtract; /**< key-code for the subtract sign on the numeric keypad. */
|
||||
static const int numberPadMultiply; /**< key-code for the multiply sign on the numeric keypad. */
|
||||
static const int numberPadDivide; /**< key-code for the divide sign on the numeric keypad. */
|
||||
static const int numberPadSeparator; /**< key-code for the comma on the numeric keypad. */
|
||||
static const int numberPadDecimalPoint; /**< key-code for the decimal point sign on the numeric keypad. */
|
||||
static const int numberPadEquals; /**< key-code for the equals key on the numeric keypad. */
|
||||
static const int numberPadDelete; /**< key-code for the delete key on the numeric keypad. */
|
||||
|
||||
static const int playKey; /**< key-code for a multimedia 'play' key, (not all keyboards will have one) */
|
||||
static const int stopKey; /**< key-code for a multimedia 'stop' key, (not all keyboards will have one) */
|
||||
static const int fastForwardKey; /**< key-code for a multimedia 'fast-forward' key, (not all keyboards will have one) */
|
||||
static const int rewindKey; /**< key-code for a multimedia 'rewind' key, (not all keyboards will have one) */
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
int keyCode;
|
||||
ModifierKeys mods;
|
||||
juce_wchar textCharacter;
|
||||
|
||||
JUCE_LEAK_DETECTOR (KeyPress)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_KEYPRESS_H_INCLUDED
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace KeyboardFocusHelpers
|
||||
{
|
||||
// This will sort a set of components, so that they are ordered in terms of
|
||||
// left-to-right and then top-to-bottom.
|
||||
struct ScreenPositionComparator
|
||||
{
|
||||
static int compareElements (const Component* const first, const Component* const second)
|
||||
{
|
||||
const int explicitOrder1 = getOrder (first);
|
||||
const int explicitOrder2 = getOrder (second);
|
||||
|
||||
if (explicitOrder1 != explicitOrder2)
|
||||
return explicitOrder1 - explicitOrder2;
|
||||
|
||||
const int yDiff = first->getY() - second->getY();
|
||||
|
||||
return yDiff == 0 ? first->getX() - second->getX()
|
||||
: yDiff;
|
||||
}
|
||||
|
||||
static int getOrder (const Component* const c)
|
||||
{
|
||||
const int order = c->getExplicitFocusOrder();
|
||||
return order > 0 ? order : (std::numeric_limits<int>::max() / 2);
|
||||
}
|
||||
};
|
||||
|
||||
static void findAllFocusableComponents (Component* const parent, Array <Component*>& comps)
|
||||
{
|
||||
if (parent->getNumChildComponents() > 0)
|
||||
{
|
||||
Array <Component*> localComps;
|
||||
ScreenPositionComparator comparator;
|
||||
|
||||
for (int i = parent->getNumChildComponents(); --i >= 0;)
|
||||
{
|
||||
Component* const c = parent->getChildComponent (i);
|
||||
|
||||
if (c->isVisible() && c->isEnabled())
|
||||
localComps.addSorted (comparator, c);
|
||||
}
|
||||
|
||||
for (int i = 0; i < localComps.size(); ++i)
|
||||
{
|
||||
Component* const c = localComps.getUnchecked (i);
|
||||
|
||||
if (c->getWantsKeyboardFocus())
|
||||
comps.add (c);
|
||||
|
||||
if (! c->isFocusContainer())
|
||||
findAllFocusableComponents (c, comps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Component* findFocusContainer (Component* c)
|
||||
{
|
||||
c = c->getParentComponent();
|
||||
|
||||
if (c != nullptr)
|
||||
while (c->getParentComponent() != nullptr && ! c->isFocusContainer())
|
||||
c = c->getParentComponent();
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static Component* getIncrementedComponent (Component* const current, const int delta)
|
||||
{
|
||||
Component* focusContainer = findFocusContainer (current);
|
||||
|
||||
if (focusContainer != nullptr)
|
||||
{
|
||||
Array <Component*> comps;
|
||||
KeyboardFocusHelpers::findAllFocusableComponents (focusContainer, comps);
|
||||
|
||||
if (comps.size() > 0)
|
||||
{
|
||||
const int index = comps.indexOf (current);
|
||||
return comps [(index + comps.size() + delta) % comps.size()];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
KeyboardFocusTraverser::KeyboardFocusTraverser() {}
|
||||
KeyboardFocusTraverser::~KeyboardFocusTraverser() {}
|
||||
|
||||
Component* KeyboardFocusTraverser::getNextComponent (Component* current)
|
||||
{
|
||||
jassert (current != nullptr);
|
||||
return KeyboardFocusHelpers::getIncrementedComponent (current, 1);
|
||||
}
|
||||
|
||||
Component* KeyboardFocusTraverser::getPreviousComponent (Component* current)
|
||||
{
|
||||
jassert (current != nullptr);
|
||||
return KeyboardFocusHelpers::getIncrementedComponent (current, -1);
|
||||
}
|
||||
|
||||
Component* KeyboardFocusTraverser::getDefaultComponent (Component* parentComponent)
|
||||
{
|
||||
Array <Component*> comps;
|
||||
|
||||
if (parentComponent != nullptr)
|
||||
KeyboardFocusHelpers::findAllFocusableComponents (parentComponent, comps);
|
||||
|
||||
return comps.getFirst();
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_KEYBOARDFOCUSTRAVERSER_H_INCLUDED
|
||||
#define JUCE_KEYBOARDFOCUSTRAVERSER_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Controls the order in which focus moves between components.
|
||||
|
||||
The default algorithm used by this class to work out the order of traversal
|
||||
is as follows:
|
||||
- if two components both have an explicit focus order specified, then the
|
||||
one with the lowest number comes first (see the Component::setExplicitFocusOrder()
|
||||
method).
|
||||
- any component with an explicit focus order greater than 0 comes before ones
|
||||
that don't have an order specified.
|
||||
- any unspecified components are traversed in a left-to-right, then top-to-bottom
|
||||
order.
|
||||
|
||||
If you need traversal in a more customised way, you can create a subclass
|
||||
of KeyboardFocusTraverser that uses your own algorithm, and use
|
||||
Component::createFocusTraverser() to create it.
|
||||
|
||||
@see Component::setExplicitFocusOrder, Component::createFocusTraverser
|
||||
*/
|
||||
class JUCE_API KeyboardFocusTraverser
|
||||
{
|
||||
public:
|
||||
KeyboardFocusTraverser();
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~KeyboardFocusTraverser();
|
||||
|
||||
/** Returns the component that should be given focus after the specified one
|
||||
when moving "forwards".
|
||||
|
||||
The default implementation will return the next component which is to the
|
||||
right of or below this one.
|
||||
|
||||
This may return nullptr if there's no suitable candidate.
|
||||
*/
|
||||
virtual Component* getNextComponent (Component* current);
|
||||
|
||||
/** Returns the component that should be given focus after the specified one
|
||||
when moving "backwards".
|
||||
|
||||
The default implementation will return the next component which is to the
|
||||
left of or above this one.
|
||||
|
||||
This may return nullptr if there's no suitable candidate.
|
||||
*/
|
||||
virtual Component* getPreviousComponent (Component* current);
|
||||
|
||||
/** Returns the component that should receive focus be default within the given
|
||||
parent component.
|
||||
|
||||
The default implementation will just return the foremost child component that
|
||||
wants focus.
|
||||
|
||||
This may return nullptr if there's no suitable candidate.
|
||||
*/
|
||||
virtual Component* getDefaultComponent (Component* parentComponent);
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_KEYBOARDFOCUSTRAVERSER_H_INCLUDED
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
ModifierKeys::ModifierKeys() noexcept : flags (0) {}
|
||||
ModifierKeys::ModifierKeys (int rawFlags) noexcept : flags (rawFlags) {}
|
||||
ModifierKeys::ModifierKeys (const ModifierKeys& other) noexcept : flags (other.flags) {}
|
||||
|
||||
ModifierKeys& ModifierKeys::operator= (const ModifierKeys other) noexcept
|
||||
{
|
||||
flags = other.flags;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ModifierKeys ModifierKeys::currentModifiers;
|
||||
|
||||
ModifierKeys ModifierKeys::getCurrentModifiers() noexcept
|
||||
{
|
||||
return currentModifiers;
|
||||
}
|
||||
|
||||
int ModifierKeys::getNumMouseButtonsDown() const noexcept
|
||||
{
|
||||
int num = 0;
|
||||
|
||||
if (isLeftButtonDown()) ++num;
|
||||
if (isRightButtonDown()) ++num;
|
||||
if (isMiddleButtonDown()) ++num;
|
||||
|
||||
return num;
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_MODIFIERKEYS_H_INCLUDED
|
||||
#define JUCE_MODIFIERKEYS_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents the state of the mouse buttons and modifier keys.
|
||||
|
||||
This is used both by mouse events and by KeyPress objects to describe
|
||||
the state of keys such as shift, control, alt, etc.
|
||||
|
||||
@see KeyPress, MouseEvent::mods
|
||||
*/
|
||||
class JUCE_API ModifierKeys
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a ModifierKeys object with no flags set. */
|
||||
ModifierKeys() noexcept;
|
||||
|
||||
/** Creates a ModifierKeys object from a raw set of flags.
|
||||
|
||||
@param flags to represent the keys that are down
|
||||
@see shiftModifier, ctrlModifier, altModifier, leftButtonModifier,
|
||||
rightButtonModifier, commandModifier, popupMenuClickModifier
|
||||
*/
|
||||
ModifierKeys (int flags) noexcept;
|
||||
|
||||
/** Creates a copy of another object. */
|
||||
ModifierKeys (const ModifierKeys& other) noexcept;
|
||||
|
||||
/** Copies this object from another one. */
|
||||
ModifierKeys& operator= (const ModifierKeys other) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Checks whether the 'command' key flag is set (or 'ctrl' on Windows/Linux).
|
||||
|
||||
This is a platform-agnostic way of checking for the operating system's
|
||||
preferred command-key modifier - so on the Mac it tests for the Apple key, on
|
||||
Windows/Linux, it's actually checking for the CTRL key.
|
||||
*/
|
||||
inline bool isCommandDown() const noexcept { return testFlags (commandModifier); }
|
||||
|
||||
/** Checks whether the user is trying to launch a pop-up menu.
|
||||
|
||||
This checks for platform-specific modifiers that might indicate that the user
|
||||
is following the operating system's normal method of showing a pop-up menu.
|
||||
|
||||
So on Windows/Linux, this method is really testing for a right-click.
|
||||
On the Mac, it tests for either the CTRL key being down, or a right-click.
|
||||
*/
|
||||
inline bool isPopupMenu() const noexcept { return testFlags (popupMenuClickModifier); }
|
||||
|
||||
/** Checks whether the flag is set for the left mouse-button. */
|
||||
inline bool isLeftButtonDown() const noexcept { return testFlags (leftButtonModifier); }
|
||||
|
||||
/** Checks whether the flag is set for the right mouse-button.
|
||||
|
||||
Note that for detecting popup-menu clicks, you should be using isPopupMenu() instead, as
|
||||
this is platform-independent (and makes your code more explanatory too).
|
||||
*/
|
||||
inline bool isRightButtonDown() const noexcept { return testFlags (rightButtonModifier); }
|
||||
|
||||
inline bool isMiddleButtonDown() const noexcept { return testFlags (middleButtonModifier); }
|
||||
|
||||
/** Tests for any of the mouse-button flags. */
|
||||
inline bool isAnyMouseButtonDown() const noexcept { return testFlags (allMouseButtonModifiers); }
|
||||
|
||||
/** Tests for any of the modifier key flags. */
|
||||
inline bool isAnyModifierKeyDown() const noexcept { return testFlags ((shiftModifier | ctrlModifier | altModifier | commandModifier)); }
|
||||
|
||||
/** Checks whether the shift key's flag is set. */
|
||||
inline bool isShiftDown() const noexcept { return testFlags (shiftModifier); }
|
||||
|
||||
/** Checks whether the CTRL key's flag is set.
|
||||
|
||||
Remember that it's better to use the platform-agnostic routines to test for command-key and
|
||||
popup-menu modifiers.
|
||||
|
||||
@see isCommandDown, isPopupMenu
|
||||
*/
|
||||
inline bool isCtrlDown() const noexcept { return testFlags (ctrlModifier); }
|
||||
|
||||
/** Checks whether the ALT key's flag is set. */
|
||||
inline bool isAltDown() const noexcept { return testFlags (altModifier); }
|
||||
|
||||
//==============================================================================
|
||||
/** Flags that represent the different keys. */
|
||||
enum Flags
|
||||
{
|
||||
/** Indicates no modifier keys. */
|
||||
noModifiers = 0,
|
||||
|
||||
/** Shift key flag. */
|
||||
shiftModifier = 1,
|
||||
|
||||
/** CTRL key flag. */
|
||||
ctrlModifier = 2,
|
||||
|
||||
/** ALT key flag. */
|
||||
altModifier = 4,
|
||||
|
||||
/** Left mouse button flag. */
|
||||
leftButtonModifier = 16,
|
||||
|
||||
/** Right mouse button flag. */
|
||||
rightButtonModifier = 32,
|
||||
|
||||
/** Middle mouse button flag. */
|
||||
middleButtonModifier = 64,
|
||||
|
||||
#if JUCE_MAC
|
||||
/** Command key flag - on windows this is the same as the CTRL key flag. */
|
||||
commandModifier = 8,
|
||||
|
||||
/** Popup menu flag - on windows this is the same as rightButtonModifier, on the
|
||||
Mac it's the same as (rightButtonModifier | ctrlModifier). */
|
||||
popupMenuClickModifier = rightButtonModifier | ctrlModifier,
|
||||
#else
|
||||
/** Command key flag - on windows this is the same as the CTRL key flag. */
|
||||
commandModifier = ctrlModifier,
|
||||
|
||||
/** Popup menu flag - on windows this is the same as rightButtonModifier, on the
|
||||
Mac it's the same as (rightButtonModifier | ctrlModifier). */
|
||||
popupMenuClickModifier = rightButtonModifier,
|
||||
#endif
|
||||
|
||||
/** Represents a combination of all the shift, alt, ctrl and command key modifiers. */
|
||||
allKeyboardModifiers = shiftModifier | ctrlModifier | altModifier | commandModifier,
|
||||
|
||||
/** Represents a combination of all the mouse buttons at once. */
|
||||
allMouseButtonModifiers = leftButtonModifier | rightButtonModifier | middleButtonModifier,
|
||||
|
||||
/** Represents a combination of all the alt, ctrl and command key modifiers. */
|
||||
ctrlAltCommandModifiers = ctrlModifier | altModifier | commandModifier
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a copy of only the mouse-button flags */
|
||||
ModifierKeys withOnlyMouseButtons() const noexcept { return ModifierKeys (flags & allMouseButtonModifiers); }
|
||||
|
||||
/** Returns a copy of only the non-mouse flags */
|
||||
ModifierKeys withoutMouseButtons() const noexcept { return ModifierKeys (flags & ~allMouseButtonModifiers); }
|
||||
|
||||
bool operator== (const ModifierKeys other) const noexcept { return flags == other.flags; }
|
||||
bool operator!= (const ModifierKeys other) const noexcept { return flags != other.flags; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the raw flags for direct testing. */
|
||||
inline int getRawFlags() const noexcept { return flags; }
|
||||
|
||||
ModifierKeys withoutFlags (int rawFlagsToClear) const noexcept { return ModifierKeys (flags & ~rawFlagsToClear); }
|
||||
ModifierKeys withFlags (int rawFlagsToSet) const noexcept { return ModifierKeys (flags | rawFlagsToSet); }
|
||||
|
||||
/** Tests a combination of flags and returns true if any of them are set. */
|
||||
bool testFlags (int flagsToTest) const noexcept { return (flags & flagsToTest) != 0; }
|
||||
|
||||
/** Returns the total number of mouse buttons that are down. */
|
||||
int getNumMouseButtonsDown() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a ModifierKeys object to represent the last-known state of the
|
||||
keyboard and mouse buttons.
|
||||
|
||||
@see getCurrentModifiersRealtime
|
||||
*/
|
||||
static ModifierKeys getCurrentModifiers() noexcept;
|
||||
|
||||
/** Creates a ModifierKeys object to represent the current state of the
|
||||
keyboard and mouse buttons.
|
||||
|
||||
This isn't often needed and isn't recommended, but will actively check all the
|
||||
mouse and key states rather than just returning their last-known state like
|
||||
getCurrentModifiers() does.
|
||||
|
||||
This is only needed in special circumstances for up-to-date modifier information
|
||||
at times when the app's event loop isn't running normally.
|
||||
|
||||
Another reason to avoid this method is that it's not stateless, and calling it may
|
||||
update the value returned by getCurrentModifiers(), which could cause subtle changes
|
||||
in the behaviour of some components.
|
||||
*/
|
||||
static ModifierKeys getCurrentModifiersRealtime() noexcept;
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
int flags;
|
||||
|
||||
friend class ComponentPeer;
|
||||
friend class MouseInputSource;
|
||||
friend class MouseInputSourceInternal;
|
||||
|
||||
static ModifierKeys currentModifiers;
|
||||
static void updateCurrentModifiers() noexcept;
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_MODIFIERKEYS_H_INCLUDED
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_SYSTEMCLIPBOARD_H_INCLUDED
|
||||
#define JUCE_SYSTEMCLIPBOARD_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Handles reading/writing to the system's clipboard.
|
||||
*/
|
||||
class JUCE_API SystemClipboard
|
||||
{
|
||||
public:
|
||||
/** Copies a string of text onto the clipboard */
|
||||
static void copyTextToClipboard (const String& text);
|
||||
|
||||
/** Gets the current clipboard's contents.
|
||||
|
||||
Obviously this might have come from another app, so could contain
|
||||
anything..
|
||||
*/
|
||||
static String getTextFromClipboard();
|
||||
};
|
||||
|
||||
#endif // JUCE_SYSTEMCLIPBOARD_H_INCLUDED
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_TEXTEDITORKEYMAPPER_H_INCLUDED
|
||||
#define JUCE_TEXTEDITORKEYMAPPER_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** This class is used to invoke a range of text-editor navigation methods on
|
||||
an object, based upon a keypress event.
|
||||
|
||||
It's currently used internally by the TextEditor and CodeEditorComponent.
|
||||
*/
|
||||
template <class CallbackClass>
|
||||
struct TextEditorKeyMapper
|
||||
{
|
||||
/** Checks the keypress and invokes one of a range of navigation functions that
|
||||
the target class must implement, based on the key event.
|
||||
*/
|
||||
static bool invokeKeyFunction (CallbackClass& target, const KeyPress& key)
|
||||
{
|
||||
const ModifierKeys& mods = key.getModifiers();
|
||||
|
||||
const bool isShiftDown = mods.isShiftDown();
|
||||
const bool ctrlOrAltDown = mods.isCtrlDown() || mods.isAltDown();
|
||||
|
||||
int numCtrlAltCommandKeys = 0;
|
||||
if (mods.isCtrlDown()) ++numCtrlAltCommandKeys;
|
||||
if (mods.isAltDown()) ++numCtrlAltCommandKeys;
|
||||
|
||||
if (key == KeyPress (KeyPress::downKey, ModifierKeys::ctrlModifier, 0) && target.scrollUp()) return true;
|
||||
if (key == KeyPress (KeyPress::upKey, ModifierKeys::ctrlModifier, 0) && target.scrollDown()) return true;
|
||||
|
||||
#if JUCE_MAC
|
||||
if (mods.isCommandDown() && ! ctrlOrAltDown)
|
||||
{
|
||||
if (key.isKeyCode (KeyPress::upKey)) return target.moveCaretToTop (isShiftDown);
|
||||
if (key.isKeyCode (KeyPress::downKey)) return target.moveCaretToEnd (isShiftDown);
|
||||
if (key.isKeyCode (KeyPress::leftKey)) return target.moveCaretToStartOfLine (isShiftDown);
|
||||
if (key.isKeyCode (KeyPress::rightKey)) return target.moveCaretToEndOfLine (isShiftDown);
|
||||
}
|
||||
|
||||
if (mods.isCommandDown())
|
||||
++numCtrlAltCommandKeys;
|
||||
#endif
|
||||
|
||||
if (numCtrlAltCommandKeys < 2)
|
||||
{
|
||||
if (key.isKeyCode (KeyPress::leftKey)) return target.moveCaretLeft (ctrlOrAltDown, isShiftDown);
|
||||
if (key.isKeyCode (KeyPress::rightKey)) return target.moveCaretRight (ctrlOrAltDown, isShiftDown);
|
||||
|
||||
if (key.isKeyCode (KeyPress::homeKey)) return ctrlOrAltDown ? target.moveCaretToTop (isShiftDown)
|
||||
: target.moveCaretToStartOfLine (isShiftDown);
|
||||
if (key.isKeyCode (KeyPress::endKey)) return ctrlOrAltDown ? target.moveCaretToEnd (isShiftDown)
|
||||
: target.moveCaretToEndOfLine (isShiftDown);
|
||||
}
|
||||
|
||||
if (numCtrlAltCommandKeys == 0)
|
||||
{
|
||||
if (key.isKeyCode (KeyPress::upKey)) return target.moveCaretUp (isShiftDown);
|
||||
if (key.isKeyCode (KeyPress::downKey)) return target.moveCaretDown (isShiftDown);
|
||||
|
||||
if (key.isKeyCode (KeyPress::pageUpKey)) return target.pageUp (isShiftDown);
|
||||
if (key.isKeyCode (KeyPress::pageDownKey)) return target.pageDown (isShiftDown);
|
||||
}
|
||||
|
||||
if (numCtrlAltCommandKeys < 2)
|
||||
{
|
||||
if (key.isKeyCode (KeyPress::backspaceKey)) return target.deleteBackwards (ctrlOrAltDown);
|
||||
if (key.isKeyCode (KeyPress::deleteKey)) return target.deleteForwards (ctrlOrAltDown);
|
||||
}
|
||||
|
||||
if (key == KeyPress ('c', ModifierKeys::commandModifier, 0)
|
||||
|| key == KeyPress (KeyPress::insertKey, ModifierKeys::ctrlModifier, 0))
|
||||
return target.copyToClipboard();
|
||||
|
||||
if (key == KeyPress ('x', ModifierKeys::commandModifier, 0)
|
||||
|| key == KeyPress (KeyPress::deleteKey, ModifierKeys::shiftModifier, 0))
|
||||
return target.cutToClipboard();
|
||||
|
||||
if (key == KeyPress ('v', ModifierKeys::commandModifier, 0)
|
||||
|| key == KeyPress (KeyPress::insertKey, ModifierKeys::shiftModifier, 0))
|
||||
return target.pasteFromClipboard();
|
||||
|
||||
if (key == KeyPress ('a', ModifierKeys::commandModifier, 0))
|
||||
return target.selectAll();
|
||||
|
||||
if (key == KeyPress ('z', ModifierKeys::commandModifier, 0))
|
||||
return target.undo();
|
||||
|
||||
if (key == KeyPress ('y', ModifierKeys::commandModifier, 0)
|
||||
|| key == KeyPress ('z', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0))
|
||||
return target.redo();
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_TEXTEDITORKEYMAPPER_H_INCLUDED
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_TEXTINPUTTARGET_H_INCLUDED
|
||||
#define JUCE_TEXTINPUTTARGET_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An abstract base class which can be implemented by components that function as
|
||||
text editors.
|
||||
|
||||
This class allows different types of text editor component to provide a uniform
|
||||
interface, which can be used by things like OS-specific input methods, on-screen
|
||||
keyboards, etc.
|
||||
*/
|
||||
class JUCE_API TextInputTarget
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** */
|
||||
TextInputTarget() {}
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~TextInputTarget() {}
|
||||
|
||||
/** Returns true if this input target is currently accepting input.
|
||||
For example, a text editor might return false if it's in read-only mode.
|
||||
*/
|
||||
virtual bool isTextInputActive() const = 0;
|
||||
|
||||
/** Returns the extents of the selected text region, or an empty range if
|
||||
nothing is selected,
|
||||
*/
|
||||
virtual Range<int> getHighlightedRegion() const = 0;
|
||||
|
||||
/** Sets the currently-selected text region. */
|
||||
virtual void setHighlightedRegion (const Range<int>& newRange) = 0;
|
||||
|
||||
/** Sets a number of temporarily underlined sections.
|
||||
This is needed by MS Windows input method UI.
|
||||
*/
|
||||
virtual void setTemporaryUnderlining (const Array <Range<int> >& underlinedRegions) = 0;
|
||||
|
||||
/** Returns a specified sub-section of the text. */
|
||||
virtual String getTextInRange (const Range<int>& range) const = 0;
|
||||
|
||||
/** Inserts some text, overwriting the selected text region, if there is one. */
|
||||
virtual void insertTextAtCaret (const String& textToInsert) = 0;
|
||||
|
||||
/** Returns the position of the caret, relative to the component's origin. */
|
||||
virtual Rectangle<int> getCaretRectangle() = 0;
|
||||
|
||||
/** A set of possible on-screen keyboard types, for use in the
|
||||
getKeyboardType() method.
|
||||
*/
|
||||
enum VirtualKeyboardType
|
||||
{
|
||||
textKeyboard = 0,
|
||||
numericKeyboard,
|
||||
urlKeyboard,
|
||||
emailAddressKeyboard,
|
||||
phoneNumberKeyboard
|
||||
};
|
||||
|
||||
/** Returns the target's preference for the type of keyboard that would be most appropriate.
|
||||
This may be ignored, depending on the capabilities of the OS.
|
||||
*/
|
||||
virtual VirtualKeyboardType getKeyboardType() { return textKeyboard; }
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_TEXTINPUTTARGET_H_INCLUDED
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_ANIMATEDPOSITION_H_INCLUDED
|
||||
#define JUCE_ANIMATEDPOSITION_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Models a 1-dimensional position that can be dragged around by the user, and which
|
||||
will then continue moving with a customisable physics behaviour when released.
|
||||
|
||||
This is useful for things like scrollable views or objects that can be dragged and
|
||||
thrown around with the mouse/touch, and by writing your own behaviour class, you can
|
||||
customise the trajectory that it follows when released.
|
||||
|
||||
The class uses its own Timer to continuously change its value when a drag ends, and
|
||||
Listener objects can be registered to receive callbacks whenever the value changes.
|
||||
|
||||
The value is stored as a double, and can be used to represent whatever units you need.
|
||||
|
||||
The template parameter Behaviour must be a class that implements various methods to
|
||||
return the physics of the value's movement - you can use the classes provided for this
|
||||
in the AnimatedPositionBehaviours namespace, or write your own custom behaviour.
|
||||
|
||||
@see AnimatedPositionBehaviours::ContinuousWithMomentum,
|
||||
AnimatedPositionBehaviours::SnapToPageBoundaries
|
||||
*/
|
||||
template <typename Behaviour>
|
||||
class AnimatedPosition : private Timer
|
||||
{
|
||||
public:
|
||||
AnimatedPosition()
|
||||
: position(), grabbedPos(), releaseVelocity(),
|
||||
range (-std::numeric_limits<double>::max(),
|
||||
std::numeric_limits<double>::max())
|
||||
{
|
||||
}
|
||||
|
||||
/** Sets a range within which the value will be constrained. */
|
||||
void setLimits (Range<double> newRange)
|
||||
{
|
||||
range = newRange;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Called to indicate that the object is now being controlled by a
|
||||
mouse-drag or similar operation.
|
||||
|
||||
After calling this method, you should make calls to the drag() method
|
||||
each time the mouse drags the position around, and always be sure to
|
||||
finish with a call to endDrag() when the mouse is released, which allows
|
||||
the position to continue moving freely according to the specified behaviour.
|
||||
*/
|
||||
void beginDrag()
|
||||
{
|
||||
grabbedPos = position;
|
||||
releaseVelocity = 0;
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
/** Called during a mouse-drag operation, to indicate that the mouse has moved.
|
||||
The delta is the difference between the position when beginDrag() was called
|
||||
and the new position that's required.
|
||||
*/
|
||||
void drag (double deltaFromStartOfDrag)
|
||||
{
|
||||
moveTo (grabbedPos + deltaFromStartOfDrag);
|
||||
}
|
||||
|
||||
/** Called after beginDrag() and drag() to indicate that the drag operation has
|
||||
now finished.
|
||||
*/
|
||||
void endDrag()
|
||||
{
|
||||
startTimer (1000 / 60);
|
||||
}
|
||||
|
||||
/** Called outside of a drag operation to cause a nudge in the specified direction.
|
||||
This is intended for use by e.g. mouse-wheel events.
|
||||
*/
|
||||
void nudge (double deltaFromCurrentPosition)
|
||||
{
|
||||
startTimer (100);
|
||||
moveTo (position + deltaFromCurrentPosition);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the current position. */
|
||||
double getPosition() const noexcept
|
||||
{
|
||||
return position;
|
||||
}
|
||||
|
||||
/** Explicitly sets the position and stops any further movement.
|
||||
This will cause a synchronous call to any listeners if the position actually
|
||||
changes.
|
||||
*/
|
||||
void setPosition (double newPosition)
|
||||
{
|
||||
stopTimer();
|
||||
setPositionAndSendChange (newPosition);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Implement this class if you need to receive callbacks when the value of
|
||||
an AnimatedPosition changes.
|
||||
@see AnimatedPosition::addListener, AnimatedPosition::removeListener
|
||||
*/
|
||||
class Listener
|
||||
{
|
||||
public:
|
||||
virtual ~Listener() {}
|
||||
|
||||
/** Called synchronously when an AnimatedPosition changes. */
|
||||
virtual void positionChanged (AnimatedPosition&, double newPosition) = 0;
|
||||
};
|
||||
|
||||
/** Adds a listener to be called when the value changes. */
|
||||
void addListener (Listener* listener) { listeners.add (listener); }
|
||||
|
||||
/** Removes a previously-registered listener. */
|
||||
void removeListener (Listener* listener) { listeners.remove (listener); }
|
||||
|
||||
//==============================================================================
|
||||
/** The behaviour object.
|
||||
This is public to let you tweak any parameters that it provides.
|
||||
*/
|
||||
Behaviour behaviour;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
double position, grabbedPos, releaseVelocity;
|
||||
Range<double> range;
|
||||
Time lastUpdate, lastDrag;
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
static double getSpeed (const Time last, double lastPos,
|
||||
const Time now, double newPos)
|
||||
{
|
||||
const double elapsedSecs = jmax (0.005, (now - last).inSeconds());
|
||||
const double v = (newPos - lastPos) / elapsedSecs;
|
||||
return std::abs (v) > 0.2 ? v : 0.0;
|
||||
}
|
||||
|
||||
void moveTo (double newPos)
|
||||
{
|
||||
const Time now (Time::getCurrentTime());
|
||||
releaseVelocity = getSpeed (lastDrag, position, now, newPos);
|
||||
behaviour.releasedWithVelocity (newPos, releaseVelocity);
|
||||
lastDrag = now;
|
||||
|
||||
setPositionAndSendChange (newPos);
|
||||
}
|
||||
|
||||
void setPositionAndSendChange (double newPosition)
|
||||
{
|
||||
newPosition = range.clipValue (newPosition);
|
||||
|
||||
if (position != newPosition)
|
||||
{
|
||||
position = newPosition;
|
||||
listeners.call (&Listener::positionChanged, *this, newPosition);
|
||||
}
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
const Time now = Time::getCurrentTime();
|
||||
|
||||
const double elapsed = jlimit (0.001, 0.020, (now - lastUpdate).inSeconds());
|
||||
lastUpdate = now;
|
||||
|
||||
const double newPos = behaviour.getNextPosition (position, elapsed);
|
||||
|
||||
if (behaviour.isStopped (newPos))
|
||||
stopTimer();
|
||||
else
|
||||
startTimer (1000 / 60);
|
||||
|
||||
setPositionAndSendChange (newPos);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnimatedPosition)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_ANIMATEDPOSITION_H_INCLUDED
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_ANIMATEDPOSITIONBEHAVIOURS_H_INCLUDED
|
||||
#define JUCE_ANIMATEDPOSITIONBEHAVIOURS_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Contains classes for different types of physics behaviours - these classes
|
||||
are used as template parameters for the AnimatedPosition class.
|
||||
*/
|
||||
namespace AnimatedPositionBehaviours
|
||||
{
|
||||
/** A non-snapping behaviour that allows the content to be freely flicked in
|
||||
either direction, with momentum based on the velocity at which it was
|
||||
released, and variable friction to make it come to a halt.
|
||||
|
||||
This class is intended to be used as a template parameter to the
|
||||
AnimatedPosition class.
|
||||
|
||||
@see AnimatedPosition
|
||||
*/
|
||||
struct ContinuousWithMomentum
|
||||
{
|
||||
ContinuousWithMomentum() noexcept
|
||||
: velocity (0), damping (0.92)
|
||||
{
|
||||
}
|
||||
|
||||
/** Sets the friction that damps the movement of the value.
|
||||
A typical value is 0.08; higher values indicate more friction.
|
||||
*/
|
||||
void setFriction (double newFriction) noexcept
|
||||
{
|
||||
damping = 1.0 - newFriction;
|
||||
}
|
||||
|
||||
/** Called by the AnimatedPosition class. This tells us the position and
|
||||
velocity at which the user is about to release the object.
|
||||
The velocity is measured in units/second.
|
||||
*/
|
||||
void releasedWithVelocity (double /*position*/, double releaseVelocity) noexcept
|
||||
{
|
||||
velocity = releaseVelocity;
|
||||
}
|
||||
|
||||
/** Called by the AnimatedPosition class to get the new position, after
|
||||
the given time has elapsed.
|
||||
*/
|
||||
double getNextPosition (double oldPos, double elapsedSeconds) noexcept
|
||||
{
|
||||
velocity *= damping;
|
||||
|
||||
if (std::abs (velocity) < 0.05)
|
||||
velocity = 0;
|
||||
|
||||
return oldPos + velocity * elapsedSeconds;
|
||||
}
|
||||
|
||||
/** Called by the AnimatedPosition class to check whether the object
|
||||
is now stationary.
|
||||
*/
|
||||
bool isStopped (double /*position*/) const noexcept
|
||||
{
|
||||
return velocity == 0;
|
||||
}
|
||||
|
||||
private:
|
||||
double velocity, damping;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** A behaviour that gravitates an AnimatedPosition object towards the nearest
|
||||
integer position when released.
|
||||
|
||||
This class is intended to be used as a template parameter to the
|
||||
AnimatedPosition class. It's handy when using an AnimatedPosition to show a
|
||||
series of pages, because it allows the pages can be scrolled smoothly, but when
|
||||
released, snaps back to show a whole page.
|
||||
|
||||
@see AnimatedPosition
|
||||
*/
|
||||
struct SnapToPageBoundaries
|
||||
{
|
||||
SnapToPageBoundaries() noexcept : targetSnapPosition()
|
||||
{
|
||||
}
|
||||
|
||||
/** Called by the AnimatedPosition class. This tells us the position and
|
||||
velocity at which the user is about to release the object.
|
||||
The velocity is measured in units/second.
|
||||
*/
|
||||
void releasedWithVelocity (double position, double releaseVelocity) noexcept
|
||||
{
|
||||
targetSnapPosition = std::floor (position + 0.5);
|
||||
|
||||
if (releaseVelocity > 1.0 && targetSnapPosition < position) ++targetSnapPosition;
|
||||
if (releaseVelocity < -1.0 && targetSnapPosition > position) --targetSnapPosition;
|
||||
}
|
||||
|
||||
/** Called by the AnimatedPosition class to get the new position, after
|
||||
the given time has elapsed.
|
||||
*/
|
||||
double getNextPosition (double oldPos, double elapsedSeconds) const noexcept
|
||||
{
|
||||
if (isStopped (oldPos))
|
||||
return targetSnapPosition;
|
||||
|
||||
const double snapSpeed = 10.0;
|
||||
const double velocity = (targetSnapPosition - oldPos) * snapSpeed;
|
||||
const double newPos = oldPos + velocity * elapsedSeconds;
|
||||
|
||||
return isStopped (newPos) ? targetSnapPosition : newPos;
|
||||
}
|
||||
|
||||
/** Called by the AnimatedPosition class to check whether the object
|
||||
is now stationary.
|
||||
*/
|
||||
bool isStopped (double position) const noexcept
|
||||
{
|
||||
return std::abs (targetSnapPosition - position) < 0.001;
|
||||
}
|
||||
|
||||
private:
|
||||
double targetSnapPosition;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_ANIMATEDPOSITIONBEHAVIOURS_H_INCLUDED
|
||||
|
|
@ -0,0 +1,328 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
class ComponentAnimator::AnimationTask
|
||||
{
|
||||
public:
|
||||
AnimationTask (Component* c) noexcept : component (c) {}
|
||||
|
||||
void reset (const Rectangle<int>& finalBounds,
|
||||
float finalAlpha,
|
||||
int millisecondsToSpendMoving,
|
||||
bool useProxyComponent,
|
||||
double startSpd, double endSpd)
|
||||
{
|
||||
msElapsed = 0;
|
||||
msTotal = jmax (1, millisecondsToSpendMoving);
|
||||
lastProgress = 0;
|
||||
destination = finalBounds;
|
||||
destAlpha = finalAlpha;
|
||||
|
||||
isMoving = (finalBounds != component->getBounds());
|
||||
isChangingAlpha = (finalAlpha != component->getAlpha());
|
||||
|
||||
left = component->getX();
|
||||
top = component->getY();
|
||||
right = component->getRight();
|
||||
bottom = component->getBottom();
|
||||
alpha = component->getAlpha();
|
||||
|
||||
const double invTotalDistance = 4.0 / (startSpd + endSpd + 2.0);
|
||||
startSpeed = jmax (0.0, startSpd * invTotalDistance);
|
||||
midSpeed = invTotalDistance;
|
||||
endSpeed = jmax (0.0, endSpd * invTotalDistance);
|
||||
|
||||
if (useProxyComponent)
|
||||
proxy = new ProxyComponent (*component);
|
||||
else
|
||||
proxy = nullptr;
|
||||
|
||||
component->setVisible (! useProxyComponent);
|
||||
}
|
||||
|
||||
bool useTimeslice (const int elapsed)
|
||||
{
|
||||
if (Component* const c = proxy != nullptr ? static_cast<Component*> (proxy)
|
||||
: static_cast<Component*> (component))
|
||||
{
|
||||
msElapsed += elapsed;
|
||||
double newProgress = msElapsed / (double) msTotal;
|
||||
|
||||
if (newProgress >= 0 && newProgress < 1.0)
|
||||
{
|
||||
newProgress = timeToDistance (newProgress);
|
||||
const double delta = (newProgress - lastProgress) / (1.0 - lastProgress);
|
||||
jassert (newProgress >= lastProgress);
|
||||
lastProgress = newProgress;
|
||||
|
||||
if (delta < 1.0)
|
||||
{
|
||||
bool stillBusy = false;
|
||||
|
||||
if (isMoving)
|
||||
{
|
||||
left += (destination.getX() - left) * delta;
|
||||
top += (destination.getY() - top) * delta;
|
||||
right += (destination.getRight() - right) * delta;
|
||||
bottom += (destination.getBottom() - bottom) * delta;
|
||||
|
||||
const Rectangle<int> newBounds (roundToInt (left),
|
||||
roundToInt (top),
|
||||
roundToInt (right - left),
|
||||
roundToInt (bottom - top));
|
||||
|
||||
if (newBounds != destination)
|
||||
{
|
||||
c->setBounds (newBounds);
|
||||
stillBusy = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isChangingAlpha)
|
||||
{
|
||||
alpha += (destAlpha - alpha) * delta;
|
||||
c->setAlpha ((float) alpha);
|
||||
stillBusy = true;
|
||||
}
|
||||
|
||||
if (stillBusy)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moveToFinalDestination();
|
||||
return false;
|
||||
}
|
||||
|
||||
void moveToFinalDestination()
|
||||
{
|
||||
if (component != nullptr)
|
||||
{
|
||||
component->setAlpha ((float) destAlpha);
|
||||
component->setBounds (destination);
|
||||
|
||||
if (proxy != nullptr)
|
||||
component->setVisible (destAlpha > 0);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class ProxyComponent : public Component
|
||||
{
|
||||
public:
|
||||
ProxyComponent (Component& c)
|
||||
{
|
||||
setWantsKeyboardFocus (false);
|
||||
setBounds (c.getBounds());
|
||||
setTransform (c.getTransform());
|
||||
setAlpha (c.getAlpha());
|
||||
setInterceptsMouseClicks (false, false);
|
||||
|
||||
if (Component* const parent = c.getParentComponent())
|
||||
parent->addAndMakeVisible (this);
|
||||
else if (c.isOnDesktop() && c.getPeer() != nullptr)
|
||||
addToDesktop (c.getPeer()->getStyleFlags() | ComponentPeer::windowIgnoresKeyPresses);
|
||||
else
|
||||
jassertfalse; // seem to be trying to animate a component that's not visible..
|
||||
|
||||
const float scale = (float) Desktop::getInstance().getDisplays()
|
||||
.getDisplayContaining (getScreenBounds().getCentre()).scale;
|
||||
|
||||
image = c.createComponentSnapshot (c.getLocalBounds(), false, scale);
|
||||
|
||||
setVisible (true);
|
||||
toBehind (&c);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.setOpacity (1.0f);
|
||||
g.drawImageTransformed (image, AffineTransform::scale (getWidth() / (float) image.getWidth(),
|
||||
getHeight() / (float) image.getHeight()), false);
|
||||
}
|
||||
|
||||
private:
|
||||
Image image;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProxyComponent)
|
||||
};
|
||||
|
||||
WeakReference<Component> component;
|
||||
ScopedPointer<Component> proxy;
|
||||
|
||||
Rectangle<int> destination;
|
||||
double destAlpha;
|
||||
|
||||
int msElapsed, msTotal;
|
||||
double startSpeed, midSpeed, endSpeed, lastProgress;
|
||||
double left, top, right, bottom, alpha;
|
||||
bool isMoving, isChangingAlpha;
|
||||
|
||||
private:
|
||||
double timeToDistance (const double time) const noexcept
|
||||
{
|
||||
return (time < 0.5) ? time * (startSpeed + time * (midSpeed - startSpeed))
|
||||
: 0.5 * (startSpeed + 0.5 * (midSpeed - startSpeed))
|
||||
+ (time - 0.5) * (midSpeed + (time - 0.5) * (endSpeed - midSpeed));
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ComponentAnimator::ComponentAnimator() : lastTime (0) {}
|
||||
ComponentAnimator::~ComponentAnimator() {}
|
||||
|
||||
//==============================================================================
|
||||
ComponentAnimator::AnimationTask* ComponentAnimator::findTaskFor (Component* const component) const noexcept
|
||||
{
|
||||
for (int i = tasks.size(); --i >= 0;)
|
||||
if (component == tasks.getUnchecked(i)->component.get())
|
||||
return tasks.getUnchecked(i);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ComponentAnimator::animateComponent (Component* const component,
|
||||
const Rectangle<int>& finalBounds,
|
||||
const float finalAlpha,
|
||||
const int millisecondsToSpendMoving,
|
||||
const bool useProxyComponent,
|
||||
const double startSpeed,
|
||||
const double endSpeed)
|
||||
{
|
||||
// the speeds must be 0 or greater!
|
||||
jassert (startSpeed >= 0 && endSpeed >= 0);
|
||||
|
||||
if (component != nullptr)
|
||||
{
|
||||
AnimationTask* at = findTaskFor (component);
|
||||
|
||||
if (at == nullptr)
|
||||
{
|
||||
at = new AnimationTask (component);
|
||||
tasks.add (at);
|
||||
sendChangeMessage();
|
||||
}
|
||||
|
||||
at->reset (finalBounds, finalAlpha, millisecondsToSpendMoving,
|
||||
useProxyComponent, startSpeed, endSpeed);
|
||||
|
||||
if (! isTimerRunning())
|
||||
{
|
||||
lastTime = Time::getMillisecondCounter();
|
||||
startTimer (1000 / 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentAnimator::fadeOut (Component* component, int millisecondsToTake)
|
||||
{
|
||||
if (component != nullptr)
|
||||
{
|
||||
if (component->isShowing() && millisecondsToTake > 0)
|
||||
animateComponent (component, component->getBounds(), 0.0f, millisecondsToTake, true, 1.0, 1.0);
|
||||
|
||||
component->setVisible (false);
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentAnimator::fadeIn (Component* component, int millisecondsToTake)
|
||||
{
|
||||
if (component != nullptr && ! (component->isVisible() && component->getAlpha() == 1.0f))
|
||||
{
|
||||
component->setAlpha (0.0f);
|
||||
component->setVisible (true);
|
||||
animateComponent (component, component->getBounds(), 1.0f, millisecondsToTake, false, 1.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentAnimator::cancelAllAnimations (const bool moveComponentsToTheirFinalPositions)
|
||||
{
|
||||
if (tasks.size() > 0)
|
||||
{
|
||||
if (moveComponentsToTheirFinalPositions)
|
||||
for (int i = tasks.size(); --i >= 0;)
|
||||
tasks.getUnchecked(i)->moveToFinalDestination();
|
||||
|
||||
tasks.clear();
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentAnimator::cancelAnimation (Component* const component,
|
||||
const bool moveComponentToItsFinalPosition)
|
||||
{
|
||||
if (AnimationTask* const at = findTaskFor (component))
|
||||
{
|
||||
if (moveComponentToItsFinalPosition)
|
||||
at->moveToFinalDestination();
|
||||
|
||||
tasks.removeObject (at);
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle<int> ComponentAnimator::getComponentDestination (Component* const component)
|
||||
{
|
||||
jassert (component != nullptr);
|
||||
|
||||
if (AnimationTask* const at = findTaskFor (component))
|
||||
return at->destination;
|
||||
|
||||
return component->getBounds();
|
||||
}
|
||||
|
||||
bool ComponentAnimator::isAnimating (Component* component) const noexcept
|
||||
{
|
||||
return findTaskFor (component) != nullptr;
|
||||
}
|
||||
|
||||
bool ComponentAnimator::isAnimating() const noexcept
|
||||
{
|
||||
return tasks.size() != 0;
|
||||
}
|
||||
|
||||
void ComponentAnimator::timerCallback()
|
||||
{
|
||||
const uint32 timeNow = Time::getMillisecondCounter();
|
||||
|
||||
if (lastTime == 0 || lastTime == timeNow)
|
||||
lastTime = timeNow;
|
||||
|
||||
const int elapsed = (int) (timeNow - lastTime);
|
||||
|
||||
for (int i = tasks.size(); --i >= 0;)
|
||||
{
|
||||
if (! tasks.getUnchecked(i)->useTimeslice (elapsed))
|
||||
{
|
||||
tasks.remove (i);
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
lastTime = timeNow;
|
||||
|
||||
if (tasks.size() == 0)
|
||||
stopTimer();
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_COMPONENTANIMATOR_H_INCLUDED
|
||||
#define JUCE_COMPONENTANIMATOR_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Animates a set of components, moving them to a new position and/or fading their
|
||||
alpha levels.
|
||||
|
||||
To animate a component, create a ComponentAnimator instance or (preferably) use the
|
||||
global animator object provided by Desktop::getAnimator(), and call its animateComponent()
|
||||
method to commence the movement.
|
||||
|
||||
If you're using your own ComponentAnimator instance, you'll need to make sure it isn't
|
||||
deleted before it finishes moving the components, or they'll be abandoned before reaching their
|
||||
destinations.
|
||||
|
||||
It's ok to delete components while they're being animated - the animator will detect this
|
||||
and safely stop using them.
|
||||
|
||||
The class is a ChangeBroadcaster and sends a notification when any components
|
||||
start or finish being animated.
|
||||
|
||||
@see Desktop::getAnimator
|
||||
*/
|
||||
class JUCE_API ComponentAnimator : public ChangeBroadcaster,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a ComponentAnimator. */
|
||||
ComponentAnimator();
|
||||
|
||||
/** Destructor. */
|
||||
~ComponentAnimator();
|
||||
|
||||
//==============================================================================
|
||||
/** Starts a component moving from its current position to a specified position.
|
||||
|
||||
If the component is already in the middle of an animation, that will be abandoned,
|
||||
and a new animation will begin, moving the component from its current location.
|
||||
|
||||
The start and end speed parameters let you apply some acceleration to the component's
|
||||
movement.
|
||||
|
||||
@param component the component to move
|
||||
@param finalBounds the destination bounds to which the component should move. To leave the
|
||||
component in the same place, just pass component->getBounds() for this value
|
||||
@param finalAlpha the alpha value that the component should have at the end of the animation
|
||||
@param animationDurationMilliseconds how long the animation should last, in milliseconds
|
||||
@param useProxyComponent if true, this means the component should be replaced by an internally
|
||||
managed temporary component which is a snapshot of the original component.
|
||||
This avoids the component having to paint itself as it moves, so may
|
||||
be more efficient. This option also allows you to delete the original
|
||||
component immediately after starting the animation, because the animation
|
||||
can proceed without it. If you use a proxy, the original component will be
|
||||
made invisible by this call, and then will become visible again at the end
|
||||
of the animation. It'll also mean that the proxy component will be temporarily
|
||||
added to the component's parent, so avoid it if this might confuse the parent
|
||||
component, or if there's a chance the parent might decide to delete its children.
|
||||
@param startSpeed a value to indicate the relative start speed of the animation. If this is 0,
|
||||
the component will start by accelerating from rest; higher values mean that it
|
||||
will have an initial speed greater than zero. If the value if greater than 1, it
|
||||
will decelerate towards the middle of its journey. To move the component at a
|
||||
constant rate for its entire animation, set both the start and end speeds to 1.0
|
||||
@param endSpeed a relative speed at which the component should be moving when the animation finishes.
|
||||
If this is 0, the component will decelerate to a standstill at its final position;
|
||||
higher values mean the component will still be moving when it stops. To move the component
|
||||
at a constant rate for its entire animation, set both the start and end speeds to 1.0
|
||||
*/
|
||||
void animateComponent (Component* component,
|
||||
const Rectangle<int>& finalBounds,
|
||||
float finalAlpha,
|
||||
int animationDurationMilliseconds,
|
||||
bool useProxyComponent,
|
||||
double startSpeed,
|
||||
double endSpeed);
|
||||
|
||||
/** Begins a fade-out of this components alpha level.
|
||||
This is a quick way of invoking animateComponent() with a target alpha value of 0.0f, using
|
||||
a proxy. You're safe to delete the component after calling this method, and this won't
|
||||
interfere with the animation's progress.
|
||||
*/
|
||||
void fadeOut (Component* component, int millisecondsToTake);
|
||||
|
||||
/** Begins a fade-in of a component.
|
||||
This is a quick way of invoking animateComponent() with a target alpha value of 1.0f.
|
||||
*/
|
||||
void fadeIn (Component* component, int millisecondsToTake);
|
||||
|
||||
/** Stops a component if it's currently being animated.
|
||||
|
||||
If moveComponentToItsFinalPosition is true, then the component will
|
||||
be immediately moved to its destination position and size. If false, it will be
|
||||
left in whatever location it currently occupies.
|
||||
*/
|
||||
void cancelAnimation (Component* component,
|
||||
bool moveComponentToItsFinalPosition);
|
||||
|
||||
/** Clears all of the active animations.
|
||||
|
||||
If moveComponentsToTheirFinalPositions is true, all the components will
|
||||
be immediately set to their final positions. If false, they will be
|
||||
left in whatever locations they currently occupy.
|
||||
*/
|
||||
void cancelAllAnimations (bool moveComponentsToTheirFinalPositions);
|
||||
|
||||
/** Returns the destination position for a component.
|
||||
|
||||
If the component is being animated, this will return the target position that
|
||||
was specified when animateComponent() was called.
|
||||
|
||||
If the specified component isn't currently being animated, this method will just
|
||||
return its current position.
|
||||
*/
|
||||
Rectangle<int> getComponentDestination (Component* component);
|
||||
|
||||
/** Returns true if the specified component is currently being animated. */
|
||||
bool isAnimating (Component* component) const noexcept;
|
||||
|
||||
/** Returns true if any components are currently being animated. */
|
||||
bool isAnimating() const noexcept;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class AnimationTask;
|
||||
OwnedArray<AnimationTask> tasks;
|
||||
uint32 lastTime;
|
||||
|
||||
AnimationTask* findTaskFor (Component*) const noexcept;
|
||||
void timerCallback();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentAnimator)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_COMPONENTANIMATOR_H_INCLUDED
|
||||
|
|
@ -0,0 +1,301 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
ComponentBoundsConstrainer::ComponentBoundsConstrainer() noexcept
|
||||
: minW (0), maxW (0x3fffffff),
|
||||
minH (0), maxH (0x3fffffff),
|
||||
minOffTop (0),
|
||||
minOffLeft (0),
|
||||
minOffBottom (0),
|
||||
minOffRight (0),
|
||||
aspectRatio (0.0)
|
||||
{
|
||||
}
|
||||
|
||||
ComponentBoundsConstrainer::~ComponentBoundsConstrainer()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComponentBoundsConstrainer::setMinimumWidth (const int minimumWidth) noexcept { minW = minimumWidth; }
|
||||
void ComponentBoundsConstrainer::setMaximumWidth (const int maximumWidth) noexcept { maxW = maximumWidth; }
|
||||
void ComponentBoundsConstrainer::setMinimumHeight (const int minimumHeight) noexcept { minH = minimumHeight; }
|
||||
void ComponentBoundsConstrainer::setMaximumHeight (const int maximumHeight) noexcept { maxH = maximumHeight; }
|
||||
|
||||
void ComponentBoundsConstrainer::setMinimumSize (const int minimumWidth, const int minimumHeight) noexcept
|
||||
{
|
||||
jassert (maxW >= minimumWidth);
|
||||
jassert (maxH >= minimumHeight);
|
||||
jassert (minimumWidth > 0 && minimumHeight > 0);
|
||||
|
||||
minW = minimumWidth;
|
||||
minH = minimumHeight;
|
||||
|
||||
if (minW > maxW) maxW = minW;
|
||||
if (minH > maxH) maxH = minH;
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::setMaximumSize (const int maximumWidth, const int maximumHeight) noexcept
|
||||
{
|
||||
jassert (maximumWidth >= minW);
|
||||
jassert (maximumHeight >= minH);
|
||||
jassert (maximumWidth > 0 && maximumHeight > 0);
|
||||
|
||||
maxW = jmax (minW, maximumWidth);
|
||||
maxH = jmax (minH, maximumHeight);
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::setSizeLimits (const int minimumWidth,
|
||||
const int minimumHeight,
|
||||
const int maximumWidth,
|
||||
const int maximumHeight) noexcept
|
||||
{
|
||||
jassert (maximumWidth >= minimumWidth);
|
||||
jassert (maximumHeight >= minimumHeight);
|
||||
jassert (maximumWidth > 0 && maximumHeight > 0);
|
||||
jassert (minimumWidth > 0 && minimumHeight > 0);
|
||||
|
||||
minW = jmax (0, minimumWidth);
|
||||
minH = jmax (0, minimumHeight);
|
||||
maxW = jmax (minW, maximumWidth);
|
||||
maxH = jmax (minH, maximumHeight);
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::setMinimumOnscreenAmounts (const int minimumWhenOffTheTop,
|
||||
const int minimumWhenOffTheLeft,
|
||||
const int minimumWhenOffTheBottom,
|
||||
const int minimumWhenOffTheRight) noexcept
|
||||
{
|
||||
minOffTop = minimumWhenOffTheTop;
|
||||
minOffLeft = minimumWhenOffTheLeft;
|
||||
minOffBottom = minimumWhenOffTheBottom;
|
||||
minOffRight = minimumWhenOffTheRight;
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::setFixedAspectRatio (const double widthOverHeight) noexcept
|
||||
{
|
||||
aspectRatio = jmax (0.0, widthOverHeight);
|
||||
}
|
||||
|
||||
double ComponentBoundsConstrainer::getFixedAspectRatio() const noexcept
|
||||
{
|
||||
return aspectRatio;
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::setBoundsForComponent (Component* const component,
|
||||
const Rectangle<int>& targetBounds,
|
||||
const bool isStretchingTop,
|
||||
const bool isStretchingLeft,
|
||||
const bool isStretchingBottom,
|
||||
const bool isStretchingRight)
|
||||
{
|
||||
jassert (component != nullptr);
|
||||
|
||||
Rectangle<int> limits, bounds (targetBounds);
|
||||
BorderSize<int> border;
|
||||
|
||||
if (Component* const parent = component->getParentComponent())
|
||||
{
|
||||
limits.setSize (parent->getWidth(), parent->getHeight());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ComponentPeer* const peer = component->getPeer())
|
||||
border = peer->getFrameSize();
|
||||
|
||||
limits = Desktop::getInstance().getDisplays().getDisplayContaining (bounds.getCentre()).userArea;
|
||||
}
|
||||
|
||||
border.addTo (bounds);
|
||||
|
||||
checkBounds (bounds,
|
||||
border.addedTo (component->getBounds()), limits,
|
||||
isStretchingTop, isStretchingLeft,
|
||||
isStretchingBottom, isStretchingRight);
|
||||
|
||||
border.subtractFrom (bounds);
|
||||
|
||||
applyBoundsToComponent (component, bounds);
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::checkComponentBounds (Component* component)
|
||||
{
|
||||
setBoundsForComponent (component, component->getBounds(),
|
||||
false, false, false, false);
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::applyBoundsToComponent (Component* component,
|
||||
const Rectangle<int>& bounds)
|
||||
{
|
||||
if (Component::Positioner* const positioner = component->getPositioner())
|
||||
positioner->applyNewBounds (bounds);
|
||||
else
|
||||
component->setBounds (bounds);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComponentBoundsConstrainer::resizeStart()
|
||||
{
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::resizeEnd()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComponentBoundsConstrainer::checkBounds (Rectangle<int>& bounds,
|
||||
const Rectangle<int>& old,
|
||||
const Rectangle<int>& limits,
|
||||
const bool isStretchingTop,
|
||||
const bool isStretchingLeft,
|
||||
const bool isStretchingBottom,
|
||||
const bool isStretchingRight)
|
||||
{
|
||||
if (isStretchingLeft)
|
||||
bounds.setLeft (jlimit (old.getRight() - maxW, old.getRight() - minW, bounds.getX()));
|
||||
else
|
||||
bounds.setWidth (jlimit (minW, maxW, bounds.getWidth()));
|
||||
|
||||
if (isStretchingTop)
|
||||
bounds.setTop (jlimit (old.getBottom() - maxH, old.getBottom() - minH, bounds.getY()));
|
||||
else
|
||||
bounds.setHeight (jlimit (minH, maxH, bounds.getHeight()));
|
||||
|
||||
if (bounds.isEmpty())
|
||||
return;
|
||||
|
||||
if (minOffTop > 0)
|
||||
{
|
||||
const int limit = limits.getY() + jmin (minOffTop - bounds.getHeight(), 0);
|
||||
|
||||
if (bounds.getY() < limit)
|
||||
{
|
||||
if (isStretchingTop)
|
||||
bounds.setTop (limits.getY());
|
||||
else
|
||||
bounds.setY (limit);
|
||||
}
|
||||
}
|
||||
|
||||
if (minOffLeft > 0)
|
||||
{
|
||||
const int limit = limits.getX() + jmin (minOffLeft - bounds.getWidth(), 0);
|
||||
|
||||
if (bounds.getX() < limit)
|
||||
{
|
||||
if (isStretchingLeft)
|
||||
bounds.setLeft (limits.getX());
|
||||
else
|
||||
bounds.setX (limit);
|
||||
}
|
||||
}
|
||||
|
||||
if (minOffBottom > 0)
|
||||
{
|
||||
const int limit = limits.getBottom() - jmin (minOffBottom, bounds.getHeight());
|
||||
|
||||
if (bounds.getY() > limit)
|
||||
{
|
||||
if (isStretchingBottom)
|
||||
bounds.setBottom (limits.getBottom());
|
||||
else
|
||||
bounds.setY (limit);
|
||||
}
|
||||
}
|
||||
|
||||
if (minOffRight > 0)
|
||||
{
|
||||
const int limit = limits.getRight() - jmin (minOffRight, bounds.getWidth());
|
||||
|
||||
if (bounds.getX() > limit)
|
||||
{
|
||||
if (isStretchingRight)
|
||||
bounds.setRight (limits.getRight());
|
||||
else
|
||||
bounds.setX (limit);
|
||||
}
|
||||
}
|
||||
|
||||
// constrain the aspect ratio if one has been specified..
|
||||
if (aspectRatio > 0.0)
|
||||
{
|
||||
bool adjustWidth;
|
||||
|
||||
if ((isStretchingTop || isStretchingBottom) && ! (isStretchingLeft || isStretchingRight))
|
||||
{
|
||||
adjustWidth = true;
|
||||
}
|
||||
else if ((isStretchingLeft || isStretchingRight) && ! (isStretchingTop || isStretchingBottom))
|
||||
{
|
||||
adjustWidth = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
const double oldRatio = (old.getHeight() > 0) ? std::abs (old.getWidth() / (double) old.getHeight()) : 0.0;
|
||||
const double newRatio = std::abs (bounds.getWidth() / (double) bounds.getHeight());
|
||||
|
||||
adjustWidth = (oldRatio > newRatio);
|
||||
}
|
||||
|
||||
if (adjustWidth)
|
||||
{
|
||||
bounds.setWidth (roundToInt (bounds.getHeight() * aspectRatio));
|
||||
|
||||
if (bounds.getWidth() > maxW || bounds.getWidth() < minW)
|
||||
{
|
||||
bounds.setWidth (jlimit (minW, maxW, bounds.getWidth()));
|
||||
bounds.setHeight (roundToInt (bounds.getWidth() / aspectRatio));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bounds.setHeight (roundToInt (bounds.getWidth() / aspectRatio));
|
||||
|
||||
if (bounds.getHeight() > maxH || bounds.getHeight() < minH)
|
||||
{
|
||||
bounds.setHeight (jlimit (minH, maxH, bounds.getHeight()));
|
||||
bounds.setWidth (roundToInt (bounds.getHeight() * aspectRatio));
|
||||
}
|
||||
}
|
||||
|
||||
if ((isStretchingTop || isStretchingBottom) && ! (isStretchingLeft || isStretchingRight))
|
||||
{
|
||||
bounds.setX (old.getX() + (old.getWidth() - bounds.getWidth()) / 2);
|
||||
}
|
||||
else if ((isStretchingLeft || isStretchingRight) && ! (isStretchingTop || isStretchingBottom))
|
||||
{
|
||||
bounds.setY (old.getY() + (old.getHeight() - bounds.getHeight()) / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isStretchingLeft)
|
||||
bounds.setX (old.getRight() - bounds.getWidth());
|
||||
|
||||
if (isStretchingTop)
|
||||
bounds.setY (old.getBottom() - bounds.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
jassert (! bounds.isEmpty());
|
||||
}
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_COMPONENTBOUNDSCONSTRAINER_H_INCLUDED
|
||||
#define JUCE_COMPONENTBOUNDSCONSTRAINER_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A class that imposes restrictions on a Component's size or position.
|
||||
|
||||
This is used by classes such as ResizableCornerComponent,
|
||||
ResizableBorderComponent and ResizableWindow.
|
||||
|
||||
The base class can impose some basic size and position limits, but you can
|
||||
also subclass this for custom uses.
|
||||
|
||||
@see ResizableCornerComponent, ResizableBorderComponent, ResizableWindow
|
||||
*/
|
||||
class JUCE_API ComponentBoundsConstrainer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** When first created, the object will not impose any restrictions on the components. */
|
||||
ComponentBoundsConstrainer() noexcept;
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~ComponentBoundsConstrainer();
|
||||
|
||||
//==============================================================================
|
||||
/** Imposes a minimum width limit. */
|
||||
void setMinimumWidth (int minimumWidth) noexcept;
|
||||
|
||||
/** Returns the current minimum width. */
|
||||
int getMinimumWidth() const noexcept { return minW; }
|
||||
|
||||
/** Imposes a maximum width limit. */
|
||||
void setMaximumWidth (int maximumWidth) noexcept;
|
||||
|
||||
/** Returns the current maximum width. */
|
||||
int getMaximumWidth() const noexcept { return maxW; }
|
||||
|
||||
/** Imposes a minimum height limit. */
|
||||
void setMinimumHeight (int minimumHeight) noexcept;
|
||||
|
||||
/** Returns the current minimum height. */
|
||||
int getMinimumHeight() const noexcept { return minH; }
|
||||
|
||||
/** Imposes a maximum height limit. */
|
||||
void setMaximumHeight (int maximumHeight) noexcept;
|
||||
|
||||
/** Returns the current maximum height. */
|
||||
int getMaximumHeight() const noexcept { return maxH; }
|
||||
|
||||
/** Imposes a minimum width and height limit. */
|
||||
void setMinimumSize (int minimumWidth,
|
||||
int minimumHeight) noexcept;
|
||||
|
||||
/** Imposes a maximum width and height limit. */
|
||||
void setMaximumSize (int maximumWidth,
|
||||
int maximumHeight) noexcept;
|
||||
|
||||
/** Set all the maximum and minimum dimensions. */
|
||||
void setSizeLimits (int minimumWidth,
|
||||
int minimumHeight,
|
||||
int maximumWidth,
|
||||
int maximumHeight) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the amount by which the component is allowed to go off-screen.
|
||||
|
||||
The values indicate how many pixels must remain on-screen when dragged off
|
||||
one of its parent's edges, so e.g. if minimumWhenOffTheTop is set to 10, then
|
||||
when the component goes off the top of the screen, its y-position will be
|
||||
clipped so that there are always at least 10 pixels on-screen. In other words,
|
||||
the lowest y-position it can take would be (10 - the component's height).
|
||||
|
||||
If you pass 0 or less for one of these amounts, the component is allowed
|
||||
to move beyond that edge completely, with no restrictions at all.
|
||||
|
||||
If you pass a very large number (i.e. larger that the dimensions of the
|
||||
component itself), then the component won't be allowed to overlap that
|
||||
edge at all. So e.g. setting minimumWhenOffTheLeft to 0xffffff will mean that
|
||||
the component will bump into the left side of the screen and go no further.
|
||||
*/
|
||||
void setMinimumOnscreenAmounts (int minimumWhenOffTheTop,
|
||||
int minimumWhenOffTheLeft,
|
||||
int minimumWhenOffTheBottom,
|
||||
int minimumWhenOffTheRight) noexcept;
|
||||
|
||||
|
||||
/** Returns the minimum distance the bounds can be off-screen. @see setMinimumOnscreenAmounts */
|
||||
int getMinimumWhenOffTheTop() const noexcept { return minOffTop; }
|
||||
/** Returns the minimum distance the bounds can be off-screen. @see setMinimumOnscreenAmounts */
|
||||
int getMinimumWhenOffTheLeft() const noexcept { return minOffLeft; }
|
||||
/** Returns the minimum distance the bounds can be off-screen. @see setMinimumOnscreenAmounts */
|
||||
int getMinimumWhenOffTheBottom() const noexcept { return minOffBottom; }
|
||||
/** Returns the minimum distance the bounds can be off-screen. @see setMinimumOnscreenAmounts */
|
||||
int getMinimumWhenOffTheRight() const noexcept { return minOffRight; }
|
||||
|
||||
//==============================================================================
|
||||
/** Specifies a width-to-height ratio that the resizer should always maintain.
|
||||
|
||||
If the value is 0, no aspect ratio is enforced. If it's non-zero, the width
|
||||
will always be maintained as this multiple of the height.
|
||||
|
||||
@see setResizeLimits
|
||||
*/
|
||||
void setFixedAspectRatio (double widthOverHeight) noexcept;
|
||||
|
||||
/** Returns the aspect ratio that was set with setFixedAspectRatio().
|
||||
|
||||
If no aspect ratio is being enforced, this will return 0.
|
||||
*/
|
||||
double getFixedAspectRatio() const noexcept;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** This callback changes the given coordinates to impose whatever the current
|
||||
constraints are set to be.
|
||||
|
||||
@param bounds the target position that should be examined and adjusted
|
||||
@param previousBounds the component's current size
|
||||
@param limits the region in which the component can be positioned
|
||||
@param isStretchingTop whether the top edge of the component is being resized
|
||||
@param isStretchingLeft whether the left edge of the component is being resized
|
||||
@param isStretchingBottom whether the bottom edge of the component is being resized
|
||||
@param isStretchingRight whether the right edge of the component is being resized
|
||||
*/
|
||||
virtual void checkBounds (Rectangle<int>& bounds,
|
||||
const Rectangle<int>& previousBounds,
|
||||
const Rectangle<int>& limits,
|
||||
bool isStretchingTop,
|
||||
bool isStretchingLeft,
|
||||
bool isStretchingBottom,
|
||||
bool isStretchingRight);
|
||||
|
||||
/** This callback happens when the resizer is about to start dragging. */
|
||||
virtual void resizeStart();
|
||||
|
||||
/** This callback happens when the resizer has finished dragging. */
|
||||
virtual void resizeEnd();
|
||||
|
||||
/** Checks the given bounds, and then sets the component to the corrected size. */
|
||||
void setBoundsForComponent (Component* component,
|
||||
const Rectangle<int>& bounds,
|
||||
bool isStretchingTop,
|
||||
bool isStretchingLeft,
|
||||
bool isStretchingBottom,
|
||||
bool isStretchingRight);
|
||||
|
||||
/** Performs a check on the current size of a component, and moves or resizes
|
||||
it if it fails the constraints.
|
||||
*/
|
||||
void checkComponentBounds (Component* component);
|
||||
|
||||
/** Called by setBoundsForComponent() to apply a new constrained size to a
|
||||
component.
|
||||
|
||||
By default this just calls setBounds(), but is virtual in case it's needed for
|
||||
extremely cunning purposes.
|
||||
*/
|
||||
virtual void applyBoundsToComponent (Component* component,
|
||||
const Rectangle<int>& bounds);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
int minW, maxW, minH, maxH;
|
||||
int minOffTop, minOffLeft, minOffBottom, minOffRight;
|
||||
double aspectRatio;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentBoundsConstrainer)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_COMPONENTBOUNDSCONSTRAINER_H_INCLUDED
|
||||
|
|
@ -0,0 +1,280 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace ComponentBuilderHelpers
|
||||
{
|
||||
static String getStateId (const ValueTree& state)
|
||||
{
|
||||
return state [ComponentBuilder::idProperty].toString();
|
||||
}
|
||||
|
||||
static Component* removeComponentWithID (OwnedArray<Component>& components, const String& compId)
|
||||
{
|
||||
jassert (compId.isNotEmpty());
|
||||
|
||||
for (int i = components.size(); --i >= 0;)
|
||||
{
|
||||
Component* const c = components.getUnchecked (i);
|
||||
|
||||
if (c->getComponentID() == compId)
|
||||
return components.removeAndReturn (i);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static Component* findComponentWithID (Component& c, const String& compId)
|
||||
{
|
||||
jassert (compId.isNotEmpty());
|
||||
if (c.getComponentID() == compId)
|
||||
return &c;
|
||||
|
||||
for (int i = c.getNumChildComponents(); --i >= 0;)
|
||||
if (Component* const child = findComponentWithID (*c.getChildComponent (i), compId))
|
||||
return child;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static Component* createNewComponent (ComponentBuilder::TypeHandler& type,
|
||||
const ValueTree& state, Component* parent)
|
||||
{
|
||||
Component* const c = type.addNewComponentFromState (state, parent);
|
||||
jassert (c != nullptr && c->getParentComponent() == parent);
|
||||
c->setComponentID (getStateId (state));
|
||||
return c;
|
||||
}
|
||||
|
||||
static void updateComponent (ComponentBuilder& builder, const ValueTree& state)
|
||||
{
|
||||
if (Component* topLevelComp = builder.getManagedComponent())
|
||||
{
|
||||
ComponentBuilder::TypeHandler* const type = builder.getHandlerForState (state);
|
||||
const String uid (getStateId (state));
|
||||
|
||||
if (type == nullptr || uid.isEmpty())
|
||||
{
|
||||
// ..handle the case where a child of the actual state node has changed.
|
||||
if (state.getParent().isValid())
|
||||
updateComponent (builder, state.getParent());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Component* const changedComp = findComponentWithID (*topLevelComp, uid))
|
||||
type->updateComponentFromState (changedComp, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
const Identifier ComponentBuilder::idProperty ("id");
|
||||
|
||||
ComponentBuilder::ComponentBuilder()
|
||||
: imageProvider (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
ComponentBuilder::ComponentBuilder (const ValueTree& state_)
|
||||
: state (state_), imageProvider (nullptr)
|
||||
{
|
||||
state.addListener (this);
|
||||
}
|
||||
|
||||
ComponentBuilder::~ComponentBuilder()
|
||||
{
|
||||
state.removeListener (this);
|
||||
|
||||
#if JUCE_DEBUG
|
||||
// Don't delete the managed component!! The builder owns that component, and will delete
|
||||
// it automatically when it gets deleted.
|
||||
jassert (componentRef.get() == static_cast <Component*> (component));
|
||||
#endif
|
||||
}
|
||||
|
||||
Component* ComponentBuilder::getManagedComponent()
|
||||
{
|
||||
if (component == nullptr)
|
||||
{
|
||||
component = createComponent();
|
||||
|
||||
#if JUCE_DEBUG
|
||||
componentRef = component;
|
||||
#endif
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
Component* ComponentBuilder::createComponent()
|
||||
{
|
||||
jassert (types.size() > 0); // You need to register all the necessary types before you can load a component!
|
||||
|
||||
if (TypeHandler* const type = getHandlerForState (state))
|
||||
return ComponentBuilderHelpers::createNewComponent (*type, state, nullptr);
|
||||
|
||||
jassertfalse; // trying to create a component from an unknown type of ValueTree
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ComponentBuilder::registerTypeHandler (ComponentBuilder::TypeHandler* const type)
|
||||
{
|
||||
jassert (type != nullptr);
|
||||
|
||||
// Don't try to move your types around! Once a type has been added to a builder, the
|
||||
// builder owns it, and you should leave it alone!
|
||||
jassert (type->builder == nullptr);
|
||||
|
||||
types.add (type);
|
||||
type->builder = this;
|
||||
}
|
||||
|
||||
ComponentBuilder::TypeHandler* ComponentBuilder::getHandlerForState (const ValueTree& s) const
|
||||
{
|
||||
const Identifier targetType (s.getType());
|
||||
|
||||
for (int i = 0; i < types.size(); ++i)
|
||||
{
|
||||
TypeHandler* const t = types.getUnchecked(i);
|
||||
|
||||
if (t->type == targetType)
|
||||
return t;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int ComponentBuilder::getNumHandlers() const noexcept
|
||||
{
|
||||
return types.size();
|
||||
}
|
||||
|
||||
ComponentBuilder::TypeHandler* ComponentBuilder::getHandler (const int index) const noexcept
|
||||
{
|
||||
return types [index];
|
||||
}
|
||||
|
||||
void ComponentBuilder::registerStandardComponentTypes()
|
||||
{
|
||||
Drawable::registerDrawableTypeHandlers (*this);
|
||||
}
|
||||
|
||||
void ComponentBuilder::setImageProvider (ImageProvider* newImageProvider) noexcept
|
||||
{
|
||||
imageProvider = newImageProvider;
|
||||
}
|
||||
|
||||
ComponentBuilder::ImageProvider* ComponentBuilder::getImageProvider() const noexcept
|
||||
{
|
||||
return imageProvider;
|
||||
}
|
||||
|
||||
void ComponentBuilder::valueTreePropertyChanged (ValueTree& tree, const Identifier&)
|
||||
{
|
||||
ComponentBuilderHelpers::updateComponent (*this, tree);
|
||||
}
|
||||
|
||||
void ComponentBuilder::valueTreeChildAdded (ValueTree& tree, ValueTree&)
|
||||
{
|
||||
ComponentBuilderHelpers::updateComponent (*this, tree);
|
||||
}
|
||||
|
||||
void ComponentBuilder::valueTreeChildRemoved (ValueTree& tree, ValueTree&)
|
||||
{
|
||||
ComponentBuilderHelpers::updateComponent (*this, tree);
|
||||
}
|
||||
|
||||
void ComponentBuilder::valueTreeChildOrderChanged (ValueTree& tree)
|
||||
{
|
||||
ComponentBuilderHelpers::updateComponent (*this, tree);
|
||||
}
|
||||
|
||||
void ComponentBuilder::valueTreeParentChanged (ValueTree& tree)
|
||||
{
|
||||
ComponentBuilderHelpers::updateComponent (*this, tree);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ComponentBuilder::TypeHandler::TypeHandler (const Identifier& valueTreeType)
|
||||
: type (valueTreeType), builder (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
ComponentBuilder::TypeHandler::~TypeHandler()
|
||||
{
|
||||
}
|
||||
|
||||
ComponentBuilder* ComponentBuilder::TypeHandler::getBuilder() const noexcept
|
||||
{
|
||||
// A type handler needs to be registered with a ComponentBuilder before using it!
|
||||
jassert (builder != nullptr);
|
||||
return builder;
|
||||
}
|
||||
|
||||
void ComponentBuilder::updateChildComponents (Component& parent, const ValueTree& children)
|
||||
{
|
||||
using namespace ComponentBuilderHelpers;
|
||||
|
||||
const int numExistingChildComps = parent.getNumChildComponents();
|
||||
|
||||
Array<Component*> componentsInOrder;
|
||||
componentsInOrder.ensureStorageAllocated (numExistingChildComps);
|
||||
|
||||
{
|
||||
OwnedArray<Component> existingComponents;
|
||||
existingComponents.ensureStorageAllocated (numExistingChildComps);
|
||||
|
||||
for (int i = 0; i < numExistingChildComps; ++i)
|
||||
existingComponents.add (parent.getChildComponent (i));
|
||||
|
||||
const int newNumChildren = children.getNumChildren();
|
||||
for (int i = 0; i < newNumChildren; ++i)
|
||||
{
|
||||
const ValueTree childState (children.getChild (i));
|
||||
Component* c = removeComponentWithID (existingComponents, getStateId (childState));
|
||||
|
||||
if (c == nullptr)
|
||||
{
|
||||
if (TypeHandler* const type = getHandlerForState (childState))
|
||||
c = ComponentBuilderHelpers::createNewComponent (*type, childState, &parent);
|
||||
else
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
if (c != nullptr)
|
||||
componentsInOrder.add (c);
|
||||
}
|
||||
|
||||
// (remaining unused items in existingComponents get deleted here as it goes out of scope)
|
||||
}
|
||||
|
||||
// Make sure the z-order is correct..
|
||||
if (componentsInOrder.size() > 0)
|
||||
{
|
||||
componentsInOrder.getLast()->toFront (false);
|
||||
|
||||
for (int i = componentsInOrder.size() - 1; --i >= 0;)
|
||||
componentsInOrder.getUnchecked(i)->toBehind (componentsInOrder.getUnchecked (i + 1));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 JUCE_COMPONENTBUILDER_H_INCLUDED
|
||||
#define JUCE_COMPONENTBUILDER_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Loads and maintains a tree of Components from a ValueTree that represents them.
|
||||
|
||||
To allow the state of a tree of components to be saved as a ValueTree and re-loaded,
|
||||
this class lets you register a set of type-handlers for the different components that
|
||||
are involved, and then uses these types to re-create a set of components from its
|
||||
stored state.
|
||||
|
||||
Essentially, to use this, you need to create a ComponentBuilder with your ValueTree,
|
||||
then use registerTypeHandler() to give it a set of type handlers that can cope with
|
||||
all the items in your tree. Then you can call getComponent() to build the component.
|
||||
Once you've got the component you can either take it and delete the ComponentBuilder
|
||||
object, or if you keep the ComponentBuilder around, it'll monitor any changes in the
|
||||
ValueTree and automatically update the component to reflect these changes.
|
||||
*/
|
||||
class JUCE_API ComponentBuilder : private ValueTree::Listener
|
||||
{
|
||||
public:
|
||||
/** Creates a ComponentBuilder that will use the given state.
|
||||
Once you've created your builder, you should use registerTypeHandler() to register some
|
||||
type handlers for it, and then you can call createComponent() or getManagedComponent()
|
||||
to get the actual component.
|
||||
*/
|
||||
explicit ComponentBuilder (const ValueTree& state);
|
||||
|
||||
/** Creates a builder that doesn't have a state object. */
|
||||
ComponentBuilder();
|
||||
|
||||
/** Destructor. */
|
||||
~ComponentBuilder();
|
||||
|
||||
/** This is the ValueTree data object that the builder is working with. */
|
||||
ValueTree state;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the builder's component (creating it if necessary).
|
||||
|
||||
The first time that this method is called, the builder will attempt to create a component
|
||||
from the ValueTree, so you must have registered some suitable type handlers before calling
|
||||
this. If there's a problem and the component can't be created, this method returns nullptr.
|
||||
|
||||
The component that is returned is owned by this ComponentBuilder, so you can put it inside
|
||||
your own parent components, but don't delete it! The ComponentBuilder will delete it automatically
|
||||
when the builder is destroyed. If you want to get a component that you can delete yourself,
|
||||
call createComponent() instead.
|
||||
|
||||
The ComponentBuilder will update this component if any changes are made to the ValueTree, so if
|
||||
there's a chance that the tree might change, be careful not to keep any pointers to sub-components,
|
||||
as they may be changed or removed.
|
||||
*/
|
||||
Component* getManagedComponent();
|
||||
|
||||
/** Creates and returns a new instance of the component that the ValueTree represents.
|
||||
The caller is responsible for using and deleting the object that is returned. Unlike
|
||||
getManagedComponent(), the component that is returned will not be updated by the builder.
|
||||
*/
|
||||
Component* createComponent();
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
The class is a base class for objects that manage the loading of a type of component
|
||||
from a ValueTree.
|
||||
|
||||
To store and re-load a tree of components as a ValueTree, each component type must have
|
||||
a TypeHandler to represent it.
|
||||
|
||||
@see ComponentBuilder::registerTypeHandler(), Drawable::registerDrawableTypeHandlers()
|
||||
*/
|
||||
class JUCE_API TypeHandler
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a TypeHandler.
|
||||
The valueTreeType must be the type name of the ValueTrees that this handler can parse.
|
||||
*/
|
||||
explicit TypeHandler (const Identifier& valueTreeType);
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~TypeHandler();
|
||||
|
||||
/** Returns the type of the ValueTrees that this handler can parse. */
|
||||
const Identifier type;
|
||||
|
||||
/** Returns the builder that this type is registered with. */
|
||||
ComponentBuilder* getBuilder() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** This method must create a new component from the given state, add it to the specified
|
||||
parent component (which may be null), and return it.
|
||||
|
||||
The ValueTree will have been pre-checked to make sure that its type matches the type
|
||||
that this handler supports.
|
||||
|
||||
There's no need to set the new Component's ID to match that of the state - the builder
|
||||
will take care of that itself.
|
||||
*/
|
||||
virtual Component* addNewComponentFromState (const ValueTree& state, Component* parent) = 0;
|
||||
|
||||
/** This method must update an existing component from a new ValueTree state.
|
||||
|
||||
A component that has been created with addNewComponentFromState() may need to be updated
|
||||
if the ValueTree changes, so this method is used to do that. Your implementation must do
|
||||
whatever's necessary to update the component from the new state provided.
|
||||
|
||||
The ValueTree will have been pre-checked to make sure that its type matches the type
|
||||
that this handler supports, and the component will have been created by this type's
|
||||
addNewComponentFromState() method.
|
||||
*/
|
||||
virtual void updateComponentFromState (Component* component, const ValueTree& state) = 0;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
friend class ComponentBuilder;
|
||||
ComponentBuilder* builder;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TypeHandler)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a type handler that the builder can use when trying to load components.
|
||||
@see Drawable::registerDrawableTypeHandlers()
|
||||
*/
|
||||
void registerTypeHandler (TypeHandler* type);
|
||||
|
||||
/** Tries to find a registered type handler that can load a component from the given ValueTree. */
|
||||
TypeHandler* getHandlerForState (const ValueTree& state) const;
|
||||
|
||||
/** Returns the number of registered type handlers.
|
||||
@see getHandler, registerTypeHandler
|
||||
*/
|
||||
int getNumHandlers() const noexcept;
|
||||
|
||||
/** Returns one of the registered type handlers.
|
||||
@see getNumHandlers, registerTypeHandler
|
||||
*/
|
||||
TypeHandler* getHandler (int index) const noexcept;
|
||||
|
||||
/** Registers handlers for various standard juce components. */
|
||||
void registerStandardComponentTypes();
|
||||
|
||||
//=============================================================================
|
||||
/** This class is used when references to images need to be stored in ValueTrees.
|
||||
|
||||
An instance of an ImageProvider provides a mechanism for converting an Image to/from
|
||||
a reference, which may be a file, URL, ID string, or whatever system is appropriate in
|
||||
your app.
|
||||
|
||||
When you're loading components from a ValueTree that may need a way of loading images, you
|
||||
should call ComponentBuilder::setImageProvider() to supply a suitable provider before
|
||||
trying to load the component.
|
||||
|
||||
@see ComponentBuilder::setImageProvider()
|
||||
*/
|
||||
class JUCE_API ImageProvider
|
||||
{
|
||||
public:
|
||||
ImageProvider() {}
|
||||
virtual ~ImageProvider() {}
|
||||
|
||||
/** Retrieves the image associated with this identifier, which could be any
|
||||
kind of string, number, filename, etc.
|
||||
|
||||
The image that is returned will be owned by the caller, but it may come
|
||||
from the ImageCache.
|
||||
*/
|
||||
virtual Image getImageForIdentifier (const var& imageIdentifier) = 0;
|
||||
|
||||
/** Returns an identifier to be used to refer to a given image.
|
||||
This is used when a reference to an image is stored in a ValueTree.
|
||||
*/
|
||||
virtual var getIdentifierForImage (const Image& image) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Gives the builder an ImageProvider object that the type handlers can use when
|
||||
loading images from stored references.
|
||||
|
||||
The object that is passed in is not owned by the builder, so the caller must delete
|
||||
it when it is no longer needed, but not while the builder may still be using it. To
|
||||
clear the image provider, just call setImageProvider (nullptr).
|
||||
*/
|
||||
void setImageProvider (ImageProvider* newImageProvider) noexcept;
|
||||
|
||||
/** Returns the current image provider that this builder is using, or nullptr if none has been set. */
|
||||
ImageProvider* getImageProvider() const noexcept;
|
||||
|
||||
//=============================================================================
|
||||
/** Updates the children of a parent component by updating them from the children of
|
||||
a given ValueTree.
|
||||
*/
|
||||
void updateChildComponents (Component& parent, const ValueTree& children);
|
||||
|
||||
/** An identifier for the property of the ValueTrees that is used to store a unique ID
|
||||
for that component.
|
||||
*/
|
||||
static const Identifier idProperty;
|
||||
|
||||
private:
|
||||
//=============================================================================
|
||||
OwnedArray<TypeHandler> types;
|
||||
ScopedPointer<Component> component;
|
||||
ImageProvider* imageProvider;
|
||||
#if JUCE_DEBUG
|
||||
WeakReference<Component> componentRef;
|
||||
#endif
|
||||
|
||||
void valueTreePropertyChanged (ValueTree&, const Identifier&) override;
|
||||
void valueTreeChildAdded (ValueTree&, ValueTree&) override;
|
||||
void valueTreeChildRemoved (ValueTree&, ValueTree&) override;
|
||||
void valueTreeChildOrderChanged (ValueTree&) override;
|
||||
void valueTreeParentChanged (ValueTree&) override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentBuilder)
|
||||
};
|
||||
|
||||
#endif // JUCE_COMPONENTBUILDER_H_INCLUDED
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue