git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce

subrepo:
  subdir:   "deps/juce"
  merged:   "b13f9084e"
upstream:
  origin:   "https://github.com/essej/JUCE.git"
  branch:   "sono6good"
  commit:   "b13f9084e"
git-subrepo:
  version:  "0.4.3"
  origin:   "https://github.com/ingydotnet/git-subrepo.git"
  commit:   "2f68596"
This commit is contained in:
essej
2022-04-18 17:51:22 -04:00
parent 63e175fee6
commit 25bd5d8adb
3210 changed files with 1045392 additions and 0 deletions

View File

@ -0,0 +1,119 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/** An action that can be performed by an accessible UI element.
@tags{Accessibility}
*/
enum class AccessibilityActionType
{
/** Represents a "press" action.
This will be called when the user "clicks" the UI element using an
accessibility client.
*/
press,
/** Represents a "toggle" action.
This will be called when the user toggles the state of a UI element,
for example a toggle button or the selection of a list item.
*/
toggle,
/** Indicates that the UI element has received focus.
This will be called when a UI element receives focus from an accessibility
client, or keyboard focus from the application.
*/
focus,
/** Represents the user showing a contextual menu for a UI element.
This will be called for UI elements which expand and collapse to
show contextual information or menus, or show a popup.
*/
showMenu
};
/** A simple wrapper for building a collection of supported accessibility actions
and corresponding callbacks for a UI element.
Pass one of these when constructing an `AccessibilityHandler` to enable users
to interact with a UI element via the supported actions.
@tags{Accessibility}
*/
class JUCE_API AccessibilityActions
{
public:
/** Constructor.
Creates a default AccessibilityActions object with no action callbacks.
*/
AccessibilityActions() = default;
/** Adds an action.
When the user performs this action with an accessibility client
`actionCallback` will be called.
Returns a reference to itself so that several calls can be chained.
*/
AccessibilityActions& addAction (AccessibilityActionType type,
std::function<void()> actionCallback)
{
actionMap[type] = std::move (actionCallback);
return *this;
}
/** Returns true if the specified action is supported. */
bool contains (AccessibilityActionType type) const
{
return actionMap.find (type) != actionMap.end();
}
/** If an action has been registered for the provided action type, invokes the
action and returns true. Otherwise, returns false.
*/
bool invoke (AccessibilityActionType type) const
{
auto iter = actionMap.find (type);
if (iter == actionMap.end())
return false;
iter->second();
return true;
}
private:
std::map<AccessibilityActionType, std::function<void()>> actionMap;
};
} // namespace juce

View File

@ -0,0 +1,81 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/** A list of events that can be notified to any subscribed accessibility clients.
To post a notification, call `AccessibilityHandler::notifyAccessibilityEvent`
on the associated handler with the appropriate `AccessibilityEvent` type and
listening clients will be notified.
@tags{Accessibility}
*/
enum class AccessibilityEvent
{
/** Indicates that the UI element's value has changed.
This should be called on the handler that implements `AccessibilityValueInterface`
for the UI element that has changed.
*/
valueChanged,
/** Indicates that the title of the UI element has changed.
This should be called on the handler whose title has changed.
*/
titleChanged,
/** Indicates that the structure of the UI elements has changed in a
significant way.
This should be called on the top-level handler whose structure has changed.
*/
structureChanged,
/** Indicates that the selection of a text element has changed.
This should be called on the handler that implements `AccessibilityTextInterface`
for the text element that has changed.
*/
textSelectionChanged,
/** Indicates that the visible text of a text element has changed.
This should be called on the handler that implements `AccessibilityTextInterface`
for the text element that has changed.
*/
textChanged,
/** Indicates that the selection of rows in a list or table has changed.
This should be called on the handler that implements `AccessibilityTableInterface`
for the UI element that has changed.
*/
rowSelectionChanged
};
}

View File

@ -0,0 +1,71 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/** The list of available roles for an AccessibilityHandler object.
When creating a custom AccessibilityHandler you should select the role that
best describes the UI element being represented.
@tags{Accessibility}
*/
enum class AccessibilityRole
{
button,
toggleButton,
radioButton,
comboBox,
image,
slider,
label,
staticText,
editableText,
menuItem,
menuBar,
popupMenu,
table,
tableHeader,
column,
row,
cell,
hyperlink,
list,
listItem,
tree,
treeItem,
progressBar,
group,
dialogWindow,
window,
scrollBar,
tooltip,
splashScreen,
ignored,
unspecified
};
}

View File

@ -0,0 +1,61 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/** An abstract interface which represents a UI element that supports a cell interface.
This typically represents a single cell inside of a UI element which implements an
AccessibilityTableInterface.
@tags{Accessibility}
*/
class JUCE_API AccessibilityCellInterface
{
public:
/** Destructor. */
virtual ~AccessibilityCellInterface() = default;
/** Returns the column index of the cell in the table. */
virtual int getColumnIndex() const = 0;
/** Returns the number of columns occupied by the cell in the table. */
virtual int getColumnSpan() const = 0;
/** Returns the row index of the cell in the table. */
virtual int getRowIndex() const = 0;
/** Returns the number of rows occupied by the cell in the table. */
virtual int getRowSpan() const = 0;
/** Returns the indentation level for the cell. */
virtual int getDisclosureLevel() const = 0;
/** Returns the AccessibilityHandler of the table which contains the cell. */
virtual const AccessibilityHandler* getTableHandler() const = 0;
};
} // namespace juce

View File

@ -0,0 +1,54 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/** An abstract interface which represents a UI element that supports a table interface.
Examples of UI elements which typically support a table interface are lists, tables,
and trees.
@tags{Accessibility}
*/
class JUCE_API AccessibilityTableInterface
{
public:
/** Destructor. */
virtual ~AccessibilityTableInterface() = default;
/** Returns the total number of rows in the table. */
virtual int getNumRows() const = 0;
/** Returns the total number of columns in the table. */
virtual int getNumColumns() const = 0;
/** Returns the AccessibilityHandler for one of the cells in the table, or
nullptr if there is no cell at the specified position.
*/
virtual const AccessibilityHandler* getCellHandler (int row, int column) const = 0;
};
} // namespace juce

View File

@ -0,0 +1,81 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/** An abstract interface which represents a UI element that supports a text interface.
A UI element can use this interface to provide extended textual information which
cannot be conveyed using just the title, description, and help text properties of
AccessibilityHandler. This is typically for text that an accessibility client might
want to read line-by-line, or provide text selection and input for.
@tags{Accessibility}
*/
class JUCE_API AccessibilityTextInterface
{
public:
/** Destructor. */
virtual ~AccessibilityTextInterface() = default;
/** Returns true if the text being displayed is protected and should not be
exposed to the user, for example a password entry field.
*/
virtual bool isDisplayingProtectedText() const = 0;
/** Returns true if the text being displayed is read-only or false if editable. */
virtual bool isReadOnly() const = 0;
/** Returns the total number of characters in the text element. */
virtual int getTotalNumCharacters() const = 0;
/** Returns the range of characters that are currently selected, or an empty
range if nothing is selected.
*/
virtual Range<int> getSelection() const = 0;
/** Selects a section of the text. */
virtual void setSelection (Range<int> newRange) = 0;
/** Gets the current text insertion position, if supported. */
virtual int getTextInsertionOffset() const = 0;
/** Returns a section of text. */
virtual String getText (Range<int> range) const = 0;
/** Replaces the text with a new string. */
virtual void setText (const String& newText) = 0;
/** Returns the bounding box in screen coordinates for a range of text.
As the range may span multiple lines, this method returns a RectangleList.
*/
virtual RectangleList<int> getTextBounds (Range<int> textRange) const = 0;
/** Returns the index of the character at a given position in screen coordinates. */
virtual int getOffsetAtPoint (Point<int> point) const = 0;
};
} // namespace juce

View File

@ -0,0 +1,222 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/** An abstract interface representing the value of an accessibility element.
Values should be used when information needs to be conveyed which cannot
be represented by the accessibility element's label alone. For example, a
gain slider with the label "Gain" needs to also provide a value for its
position whereas a "Save" button does not.
This class allows for full control over the value text/numeric conversion,
ranged, and read-only properties but in most cases you'll want to use one
of the derived classes below which handle some of this for you.
@see AccessibilityTextValueInterface, AccessibilityNumericValueInterface,
AccessibilityRangedNumericValueInterface
@tags{Accessibility}
*/
class JUCE_API AccessibilityValueInterface
{
public:
/** Destructor. */
virtual ~AccessibilityValueInterface() = default;
/** Returns true if the value is read-only and cannot be modified by an
accessibility client.
@see setValue, setValueAsString
*/
virtual bool isReadOnly() const = 0;
/** Returns the current value as a double. */
virtual double getCurrentValue() const = 0;
/** Returns the current value as a String. */
virtual String getCurrentValueAsString() const = 0;
/** Sets the current value to a new double value. */
virtual void setValue (double newValue) = 0;
/** Sets the current value to a new String value. */
virtual void setValueAsString (const String& newValue) = 0;
/** Represents the range of this value, if supported.
Return one of these from the `getRange()` method, providing a minimum,
maximum, and interval value for the range to indicate that this is a
ranged value.
The default state is an "invalid" range, indicating that the accessibility
element does not support ranged values.
@see AccessibilityRangedNumericValueInterface
@tags{Accessibility}
*/
class JUCE_API AccessibleValueRange
{
public:
/** Constructor.
Creates a default, "invalid" range that can be returned from
`AccessibilityValueInterface::getRange()` to indicate that the value
interface does not support ranged values.
*/
AccessibleValueRange() = default;
/** The minimum and maximum values for this range, inclusive. */
struct JUCE_API MinAndMax { double min, max; };
/** Constructor.
Creates a valid AccessibleValueRange with the provided minimum, maximum,
and interval values.
*/
AccessibleValueRange (MinAndMax valueRange, double interval)
: valid (true),
range (valueRange),
stepSize (interval)
{
jassert (range.min < range.max);
}
/** Returns true if this represents a valid range. */
bool isValid() const noexcept { return valid; }
/** Returns the minimum value for this range. */
double getMinimumValue() const noexcept { return range.min; }
/** Returns the maxiumum value for this range. */
double getMaximumValue() const noexcept { return range.max; }
/** Returns the interval for this range. */
double getInterval() const noexcept { return stepSize; }
private:
bool valid = false;
MinAndMax range {};
double stepSize = 0.0;
};
/** If this is a ranged value, this should return a valid AccessibleValueRange
object representing the supported numerical range.
*/
virtual AccessibleValueRange getRange() const = 0;
};
//==============================================================================
/** A value interface that represents a text value.
@tags{Accessibility}
*/
class JUCE_API AccessibilityTextValueInterface : public AccessibilityValueInterface
{
public:
/** Returns true if the value is read-only and cannot be modified by an
accessibility client.
@see setValueAsString
*/
bool isReadOnly() const override = 0;
/** Returns the current value. */
String getCurrentValueAsString() const override = 0;
/** Sets the current value to a new value. */
void setValueAsString (const String& newValue) override = 0;
/** @internal */
double getCurrentValue() const final { return getCurrentValueAsString().getDoubleValue(); }
/** @internal */
void setValue (double newValue) final { setValueAsString (String (newValue)); }
/** @internal */
AccessibleValueRange getRange() const final { return {}; }
};
//==============================================================================
/** A value interface that represents a non-ranged numeric value.
@tags{Accessibility}
*/
class JUCE_API AccessibilityNumericValueInterface : public AccessibilityValueInterface
{
public:
/** Returns true if the value is read-only and cannot be modified by an
accessibility client.
@see setValue
*/
bool isReadOnly() const override = 0;
/** Returns the current value. */
double getCurrentValue() const override = 0;
/** Sets the current value to a new value. */
void setValue (double newValue) override = 0;
/** @internal */
String getCurrentValueAsString() const final { return String (getCurrentValue()); }
/** @internal */
void setValueAsString (const String& newValue) final { setValue (newValue.getDoubleValue()); }
/** @internal */
AccessibleValueRange getRange() const final { return {}; }
};
//==============================================================================
/** A value interface that represents a ranged numeric value.
@tags{Accessibility}
*/
class JUCE_API AccessibilityRangedNumericValueInterface : public AccessibilityValueInterface
{
public:
/** Returns true if the value is read-only and cannot be modified by an
accessibility client.
@see setValueAsString
*/
bool isReadOnly() const override = 0;
/** Returns the current value. */
double getCurrentValue() const override = 0;
/** Sets the current value to a new value. */
void setValue (double newValue) override = 0;
/** Returns the range. */
AccessibleValueRange getRange() const override = 0;
/** @internal */
String getCurrentValueAsString() const final { return String (getCurrentValue()); }
/** @internal */
void setValueAsString (const String& newValue) final { setValue (newValue.getDoubleValue()); }
};
} // namespace juce

View File

@ -0,0 +1,340 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
AccessibilityHandler* AccessibilityHandler::currentlyFocusedHandler = nullptr;
enum class InternalAccessibilityEvent
{
elementCreated,
elementDestroyed,
elementMovedOrResized,
focusChanged,
windowOpened,
windowClosed
};
void notifyAccessibilityEventInternal (const AccessibilityHandler&, InternalAccessibilityEvent);
inline String getAccessibleApplicationOrPluginName()
{
#if defined (JucePlugin_Name)
return JucePlugin_Name;
#else
if (auto* app = JUCEApplicationBase::getInstance())
return app->getApplicationName();
return "JUCE Application";
#endif
}
AccessibilityHandler::AccessibilityHandler (Component& comp,
AccessibilityRole accessibilityRole,
AccessibilityActions accessibilityActions,
Interfaces interfacesIn)
: component (comp),
typeIndex (typeid (component)),
role (accessibilityRole),
actions (std::move (accessibilityActions)),
interfaces (std::move (interfacesIn)),
nativeImpl (createNativeImpl (*this))
{
notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::elementCreated);
}
AccessibilityHandler::~AccessibilityHandler()
{
giveAwayFocus();
notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::elementDestroyed);
}
//==============================================================================
AccessibleState AccessibilityHandler::getCurrentState() const
{
if (component.isCurrentlyBlockedByAnotherModalComponent()
&& Component::getCurrentlyModalComponent()->isVisible())
return {};
auto state = AccessibleState().withFocusable();
return hasFocus (false) ? state.withFocused() : state;
}
bool AccessibilityHandler::isIgnored() const
{
return role == AccessibilityRole::ignored || getCurrentState().isIgnored();
}
static bool isComponentVisibleWithinWindow (const Component& comp)
{
if (auto* peer = comp.getPeer())
return ! peer->getAreaCoveredBy (comp).getIntersection (peer->getComponent().getLocalBounds()).isEmpty();
return false;
}
static bool isComponentVisibleWithinParent (Component* comp)
{
if (auto* parent = comp->getParentComponent())
{
if (comp->getBoundsInParent().getIntersection (parent->getLocalBounds()).isEmpty())
return false;
return isComponentVisibleWithinParent (parent);
}
return true;
}
bool AccessibilityHandler::isVisibleWithinParent() const
{
return getCurrentState().isAccessibleOffscreen()
|| (isComponentVisibleWithinParent (&component) && isComponentVisibleWithinWindow (component));
}
//==============================================================================
const AccessibilityActions& AccessibilityHandler::getActions() const noexcept
{
return actions;
}
AccessibilityValueInterface* AccessibilityHandler::getValueInterface() const
{
return interfaces.value.get();
}
AccessibilityTableInterface* AccessibilityHandler::getTableInterface() const
{
return interfaces.table.get();
}
AccessibilityCellInterface* AccessibilityHandler::getCellInterface() const
{
return interfaces.cell.get();
}
AccessibilityTextInterface* AccessibilityHandler::getTextInterface() const
{
return interfaces.text.get();
}
//==============================================================================
static AccessibilityHandler* findEnclosingHandler (Component* comp)
{
if (comp != nullptr)
{
if (auto* handler = comp->getAccessibilityHandler())
return handler;
return findEnclosingHandler (comp->getParentComponent());
}
return nullptr;
}
static AccessibilityHandler* getUnignoredAncestor (AccessibilityHandler* handler)
{
while (handler != nullptr
&& (handler->isIgnored() || ! handler->isVisibleWithinParent())
&& handler->getParent() != nullptr)
{
handler = handler->getParent();
}
return handler;
}
static AccessibilityHandler* findFirstUnignoredChild (const std::vector<AccessibilityHandler*>& handlers)
{
if (! handlers.empty())
{
const auto iter = std::find_if (handlers.cbegin(), handlers.cend(),
[] (const AccessibilityHandler* handler) { return ! handler->isIgnored() && handler->isVisibleWithinParent(); });
if (iter != handlers.cend())
return *iter;
for (auto* handler : handlers)
if (auto* unignored = findFirstUnignoredChild (handler->getChildren()))
return unignored;
}
return nullptr;
}
static AccessibilityHandler* getFirstUnignoredDescendant (AccessibilityHandler* handler)
{
if (handler != nullptr && (handler->isIgnored() || ! handler->isVisibleWithinParent()))
return findFirstUnignoredChild (handler->getChildren());
return handler;
}
AccessibilityHandler* AccessibilityHandler::getParent() const
{
if (auto* focusContainer = component.findFocusContainer())
return getUnignoredAncestor (findEnclosingHandler (focusContainer));
return nullptr;
}
std::vector<AccessibilityHandler*> AccessibilityHandler::getChildren() const
{
if (! component.isFocusContainer() && component.getParentComponent() != nullptr)
return {};
const auto addChildComponentHandler = [this] (Component* focusableComponent,
std::vector<AccessibilityHandler*>& childHandlers)
{
if (focusableComponent == nullptr)
return;
if (auto* handler = findEnclosingHandler (focusableComponent))
{
if (! handler->getCurrentState().isFocusable() || ! isParentOf (handler))
return;
if (auto* unignored = getFirstUnignoredDescendant (handler))
if (std::find (childHandlers.cbegin(), childHandlers.cend(), unignored) == childHandlers.cend())
childHandlers.push_back (unignored);
}
};
std::vector<AccessibilityHandler*> children;
if (auto traverser = component.createFocusTraverser())
{
addChildComponentHandler (traverser->getDefaultComponent (&component), children);
for (auto* focusableChild : traverser->getAllComponents (&component))
addChildComponentHandler (focusableChild, children);
}
return children;
}
bool AccessibilityHandler::isParentOf (const AccessibilityHandler* possibleChild) const noexcept
{
while (possibleChild != nullptr)
{
possibleChild = possibleChild->getParent();
if (possibleChild == this)
return true;
}
return false;
}
AccessibilityHandler* AccessibilityHandler::getChildAt (Point<int> screenPoint)
{
if (auto* comp = Desktop::getInstance().findComponentAt (screenPoint))
{
if (auto* handler = getUnignoredAncestor (findEnclosingHandler (comp)))
if (isParentOf (handler))
return handler;
}
return nullptr;
}
AccessibilityHandler* AccessibilityHandler::getChildFocus()
{
return hasFocus (true) ? getUnignoredAncestor (currentlyFocusedHandler)
: nullptr;
}
bool AccessibilityHandler::hasFocus (bool trueIfChildFocused) const
{
return currentlyFocusedHandler != nullptr
&& (currentlyFocusedHandler == this
|| (trueIfChildFocused && isParentOf (currentlyFocusedHandler)));
}
void AccessibilityHandler::grabFocus()
{
if (! hasFocus (false))
grabFocusInternal (true);
}
void AccessibilityHandler::giveAwayFocus() const
{
if (hasFocus (true))
giveAwayFocusInternal();
}
void AccessibilityHandler::grabFocusInternal (bool canTryParent)
{
if (getCurrentState().isFocusable() && ! isIgnored())
{
takeFocus();
return;
}
if (isParentOf (currentlyFocusedHandler))
return;
if (auto traverser = component.createFocusTraverser())
{
if (auto* defaultComp = traverser->getDefaultComponent (&component))
{
if (auto* handler = getUnignoredAncestor (findEnclosingHandler (defaultComp)))
{
if (isParentOf (handler))
{
handler->grabFocusInternal (false);
return;
}
}
}
}
if (canTryParent)
if (auto* parent = getParent())
parent->grabFocusInternal (true);
}
void AccessibilityHandler::giveAwayFocusInternal() const
{
currentlyFocusedHandler = nullptr;
notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::focusChanged);
}
void AccessibilityHandler::takeFocus()
{
currentlyFocusedHandler = this;
notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::focusChanged);
if ((component.isShowing() || component.isOnDesktop())
&& component.getWantsKeyboardFocus()
&& ! component.hasKeyboardFocus (true))
{
component.grabKeyboardFocus();
}
}
} // namespace juce

View File

@ -0,0 +1,325 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
class AccessibilityNativeHandle;
/** Base class for accessible Components.
This class wraps a Component and provides methods that allow an accessibility client,
such as VoiceOver on macOS, or Narrator on Windows, to control it.
It handles hierarchical navigation, properties, state, and various interfaces.
@tags{Accessibility}
*/
class JUCE_API AccessibilityHandler
{
public:
/** Utility struct which holds one or more accessibility interfaces.
The main purpose of this class is to provide convenience constructors from each
of the four types of accessibility interface.
*/
struct JUCE_API Interfaces
{
Interfaces() = default;
Interfaces (std::unique_ptr<AccessibilityValueInterface> ptr) : value (std::move (ptr)) {}
Interfaces (std::unique_ptr<AccessibilityTextInterface> ptr) : text (std::move (ptr)) {}
Interfaces (std::unique_ptr<AccessibilityTableInterface> ptr) : table (std::move (ptr)) {}
Interfaces (std::unique_ptr<AccessibilityCellInterface> ptr) : cell (std::move (ptr)) {}
Interfaces (std::unique_ptr<AccessibilityValueInterface> valueIn,
std::unique_ptr<AccessibilityTextInterface> textIn,
std::unique_ptr<AccessibilityTableInterface> tableIn,
std::unique_ptr<AccessibilityCellInterface> cellIn)
: value (std::move (valueIn)),
text (std::move (textIn)),
table (std::move (tableIn)),
cell (std::move (cellIn))
{
}
std::unique_ptr<AccessibilityValueInterface> value;
std::unique_ptr<AccessibilityTextInterface> text;
std::unique_ptr<AccessibilityTableInterface> table;
std::unique_ptr<AccessibilityCellInterface> cell;
};
/** Constructor.
This will create a AccessibilityHandler which wraps the provided Component and makes
it visible to accessibility clients. You must also specify a role for the UI element
from the `AccessibilityRole` list which best describes it.
To enable users to interact with the UI element you should provide the set of supported
actions and their associated callbacks via the `accessibilityActions` parameter.
For UI elements that support more complex interaction the value, text, table, and cell
interfaces should be implemented as required and passed as the final argument of this
constructor. See the documentation of these classes for more information about the
types of control they represent and which methods need to be implemented.
*/
AccessibilityHandler (Component& componentToWrap,
AccessibilityRole accessibilityRole,
AccessibilityActions actions = {},
Interfaces interfaces = {});
/** Destructor. */
virtual ~AccessibilityHandler();
//==============================================================================
/** Returns the Component that this handler represents. */
const Component& getComponent() const noexcept { return component; }
/** Returns the Component that this handler represents. */
Component& getComponent() noexcept { return component; }
//==============================================================================
/** The type of UI element that this accessibility handler represents.
@see AccessibilityRole
*/
AccessibilityRole getRole() const noexcept { return role; }
/** The title of the UI element.
This will be read out by the system and should be concise, preferably matching
the visible title of the UI element (if any). For example, this might be the
text of a button or a simple label.
The default implementation will call `Component::getTitle()`, but you can override
this to return a different string if required.
If neither a name nor a description is provided then the UI element may be
ignored by accessibility clients.
This must be a localised string.
*/
virtual String getTitle() const { return component.getTitle(); }
/** A short description of the UI element.
This may be read out by the system. It should not include the type of the UI
element and should ideally be a single word, for example "Open" for a button
that opens a window.
The default implementation will call `Component::getDescription()`, but you
can override this to return a different string if required.
If neither a name nor a description is provided then the UI element may be
ignored by accessibility clients.
This must be a localised string.
*/
virtual String getDescription() const { return component.getDescription(); }
/** Some help text for the UI element (if required).
This may be read out by the system. This string functions in a similar way to
a tooltip, for example "Click to open window." for a button which opens a window.
The default implementation will call `Component::getHelpText()`, but you can
override this to return a different string if required.
This must be a localised string.
*/
virtual String getHelp() const { return component.getHelpText(); }
/** Returns the current state of the UI element.
The default implementation of this method will set the focusable flag and, if
this UI element is currently focused, will also set the focused flag.
*/
virtual AccessibleState getCurrentState() const;
/** Returns true if this UI element should be ignored by accessibility clients. */
bool isIgnored() const;
/** Returns true if this UI element is visible within its parent.
This will always return true for UI elements with the AccessibleState::accessibleOffscreen
flag set.
*/
bool isVisibleWithinParent() const;
//==============================================================================
/** Returns the set of actions that the UI element supports and the associated
callbacks.
*/
const AccessibilityActions& getActions() const noexcept;
/** Returns the value interface for this UI element, or nullptr if it is not supported.
@see AccessibilityValueInterface
*/
AccessibilityValueInterface* getValueInterface() const;
/** Returns the table interface for this UI element, or nullptr if it is not supported.
@see AccessibilityTableInterface
*/
AccessibilityTableInterface* getTableInterface() const;
/** Returns the cell interface for this UI element, or nullptr if it is not supported.
@see AccessibilityCellInterface
*/
AccessibilityCellInterface* getCellInterface() const;
/** Returns the text interface for this UI element, or nullptr if it is not supported.
@see AccessibilityTextInterface
*/
AccessibilityTextInterface* getTextInterface() const;
//==============================================================================
/** Returns the first unignored parent of this UI element in the accessibility hierarchy,
or nullptr if this is a root element without a parent.
*/
AccessibilityHandler* getParent() const;
/** Returns the unignored children of this UI element in the accessibility hierarchy. */
std::vector<AccessibilityHandler*> getChildren() const;
/** Checks whether a given UI element is a child of this one in the accessibility
hierarchy.
*/
bool isParentOf (const AccessibilityHandler* possibleChild) const noexcept;
/** Returns the deepest child of this UI element in the accessibility hierarchy that
contains the given screen point, or nullptr if there is no child at this point.
*/
AccessibilityHandler* getChildAt (Point<int> screenPoint);
/** Returns the deepest UI element which currently has focus.
This can be a child of this UI element or, if no child is focused,
this element itself.
Note that this can be different to the value of the Component with keyboard
focus returned by Component::getCurrentlyFocusedComponent().
@see hasFocus
*/
AccessibilityHandler* getChildFocus();
/** Returns true if this UI element has the focus.
@param trueIfChildFocused if this is true, this method will also return true
if any child of this UI element in the accessibility
hierarchy has focus
*/
bool hasFocus (bool trueIfChildFocused) const;
/** Tries to give focus to this UI element.
If the UI element is focusable and not ignored this will update the currently focused
element, try to give keyboard focus to the Component it represents, and notify any
listening accessibility clients that the current focus has changed.
@see hasFocus, giveAwayFocus
*/
void grabFocus();
/** If this UI element or any of its children in the accessibility hierarchy currently
have focus, this will defocus it.
This will also give away the keyboard focus from the Component it represents, and
notify any listening accessibility clients that the current focus has changed.
@see hasFocus, grabFocus
*/
void giveAwayFocus() const;
//==============================================================================
/** Used to send a notification to any observing accessibility clients that something
has changed in the UI element.
@see AccessibilityEvent
*/
void notifyAccessibilityEvent (AccessibilityEvent event) const;
/** A priority level that can help an accessibility client determine how to handle
an announcement request.
Exactly what this controls is platform-specific, but generally a low priority
announcement will be read when the screen reader is free, whereas a high priority
announcement will interrupt the current speech.
*/
enum class AnnouncementPriority
{
low,
medium,
high
};
/** Posts an announcement to be made to the user.
@param announcementString a localised string containing the announcement to be read out
@param priority the appropriate priority level for the announcement
*/
static void postAnnouncement (const String& announcementString, AnnouncementPriority priority);
//==============================================================================
/** @internal */
AccessibilityNativeHandle* getNativeImplementation() const;
/** @internal */
std::type_index getTypeIndex() const { return typeIndex; }
private:
//==============================================================================
friend class AccessibilityNativeHandle;
//==============================================================================
void grabFocusInternal (bool);
void giveAwayFocusInternal() const;
void takeFocus();
static AccessibilityHandler* currentlyFocusedHandler;
//==============================================================================
Component& component;
std::type_index typeIndex;
const AccessibilityRole role;
AccessibilityActions actions;
Interfaces interfaces;
//==============================================================================
class AccessibilityNativeImpl;
std::unique_ptr<AccessibilityNativeImpl> nativeImpl;
static std::unique_ptr<AccessibilityNativeImpl> createNativeImpl (AccessibilityHandler&);
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityHandler)
};
} // namespace juce

View File

@ -0,0 +1,227 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/** Represents the state of an accessible UI element.
An instance of this class is returned by `AccessibilityHandler::getCurrentState()`
to convey its current state to an accessibility client.
@see AccessibilityHandler
@tags{Accessibility}
*/
class JUCE_API AccessibleState
{
public:
/** Constructor.
Represents a "default" state with no flags set. To set a flag, use one of the
`withX()` methods - these can be chained together to set multiple flags.
*/
AccessibleState() = default;
//==============================================================================
/** Sets the checkable flag and returns the new state.
@see isCheckable
*/
AccessibleState withCheckable() const noexcept { return withFlag (Flags::checkable); }
/** Sets the checked flag and returns the new state.
@see isChecked
*/
AccessibleState withChecked() const noexcept { return withFlag (Flags::checked); }
/** Sets the collapsed flag and returns the new state.
@see isCollapsed
*/
AccessibleState withCollapsed() const noexcept { return withFlag (Flags::collapsed); }
/** Sets the expandable flag and returns the new state.
@see isExpandable
*/
AccessibleState withExpandable() const noexcept { return withFlag (Flags::expandable); }
/** Sets the expanded flag and returns the new state.
@see isExpanded
*/
AccessibleState withExpanded() const noexcept { return withFlag (Flags::expanded); }
/** Sets the focusable flag and returns the new state.
@see isFocusable
*/
AccessibleState withFocusable() const noexcept { return withFlag (Flags::focusable); }
/** Sets the focused flag and returns the new state.
@see isFocused
*/
AccessibleState withFocused() const noexcept { return withFlag (Flags::focused); }
/** Sets the ignored flag and returns the new state.
@see isIgnored
*/
AccessibleState withIgnored() const noexcept { return withFlag (Flags::ignored); }
/** Sets the selectable flag and returns the new state.
@see isSelectable
*/
AccessibleState withSelectable() const noexcept { return withFlag (Flags::selectable); }
/** Sets the multiSelectable flag and returns the new state.
@see isMultiSelectable
*/
AccessibleState withMultiSelectable() const noexcept { return withFlag (Flags::multiSelectable); }
/** Sets the selected flag and returns the new state.
@see isSelected
*/
AccessibleState withSelected() const noexcept { return withFlag (Flags::selected); }
/** Sets the accessible offscreen flag and returns the new state.
@see isSelected
*/
AccessibleState withAccessibleOffscreen() const noexcept { return withFlag (Flags::accessibleOffscreen); }
//==============================================================================
/** Returns true if the UI element is checkable.
@see withCheckable
*/
bool isCheckable() const noexcept { return isFlagSet (Flags::checkable); }
/** Returns true if the UI element is checked.
@see withChecked
*/
bool isChecked() const noexcept { return isFlagSet (Flags::checked); }
/** Returns true if the UI element is collapsed.
@see withCollapsed
*/
bool isCollapsed() const noexcept { return isFlagSet (Flags::collapsed); }
/** Returns true if the UI element is expandable.
@see withExpandable
*/
bool isExpandable() const noexcept { return isFlagSet (Flags::expandable); }
/** Returns true if the UI element is expanded.
@see withExpanded
*/
bool isExpanded() const noexcept { return isFlagSet (Flags::expanded); }
/** Returns true if the UI element is focusable.
@see withFocusable
*/
bool isFocusable() const noexcept { return isFlagSet (Flags::focusable); }
/** Returns true if the UI element is focused.
@see withFocused
*/
bool isFocused() const noexcept { return isFlagSet (Flags::focused); }
/** Returns true if the UI element is ignored.
@see withIgnored
*/
bool isIgnored() const noexcept { return isFlagSet (Flags::ignored); }
/** Returns true if the UI element supports multiple item selection.
@see withMultiSelectable
*/
bool isMultiSelectable() const noexcept { return isFlagSet (Flags::multiSelectable); }
/** Returns true if the UI element is selectable.
@see withSelectable
*/
bool isSelectable() const noexcept { return isFlagSet (Flags::selectable); }
/** Returns true if the UI element is selected.
@see withSelected
*/
bool isSelected() const noexcept { return isFlagSet (Flags::selected); }
/** Returns true if the UI element is accessible offscreen.
@see withSelected
*/
bool isAccessibleOffscreen() const noexcept { return isFlagSet (Flags::accessibleOffscreen); }
private:
enum Flags
{
checkable = (1 << 0),
checked = (1 << 1),
collapsed = (1 << 2),
expandable = (1 << 3),
expanded = (1 << 4),
focusable = (1 << 5),
focused = (1 << 6),
ignored = (1 << 7),
multiSelectable = (1 << 8),
selectable = (1 << 9),
selected = (1 << 10),
accessibleOffscreen = (1 << 11)
};
AccessibleState withFlag (int flag) const noexcept
{
auto copy = *this;
copy.flags |= flag;
return copy;
}
bool isFlagSet (int flag) const noexcept
{
return (flags & flag) != 0;
}
int flags = 0;
};
} // namespace juce

View File

@ -0,0 +1,104 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
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;
}
} // namespace juce

View File

@ -0,0 +1,193 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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.reset (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:
std::unique_ptr<MyApplicationWindow> myMainWindow;
};
// this generates boilerplate code to launch our app class:
START_JUCE_APPLICATION (MyJUCEApp)
@endcode
@see JUCEApplicationBase, START_JUCE_APPLICATION
@tags{GUI}
*/
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() override;
//==============================================================================
/** Returns the global instance of the application object being run. */
static JUCEApplication* JUCE_CALLTYPE getInstance() noexcept;
//==============================================================================
#if DOXYGEN
/** Returns the application's name. */
virtual const String getApplicationName() = 0;
/** Returns the application's version number. */
virtual const String getApplicationVersion() = 0;
#endif
/** Checks whether multiple instances of the app are allowed.
If your 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 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() override;
/** @internal */
void getCommandInfo (CommandID, ApplicationCommandInfo&) override;
/** @internal */
void getAllCommands (Array<CommandID>&) override;
/** @internal */
bool perform (const InvocationInfo&) override;
private:
bool initialiseApp() override;
JUCE_DECLARE_NON_COPYABLE (JUCEApplication)
};
} // namespace juce

View File

@ -0,0 +1,51 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
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 (MathConstants<float>::twoPi * arrowDirectionInRadians, 0.5f, 0.5f));
}
ArrowButton::~ArrowButton() {}
void ArrowButton::paintButton (Graphics& g, bool /*shouldDrawButtonAsHighlighted*/, bool shouldDrawButtonAsDown)
{
Path p (path);
const float offset = shouldDrawButtonAsDown ? 1.0f : 0.0f;
p.applyTransform (path.getTransformToScaleToFit (offset, offset, (float) getWidth() - 3.0f, (float) getHeight() - 3.0f, false));
DropShadow (Colours::black.withAlpha (0.3f), shouldDrawButtonAsDown ? 2 : 4, Point<int>()).drawForPath (g, p);
g.setColour (colour);
g.fillPath (p);
}
} // namespace juce

View File

@ -0,0 +1,65 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A button with an arrow in it.
@see Button
@tags{GUI}
*/
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() override;
/** @internal */
void paintButton (Graphics&, bool, bool) override;
private:
Colour colour;
Path path;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ArrowButton)
};
} // namespace juce

View File

@ -0,0 +1,792 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
struct Button::CallbackHelper : public Timer,
public ApplicationCommandManagerListener,
public Value::Listener,
public KeyListener
{
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(), dontSendNotification, 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();
}
Button& button;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHelper)
};
//==============================================================================
Button::Button (const String& name) : Component (name), text (name)
{
callbackHelper.reset (new CallbackHelper (*this));
setWantsKeyboardFocus (true);
isOn.addListener (callbackHelper.get());
}
Button::~Button()
{
clearShortcuts();
if (commandManagerToUse != nullptr)
commandManagerToUse->removeListener (callbackHelper.get());
isOn.removeListener (callbackHelper.get());
callbackHelper.reset();
}
//==============================================================================
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)
{
auto tt = info.description.isNotEmpty() ? info.description
: info.shortName;
for (auto& kp : commandManagerToUse->getKeyMappings()->getKeyPressesAssignedToCommand (commandID))
{
auto key = kp.getTextDescription();
tt << " [";
if (key.length() == 1)
tt << TRANS("shortcut") << ": '" << key << "']";
else
tt << key << ']';
}
SettableTooltipClient::setTooltip (tt);
}
}
void Button::setConnectedEdges (int newFlags)
{
if (connectedEdgeFlags != newFlags)
{
connectedEdgeFlags = newFlags;
repaint();
}
}
//==============================================================================
void Button::setToggleState (bool shouldBeOn, NotificationType notification)
{
setToggleState (shouldBeOn, notification, notification);
}
void Button::setToggleState (bool shouldBeOn, NotificationType clickNotification, NotificationType stateNotification)
{
if (shouldBeOn != lastToggleState)
{
WeakReference<Component> deletionWatcher (this);
if (shouldBeOn)
{
turnOffOtherButtonsInGroup (clickNotification, stateNotification);
if (deletionWatcher == nullptr)
return;
}
// This test is done so that if the value is void rather than explicitly set to
// false, the value won't be changed unless the required value is true.
if (getToggleState() != shouldBeOn)
{
isOn = shouldBeOn;
if (deletionWatcher == nullptr)
return;
}
lastToggleState = shouldBeOn;
repaint();
if (clickNotification != dontSendNotification)
{
// async callbacks aren't possible here
jassert (clickNotification != sendNotificationAsync);
sendClickMessage (ModifierKeys::currentModifiers);
if (deletionWatcher == nullptr)
return;
}
if (stateNotification != dontSendNotification)
sendStateMessage();
else
buttonStateChanged();
if (auto* handler = getAccessibilityHandler())
handler->notifyAccessibilityEvent (AccessibilityEvent::valueChanged);
}
}
void Button::setToggleState (bool shouldBeOn, bool sendChange)
{
setToggleState (shouldBeOn, sendChange ? sendNotification : dontSendNotification);
}
void Button::setClickingTogglesState (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 (int newGroupId, NotificationType notification)
{
if (radioGroupId != newGroupId)
{
radioGroupId = newGroupId;
if (lastToggleState)
turnOffOtherButtonsInGroup (notification, notification);
invalidateAccessibilityHandler();
}
}
void Button::turnOffOtherButtonsInGroup (NotificationType clickNotification, NotificationType stateNotification)
{
if (auto* p = getParentComponent())
{
if (radioGroupId != 0)
{
WeakReference<Component> deletionWatcher (this);
for (auto* c : p->getChildren())
{
if (c != this)
{
if (auto b = dynamic_cast<Button*> (c))
{
if (b->getRadioGroupId() == radioGroupId)
{
b->setToggleState (false, clickNotification, stateNotification);
if (deletionWatcher == nullptr)
return;
}
}
}
}
}
}
}
//==============================================================================
void Button::enablementChanged()
{
updateState();
repaint();
}
Button::ButtonState Button::updateState()
{
return updateState (isMouseOver (true), isMouseButtonDown());
}
Button::ButtonState Button::updateState (bool over, 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 (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
{
auto now = Time::getApproximateMillisecondCounter();
return now > buttonPressTime ? now - buttonPressTime : 0;
}
void Button::setTriggeredOnMouseDown (bool isTriggeredOnMouseDown) noexcept
{
triggerOnMouseDown = isTriggeredOnMouseDown;
}
bool Button::getTriggeredOnMouseDown() const noexcept
{
return triggerOnMouseDown;
}
//==============================================================================
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::currentModifiers);
}
}
else
{
Component::handleCommandMessage (commandId);
}
}
//==============================================================================
void Button::addListener (Listener* l) { buttonListeners.add (l); }
void Button::removeListener (Listener* l) { buttonListeners.remove (l); }
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())
return;
buttonListeners.callChecked (checker, [this] (Listener& l) { l.buttonClicked (this); });
if (checker.shouldBailOut())
return;
if (onClick != nullptr)
onClick();
}
void Button::sendStateMessage()
{
Component::BailOutChecker checker (this);
buttonStateChanged();
if (checker.shouldBailOut())
return;
buttonListeners.callChecked (checker, [this] (Listener& l) { l.buttonStateChanged (this); });
if (checker.shouldBailOut())
return;
if (onStateChange != nullptr)
onStateChange();
}
//==============================================================================
void Button::paint (Graphics& g)
{
if (needsToRelease && isEnabled())
{
needsToRelease = false;
needsRepainting = true;
}
paintButton (g, isOver(), isDown());
lastStatePainted = buttonState;
}
//==============================================================================
void Button::mouseEnter (const MouseEvent&) { updateState (true, false); }
void Button::mouseExit (const MouseEvent&) { updateState (false, false); }
bool Button::isInDragToScrollViewport() const noexcept
{
if (auto* vp = findParentComponentOfClass<Viewport>())
return vp->isScrollOnDragEnabled() && (vp->canScrollVertically() || vp->canScrollHorizontally());
return false;
}
void Button::mouseDown (const MouseEvent& e)
{
isInDraggableViewport = isInDragToScrollViewport();
isDraggingToScroll = false;
updateState (true, true);
if (isDown())
{
if (autoRepeatDelay >= 0)
callbackHelper->startTimer (autoRepeatDelay);
if (triggerOnMouseDown)
internalClickCallback (e.mods);
}
}
void Button::mouseUp (const MouseEvent& e)
{
const auto wasDown = isDown();
const auto wasOver = isOver();
updateState (isMouseSourceOver (e), false);
if (wasDown && wasOver && ! triggerOnMouseDown && ! isDraggingToScroll)
{
if (lastStatePainted != buttonDown)
flashButtonState();
WeakReference<Component> deletionWatcher (this);
internalClickCallback (e.mods);
if (deletionWatcher != nullptr)
updateState (isMouseSourceOver (e), false);
}
}
void Button::mouseDrag (const MouseEvent& e)
{
auto oldState = buttonState;
updateState (isMouseSourceOver (e), true);
if (autoRepeatDelay >= 0 && buttonState != oldState && isDown())
callbackHelper->startTimer (autoRepeatSpeed);
if (isInDraggableViewport && ! isDraggingToScroll)
if (auto* vp = findParentComponentOfClass<Viewport>())
isDraggingToScroll = vp->isCurrentlyScrollingOnDrag();
}
bool Button::isMouseSourceOver (const MouseEvent& e)
{
if (e.source.isTouch() || e.source.isPen())
return getLocalBounds().toFloat().contains (e.position);
return isMouseOver();
}
void Button::focusGained (FocusChangeType)
{
updateState();
repaint();
}
void Button::focusLost (FocusChangeType)
{
updateState();
repaint();
}
void Button::visibilityChanged()
{
needsToRelease = false;
updateState();
}
void Button::parentHierarchyChanged()
{
auto* newKeySource = shortcuts.isEmpty() ? nullptr : getTopLevelComponent();
if (newKeySource != keySource.get())
{
if (keySource != nullptr)
keySource->removeKeyListener (callbackHelper.get());
keySource = newKeySource;
if (keySource != nullptr)
keySource->addKeyListener (callbackHelper.get());
}
}
//==============================================================================
void Button::setCommandToTrigger (ApplicationCommandManager* newCommandManager,
CommandID newCommandID, bool generateTip)
{
commandID = newCommandID;
generateTooltip = generateTip;
if (commandManagerToUse != newCommandManager)
{
if (commandManagerToUse != nullptr)
commandManagerToUse->removeListener (callbackHelper.get());
commandManagerToUse = newCommandManager;
if (commandManagerToUse != nullptr)
commandManagerToUse->addListener (callbackHelper.get());
// 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 (auto& s : shortcuts)
if (s.isCurrentlyDown())
return true;
return false;
}
bool Button::isRegisteredForShortcut (const KeyPress& key) const
{
for (auto& s : shortcuts)
if (key == s)
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::currentModifiers);
// (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 (int initialDelayMillisecs,
int repeatMillisecs,
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)))
{
auto repeatSpeed = autoRepeatSpeed;
if (autoRepeatMinimumDelay >= 0)
{
auto timeHeldDown = jmin (1.0, getMillisecondsSinceButtonDown() / 4000.0);
timeHeldDown *= timeHeldDown;
repeatSpeed = repeatSpeed + (int) (timeHeldDown * (autoRepeatMinimumDelay - repeatSpeed));
}
repeatSpeed = jmax (1, repeatSpeed);
auto 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::currentModifiers);
}
else if (! needsToRelease)
{
callbackHelper->stopTimer();
}
}
//==============================================================================
class ButtonAccessibilityHandler : public AccessibilityHandler
{
public:
explicit ButtonAccessibilityHandler (Button& buttonToWrap, AccessibilityRole roleIn)
: AccessibilityHandler (buttonToWrap,
isRadioButton (buttonToWrap) ? AccessibilityRole::radioButton : buttonToWrap.getClickingTogglesState() ? AccessibilityRole::toggleButton : roleIn,
getAccessibilityActions (buttonToWrap, roleIn)),
button (buttonToWrap)
{
}
AccessibleState getCurrentState() const override
{
auto state = AccessibilityHandler::getCurrentState();
if (isToggleButton (getRole()) || isRadioButton (button) || button.getClickingTogglesState())
{
state = state.withCheckable();
if (button.getToggleState())
state = state.withChecked();
}
return state;
}
String getTitle() const override
{
auto title = AccessibilityHandler::getTitle();
if (title.isEmpty())
return button.getButtonText();
return title;
}
String getHelp() const override { return button.getTooltip(); }
private:
static bool isToggleButton (AccessibilityRole role) noexcept
{
return role == AccessibilityRole::toggleButton;
}
static bool isRadioButton (const Button& button) noexcept
{
return button.getRadioGroupId() != 0;
}
static AccessibilityActions getAccessibilityActions (Button& button, AccessibilityRole role)
{
auto actions = AccessibilityActions().addAction (AccessibilityActionType::press,
[&button] { button.triggerClick(); });
if (isToggleButton (role) || button.getClickingTogglesState())
actions = actions.addAction (AccessibilityActionType::toggle,
[&button] { button.setToggleState (! button.getToggleState(), sendNotification); });
return actions;
}
Button& button;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonAccessibilityHandler)
};
std::unique_ptr<AccessibilityHandler> Button::createAccessibilityHandler()
{
return std::make_unique<ButtonAccessibilityHandler> (*this, AccessibilityRole::button);
}
} // namespace juce

View File

@ -0,0 +1,528 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
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. */
~Button() override;
//==============================================================================
/** 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 button'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() = default;
/** 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);
//==============================================================================
/** You can assign a lambda to this callback object to have it called when the button is clicked. */
std::function<void()> onClick;
/** You can assign a lambda to this callback object to have it called when the button's state changes. */
std::function<void()> onStateChange;
//==============================================================================
/** 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 whether the button click happens when the mouse is pressed or released.
@see setTriggeredOnMouseDown
*/
bool getTriggeredOnMouseDown() const 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 relevant 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 abstract base class is implemented by LookAndFeel classes to provide
button-drawing functionality.
*/
struct JUCE_API LookAndFeelMethods
{
virtual ~LookAndFeelMethods() = default;
virtual void drawButtonBackground (Graphics&, Button&, const Colour& backgroundColour,
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) = 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 shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) = 0;
/** Draws the contents of a standard ToggleButton. */
virtual void drawToggleButton (Graphics&, ToggleButton&,
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) = 0;
virtual void changeToggleButtonWidthToFitText (ToggleButton&) = 0;
virtual void drawTickBox (Graphics&, Component&, float x, float y, float w, float h,
bool ticked, bool isEnabled,
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) = 0;
virtual void drawDrawableButton (Graphics&, DrawableButton&,
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) = 0;
};
//==============================================================================
#ifndef DOXYGEN
[[deprecated ("This method's parameters have changed.")]]
void setToggleState (bool, bool);
#endif
protected:
//==============================================================================
/** This method is called when the button has been clicked.
Subclasses can override this to perform whatever actions they need to do.
In general, you wouldn't use this method to receive clicks, but should get your callbacks
by attaching a std::function to the onClick callback, or adding a Button::Listener.
@see triggerClick, onClick
*/
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 shouldDrawButtonAsHighlighted true if the button is either in the 'over' or 'down' state
@param shouldDrawButtonAsDown true if the button should be drawn in the 'down' position
*/
virtual void paintButton (Graphics& g,
bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) = 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;
struct CallbackHelper;
std::unique_ptr<CallbackHelper> callbackHelper;
uint32 buttonPressTime = 0, lastRepeatTime = 0;
ApplicationCommandManager* commandManagerToUse = nullptr;
int autoRepeatDelay = -1, autoRepeatSpeed = 0, autoRepeatMinimumDelay = -1;
int radioGroupId = 0, connectedEdgeFlags = 0;
CommandID commandID = {};
ButtonState buttonState = buttonNormal, lastStatePainted = buttonNormal;
Value isOn;
bool lastToggleState = false;
bool clickTogglesState = false;
bool needsToRelease = false;
bool needsRepainting = false;
bool isKeyDown = false;
bool triggerOnMouseDown = false;
bool generateTooltip = false;
bool isInDraggableViewport = false ,isDraggingToScroll = false;
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
void repeatTimerCallback();
bool keyStateChangedCallback();
void applicationCommandListChangeCallback();
void updateAutomaticTooltip (const ApplicationCommandInfo&);
ButtonState updateState();
ButtonState updateState (bool isOver, bool isDown);
bool isShortcutPressed() const;
void turnOffOtherButtonsInGroup (NotificationType click, NotificationType state);
void flashButtonState();
void sendClickMessage (const ModifierKeys&);
void sendStateMessage();
void setToggleState (bool shouldBeOn, NotificationType click, NotificationType state);
bool isMouseSourceOver (const MouseEvent& e);
bool isInDragToScrollViewport() const noexcept;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Button)
};
} // namespace juce

View File

@ -0,0 +1,253 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
DrawableButton::DrawableButton (const String& name, const DrawableButton::ButtonStyle buttonStyle)
: Button (name), style (buttonStyle)
{
}
DrawableButton::~DrawableButton()
{
}
//==============================================================================
static std::unique_ptr<Drawable> copyDrawableIfNotNull (const Drawable* const d)
{
if (d != nullptr)
return d->createCopy();
return {};
}
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);
currentImage = nullptr;
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
{
auto r = getLocalBounds();
if (style != ImageStretched)
{
auto indentX = jmin (edgeIndent, proportionOfWidth (0.3f));
auto indentY = jmin (edgeIndent, proportionOfHeight (0.3f));
if (shouldDrawButtonBackground())
{
indentX = jmax (getWidth() / 4, indentX);
indentY = jmax (getHeight() / 4, indentY);
}
else if (style == ImageAboveTextLabel)
{
r = r.withTrimmedBottom (jmin (16, proportionOfHeight (0.25f)));
}
else if (getStyle() == ImageBelowTextLabel)
{
r = r.withTrimmedTop (jmin (14, proportionOfHeight (0.25f)));
}
else if (getStyle() == ImageLeftOfTextLabel)
{
r = r.withTrimmedRight (proportionOfWidth (0.5f));
}
else if (getStyle() == ImageRightOfTextLabel)
{
r = r.withTrimmedLeft (proportionOfWidth (0.5f));
}
r = r.reduced (indentX, indentY);
}
return r.toFloat();
}
void DrawableButton::resized()
{
Button::resized();
if (currentImage != nullptr)
{
if (style != ImageRaw)
{
int transformFlags = 0;
if (style == ImageStretched)
{
transformFlags |= RectanglePlacement::stretchToFit;
}
else
{
transformFlags |= RectanglePlacement::centred;
if (style == ImageOnButtonBackgroundOriginalSize)
transformFlags |= RectanglePlacement::doNotResize;
}
currentImage->setTransformToFit (getImageBounds(), transformFlags);
}
}
}
void DrawableButton::buttonStateChanged()
{
repaint();
Drawable* imageToDraw = nullptr;
float opacity = 1.0f;
if (isEnabled())
{
imageToDraw = getCurrentImage();
}
else
{
imageToDraw = getToggleState() ? disabledImageOn.get()
: disabledImage.get();
if (imageToDraw == nullptr)
{
opacity = 0.4f;
imageToDraw = getNormalImage();
}
}
if (imageToDraw != currentImage)
{
removeChildComponent (currentImage);
currentImage = imageToDraw;
if (currentImage != nullptr)
{
currentImage->setInterceptsMouseClicks (false, false);
currentImage->setAccessible(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 shouldDrawButtonAsHighlighted,
const bool shouldDrawButtonAsDown)
{
auto& lf = getLookAndFeel();
if (shouldDrawButtonBackground())
lf.drawButtonBackground (g, *this,
findColour (getToggleState() ? TextButton::buttonOnColourId
: TextButton::buttonColourId),
shouldDrawButtonAsHighlighted, shouldDrawButtonAsDown);
else
lf.drawDrawableButton (g, *this, shouldDrawButtonAsHighlighted, shouldDrawButtonAsDown);
}
//==============================================================================
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.get()
: normalImage.get();
}
Drawable* DrawableButton::getOverImage() const noexcept
{
if (getToggleState())
{
if (overImageOn != nullptr) return overImageOn.get();
if (normalImageOn != nullptr) return normalImageOn.get();
}
return overImage != nullptr ? overImage.get() : normalImage.get();
}
Drawable* DrawableButton::getDownImage() const noexcept
{
if (auto* d = getToggleState() ? downImageOn.get() : downImage.get())
return d;
return getOverImage();
}
} // namespace juce

View File

@ -0,0 +1,200 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
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. The image will be resized
to match the button's proportions.
Note that if you use this style, the colour IDs that control the button colour are
TextButton::buttonColourId and TextButton::buttonOnColourId. */
ImageOnButtonBackgroundOriginalSize, /** Same as ImageOnButtonBackground, but keeps the original image size. */
ImageStretched, /**< Fills the button with a stretched version of the image. */
ImageBelowTextLabel, /**< Draws the button as a text label across the top with the image resized and scaled to fit below it. */
ImageLeftOfTextLabel, /**< Draws the button as a text label on the right with the image resized and scaled to fit beside it. */
ImageRightOfTextLabel /**< Draws the button as a text label on the left with the image resized and scaled to fit beside it. */
};
//==============================================================================
/** 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() override;
//==============================================================================
/** 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-null.
@param overImage the thing to draw for the button's 'over' state - if this is
null, 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-null.
@param downImage the thing to draw for the button's 'down' state - if this is
null, 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-null.
@param disabledImage an image to draw when the button is disabled. If this is null,
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-null.
@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 current edge indent size. */
int getEdgeIndent() const noexcept { return edgeIndent; }
//==============================================================================
/** 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, bool) override;
/** @internal */
void buttonStateChanged() override;
/** @internal */
void resized() override;
/** @internal */
void enablementChanged() override;
/** @internal */
void colourChanged() override;
private:
//==============================================================================
bool shouldDrawButtonBackground() const { return style == ImageOnButtonBackground || style == ImageOnButtonBackgroundOriginalSize; }
//==============================================================================
ButtonStyle style;
std::unique_ptr<Drawable> normalImage, overImage, downImage, disabledImage,
normalImageOn, overImageOn, downImageOn, disabledImageOn;
Drawable* currentImage = nullptr;
int edgeIndent = 3;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DrawableButton)
};
} // namespace juce

View File

@ -0,0 +1,124 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
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()),
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 ((float) getHeight() * 0.7f);
return font;
}
void HyperlinkButton::changeWidthToFitText()
{
setSize (getFontToUse().getStringWidth (getButtonText()) + 6, getHeight());
}
void HyperlinkButton::setJustificationType (Justification newJustification)
{
if (justification != newJustification)
{
justification = newJustification;
repaint();
}
}
void HyperlinkButton::colourChanged()
{
repaint();
}
//==============================================================================
void HyperlinkButton::clicked()
{
if (url.isWellFormed())
url.launchInDefaultBrowser();
}
void HyperlinkButton::paintButton (Graphics& g,
bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown)
{
const Colour textColour (findColour (textColourId));
if (isEnabled())
g.setColour ((shouldDrawButtonAsHighlighted) ? textColour.darker ((shouldDrawButtonAsDown) ? 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);
}
} // namespace juce

View File

@ -0,0 +1,127 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A button showing an underlined weblink, that will launch the link
when it's clicked.
@see Button
@tags{GUI}
*/
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::setButtonText() 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() override;
//==============================================================================
/** 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();
//==============================================================================
/** Sets the style of justification to be used for positioning the text.
(The default is Justification::centred)
*/
void setJustificationType (Justification justification);
/** Returns the type of justification, as set in setJustificationType(). */
Justification getJustificationType() const noexcept { return justification; }
protected:
//==============================================================================
/** @internal */
void clicked() override;
/** @internal */
void colourChanged() override;
/** @internal */
void paintButton (Graphics&, bool, bool) override;
private:
//==============================================================================
using Button::clicked;
Font getFontToUse() const;
//==============================================================================
URL url;
Font font;
bool resizeFont;
Justification justification;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HyperlinkButton)
};
} // namespace juce

View File

@ -0,0 +1,208 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
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();
}
void ImageButton::setHitTestAlphaThreshold(float thresh)
{
alphaThreshold = (uint8) jlimit (0, 0xff, roundToInt (255.0f * thresh));
}
float ImageButton::getHitTestAlphaThreshold() const
{
return alphaThreshold / 255.0f;
}
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 shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown)
{
if (! isEnabled())
{
shouldDrawButtonAsHighlighted = false;
shouldDrawButtonAsDown = 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 = (float) ih / (float) iw;
const float destRatio = (float) h / (float) w;
if (imRatio > destRatio)
{
newW = roundToInt ((float) h / imRatio);
newH = h;
}
else
{
newW = w;
newH = roundToInt ((float) 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 = shouldDrawButtonAsDown || getToggleState();
getLookAndFeel().drawImageButton (g, &im, x, y, w, h,
useDownImage ? downOverlay
: (shouldDrawButtonAsHighlighted ? overOverlay
: normalOverlay),
useDownImage ? downOpacity
: (shouldDrawButtonAsHighlighted ? overOpacity
: normalOpacity),
*this);
}
}
bool ImageButton::hitTest (int x, int y)
{
if (! Component::hitTest (x, y)) // handle setInterceptsMouseClicks
return false;
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());
}
} // namespace juce

View File

@ -0,0 +1,164 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
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());
/** Destructor. */
~ImageButton() override;
//==============================================================================
/** 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;
void setHitTestAlphaThreshold(float thresh);
float getHitTestAlphaThreshold() const;
//==============================================================================
/** This abstract base class is implemented by LookAndFeel classes. */
struct JUCE_API LookAndFeelMethods
{
virtual ~LookAndFeelMethods() = default;
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, bool) 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)
};
} // namespace juce

View File

@ -0,0 +1,137 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
ShapeButton::ShapeButton (const String& t, Colour n, Colour o, Colour d)
: Button (t),
normalColour (n), overColour (o), downColour (d),
normalColourOn (n), overColourOn (o), downColourOn (d),
useOnColours(false),
maintainShapeProportions (false),
outlineWidth (0.0f)
{
}
ShapeButton::~ShapeButton() {}
void ShapeButton::setColours (Colour newNormalColour, Colour newOverColour, Colour newDownColour)
{
normalColour = newNormalColour;
overColour = newOverColour;
downColour = newDownColour;
}
void ShapeButton::setOnColours (Colour newNormalColourOn, Colour newOverColourOn, Colour newDownColourOn)
{
normalColourOn = newNormalColourOn;
overColourOn = newOverColourOn;
downColourOn = newDownColourOn;
}
void ShapeButton::shouldUseOnColours (bool shouldUse)
{
useOnColours = shouldUse;
}
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)
{
auto 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 shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown)
{
if (! isEnabled())
{
shouldDrawButtonAsHighlighted = false;
shouldDrawButtonAsDown = false;
}
auto r = border.subtractedFrom (getLocalBounds())
.toFloat()
.reduced (outlineWidth * 0.5f);
if (getComponentEffect() != nullptr)
r = r.reduced (2.0f);
if (shouldDrawButtonAsDown)
{
const float sizeReductionWhenPressed = 0.04f;
r = r.reduced (sizeReductionWhenPressed * r.getWidth(),
sizeReductionWhenPressed * r.getHeight());
}
auto trans = shape.getTransformToScaleToFit (r, maintainShapeProportions);
if (shouldDrawButtonAsDown) g.setColour (getToggleState() && useOnColours ? downColourOn : downColour);
else if (shouldDrawButtonAsHighlighted) g.setColour (getToggleState() && useOnColours ? overColourOn : overColour);
else g.setColour (getToggleState() && useOnColours ? normalColourOn : normalColour);
g.fillPath (shape, trans);
if (outlineWidth > 0.0f)
{
g.setColour (outlineColour);
g.strokePath (shape, PathStrokeType (outlineWidth), trans);
}
}
} // namespace juce

View File

@ -0,0 +1,126 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A button that contains a filled shape.
@see Button, ImageButton, TextButton, ArrowButton
@tags{GUI}
*/
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() override;
//==============================================================================
/** 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 the colours to use for drawing the shape when the button's toggle state is 'on'. To enable this behaviour, use the
shouldUseOnColours() method.
@param normalColourOn the colour to fill the shape with when the mouse isn't over and the button's toggle state is 'on'
@param overColourOn the colour to use when the mouse is over the shape and the button's toggle state is 'on'
@param downColourOn the colour to use when the button is in the pressed-down state and the button's toggle state is 'on'
*/
void setOnColours (Colour normalColourOn,
Colour overColourOn,
Colour downColourOn);
/** Set whether the button should use the 'on' set of colours when its toggle state is 'on'.
By default these will be the same as the normal colours but the setOnColours method can be
used to provide a different set of colours.
*/
void shouldUseOnColours (bool shouldUse);
/** 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, bool) override;
private:
//==============================================================================
Colour normalColour, overColour, downColour,
normalColourOn, overColourOn, downColourOn, outlineColour;
bool useOnColours;
DropShadowEffect shadow;
Path shape;
BorderSize<int> border;
bool maintainShapeProportions;
float outlineWidth;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ShapeButton)
};
} // namespace juce

View File

@ -0,0 +1,77 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
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 shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown)
{
auto& lf = getLookAndFeel();
lf.drawButtonBackground (g, *this,
findColour (getToggleState() ? buttonOnColourId : buttonColourId),
shouldDrawButtonAsHighlighted, shouldDrawButtonAsDown);
lf.drawButtonText (g, *this, shouldDrawButtonAsHighlighted, shouldDrawButtonAsDown);
}
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);
}
} // namespace juce

View File

@ -0,0 +1,109 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A button that uses the standard lozenge-shaped background with a line of
text on it.
@see Button, DrawableButton
@tags{GUI}
*/
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 tooltip
*/
TextButton (const String& buttonName, const String& toolTip);
/** Destructor. */
~TextButton() override;
//==============================================================================
/** 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, bool) override;
/** @internal */
void colourChanged() override;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TextButton)
};
} // namespace juce

View File

@ -0,0 +1,65 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
ToggleButton::ToggleButton()
: Button (String())
{
setClickingTogglesState (true);
}
ToggleButton::ToggleButton (const String& buttonText)
: Button (buttonText)
{
setClickingTogglesState (true);
}
ToggleButton::~ToggleButton()
{
}
void ToggleButton::paintButton (Graphics& g, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown)
{
getLookAndFeel().drawToggleButton (g, *this, shouldDrawButtonAsHighlighted, shouldDrawButtonAsDown);
}
void ToggleButton::changeWidthToFitText()
{
getLookAndFeel().changeToggleButtonWidthToFitText (*this);
}
void ToggleButton::colourChanged()
{
repaint();
}
std::unique_ptr<AccessibilityHandler> ToggleButton::createAccessibilityHandler()
{
return std::make_unique<ButtonAccessibilityHandler> (*this, AccessibilityRole::toggleButton);
}
} // namespace juce

View File

@ -0,0 +1,92 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
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() override;
//==============================================================================
/** 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. */
tickColourId = 0x1006502, /**< The colour to use for the tick mark. */
tickDisabledColourId = 0x1006503 /**< The colour to use for the disabled tick mark and/or outline. */
};
protected:
//==============================================================================
/** @internal */
void paintButton (Graphics&, bool, bool) override;
/** @internal */
void colourChanged() override;
private:
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToggleButton)
};
} // namespace juce

View File

@ -0,0 +1,113 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
ToolbarButton::ToolbarButton (const int iid, const String& buttonText,
std::unique_ptr<Drawable> normalIm,
std::unique_ptr<Drawable> toggledOnIm)
: ToolbarItemComponent (iid, buttonText, true),
normalImage (std::move (normalIm)),
toggledOnImage (std::move (toggledOnIm))
{
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.get();
return normalImage.get();
}
void ToolbarButton::buttonStateChanged()
{
setCurrentImage (getImageToUse());
}
} // namespace juce

View File

@ -0,0 +1,98 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
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,
std::unique_ptr<Drawable> normalImage,
std::unique_ptr<Drawable> toggledOnImage);
/** Destructor. */
~ToolbarButton() override;
//==============================================================================
/** @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:
//==============================================================================
std::unique_ptr<Drawable> normalImage, toggledOnImage;
Drawable* currentImage = nullptr;
void updateDrawable();
Drawable* getImageToUse() const;
void setCurrentImage (Drawable*);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToolbarButton)
};
} // namespace juce

View File

@ -0,0 +1,90 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/** 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
*/
using CommandID = int;
//==============================================================================
/** 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
};
}
} // namespace juce

View File

@ -0,0 +1,66 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
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));
}
} // namespace juce

View File

@ -0,0 +1,190 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
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 permissible 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;
};
} // namespace juce

View File

@ -0,0 +1,322 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
ApplicationCommandManager::ApplicationCommandManager()
{
keyMappings.reset (new KeyPressMappingSet (*this));
Desktop::getInstance().addFocusChangeListener (this);
}
ApplicationCommandManager::~ApplicationCommandManager()
{
Desktop::getInstance().removeFocusChangeListener (this);
keyMappings.reset();
}
//==============================================================================
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 (auto* 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
{
auto* 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 (CommandID commandID) const noexcept
{
return getMutableCommandForID (commandID);
}
String ApplicationCommandManager::getNameOfCommand (CommandID commandID) const noexcept
{
if (auto* ci = getCommandForID (commandID))
return ci->shortName;
return {};
}
String ApplicationCommandManager::getDescriptionOfCommand (CommandID commandID) const noexcept
{
if (auto* ci = getCommandForID (commandID))
return ci->description.isNotEmpty() ? ci->description
: ci->shortName;
return {};
}
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 (CommandID commandID, bool asynchronously)
{
ApplicationCommandTarget::InvocationInfo info (commandID);
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::direct;
return invoke (info, asynchronously);
}
bool ApplicationCommandManager::invoke (const ApplicationCommandTarget::InvocationInfo& inf, bool asynchronously)
{
// This call isn't thread-safe for use from a non-UI thread without locking the message
// manager first..
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
bool ok = false;
ApplicationCommandInfo commandInfo (0);
if (auto* 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 (CommandID)
{
return firstTarget != nullptr ? firstTarget
: findDefaultComponentTarget();
}
void ApplicationCommandManager::setFirstCommandTarget (ApplicationCommandTarget* newTarget) noexcept
{
firstTarget = newTarget;
}
ApplicationCommandTarget* ApplicationCommandManager::getTargetForCommand (CommandID commandID,
ApplicationCommandInfo& upToDateInfo)
{
auto* 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)
{
auto* target = dynamic_cast<ApplicationCommandTarget*> (c);
if (target == nullptr && c != nullptr)
target = c->findParentComponentOfClass<ApplicationCommandTarget>();
return target;
}
ApplicationCommandTarget* ApplicationCommandManager::findDefaultComponentTarget()
{
auto* c = Component::getCurrentlyFocusedComponent();
if (c == nullptr)
{
if (auto* activeWindow = TopLevelWindow::getActiveTopLevelWindow())
{
if (auto* peer = activeWindow->getPeer())
{
c = peer->getLastFocusedSubcomponent();
if (c == nullptr)
c = activeWindow;
}
}
}
if (c == nullptr)
{
auto& desktop = Desktop::getInstance();
// getting a bit desperate now: try all desktop comps..
for (int i = desktop.getNumComponents(); --i >= 0;)
if (auto* component = desktop.getComponent (i))
if (isForegroundOrEmbeddedProcess (component))
if (auto* peer = component->getPeer())
if (auto* 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 (auto* resizableWindow = dynamic_cast<ResizableWindow*> (c))
if (auto* content = resizableWindow->getContentComponent())
c = content;
if (auto* target = findTargetForComponent (c))
return target;
}
return JUCEApplication::getInstance();
}
//==============================================================================
void ApplicationCommandManager::addListener (ApplicationCommandManagerListener* listener)
{
listeners.add (listener);
}
void ApplicationCommandManager::removeListener (ApplicationCommandManagerListener* listener)
{
listeners.remove (listener);
}
void ApplicationCommandManager::sendListenerInvokeCallback (const ApplicationCommandTarget::InvocationInfo& info)
{
listeners.call ([&] (ApplicationCommandManagerListener& l) { l.applicationCommandInvoked (info); });
}
void ApplicationCommandManager::handleAsyncUpdate()
{
listeners.call ([] (ApplicationCommandManagerListener& l) { l.applicationCommandListChanged(); });
}
void ApplicationCommandManager::globalFocusChanged (Component*)
{
commandStatusChanged();
}
} // namespace juce

View File

@ -0,0 +1,349 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
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.
*/
~ApplicationCommandManager() override;
//==============================================================================
/** 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 nullptr.
*/
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.get(); }
//==============================================================================
/** 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;
std::unique_ptr<KeyPressMappingSet> keyMappings;
ApplicationCommandTarget* firstTarget = nullptr;
void sendListenerInvokeCallback (const ApplicationCommandTarget::InvocationInfo&);
void handleAsyncUpdate() override;
void globalFocusChanged (Component*) override;
ApplicationCommandInfo* getMutableCommandForID (CommandID) const noexcept;
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
@tags{GUI}
*/
class JUCE_API ApplicationCommandManagerListener
{
public:
//==============================================================================
/** Destructor. */
virtual ~ApplicationCommandManagerListener() = default;
/** 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;
};
} // namespace juce

View File

@ -0,0 +1,186 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
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() {}
//==============================================================================
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)
{
}
} // namespace juce

View File

@ -0,0 +1,244 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
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 search 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:
//==============================================================================
class CommandMessage;
friend class CommandMessage;
bool tryToInvoke (const InvocationInfo&, bool async);
JUCE_DECLARE_WEAK_REFERENCEABLE (ApplicationCommandTarget)
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ApplicationCommandTarget)
};
} // namespace juce

View File

@ -0,0 +1,418 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
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 {};
}
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();
}
for (auto* map : xmlVersion.getChildIterator())
{
const CommandID commandId = map->getStringAttribute ("commandId").getHexValue32();
if (commandId != 0)
{
auto key = KeyPress::createFromDescription (map->getStringAttribute ("key"));
if (map->hasTagName ("MAPPING"))
{
addKeyPress (commandId, key);
}
else if (map->hasTagName ("UNMAPPING"))
{
for (auto& m : mappings)
if (m->commandID == commandId)
m->keypresses.removeAllInstancesOf (key);
}
}
}
return true;
}
return false;
}
std::unique_ptr<XmlElement> KeyPressMappingSet::createXml (const bool saveDifferencesFromDefaultSet) const
{
std::unique_ptr<KeyPressMappingSet> defaultSet;
if (saveDifferencesFromDefaultSet)
{
defaultSet = std::make_unique<KeyPressMappingSet> (commandManager);
defaultSet->resetToDefaultMappings();
}
auto doc = std::make_unique<XmlElement> ("KEYMAPPINGS");
doc->setAttribute ("basedOnDefaults", saveDifferencesFromDefaultSet);
for (int i = 0; i < mappings.size(); ++i)
{
auto& cm = *mappings.getUnchecked(i);
for (int j = 0; j < cm.keypresses.size(); ++j)
{
if (defaultSet == nullptr
|| ! defaultSet->containsMapping (cm.commandID, cm.keypresses.getReference (j)))
{
auto 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)
{
auto& cm = *defaultSet->mappings.getUnchecked(i);
for (int j = 0; j < cm.keypresses.size(); ++j)
{
if (! containsMapping (cm.commandID, cm.keypresses.getReference (j)))
{
auto 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);
}
} // namespace juce

View File

@ -0,0 +1,244 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
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() override;
//==============================================================================
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.
@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
*/
std::unique_ptr<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)
};
} // namespace juce

View File

@ -0,0 +1,72 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
class JUCE_API CachedComponentImage
{
public:
CachedComponentImage() = default;
virtual ~CachedComponentImage() = default;
//==============================================================================
/** 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;
};
} // namespace juce

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
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&) {}
void ComponentListener::componentEnablementChanged (Component&) {}
} // namespace juce

View File

@ -0,0 +1,122 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
class JUCE_API ComponentListener
{
public:
/** Destructor. */
virtual ~ComponentListener() = default;
/** 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.
@param component the component that had its name 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!
@param component the component that was deleted
*/
virtual void componentBeingDeleted (Component& component);
/* Called when the component's enablement is changed.
@param component the component that had its enablement changed
@see Component::setEnabled, Component::isEnabled, Component::enablementChanged
*/
virtual void componentEnablementChanged (Component& component);
};
} // namespace juce

View File

@ -0,0 +1,72 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Base class for traversing components.
If you need custom focus or keyboard focus traversal for a component you can
create a subclass of ComponentTraverser and return it from
Component::createFocusTraverser() or Component::createKeyboardFocusTraverser().
@see Component::createFocusTraverser, Component::createKeyboardFocusTraverser
@tags{GUI}
*/
class JUCE_API ComponentTraverser
{
public:
/** Destructor. */
virtual ~ComponentTraverser() = default;
/** Returns the component that should be used as the traversal entry point
within the given parent component.
This must return nullptr if there is no default component.
*/
virtual Component* getDefaultComponent (Component* parentComponent) = 0;
/** Returns the component that comes after the specified one when moving "forwards".
This must return nullptr if there is no next component.
*/
virtual Component* getNextComponent (Component* current) = 0;
/** Returns the component that comes after the specified one when moving "backwards".
This must return nullptr if there is no previous component.
*/
virtual Component* getPreviousComponent (Component* current) = 0;
/** Returns all of the traversable components within the given parent component in
traversal order.
*/
virtual std::vector<Component*> getAllComponents (Component* parentComponent) = 0;
};
} // namespace juce

View File

@ -0,0 +1,360 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
namespace FocusHelpers
{
static int getOrder (const Component* c)
{
auto order = c->getExplicitFocusOrder();
return order > 0 ? order : std::numeric_limits<int>::max();
}
template <typename FocusContainerFn>
static void findAllComponents (Component* parent,
std::vector<Component*>& components,
FocusContainerFn isFocusContainer)
{
if (parent == nullptr || parent->getNumChildComponents() == 0)
return;
std::vector<Component*> localComponents;
for (auto* c : parent->getChildren())
if (c->isVisible() && c->isEnabled())
localComponents.push_back (c);
const auto compareComponents = [&] (const Component* a, const Component* b)
{
const auto getComponentOrderAttributes = [] (const Component* c)
{
return std::make_tuple (getOrder (c),
c->isAlwaysOnTop() ? 0 : 1,
c->getY(),
c->getX());
};
return getComponentOrderAttributes (a) < getComponentOrderAttributes (b);
};
// This will sort so that they are ordered in terms of explicit focus,
// always on top, left-to-right, and then top-to-bottom.
std::stable_sort (localComponents.begin(), localComponents.end(), compareComponents);
for (auto* c : localComponents)
{
components.push_back (c);
if (! (c->*isFocusContainer)())
findAllComponents (c, components, isFocusContainer);
}
}
enum class NavigationDirection { forwards, backwards };
template <typename FocusContainerFn>
static Component* navigateFocus (Component* current,
Component* focusContainer,
NavigationDirection direction,
FocusContainerFn isFocusContainer)
{
if (focusContainer != nullptr)
{
std::vector<Component*> components;
findAllComponents (focusContainer, components, isFocusContainer);
const auto iter = std::find (components.cbegin(), components.cend(), current);
if (iter == components.cend())
return nullptr;
switch (direction)
{
case NavigationDirection::forwards:
if (iter != std::prev (components.cend()))
return *std::next (iter);
break;
case NavigationDirection::backwards:
if (iter != components.cbegin())
return *std::prev (iter);
break;
}
}
return nullptr;
}
}
//==============================================================================
Component* FocusTraverser::getNextComponent (Component* current)
{
jassert (current != nullptr);
return FocusHelpers::navigateFocus (current,
current->findFocusContainer(),
FocusHelpers::NavigationDirection::forwards,
&Component::isFocusContainer);
}
Component* FocusTraverser::getPreviousComponent (Component* current)
{
jassert (current != nullptr);
return FocusHelpers::navigateFocus (current,
current->findFocusContainer(),
FocusHelpers::NavigationDirection::backwards,
&Component::isFocusContainer);
}
Component* FocusTraverser::getDefaultComponent (Component* parentComponent)
{
if (parentComponent != nullptr)
{
std::vector<Component*> components;
FocusHelpers::findAllComponents (parentComponent,
components,
&Component::isFocusContainer);
if (! components.empty())
return components.front();
}
return nullptr;
}
std::vector<Component*> FocusTraverser::getAllComponents (Component* parentComponent)
{
std::vector<Component*> components;
FocusHelpers::findAllComponents (parentComponent,
components,
&Component::isFocusContainer);
return components;
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
struct FocusTraverserTests : public UnitTest
{
FocusTraverserTests()
: UnitTest ("FocusTraverser", UnitTestCategories::gui)
{}
void runTest() override
{
ScopedJuceInitialiser_GUI libraryInitialiser;
const MessageManagerLock mml;
beginTest ("Basic traversal");
{
TestComponent parent;
expect (traverser.getDefaultComponent (&parent) == &parent.children.front());
for (auto iter = parent.children.begin(); iter != parent.children.end(); ++iter)
expect (traverser.getNextComponent (&(*iter)) == (iter == std::prev (parent.children.cend()) ? nullptr
: &(*std::next (iter))));
for (auto iter = parent.children.rbegin(); iter != parent.children.rend(); ++iter)
expect (traverser.getPreviousComponent (&(*iter)) == (iter == std::prev (parent.children.rend()) ? nullptr
: &(*std::next (iter))));
auto allComponents = traverser.getAllComponents (&parent);
expect (std::equal (allComponents.cbegin(), allComponents.cend(), parent.children.cbegin(),
[] (const Component* c1, const Component& c2) { return c1 == &c2; }));
}
beginTest ("Disabled components are ignored");
{
checkIgnored ([] (Component& c) { c.setEnabled (false); });
}
beginTest ("Invisible components are ignored");
{
checkIgnored ([] (Component& c) { c.setVisible (false); });
}
beginTest ("Explicit focus order comes before unspecified");
{
TestComponent parent;
auto& explicitFocusComponent = parent.children[2];
explicitFocusComponent.setExplicitFocusOrder (1);
expect (traverser.getDefaultComponent (&parent) == &explicitFocusComponent);
expect (traverser.getAllComponents (&parent).front() == &explicitFocusComponent);
}
beginTest ("Explicit focus order comparison");
{
checkComponentProperties ([this] (Component& child) { child.setExplicitFocusOrder (getRandom().nextInt ({ 1, 100 })); },
[] (const Component& c1, const Component& c2) { return c1.getExplicitFocusOrder()
<= c2.getExplicitFocusOrder(); });
}
beginTest ("Left to right");
{
checkComponentProperties ([this] (Component& child) { child.setTopLeftPosition (getRandom().nextInt ({ 0, 100 }), 0); },
[] (const Component& c1, const Component& c2) { return c1.getX() <= c2.getX(); });
}
beginTest ("Top to bottom");
{
checkComponentProperties ([this] (Component& child) { child.setTopLeftPosition (0, getRandom().nextInt ({ 0, 100 })); },
[] (const Component& c1, const Component& c2) { return c1.getY() <= c2.getY(); });
}
beginTest ("Focus containers have their own focus");
{
Component root;
TestComponent container;
container.setFocusContainerType (Component::FocusContainerType::focusContainer);
root.addAndMakeVisible (container);
expect (traverser.getDefaultComponent (&root) == &container);
expect (traverser.getNextComponent (&container) == nullptr);
expect (traverser.getPreviousComponent (&container) == nullptr);
expect (traverser.getDefaultComponent (&container) == &container.children.front());
for (auto iter = container.children.begin(); iter != container.children.end(); ++iter)
expect (traverser.getNextComponent (&(*iter)) == (iter == std::prev (container.children.cend()) ? nullptr
: &(*std::next (iter))));
for (auto iter = container.children.rbegin(); iter != container.children.rend(); ++iter)
expect (traverser.getPreviousComponent (&(*iter)) == (iter == std::prev (container.children.rend()) ? nullptr
: &(*std::next (iter))));
expect (traverser.getAllComponents (&root).size() == 1);
auto allContainerComponents = traverser.getAllComponents (&container);
expect (std::equal (allContainerComponents.cbegin(), allContainerComponents.cend(), container.children.cbegin(),
[] (const Component* c1, const Component& c2) { return c1 == &c2; }));
}
beginTest ("Non-focus containers pass-through focus");
{
Component root;
TestComponent container;
container.setFocusContainerType (Component::FocusContainerType::none);
root.addAndMakeVisible (container);
expect (traverser.getDefaultComponent (&root) == &container);
expect (traverser.getNextComponent (&container) == &container.children.front());
expect (traverser.getPreviousComponent (&container) == nullptr);
expect (traverser.getDefaultComponent (&container) == &container.children.front());
for (auto iter = container.children.begin(); iter != container.children.end(); ++iter)
expect (traverser.getNextComponent (&(*iter)) == (iter == std::prev (container.children.cend()) ? nullptr
: &(*std::next (iter))));
for (auto iter = container.children.rbegin(); iter != container.children.rend(); ++iter)
expect (traverser.getPreviousComponent (&(*iter)) == (iter == std::prev (container.children.rend()) ? &container
: &(*std::next (iter))));
expect (traverser.getAllComponents (&root).size() == container.children.size() + 1);
}
}
private:
struct TestComponent : public Component
{
TestComponent()
{
for (auto& child : children)
addAndMakeVisible (child);
}
std::array<Component, 10> children;
};
void checkComponentProperties (std::function<void (Component&)>&& childFn,
std::function<bool (const Component&, const Component&)>&& testProperty)
{
TestComponent parent;
for (auto& child : parent.children)
childFn (child);
auto* comp = traverser.getDefaultComponent (&parent);
for (const auto& child : parent.children)
if (&child != comp)
expect (testProperty (*comp, child));
for (;;)
{
auto* next = traverser.getNextComponent (comp);
if (next == nullptr)
break;
expect (testProperty (*comp, *next));
comp = next;
}
}
void checkIgnored (const std::function<void(Component&)>& makeIgnored)
{
TestComponent parent;
auto iter = parent.children.begin();
makeIgnored (*iter);
expect (traverser.getDefaultComponent (&parent) == std::addressof (*std::next (iter)));
iter += 5;
makeIgnored (*iter);
expect (traverser.getNextComponent (std::addressof (*std::prev (iter))) == std::addressof (*std::next (iter)));
expect (traverser.getPreviousComponent (std::addressof (*std::next (iter))) == std::addressof (*std::prev (iter)));
auto allComponents = traverser.getAllComponents (&parent);
expect (std::find (allComponents.cbegin(), allComponents.cend(), &parent.children.front()) == allComponents.cend());
expect (std::find (allComponents.cbegin(), allComponents.cend(), std::addressof (*iter)) == allComponents.cend());
}
FocusTraverser traverser;
};
static FocusTraverserTests focusTraverserTests;
#endif
} // namespace juce

View File

@ -0,0 +1,93 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Controls the order in which focus moves between components.
The algorithm used by this class to work out the order of traversal is as
follows:
- Only visible and enabled components are considered focusable.
- 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.
- Components with their 'always on top' flag set come before those without.
- Any unspecified components are traversed in a left-to-right, then
top-to-bottom order.
If you need focus traversal in a more customised way you can create a
ComponentTraverser subclass that uses your own algorithm and return it
from Component::createFocusTraverser().
@see ComponentTraverser, Component::createFocusTraverser
@tags{GUI}
*/
class JUCE_API FocusTraverser : public ComponentTraverser
{
public:
/** Destructor. */
~FocusTraverser() override = default;
/** Returns the component that should receive focus by default within the given
parent component.
The default implementation will just return the foremost visible and enabled
child component, and will return nullptr if there is no suitable component.
*/
Component* getDefaultComponent (Component* parentComponent) override;
/** Returns the component that should be given focus after the specified one when
moving "forwards".
The default implementation will return the next visible and enabled component
which is to the right of or below this one, and will return nullptr if there
is no suitable component.
*/
Component* getNextComponent (Component* current) override;
/** Returns the component that should be given focus after the specified one when
moving "backwards".
The default implementation will return the previous visible and enabled component
which is to the left of or above this one, and will return nullptr if there
is no suitable component.
*/
Component* getPreviousComponent (Component* current) override;
/** Returns all of the components that can receive focus within the given parent
component in traversal order.
The default implementation will return all visible and enabled child components.
*/
std::vector<Component*> getAllComponents (Component* parentComponent) override;
};
} // namespace juce

View File

@ -0,0 +1,292 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
struct ModalComponentManager::ModalItem : public ComponentMovementWatcher
{
ModalItem (Component* comp, bool shouldAutoDelete)
: ComponentMovementWatcher (comp),
component (comp), autoDelete (shouldAutoDelete)
{
jassert (comp != nullptr);
}
~ModalItem() override
{
if (autoDelete)
std::unique_ptr<Component> componentDeleter (component);
}
void componentMovedOrResized (bool, bool) override {}
using ComponentMovementWatcher::componentMovedOrResized;
void componentPeerChanged() override
{
componentVisibilityChanged();
}
void componentVisibilityChanged() override
{
if (! component->isShowing())
cancel();
}
using ComponentMovementWatcher::componentVisibilityChanged;
void componentBeingDeleted (Component& comp) override
{
ComponentMovementWatcher::componentBeingDeleted (comp);
if (component == &comp || comp.isParentOf (component))
{
autoDelete = false;
cancel();
}
}
void cancel()
{
if (isActive)
{
isActive = false;
if (auto* mcm = ModalComponentManager::getInstanceWithoutCreating())
mcm->triggerAsyncUpdate();
}
}
Component* component;
OwnedArray<Callback> callbacks;
int returnValue = 0;
bool isActive = true, autoDelete;
JUCE_DECLARE_NON_COPYABLE (ModalItem)
};
//==============================================================================
ModalComponentManager::ModalComponentManager()
{
}
ModalComponentManager::~ModalComponentManager()
{
stack.clear();
clearSingletonInstance();
}
JUCE_IMPLEMENT_SINGLETON (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)
{
std::unique_ptr<Callback> callbackDeleter (callback);
for (int i = stack.size(); --i >= 0;)
{
auto* 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;)
{
auto* item = stack.getUnchecked (i);
if (item->component == component)
item->cancel();
}
}
void ModalComponentManager::endModal (Component* component, int returnValue)
{
for (int i = stack.size(); --i >= 0;)
{
auto* item = stack.getUnchecked (i);
if (item->component == component)
{
item->returnValue = returnValue;
item->cancel();
}
}
}
int ModalComponentManager::getNumModalComponents() const
{
int n = 0;
for (auto* item : stack)
if (item->isActive)
++n;
return n;
}
Component* ModalComponentManager::getModalComponent (int index) const
{
int n = 0;
for (int i = stack.size(); --i >= 0;)
{
auto* item = stack.getUnchecked (i);
if (item->isActive)
if (n++ == index)
return item->component;
}
return nullptr;
}
bool ModalComponentManager::isModal (const Component* comp) const
{
for (auto* item : stack)
if (item->isActive && item->component == comp)
return true;
return false;
}
bool ModalComponentManager::isFrontModalComponent (const Component* comp) const
{
return comp == getModalComponent (0);
}
void ModalComponentManager::handleAsyncUpdate()
{
for (int i = stack.size(); --i >= 0;)
{
auto* item = stack.getUnchecked (i);
if (! item->isActive)
{
std::unique_ptr<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)
{
auto* c = getModalComponent (i);
if (c == nullptr)
break;
if (auto* peer = c->getPeer())
{
if (peer != lastOne)
{
if (lastOne == nullptr)
{
peer->toFront (topOneShouldGrabFocus);
if (topOneShouldGrabFocus)
peer->grabFocus();
}
else
{
peer->toBehind (lastOne);
}
lastOne = peer;
}
}
}
}
bool ModalComponentManager::cancelAllModalComponents()
{
auto numModal = getNumModalComponents();
for (int i = numModal; --i >= 0;)
if (auto* c = getModalComponent (i))
c->exitModalState (0);
return numModal > 0;
}
//==============================================================================
#if JUCE_MODAL_LOOPS_PERMITTED
int ModalComponentManager::runEventLoopForCurrentComponent()
{
// This can only be run from the message thread!
JUCE_ASSERT_MESSAGE_THREAD
int returnValue = 0;
if (auto* currentlyModal = getModalComponent (0))
{
FocusRestorer focusRestorer;
bool finished = false;
attachCallback (currentlyModal, ModalCallbackFunction::create ([&] (int r) { returnValue = r; finished = true; }));
JUCE_TRY
{
while (! finished)
{
if (! MessageManager::getInstance()->runDispatchLoopUntil (20))
break;
}
}
JUCE_CATCH_EXCEPTION
}
return returnValue;
}
#endif
} // namespace juce

View File

@ -0,0 +1,324 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
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 JUCE_API Callback
{
public:
/** */
Callback() = default;
/** Destructor. */
virtual ~Callback() = default;
/** 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;
};
//==============================================================================
#ifndef DOXYGEN
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (ModalComponentManager)
#endif
//==============================================================================
/** 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 (const Component* component) const;
/** Returns true if the specified component is currently the topmost modal component. */
bool isFrontModalComponent (const 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
protected:
/** Creates a ModalComponentManager.
You shouldn't ever call the constructor - it's a singleton, so use ModalComponentManager::getInstance()
*/
ModalComponentManager();
/** Destructor. */
~ModalComponentManager() override;
/** @internal */
void handleAsyncUpdate() override;
private:
//==============================================================================
friend class Component;
struct 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.
@tags{GUI}
*/
class JUCE_API ModalCallbackFunction
{
public:
/** This is a utility function to create a ModalComponentManager::Callback that will
call a callable object.
The function that you supply must take an integer parameter, which is the result code that
was returned when the modal component was dismissed.
@see ModalComponentManager::Callback
*/
template <typename CallbackFn>
static ModalComponentManager::Callback* create (CallbackFn&& fn)
{
struct Callable : public ModalComponentManager::Callback
{
explicit Callable (CallbackFn&& f) : fn (std::forward<CallbackFn> (f)) {}
void modalStateFinished (int result) override { NullCheckedInvocation::invoke (std::move (fn), result); }
std::remove_reference_t<CallbackFn> fn;
};
return new Callable (std::forward<CallbackFn> (fn));
}
//==============================================================================
/** 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 (true, ModalCallbackFunction::create (myCallbackFunction, 3.0));
@endcode
@see ModalComponentManager::Callback
*/
template <typename ParamType>
static ModalComponentManager::Callback* create (void (*functionToCall) (int, ParamType),
ParamType parameterValue)
{
return create ([functionToCall, parameterValue] (int r)
{
functionToCall (r, 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 (true, 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 create ([functionToCall, parameterValue1, parameterValue2] (int r)
{
functionToCall (r, 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 (true, ModalCallbackFunction::forComponent (myCallbackFunction, mySlider));
@endcode
@see ModalComponentManager::Callback
*/
template <class ComponentType>
static ModalComponentManager::Callback* forComponent (void (*functionToCall) (int, ComponentType*),
ComponentType* component)
{
return create ([functionToCall, comp = WeakReference<Component> { component }] (int r)
{
functionToCall (r, static_cast<ComponentType*> (comp.get()));
});
}
//==============================================================================
/** 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 (true, 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 create ([functionToCall, param, comp = WeakReference<Component> { component }] (int r)
{
functionToCall (r, static_cast<ComponentType*> (comp.get()), param);
});
}
private:
ModalCallbackFunction() = delete;
~ModalCallbackFunction() = delete;
};
} // namespace juce

View File

@ -0,0 +1,348 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
Desktop::Desktop()
: mouseSources (new MouseInputSource::SourceList()),
masterScaleFactor ((float) getDefaultMasterScale()),
nativeDarkModeChangeDetectorImpl (createNativeDarkModeChangeDetectorImpl())
{
displays.reset (new Displays (*this));
}
Desktop::~Desktop()
{
setScreenSaverEnabled (true);
animator.cancelAllAnimations (false);
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 (int index) const noexcept
{
return desktopComponents [index];
}
Component* Desktop::findComponentAt (Point<int> screenPosition) const
{
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
for (int i = desktopComponents.size(); --i >= 0;)
{
auto* c = desktopComponents.getUnchecked(i);
if (c->isVisible())
{
auto relative = c->getLocalPoint (nullptr, screenPosition);
if (c->contains (relative))
return c->getComponentAt (relative);
}
}
return nullptr;
}
//==============================================================================
LookAndFeel& Desktop::getDefaultLookAndFeel() noexcept
{
if (auto lf = currentLookAndFeel.get())
return *lf;
if (defaultLookAndFeel == nullptr)
defaultLookAndFeel.reset (new LookAndFeel_V4());
auto lf = defaultLookAndFeel.get();
jassert (lf != nullptr);
currentLookAndFeel = lf;
return *lf;
}
void Desktop::setDefaultLookAndFeel (LookAndFeel* newDefaultLookAndFeel)
{
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
currentLookAndFeel = newDefaultLookAndFeel;
for (int i = getNumComponents(); --i >= 0;)
if (auto* c = getComponent (i))
c->sendLookAndFeelChange();
}
//==============================================================================
void Desktop::addDesktopComponent (Component* c)
{
jassert (c != nullptr);
jassert (! desktopComponents.contains (c));
desktopComponents.addIfNotAlreadyThere (c);
}
void Desktop::removeDesktopComponent (Component* c)
{
desktopComponents.removeFirstMatchingValue (c);
}
void Desktop::componentBroughtToFront (Component* c)
{
auto 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* l) { focusListeners.add (l); }
void Desktop::removeFocusChangeListener (FocusChangeListener* l) { focusListeners.remove (l); }
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).
focusListeners.call ([currentFocus = WeakReference<Component> { Component::getCurrentlyFocusedComponent() }] (FocusChangeListener& l)
{
l.globalFocusChanged (currentFocus.get());
});
}
//==============================================================================
void Desktop::addDarkModeSettingListener (DarkModeSettingListener* l) { darkModeSettingListeners.add (l); }
void Desktop::removeDarkModeSettingListener (DarkModeSettingListener* l) { darkModeSettingListeners.remove (l); }
void Desktop::darkModeChanged() { darkModeSettingListeners.call ([] (DarkModeSettingListener& l) { l.darkModeSettingChanged(); }); }
//==============================================================================
void Desktop::resetTimer()
{
if (mouseListeners.size() == 0)
stopTimer();
else
startTimer (100);
lastFakeMouseMove = getMousePositionFloat();
}
ListenerList<MouseListener>& Desktop::getMouseListeners()
{
resetTimer();
return mouseListeners;
}
void Desktop::addGlobalMouseListener (MouseListener* listener)
{
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
mouseListeners.add (listener);
resetTimer();
}
void Desktop::removeGlobalMouseListener (MouseListener* listener)
{
JUCE_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 (auto* target = findComponentAt (lastFakeMouseMove.roundToInt()))
{
Component::BailOutChecker checker (target);
auto pos = target->getLocalPoint (nullptr, lastFakeMouseMove);
auto now = Time::getCurrentTime();
const MouseEvent me (getMainMouseSource(), pos, ModifierKeys::currentModifiers, MouseInputSource::invalidPressure,
MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
target, target, now, pos, now, 0, false);
if (me.mods.isAnyMouseButtonDown())
mouseListeners.callChecked (checker, [&] (MouseListener& l) { l.mouseDrag (me); });
else
mouseListeners.callChecked (checker, [&] (MouseListener& l) { l.mouseMove (me); });
}
}
}
//==============================================================================
void Desktop::setKioskModeComponent (Component* componentToUse, 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 (auto* 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 (int newOrientations)
{
if (allowedOrientations != 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;
allowedOrientationsChanged();
}
}
int Desktop::getOrientationsEnabled() const noexcept
{
return allowedOrientations;
}
bool Desktop::isOrientationEnabled (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
{
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
if (masterScaleFactor != newScaleFactor)
{
masterScaleFactor = newScaleFactor;
displays->refresh();
}
}
bool Desktop::isHeadless() const noexcept
{
return displays->displays.isEmpty();
}
} // namespace juce

View File

@ -0,0 +1,481 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
class JUCE_API FocusChangeListener
{
public:
/** Destructor. */
virtual ~FocusChangeListener() = default;
/** Callback to indicate that the currently focused component has changed. */
virtual void globalFocusChanged (Component* focusedComponent) = 0;
};
//==============================================================================
/**
Classes can implement this interface and register themselves with the Desktop class
to receive callbacks when the operating system dark mode setting changes. The
Desktop::isDarkModeActive() method can then be used to query the current setting.
@see Desktop::addDarkModeSettingListener, Desktop::removeDarkModeSettingListener,
Desktop::isDarkModeActive
@tags{GUI}
*/
class JUCE_API DarkModeSettingListener
{
public:
/** Destructor. */
virtual ~DarkModeSettingListener() = default;
/** Callback to indicate that the dark mode setting has changed. */
virtual void darkModeSettingChanged() = 0;
};
//==============================================================================
/**
Describes and controls aspects of the computer's desktop.
@tags{GUI}
*/
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.
Note that this is a pretty old method, kept around mainly for backwards-compatibility,
and you should use the MouseInputSource class directly in new code.
*/
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 addGlobalMouseListener().
@see addGlobalMouseListener
*/
void removeGlobalMouseListener (MouseListener* listener);
//==============================================================================
/** Registers a FocusChangeListener that will receive a callback whenever the focused
component changes.
@see removeFocusChangeListener
*/
void addFocusChangeListener (FocusChangeListener* listener);
/** Unregisters a FocusChangeListener that was added with addFocusChangeListener().
@see addFocusChangeListener
*/
void removeFocusChangeListener (FocusChangeListener* listener);
//==============================================================================
/** Registers a DarkModeSettingListener that will receive a callback when the
operating system dark mode setting changes. To query whether dark mode is on
use the isDarkModeActive() method.
@see isDarkModeActive, removeDarkModeSettingListener
*/
void addDarkModeSettingListener (DarkModeSettingListener* listener);
/** Unregisters a DarkModeSettingListener that was added with addDarkModeSettingListener().
@see addDarkModeSettingListener
*/
void removeDarkModeSettingListener (DarkModeSettingListener* listener);
/** True if the operating system "dark mode" is active.
To receive a callback when this setting changes implement the DarkModeSettingListener
interface and use the addDarkModeSettingListener() to register a listener.
@see addDarkModeSettingListener, removeDarkModeSettingListener
*/
bool isDarkModeActive() const;
//==============================================================================
/** 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 paraphernalia 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/mobile device which can be turned around, this is used to indicate the orientation. */
enum DisplayOrientation
{
upright = 1, /**< Indicates that the device is the normal way up. */
upsideDown = 2, /**< Indicates that the device is upside-down. */
rotatedClockwise = 4, /**< Indicates that the device is turned 90 degrees clockwise from its upright position. */
rotatedAntiClockwise = 8, /**< Indicates that the device 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 the set of orientations the display is allowed to rotate to.
@see setOrientationsEnabled
*/
int getOrientationsEnabled() const noexcept;
/** 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;
//==============================================================================
/** Returns the Displays object representing the connected displays.
@see Displays
*/
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;
#if JUCE_MAC && ! defined (DOXYGEN)
[[deprecated ("This macOS-specific method has been deprecated in favour of the cross-platform "
" isDarkModeActive() method.")]]
static bool isOSXDarkModeActive() { return Desktop::getInstance().isDarkModeActive(); }
#endif
//==============================================================================
/** Returns true on a headless system where there are no connected displays. */
bool isHeadless() const noexcept;
private:
//==============================================================================
static Desktop* instance;
friend class Component;
friend class ComponentPeer;
friend class MouseInputSourceInternal;
friend class DeletedAtShutdown;
friend class TopLevelWindowManager;
friend class Displays;
std::unique_ptr<MouseInputSource::SourceList> mouseSources;
ListenerList<MouseListener> mouseListeners;
ListenerList<FocusChangeListener> focusListeners;
ListenerList<DarkModeSettingListener> darkModeSettingListeners;
Array<Component*> desktopComponents;
Array<ComponentPeer*> peers;
std::unique_ptr<Displays> displays;
Point<float> lastFakeMouseMove;
void sendMouseMove();
int mouseClickCounter = 0, mouseWheelCounter = 0;
void incrementMouseClickCounter() noexcept;
void incrementMouseWheelCounter() noexcept;
std::unique_ptr<LookAndFeel> defaultLookAndFeel;
WeakReference<LookAndFeel> currentLookAndFeel;
Component* kioskModeComponent = nullptr;
Rectangle<int> kioskComponentOriginalBounds;
bool kioskModeReentrant = false;
int allowedOrientations = allOrientations;
void allowedOrientationsChanged();
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() override;
//==============================================================================
class NativeDarkModeChangeDetectorImpl;
std::unique_ptr<NativeDarkModeChangeDetectorImpl> nativeDarkModeChangeDetectorImpl;
static std::unique_ptr<NativeDarkModeChangeDetectorImpl> createNativeDarkModeChangeDetectorImpl();
void darkModeChanged();
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Desktop)
};
} // namespace juce

View File

@ -0,0 +1,438 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
Displays::Displays (Desktop& desktop)
{
init (desktop);
}
void Displays::init (Desktop& desktop)
{
findDisplays (desktop.getGlobalScaleFactor());
}
const Displays::Display* Displays::getDisplayForRect (Rectangle<int> rect, bool isPhysical) const noexcept
{
int maxArea = -1;
const Display* foundDisplay = nullptr;
for (auto& display : displays)
{
auto displayArea = display.totalArea;
if (isPhysical)
displayArea = (displayArea.withZeroOrigin() * display.scale) + display.topLeftPhysical;
displayArea = displayArea.getIntersection (rect);
auto area = displayArea.getWidth() * displayArea.getHeight();
if (area >= maxArea)
{
maxArea = area;
foundDisplay = &display;
}
}
return foundDisplay;
}
const Displays::Display* Displays::getDisplayForPoint (Point<int> point, bool isPhysical) const noexcept
{
auto minDistance = std::numeric_limits<int>::max();
const Display* foundDisplay = nullptr;
for (auto& display : displays)
{
auto displayArea = display.totalArea;
if (isPhysical)
displayArea = (displayArea.withZeroOrigin() * display.scale) + display.topLeftPhysical;
if (displayArea.contains (point))
return &display;
auto distance = displayArea.getCentre().getDistanceFrom (point);
if (distance <= minDistance)
{
minDistance = distance;
foundDisplay = &display;
}
}
return foundDisplay;
}
Rectangle<int> Displays::physicalToLogical (Rectangle<int> rect, const Display* useScaleFactorOfDisplay) const noexcept
{
return physicalToLogical (rect.toFloat(), useScaleFactorOfDisplay).toNearestInt();
}
Rectangle<float> Displays::physicalToLogical (Rectangle<float> rect, const Display* useScaleFactorOfDisplay) const noexcept
{
const auto* display = useScaleFactorOfDisplay != nullptr ? useScaleFactorOfDisplay
: getDisplayForRect (rect.toNearestInt(), true);
if (display == nullptr)
return rect;
auto globalScale = Desktop::getInstance().getGlobalScaleFactor();
return ((rect - display->topLeftPhysical.toFloat()) / (display->scale / globalScale))
+ (display->totalArea.getTopLeft().toFloat() * globalScale);
}
Rectangle<int> Displays::logicalToPhysical (Rectangle<int> rect, const Display* useScaleFactorOfDisplay) const noexcept
{
return logicalToPhysical (rect.toFloat(), useScaleFactorOfDisplay).toNearestInt();
}
Rectangle<float> Displays::logicalToPhysical (Rectangle<float> rect, const Display* useScaleFactorOfDisplay) const noexcept
{
const auto* display = useScaleFactorOfDisplay != nullptr ? useScaleFactorOfDisplay
: getDisplayForRect (rect.toNearestInt(), false);
if (display == nullptr)
return rect;
auto globalScale = Desktop::getInstance().getGlobalScaleFactor();
return ((rect.toFloat() - (display->totalArea.getTopLeft().toFloat() * globalScale)) * (display->scale / globalScale))
+ display->topLeftPhysical.toFloat();
}
template <typename ValueType>
Point<ValueType> Displays::physicalToLogical (Point<ValueType> point, const Display* useScaleFactorOfDisplay) const noexcept
{
const auto* display = useScaleFactorOfDisplay != nullptr ? useScaleFactorOfDisplay
: getDisplayForPoint (point.roundToInt(), true);
if (display == nullptr)
return point;
auto globalScale = Desktop::getInstance().getGlobalScaleFactor();
Point<ValueType> logicalTopLeft (static_cast<ValueType> (display->totalArea.getX()), static_cast<ValueType> (display->totalArea.getY()));
Point<ValueType> physicalTopLeft (static_cast<ValueType> (display->topLeftPhysical.getX()), static_cast<ValueType> (display->topLeftPhysical.getY()));
return ((point - physicalTopLeft) / (display->scale / globalScale)) + (logicalTopLeft * globalScale);
}
template <typename ValueType>
Point<ValueType> Displays::logicalToPhysical (Point<ValueType> point, const Display* useScaleFactorOfDisplay) const noexcept
{
const auto* display = useScaleFactorOfDisplay != nullptr ? useScaleFactorOfDisplay
: getDisplayForPoint (point.roundToInt(), false);
if (display == nullptr)
return point;
auto globalScale = Desktop::getInstance().getGlobalScaleFactor();
Point<ValueType> logicalTopLeft (static_cast<ValueType> (display->totalArea.getX()), static_cast<ValueType> (display->totalArea.getY()));
Point<ValueType> physicalTopLeft (static_cast<ValueType> (display->topLeftPhysical.getX()), static_cast<ValueType> (display->topLeftPhysical.getY()));
return ((point - (logicalTopLeft * globalScale)) * (display->scale / globalScale)) + physicalTopLeft;
}
const Displays::Display* Displays::getPrimaryDisplay() const noexcept
{
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
for (auto& d : displays)
if (d.isMain)
return &d;
return nullptr;
}
RectangleList<int> Displays::getRectangleList (bool userAreasOnly) const
{
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
RectangleList<int> rl;
for (auto& d : displays)
rl.addWithoutMerging (userAreasOnly ? d.userArea : d.totalArea);
return rl;
}
Rectangle<int> Displays::getTotalBounds (bool userAreasOnly) const
{
return getRectangleList (userAreasOnly).getBounds();
}
void Displays::refresh()
{
Array<Display> oldDisplays;
oldDisplays.swapWith (displays);
init (Desktop::getInstance());
if (oldDisplays != displays)
{
for (auto i = ComponentPeer::getNumPeers(); --i >= 0;)
if (auto* peer = ComponentPeer::getPeer (i))
peer->handleScreenSizeChange();
}
}
bool operator== (const Displays::Display& d1, const Displays::Display& d2) noexcept;
bool operator== (const Displays::Display& d1, const Displays::Display& d2) noexcept
{
return d1.isMain == d2.isMain
&& d1.totalArea == d2.totalArea
&& d1.userArea == d2.userArea
&& d1.topLeftPhysical == d2.topLeftPhysical
&& d1.scale == d2.scale
&& d1.dpi == d2.dpi;
}
bool operator!= (const Displays::Display& d1, const Displays::Display& d2) noexcept;
bool operator!= (const Displays::Display& d1, const Displays::Display& d2) noexcept { return ! (d1 == d2); }
//==============================================================================
// These methods are used for converting the totalArea and userArea Rectangles in Display from physical to logical
// pixels. We do this by constructing a graph of connected displays where the root node has position (0, 0); this can be
// safely converted to logical pixels using its scale factor and we can then traverse the graph and work out the logical pixels
// for all the other connected displays. We need to do this as the logical bounds of a display depend not only on its scale
// factor but also the scale factor of the displays connected to it.
/**
Represents a node in our graph of displays.
*/
struct DisplayNode
{
/** The Display object that this represents. */
Displays::Display* display;
/** True if this represents the 'root' display with position (0, 0). */
bool isRoot = false;
/** The parent node of this node in our display graph. This will have a correct logicalArea. */
DisplayNode* parent = nullptr;
/** The logical area to be calculated. This will be valid after processDisplay() has
been called on this node.
*/
Rectangle<double> logicalArea;
};
/** Recursive - will calculate and set the logicalArea member of current. */
static void processDisplay (DisplayNode* currentNode, Array<DisplayNode>& allNodes)
{
const auto physicalArea = currentNode->display->totalArea.toDouble();
const auto scale = currentNode->display->scale;
if (! currentNode->isRoot)
{
const auto logicalWidth = physicalArea.getWidth() / scale;
const auto logicalHeight = physicalArea.getHeight() / scale;
const auto physicalParentArea = currentNode->parent->display->totalArea.toDouble();
const auto logicalParentArea = currentNode->parent->logicalArea; // logical area of parent has already been calculated
const auto parentScale = currentNode->parent->display->scale;
Rectangle<double> logicalArea (0.0, 0.0, logicalWidth, logicalHeight);
if (physicalArea.getRight() == physicalParentArea.getX()) logicalArea.setPosition ({ logicalParentArea.getX() - logicalWidth, physicalArea.getY() / parentScale }); // on left
else if (physicalArea.getX() == physicalParentArea.getRight()) logicalArea.setPosition ({ logicalParentArea.getRight(), physicalArea.getY() / parentScale }); // on right
else if (physicalArea.getBottom() == physicalParentArea.getY()) logicalArea.setPosition ({ physicalArea.getX() / parentScale, logicalParentArea.getY() - logicalHeight }); // on top
else if (physicalArea.getY() == physicalParentArea.getBottom()) logicalArea.setPosition ({ physicalArea.getX() / parentScale, logicalParentArea.getBottom() }); // on bottom
else jassertfalse;
currentNode->logicalArea = logicalArea;
}
else
{
// If currentNode is the root (position (0, 0)) then we can just scale the physical area
currentNode->logicalArea = physicalArea / scale;
currentNode->parent = currentNode;
}
// Find child nodes
Array<DisplayNode*> children;
for (auto& node : allNodes)
{
// Already calculated
if (node.parent != nullptr)
continue;
const auto otherPhysicalArea = node.display->totalArea.toDouble();
// If the displays are touching on any side
if (otherPhysicalArea.getX() == physicalArea.getRight() || otherPhysicalArea.getRight() == physicalArea.getX()
|| otherPhysicalArea.getY() == physicalArea.getBottom() || otherPhysicalArea.getBottom() == physicalArea.getY())
{
node.parent = currentNode;
children.add (&node);
}
}
// Recursively process all child nodes
for (auto child : children)
processDisplay (child, allNodes);
}
/** This is called when the displays Array has been filled out with the info for all connected displays and the
totalArea and userArea Rectangles need to be converted from physical to logical coordinates.
*/
void Displays::updateToLogical()
{
if (displays.size() == 1)
{
auto& display = displays.getReference (0);
display.totalArea = (display.totalArea.toDouble() / display.scale).toNearestInt();
display.userArea = (display.userArea.toDouble() / display.scale).toNearestInt();
return;
}
Array<DisplayNode> displayNodes;
for (auto& d : displays)
{
DisplayNode node;
node.display = &d;
if (d.totalArea.getTopLeft() == Point<int>())
node.isRoot = true;
displayNodes.add (node);
}
auto* root = [&displayNodes]() -> DisplayNode*
{
for (auto& node : displayNodes)
if (node.isRoot)
return &node;
auto minDistance = std::numeric_limits<int>::max();
DisplayNode* retVal = nullptr;
for (auto& node : displayNodes)
{
auto distance = node.display->totalArea.getTopLeft().getDistanceFrom ({});
if (distance < minDistance)
{
minDistance = distance;
retVal = &node;
}
}
if (retVal != nullptr)
retVal->isRoot = true;
return retVal;
}();
// Must have a root node!
jassert (root != nullptr);
// Recursively traverse the display graph from the root and work out logical bounds
processDisplay (root, displayNodes);
for (auto& node : displayNodes)
{
// All of the nodes should have a parent
jassert (node.parent != nullptr);
auto relativeUserArea = (node.display->userArea.toDouble() - node.display->totalArea.toDouble().getTopLeft()) / node.display->scale;
// Now set Display::totalArea and ::userArea using the logical area that we have calculated
node.display->topLeftPhysical = node.display->totalArea.getTopLeft();
node.display->totalArea = node.logicalArea.toNearestInt();
node.display->userArea = (relativeUserArea + node.logicalArea.getTopLeft()).toNearestInt();
}
}
#ifndef DOXYGEN
// explicit template instantiations
template Point<int> Displays::physicalToLogical (Point<int>, const Display*) const noexcept;
template Point<float> Displays::physicalToLogical (Point<float>, const Display*) const noexcept;
template Point<int> Displays::logicalToPhysical (Point<int>, const Display*) const noexcept;
template Point<float> Displays::logicalToPhysical (Point<float>, const Display*) const noexcept;
#endif
//==============================================================================
// Deprecated methods
const Displays::Display& Displays::getDisplayContaining (Point<int> position) const noexcept
{
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
const auto* best = &displays.getReference (0);
auto bestDistance = std::numeric_limits<int>::max();
for (auto& d : displays)
{
if (d.totalArea.contains (position))
{
best = &d;
break;
}
auto distance = d.totalArea.getCentre().getDistanceFrom (position);
if (distance < bestDistance)
{
bestDistance = distance;
best = &d;
}
}
return *best;
}
const Displays::Display& Displays::findDisplayForRect (Rectangle<int> rect, bool isPhysical) const noexcept
{
if (auto* display = getDisplayForRect (rect, isPhysical))
return *display;
return emptyDisplay;
}
const Displays::Display& Displays::findDisplayForPoint (Point<int> point, bool isPhysical) const noexcept
{
if (auto* display = getDisplayForPoint (point, isPhysical))
return *display;
return emptyDisplay;
}
const Displays::Display& Displays::getMainDisplay() const noexcept
{
if (auto* display = getPrimaryDisplay())
return *display;
return emptyDisplay;
}
} // namespace juce

View File

@ -0,0 +1,194 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Manages details about connected display devices.
@tags{GUI}
*/
class JUCE_API Displays
{
private:
Displays (Desktop&);
public:
//==============================================================================
/** Represents a connected display device. */
struct JUCE_API Display
{
/** This will be true if this is the user's main display device. */
bool isMain;
/** The total area of this display in logical pixels including any OS-dependent objects
like the taskbar, menu bar, etc.
*/
Rectangle<int> totalArea;
/** The total area of this display in logical pixels which isn't covered by OS-dependent
objects like the taskbar, menu bar, etc.
*/
Rectangle<int> userArea;
/** Represents the area of this display in logical pixels that is not functional for
displaying content.
On mobile devices this may be the area covered by display cutouts and notches, where
you still want to draw a background but should not position important content.
*/
BorderSize<int> safeAreaInsets;
/** The top-left of this display in physical coordinates. */
Point<int> topLeftPhysical;
/** The scale factor of this display.
For higher-resolution displays, or displays with a user-defined scale factor set,
this may be a value other than 1.0.
This value is used to convert between physical and logical pixels. For example, a Component
with size 10x10 will use 20x20 physical pixels on a display with a scale factor of 2.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;
};
//==============================================================================
/** Converts an integer Rectangle from physical to logical pixels.
If useScaleFactorOfDisplay is not null then its scale factor will be used for the conversion
regardless of the display that the Rectangle to be converted is on.
*/
Rectangle<int> physicalToLogical (Rectangle<int> physicalRect,
const Display* useScaleFactorOfDisplay = nullptr) const noexcept;
/** Converts a floating-point Rectangle from physical to logical pixels.
If useScaleFactorOfDisplay is not null then its scale factor will be used for the conversion
regardless of the display that the Rectangle to be converted is on.
*/
Rectangle<float> physicalToLogical (Rectangle<float> physicalRect,
const Display* useScaleFactorOfDisplay = nullptr) const noexcept;
/** Converts an integer Rectangle from logical to physical pixels.
If useScaleFactorOfDisplay is not null then its scale factor will be used for the conversion
regardless of the display that the Rectangle to be converted is on.
*/
Rectangle<int> logicalToPhysical (Rectangle<int> logicalRect,
const Display* useScaleFactorOfDisplay = nullptr) const noexcept;
/** Converts a floating-point Rectangle from logical to physical pixels.
If useScaleFactorOfDisplay is not null then its scale factor will be used for the conversion
regardless of the display that the Rectangle to be converted is on.
*/
Rectangle<float> logicalToPhysical (Rectangle<float> logicalRect,
const Display* useScaleFactorOfDisplay = nullptr) const noexcept;
/** Converts a Point from physical to logical pixels.
If useScaleFactorOfDisplay is not null then its scale factor will be used for the conversion
regardless of the display that the Point to be converted is on.
*/
template <typename ValueType>
Point<ValueType> physicalToLogical (Point<ValueType> physicalPoint,
const Display* useScaleFactorOfDisplay = nullptr) const noexcept;
/** Converts a Point from logical to physical pixels.
If useScaleFactorOfDisplay is not null then its scale factor will be used for the conversion
regardless of the display that the Point to be converted is on.
*/
template <typename ValueType>
Point<ValueType> logicalToPhysical (Point<ValueType> logicalPoint,
const Display* useScaleFactorOfDisplay = nullptr) const noexcept;
/** Returns the Display object representing the display containing a given Rectangle (either
in logical or physical pixels), or nullptr if there are no connected displays.
If the Rectangle lies outside all the displays then the nearest one will be returned.
*/
const Display* getDisplayForRect (Rectangle<int> rect, bool isPhysical = false) const noexcept;
/** Returns the Display object representing the display containing a given Point (either
in logical or physical pixels), or nullptr if there are no connected displays.
If the Point lies outside all the displays then the nearest one will be returned.
*/
const Display* getDisplayForPoint (Point<int> point, bool isPhysical = false) const noexcept;
/** Returns the Display object representing the display acting as the user's main screen, or nullptr
if there are no connected displays.
*/
const Display* getPrimaryDisplay() const noexcept;
/** Returns a RectangleList made up of all the displays in LOGICAL pixels. */
RectangleList<int> getRectangleList (bool userAreasOnly) const;
/** Returns the smallest bounding box which contains all the displays in LOGICAL pixels. */
Rectangle<int> getTotalBounds (bool userAreasOnly) const;
/** An Array containing the Display objects for all of the connected displays. */
Array<Display> displays;
#ifndef DOXYGEN
/** @internal */
void refresh();
/** @internal */
~Displays() = default;
[[deprecated ("Use the getDisplayForPoint or getDisplayForRect methods instead "
"as they can deal with converting between logical and physical pixels.")]]
const Display& getDisplayContaining (Point<int> position) const noexcept;
// These methods have been deprecated - use the methods which return a Display* instead as they will return
// nullptr on headless systems with no connected displays
[[deprecated]] const Display& findDisplayForRect (Rectangle<int>, bool isPhysical = false) const noexcept;
[[deprecated]] const Display& findDisplayForPoint (Point<int>, bool isPhysical = false) const noexcept;
[[deprecated]] const Display& getMainDisplay() const noexcept;
#endif
private:
friend class Desktop;
void init (Desktop&);
void findDisplays (float masterScale);
void updateToLogical();
Display emptyDisplay;
};
} // namespace juce

View File

@ -0,0 +1,201 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
Drawable::Drawable()
{
setInterceptsMouseClicks (false, false);
setPaintingIsUnclipped (true);
setAccessible (false);
}
Drawable::Drawable (const Drawable& other)
: Component (other.getName())
{
setInterceptsMouseClicks (false, false);
setPaintingIsUnclipped (true);
setAccessible (false);
setComponentID (other.getComponentID());
setTransform (other.getTransform());
if (auto* clipPath = other.drawableClipPath.get())
setClipPath (clipPath->createCopy());
}
Drawable::~Drawable()
{
}
void Drawable::applyDrawableClipPath (Graphics& g)
{
if (drawableClipPath != nullptr)
{
auto clipPath = drawableClipPath->getOutlineAsPath();
if (! clipPath.isEmpty())
g.getInternalContext().clipToPath (clipPath, {});
}
}
//==============================================================================
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));
applyDrawableClipPath (g);
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, 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::setClipPath (std::unique_ptr<Drawable> clipPath)
{
if (drawableClipPath != clipPath)
{
drawableClipPath = std::move (clipPath);
repaint();
}
}
void Drawable::transformContextToCorrectOrigin (Graphics& g)
{
g.setOrigin (originRelativeToComponent);
}
void Drawable::parentHierarchyChanged()
{
setBoundsToEnclose (getDrawableBounds());
}
void Drawable::setBoundsToEnclose (Rectangle<float> area)
{
Point<int> parentOrigin;
if (auto* parent = getParent())
parentOrigin = parent->originRelativeToComponent;
auto newBounds = area.getSmallestIntegerContainer() + parentOrigin;
originRelativeToComponent = parentOrigin - newBounds.getPosition();
setBounds (newBounds);
}
//==============================================================================
bool Drawable::replaceColour (Colour original, Colour replacement)
{
bool changed = false;
for (auto* c : getChildren())
if (auto* d = dynamic_cast<Drawable*> (c))
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));
}
//==============================================================================
std::unique_ptr<Drawable> Drawable::createFromImageData (const void* data, const size_t numBytes)
{
auto image = ImageFileFormat::loadFrom (data, numBytes);
if (image.isValid())
return std::make_unique<DrawableImage> (image);
if (auto svg = parseXMLIfTagMatches (String::createStringFromData (data, (int) numBytes), "svg"))
return Drawable::createFromSVG (*svg);
return {};
}
std::unique_ptr<Drawable> Drawable::createFromImageDataStream (InputStream& dataSource)
{
MemoryOutputStream mo;
mo << dataSource;
return createFromImageData (mo.getData(), mo.getDataSize());
}
std::unique_ptr<Drawable> Drawable::createFromImageFile (const File& file)
{
FileInputStream fin (file);
if (fin.openedOk())
return createFromImageDataStream (fin);
return {};
}
} // namespace juce

View File

@ -0,0 +1,213 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
The base class for objects which can draw themselves, e.g. polygons, images, etc.
@see DrawableComposite, DrawableImage, DrawablePath, DrawableText
@tags{GUI}
*/
class JUCE_API Drawable : public Component
{
protected:
//==============================================================================
/** The base class can't be instantiated directly.
@see DrawableComposite, DrawableImage, DrawablePath, DrawableText
*/
Drawable();
public:
/** Destructor. */
~Drawable() override;
//==============================================================================
/** 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 std::unique_ptr<Drawable> createCopy() const = 0;
/** Creates a path that describes the outline of this drawable. */
virtual Path getOutlineAsPath() 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()) 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,
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;
/** Sets a the clipping region of this drawable using another drawable.
The drawable passed in will be deleted when no longer needed.
*/
void setClipPath (std::unique_ptr<Drawable> drawableClipPath);
//==============================================================================
/** 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 std::unique_ptr<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 std::unique_ptr<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 std::unique_ptr<Drawable> createFromImageFile (const File& file);
/** Attempts to parse an SVG (Scalable Vector Graphics) document, and to turn this
into a Drawable tree.
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 std::unique_ptr<Drawable> createFromSVG (const XmlElement& svgDocument);
/** Attempts to parse an SVG (Scalable Vector Graphics) document from a file,
and to turn this into a Drawable tree.
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.
Any references to references to external image files will be relative to
the parent directory of the file passed.
*/
static std::unique_ptr<Drawable> createFromSVGFile (const File& svgFile);
/** Parses an SVG path string and returns it. */
static Path parseSVGPath (const String& svgPath);
//==============================================================================
/** Returns the area that this drawable 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);
protected:
//==============================================================================
friend class DrawableComposite;
friend class DrawableShape;
/** @internal */
void transformContextToCorrectOrigin (Graphics&);
/** @internal */
void parentHierarchyChanged() override;
/** @internal */
void setBoundsToEnclose (Rectangle<float>);
/** @internal */
void applyDrawableClipPath (Graphics&);
Point<int> originRelativeToComponent;
std::unique_ptr<Drawable> drawableClipPath;
void nonConstDraw (Graphics&, float opacity, const AffineTransform&);
Drawable (const Drawable&);
Drawable& operator= (const Drawable&);
JUCE_LEAK_DETECTOR (Drawable)
};
} // namespace juce

View File

@ -0,0 +1,164 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
DrawableComposite::DrawableComposite()
: bounds ({ 0.0f, 0.0f, 100.0f, 100.0f })
{
setContentArea ({ 0.0f, 0.0f, 100.0f, 100.0f });
}
DrawableComposite::DrawableComposite (const DrawableComposite& other)
: Drawable (other),
bounds (other.bounds),
contentArea (other.contentArea)
{
for (auto* c : other.getChildren())
if (auto* d = dynamic_cast<const Drawable*> (c))
addAndMakeVisible (d->createCopy().release());
}
DrawableComposite::~DrawableComposite()
{
deleteAllChildren();
}
std::unique_ptr<Drawable> DrawableComposite::createCopy() const
{
return std::make_unique<DrawableComposite> (*this);
}
//==============================================================================
Rectangle<float> DrawableComposite::getDrawableBounds() const
{
Rectangle<float> r;
for (auto* c : getChildren())
if (auto* d = dynamic_cast<const Drawable*> (c))
r = r.getUnion (d->isTransformed() ? d->getDrawableBounds().transformedBy (d->getTransform())
: d->getDrawableBounds());
return r;
}
void DrawableComposite::setContentArea (Rectangle<float> newArea)
{
contentArea = newArea;
}
void DrawableComposite::setBoundingBox (Rectangle<float> newBounds)
{
setBoundingBox (Parallelogram<float> (newBounds));
}
void DrawableComposite::setBoundingBox (Parallelogram<float> newBounds)
{
if (bounds != newBounds)
{
bounds = newBounds;
auto t = AffineTransform::fromTargetPoints (contentArea.getTopLeft(), bounds.topLeft,
contentArea.getTopRight(), bounds.topRight,
contentArea.getBottomLeft(), bounds.bottomLeft);
if (t.isSingularity())
t = {};
setTransform (t);
}
}
void DrawableComposite::resetBoundingBoxToContentArea()
{
setBoundingBox (contentArea);
}
void DrawableComposite::resetContentAreaAndBoundingBoxToFitChildren()
{
setContentArea (getDrawableBounds());
resetBoundingBoxToContentArea();
}
void DrawableComposite::parentHierarchyChanged()
{
if (auto* parent = getParent())
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 (auto* c : getChildren())
childArea = childArea.getUnion (c->getBoundsInParent());
auto delta = childArea.getPosition();
childArea += getPosition();
if (childArea != getBounds())
{
if (! delta.isOrigin())
{
originRelativeToComponent -= delta;
for (auto* c : getChildren())
c->setBounds (c->getBounds() - delta);
}
setBounds (childArea);
}
}
}
//==============================================================================
Path DrawableComposite::getOutlineAsPath() const
{
Path p;
for (auto* c : getChildren())
if (auto* d = dynamic_cast<Drawable*> (c))
p.addPath (d->getOutlineAsPath());
p.applyTransform (getTransform());
return p;
}
} // namespace juce

View File

@ -0,0 +1,117 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A drawable object which acts as a container for a set of other Drawables.
Note that although this is a Component, it takes ownership of its child components
and will delete them, so that you can use it as a self-contained graphic object.
The intention is that you should not add your own components to it, only add other
Drawable objects.
@see Drawable
@tags{GUI}
*/
class JUCE_API DrawableComposite : public Drawable
{
public:
//==============================================================================
/** Creates a composite Drawable. */
DrawableComposite();
/** Creates a copy of a DrawableComposite. */
DrawableComposite (const DrawableComposite&);
/** Destructor. */
~DrawableComposite() override;
//==============================================================================
/** Sets the parallelogram that defines the target position of the content rectangle when the drawable is rendered.
@see setContentArea
*/
void setBoundingBox (Parallelogram<float> newBoundingBox);
/** Sets the rectangle that defines the target position of the content rectangle when the drawable is rendered.
@see setContentArea
*/
void setBoundingBox (Rectangle<float> newBoundingBox);
/** Returns the parallelogram that defines the target position of the content rectangle when the drawable is rendered.
@see setBoundingBox
*/
Parallelogram<float> 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.
@see contentLeftMarkerName, contentRightMarkerName, contentTopMarkerName, contentBottomMarkerName
*/
Rectangle<float> getContentArea() const noexcept { return contentArea; }
/** Changes the main content area.
@see setBoundingBox, contentLeftMarkerName, contentRightMarkerName, contentTopMarkerName, contentBottomMarkerName
*/
void setContentArea (Rectangle<float> newArea);
/** Resets the content area and the bounding transform to fit around the area occupied
by the child components.
*/
void resetContentAreaAndBoundingBoxToFitChildren();
//==============================================================================
/** @internal */
std::unique_ptr<Drawable> createCopy() const override;
/** @internal */
Rectangle<float> getDrawableBounds() const override;
/** @internal */
void childBoundsChanged (Component*) override;
/** @internal */
void childrenChanged() override;
/** @internal */
void parentHierarchyChanged() override;
/** @internal */
Path getOutlineAsPath() const override;
private:
//==============================================================================
Parallelogram<float> bounds;
Rectangle<float> contentArea;
bool updateBoundsReentrant = false;
void updateBoundsToFitChildren();
DrawableComposite& operator= (const DrawableComposite&);
JUCE_LEAK_DETECTOR (DrawableComposite)
};
} // namespace juce

View File

@ -0,0 +1,156 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
DrawableImage::DrawableImage() : bounds ({ 0.0f, 0.0f, 1.0f, 1.0f })
{
}
DrawableImage::DrawableImage (const DrawableImage& other)
: Drawable (other),
image (other.image),
opacity (other.opacity),
overlayColour (other.overlayColour),
bounds (other.bounds)
{
setBounds (other.getBounds());
}
DrawableImage::DrawableImage (const Image& imageToUse)
{
setImageInternal (imageToUse);
}
DrawableImage::~DrawableImage()
{
}
std::unique_ptr<Drawable> DrawableImage::createCopy() const
{
return std::make_unique<DrawableImage> (*this);
}
//==============================================================================
void DrawableImage::setImage (const Image& imageToUse)
{
if (setImageInternal (imageToUse))
repaint();
}
void DrawableImage::setOpacity (const float newOpacity)
{
opacity = newOpacity;
}
void DrawableImage::setOverlayColour (Colour newOverlayColour)
{
overlayColour = newOverlayColour;
}
void DrawableImage::setBoundingBox (Rectangle<float> newBounds)
{
setBoundingBox (Parallelogram<float> (newBounds));
}
void DrawableImage::setBoundingBox (Parallelogram<float> newBounds)
{
if (bounds != newBounds)
{
bounds = newBounds;
if (image.isValid())
{
auto tr = bounds.topLeft + (bounds.topRight - bounds.topLeft) / (float) image.getWidth();
auto bl = bounds.topLeft + (bounds.bottomLeft - bounds.topLeft) / (float) image.getHeight();
auto t = AffineTransform::fromTargetPoints (bounds.topLeft.x, bounds.topLeft.y,
tr.x, tr.y,
bl.x, bl.y);
if (t.isSingularity())
t = {};
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;
}
Path DrawableImage::getOutlineAsPath() const
{
return {}; // not applicable for images
}
//==============================================================================
bool DrawableImage::setImageInternal (const Image& imageToUse)
{
if (image != imageToUse)
{
image = imageToUse;
setBounds (image.getBounds());
setBoundingBox (image.getBounds().toFloat());
return true;
}
return false;
}
//==============================================================================
std::unique_ptr<AccessibilityHandler> DrawableImage::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::image);
}
} // namespace juce

View File

@ -0,0 +1,116 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A drawable object which is a bitmap image.
@see Drawable
@tags{GUI}
*/
class JUCE_API DrawableImage : public Drawable
{
public:
//==============================================================================
DrawableImage();
DrawableImage (const DrawableImage&);
/** Sets the image that this drawable will render. */
explicit DrawableImage (const Image& imageToUse);
/** Destructor. */
~DrawableImage() override;
//==============================================================================
/** 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 (Parallelogram<float> newBounds);
/** Sets the bounding box within which the image should be displayed. */
void setBoundingBox (Rectangle<float> 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
*/
Parallelogram<float> getBoundingBox() const noexcept { return bounds; }
//==============================================================================
/** @internal */
void paint (Graphics&) override;
/** @internal */
bool hitTest (int x, int y) override;
/** @internal */
std::unique_ptr<Drawable> createCopy() const override;
/** @internal */
Rectangle<float> getDrawableBounds() const override;
/** @internal */
Path getOutlineAsPath() const override;
private:
//==============================================================================
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
bool setImageInternal (const Image&);
//==============================================================================
Image image;
float opacity = 1.0f;
Colour overlayColour { 0 };
Parallelogram<float> bounds;
DrawableImage& operator= (const DrawableImage&);
JUCE_LEAK_DETECTOR (DrawableImage)
};
} // namespace juce

View File

@ -0,0 +1,57 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
DrawablePath::DrawablePath() {}
DrawablePath::~DrawablePath() {}
DrawablePath::DrawablePath (const DrawablePath& other) : DrawableShape (other)
{
setPath (other.path);
}
std::unique_ptr<Drawable> DrawablePath::createCopy() const
{
return std::make_unique<DrawablePath> (*this);
}
void DrawablePath::setPath (const Path& newPath)
{
path = newPath;
pathChanged();
}
void DrawablePath::setPath (Path&& newPath)
{
path = std::move (newPath);
pathChanged();
}
const Path& DrawablePath::getPath() const { return path; }
const Path& DrawablePath::getStrokePath() const { return strokePath; }
} // namespace juce

View File

@ -0,0 +1,77 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
class JUCE_API DrawablePath : public DrawableShape
{
public:
//==============================================================================
/** Creates a DrawablePath. */
DrawablePath();
DrawablePath (const DrawablePath&);
/** Destructor. */
~DrawablePath() override;
//==============================================================================
/** Changes the path that will be drawn.
@see setFill, setStrokeType
*/
void setPath (const Path& newPath);
/** Changes the path that will be drawn.
@see setFill, setStrokeType
*/
void setPath (Path&& newPath);
/** Returns the current path. */
const Path& getPath() const;
/** Returns the current path for the outline. */
const Path& getStrokePath() const;
//==============================================================================
/** @internal */
std::unique_ptr<Drawable> createCopy() const override;
private:
//==============================================================================
DrawablePath& operator= (const DrawablePath&);
JUCE_LEAK_DETECTOR (DrawablePath)
};
} // namespace juce

View File

@ -0,0 +1,87 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
DrawableRectangle::DrawableRectangle() {}
DrawableRectangle::~DrawableRectangle() {}
DrawableRectangle::DrawableRectangle (const DrawableRectangle& other)
: DrawableShape (other),
bounds (other.bounds),
cornerSize (other.cornerSize)
{
rebuildPath();
}
std::unique_ptr<Drawable> DrawableRectangle::createCopy() const
{
return std::make_unique<DrawableRectangle> (*this);
}
//==============================================================================
void DrawableRectangle::setRectangle (Parallelogram<float> newBounds)
{
if (bounds != newBounds)
{
bounds = newBounds;
rebuildPath();
}
}
void DrawableRectangle::setCornerSize (Point<float> newSize)
{
if (cornerSize != newSize)
{
cornerSize = newSize;
rebuildPath();
}
}
void DrawableRectangle::rebuildPath()
{
auto w = bounds.getWidth();
auto h = bounds.getHeight();
Path newPath;
if (cornerSize.x > 0 && cornerSize.y > 0)
newPath.addRoundedRectangle (0, 0, w, h, cornerSize.x, cornerSize.y);
else
newPath.addRectangle (0, 0, w, h);
newPath.applyTransform (AffineTransform::fromTargetPoints (Point<float>(), bounds.topLeft,
Point<float> (w, 0), bounds.topRight,
Point<float> (0, h), bounds.bottomLeft));
if (path != newPath)
{
path.swapWithPath (newPath);
pathChanged();
}
}
} // namespace juce

View File

@ -0,0 +1,76 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A Drawable object which draws a rectangle.
For details on how to change the fill and stroke, see the DrawableShape class.
@see Drawable, DrawableShape
@tags{GUI}
*/
class JUCE_API DrawableRectangle : public DrawableShape
{
public:
//==============================================================================
DrawableRectangle();
DrawableRectangle (const DrawableRectangle&);
/** Destructor. */
~DrawableRectangle() override;
//==============================================================================
/** Sets the rectangle's bounds. */
void setRectangle (Parallelogram<float> newBounds);
/** Returns the rectangle's bounds. */
Parallelogram<float> getRectangle() const noexcept { return bounds; }
/** Returns the corner size to be used. */
Point<float> getCornerSize() const noexcept { return cornerSize; }
/** Sets a new corner size for the rectangle */
void setCornerSize (Point<float> newSize);
//==============================================================================
/** @internal */
std::unique_ptr<Drawable> createCopy() const override;
private:
Parallelogram<float> bounds;
Point<float> cornerSize;
void rebuildPath();
DrawableRectangle& operator= (const DrawableRectangle&);
JUCE_LEAK_DETECTOR (DrawableRectangle)
};
} // namespace juce

View File

@ -0,0 +1,181 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
DrawableShape::DrawableShape()
: strokeType (0.0f),
mainFill (Colours::black),
strokeFill (Colours::black)
{
}
DrawableShape::DrawableShape (const DrawableShape& other)
: Drawable (other),
strokeType (other.strokeType),
dashLengths (other.dashLengths),
mainFill (other.mainFill),
strokeFill (other.strokeFill)
{
}
DrawableShape::~DrawableShape()
{
}
//==============================================================================
void DrawableShape::setFill (const FillType& newFill)
{
if (mainFill != newFill)
{
mainFill = newFill;
repaint();
}
}
void DrawableShape::setStrokeFill (const FillType& newFill)
{
if (strokeFill != newFill)
{
strokeFill = newFill;
repaint();
}
}
void DrawableShape::setStrokeType (const PathStrokeType& newStrokeType)
{
if (strokeType != newStrokeType)
{
strokeType = newStrokeType;
strokeChanged();
}
}
void DrawableShape::setDashLengths (const Array<float>& newDashLengths)
{
if (dashLengths != newDashLengths)
{
dashLengths = newDashLengths;
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.isInvisible();
}
//==============================================================================
void DrawableShape::paint (Graphics& g)
{
transformContextToCorrectOrigin (g);
applyDrawableClipPath (g);
g.setFillType (mainFill);
g.fillPath (path);
if (isStrokeVisible())
{
g.setFillType (strokeFill);
g.fillPath (strokePath);
}
}
void DrawableShape::pathChanged()
{
strokeChanged();
}
void DrawableShape::strokeChanged()
{
strokePath.clear();
const float extraAccuracy = 4.0f;
if (dashLengths.isEmpty())
strokeType.createStrokedPath (strokePath, path, AffineTransform(), extraAccuracy);
else
strokeType.createDashedStroke (strokePath, path, dashLengths.getRawDataPointer(),
dashLengths.size(), AffineTransform(), extraAccuracy);
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;
auto globalX = (float) (x - originRelativeToComponent.x);
auto globalY = (float) (y - originRelativeToComponent.y);
return path.contains (globalX, globalY)
|| (isStrokeVisible() && strokePath.contains (globalX, globalY));
}
//==============================================================================
static bool replaceColourInFill (FillType& fill, Colour original, Colour replacement)
{
if (fill.colour == original && 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;
}
Path DrawableShape::getOutlineAsPath() const
{
auto outline = isStrokeVisible() ? strokePath : path;
outline.applyTransform (getTransform());
return outline;
}
} // namespace juce

View File

@ -0,0 +1,126 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A base class implementing common functionality for Drawable classes which
consist of some kind of filled and stroked outline.
@see DrawablePath, DrawableRectangle
@tags{GUI}
*/
class JUCE_API DrawableShape : public Drawable
{
protected:
//==============================================================================
DrawableShape();
DrawableShape (const DrawableShape&);
public:
/** Destructor. */
~DrawableShape() override;
//==============================================================================
/** 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);
/** Returns the current fill type.
@see setFill
*/
const FillType& getFill() const noexcept { return mainFill; }
/** Sets the fill type with which the outline will be drawn.
@see setFill
*/
void setStrokeFill (const FillType& newStrokeFill);
/** Returns the current stroke fill.
@see setStrokeFill
*/
const FillType& 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; }
/** Provides a set of dash lengths to use for stroking the path. */
void setDashLengths (const Array<float>& newDashLengths);
/** Returns the set of dash lengths that the path is using. */
const Array<float>& getDashLengths() const noexcept { return dashLengths; }
//==============================================================================
/** @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;
/** @internal */
Path getOutlineAsPath() const 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;
//==============================================================================
PathStrokeType strokeType;
Array<float> dashLengths;
Path path, strokePath;
private:
FillType mainFill, strokeFill;
DrawableShape& operator= (const DrawableShape&);
};
} // namespace juce

View File

@ -0,0 +1,232 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
DrawableText::DrawableText()
: colour (Colours::black),
justification (Justification::centredLeft)
{
setBoundingBox (Parallelogram<float> ({ 0.0f, 0.0f, 50.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()
{
}
std::unique_ptr<Drawable> DrawableText::createCopy() const
{
return std::make_unique<DrawableText> (*this);
}
//==============================================================================
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 (Parallelogram<float> newBounds)
{
if (bounds != newBounds)
{
bounds = newBounds;
refreshBounds();
}
}
void DrawableText::setFontHeight (float newHeight)
{
if (fontHeight != newHeight)
{
fontHeight = newHeight;
refreshBounds();
}
}
void DrawableText::setFontHorizontalScale (float newScale)
{
if (fontHScale != newScale)
{
fontHScale = newScale;
refreshBounds();
}
}
void DrawableText::refreshBounds()
{
auto w = bounds.getWidth();
auto h = bounds.getHeight();
auto height = jlimit (0.01f, jmax (0.01f, h), fontHeight);
auto hscale = jlimit (0.01f, jmax (0.01f, w), fontHScale);
scaledFont = font;
scaledFont.setHeight (height);
scaledFont.setHorizontalScale (hscale);
setBoundsToEnclose (getDrawableBounds());
repaint();
}
//==============================================================================
Rectangle<int> DrawableText::getTextArea (float w, float h) const
{
return Rectangle<float> (w, h).getSmallestIntegerContainer();
}
AffineTransform DrawableText::getTextTransform (float w, float h) const
{
return AffineTransform::fromTargetPoints (Point<float>(), bounds.topLeft,
Point<float> (w, 0), bounds.topRight,
Point<float> (0, h), bounds.bottomLeft);
}
void DrawableText::paint (Graphics& g)
{
transformContextToCorrectOrigin (g);
auto w = bounds.getWidth();
auto h = bounds.getHeight();
g.addTransform (getTextTransform (w, h));
g.setFont (scaledFont);
g.setColour (colour);
g.drawFittedText (text, getTextArea (w, h), justification, 0x100000);
}
Rectangle<float> DrawableText::getDrawableBounds() const
{
return bounds.getBoundingBox();
}
Path DrawableText::getOutlineAsPath() const
{
auto w = bounds.getWidth();
auto h = bounds.getHeight();
auto area = getTextArea (w, h).toFloat();
GlyphArrangement arr;
arr.addFittedText (scaledFont, text,
area.getX(), area.getY(),
area.getWidth(), area.getHeight(),
justification,
0x100000);
Path pathOfAllGlyphs;
for (auto& glyph : arr)
{
Path gylphPath;
glyph.createPath (gylphPath);
pathOfAllGlyphs.addPath (gylphPath);
}
pathOfAllGlyphs.applyTransform (getTextTransform (w, h).followedBy (getTransform()));
return pathOfAllGlyphs;
}
bool DrawableText::replaceColour (Colour originalColour, Colour replacementColour)
{
if (colour != originalColour)
return false;
setColour (replacementColour);
return true;
}
//==============================================================================
std::unique_ptr<AccessibilityHandler> DrawableText::createAccessibilityHandler()
{
class DrawableTextAccessibilityHandler : public AccessibilityHandler
{
public:
DrawableTextAccessibilityHandler (DrawableText& drawableTextToWrap)
: AccessibilityHandler (drawableTextToWrap, AccessibilityRole::staticText),
drawableText (drawableTextToWrap)
{
}
String getTitle() const override { return drawableText.getText(); }
private:
DrawableText& drawableText;
};
return std::make_unique<DrawableTextAccessibilityHandler> (*this);
}
} // namespace juce

View File

@ -0,0 +1,120 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A drawable object which renders a line of text.
@see Drawable
@tags{GUI}
*/
class JUCE_API DrawableText : public Drawable
{
public:
//==============================================================================
/** Creates a DrawableText object. */
DrawableText();
DrawableText (const DrawableText&);
/** Destructor. */
~DrawableText() override;
//==============================================================================
/** 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 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. */
Parallelogram<float> getBoundingBox() const noexcept { return bounds; }
/** Sets the bounding box that contains the text. */
void setBoundingBox (Parallelogram<float> newBounds);
float getFontHeight() const noexcept { return fontHeight; }
void setFontHeight (float newHeight);
float getFontHorizontalScale() const noexcept { return fontHScale; }
void setFontHorizontalScale (float newScale);
//==============================================================================
/** @internal */
void paint (Graphics&) override;
/** @internal */
std::unique_ptr<Drawable> createCopy() const override;
/** @internal */
Rectangle<float> getDrawableBounds() const override;
/** @internal */
Path getOutlineAsPath() const override;
/** @internal */
bool replaceColour (Colour originalColour, Colour replacementColour) override;
private:
//==============================================================================
Parallelogram<float> bounds;
float fontHeight, fontHScale;
Font font, scaledFont;
String text;
Colour colour;
Justification justification;
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
void refreshBounds();
Rectangle<int> getTextArea (float width, float height) const;
AffineTransform getTextTransform (float width, float height) const;
DrawableText& operator= (const DrawableText&);
JUCE_LEAK_DETECTOR (DrawableText)
};
} // namespace juce

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,280 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#if JUCE_CONTENT_SHARING
//==============================================================================
class ContentSharer::PrepareImagesThread : private Thread
{
public:
PrepareImagesThread (ContentSharer& cs, const Array<Image>& imagesToUse,
ImageFileFormat* imageFileFormatToUse)
: Thread ("ContentSharer::PrepareImagesThread"),
owner (cs),
images (imagesToUse),
imageFileFormat (imageFileFormatToUse == nullptr ? new PNGImageFormat()
: imageFileFormatToUse),
extension (imageFileFormat->getFormatName().toLowerCase())
{
startThread();
}
~PrepareImagesThread() override
{
signalThreadShouldExit();
waitForThreadToExit (10000);
}
private:
void run() override
{
for (const auto& image : images)
{
if (threadShouldExit())
return;
File tempFile = File::createTempFile (extension);
if (! tempFile.create().wasOk())
break;
std::unique_ptr<FileOutputStream> outputStream (tempFile.createOutputStream());
if (outputStream == nullptr)
break;
if (imageFileFormat->writeImageToStream (image, *outputStream))
owner.temporaryFiles.add (tempFile);
}
finish();
}
void finish()
{
MessageManager::callAsync ([this]() { owner.filesToSharePrepared(); });
}
ContentSharer& owner;
const Array<Image> images;
std::unique_ptr<ImageFileFormat> imageFileFormat;
String extension;
};
//==============================================================================
class ContentSharer::PrepareDataThread : private Thread
{
public:
PrepareDataThread (ContentSharer& cs, const MemoryBlock& mb)
: Thread ("ContentSharer::PrepareDataThread"),
owner (cs),
data (mb)
{
startThread();
}
~PrepareDataThread() override
{
signalThreadShouldExit();
waitForThreadToExit (10000);
}
private:
void run() override
{
File tempFile = File::createTempFile ("data");
if (tempFile.create().wasOk())
{
if (auto outputStream = std::unique_ptr<FileOutputStream> (tempFile.createOutputStream()))
{
size_t pos = 0;
size_t totalSize = data.getSize();
while (pos < totalSize)
{
if (threadShouldExit())
return;
size_t numToWrite = std::min ((size_t) 8192, totalSize - pos);
outputStream->write (data.begin() + pos, numToWrite);
pos += numToWrite;
}
owner.temporaryFiles.add (tempFile);
}
}
finish();
}
void finish()
{
MessageManager::callAsync ([this]() { owner.filesToSharePrepared(); });
}
ContentSharer& owner;
const MemoryBlock data;
};
#endif
//==============================================================================
JUCE_IMPLEMENT_SINGLETON (ContentSharer)
ContentSharer::ContentSharer() {}
ContentSharer::~ContentSharer() { clearSingletonInstance(); }
void ContentSharer::shareFiles (const Array<URL>& files,
std::function<void (bool, const String&)> callbackToUse)
{
#if JUCE_CONTENT_SHARING
startNewShare (callbackToUse);
pimpl->shareFiles (files);
#else
ignoreUnused (files);
// Content sharing is not available on this platform!
jassertfalse;
if (callbackToUse)
callbackToUse (false, "Content sharing is not available on this platform!");
#endif
}
#if JUCE_CONTENT_SHARING
void ContentSharer::startNewShare (std::function<void (bool, const String&)> callbackToUse)
{
// You should not start another sharing operation before the previous one is finished.
// Forcibly stopping a previous sharing operation is rarely a good idea!
jassert (pimpl == nullptr);
pimpl.reset();
prepareDataThread = nullptr;
prepareImagesThread = nullptr;
deleteTemporaryFiles();
// You need to pass a valid callback.
jassert (callbackToUse);
callback = std::move (callbackToUse);
pimpl.reset (createPimpl());
}
#endif
void ContentSharer::shareText (const String& text,
std::function<void (bool, const String&)> callbackToUse)
{
#if JUCE_CONTENT_SHARING
startNewShare (callbackToUse);
pimpl->shareText (text);
#else
ignoreUnused (text);
// Content sharing is not available on this platform!
jassertfalse;
if (callbackToUse)
callbackToUse (false, "Content sharing is not available on this platform!");
#endif
}
void ContentSharer::shareImages (const Array<Image>& images,
std::function<void (bool, const String&)> callbackToUse,
ImageFileFormat* imageFileFormatToUse)
{
#if JUCE_CONTENT_SHARING
startNewShare (callbackToUse);
prepareImagesThread.reset (new PrepareImagesThread (*this, images, imageFileFormatToUse));
#else
ignoreUnused (images, imageFileFormatToUse);
// Content sharing is not available on this platform!
jassertfalse;
if (callbackToUse)
callbackToUse (false, "Content sharing is not available on this platform!");
#endif
}
#if JUCE_CONTENT_SHARING
void ContentSharer::filesToSharePrepared()
{
Array<URL> urls;
for (const auto& tempFile : temporaryFiles)
urls.add (URL (tempFile));
prepareImagesThread = nullptr;
prepareDataThread = nullptr;
pimpl->shareFiles (urls);
}
#endif
void ContentSharer::shareData (const MemoryBlock& mb,
std::function<void (bool, const String&)> callbackToUse)
{
#if JUCE_CONTENT_SHARING
startNewShare (callbackToUse);
prepareDataThread.reset (new PrepareDataThread (*this, mb));
#else
ignoreUnused (mb);
if (callbackToUse)
callbackToUse (false, "Content sharing not available on this platform!");
#endif
}
void ContentSharer::sharingFinished (bool succeeded, const String& errorDescription)
{
deleteTemporaryFiles();
std::function<void (bool, String)> cb;
std::swap (cb, callback);
String error (errorDescription);
#if JUCE_CONTENT_SHARING
pimpl.reset();
#endif
if (cb)
cb (succeeded, error);
}
void ContentSharer::deleteTemporaryFiles()
{
for (auto& f : temporaryFiles)
f.deleteFile();
temporaryFiles.clear();
}
} // namespace juce

View File

@ -0,0 +1,143 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
namespace juce
{
/** A singleton class responsible for sharing content between apps and devices.
You can share text, images, files or an arbitrary data block.
@tags{GUI}
*/
class JUCE_API ContentSharer : public DeletedAtShutdown
{
public:
JUCE_DECLARE_SINGLETON (ContentSharer, false)
/** Shares the given files. Each URL should be either a full file path
or it should point to a resource within the application bundle. For
resources on iOS it should be something like "content/image.png" if you
want to specify a file from application bundle located in "content"
directory. On Android you should specify only a filename, without an
extension.
Upon completion you will receive a callback with a sharing result. Note:
Sadly on Android the returned success flag may be wrong as there is no
standard way the sharing targets report if the sharing operation
succeeded. Also, the optional error message is always empty on Android.
*/
void shareFiles (const Array<URL>& files,
std::function<void (bool /*success*/, const String& /*error*/)> callback);
/** Shares the given text.
Upon completion you will receive a callback with a sharing result. Note:
Sadly on Android the returned success flag may be wrong as there is no
standard way the sharing targets report if the sharing operation
succeeded. Also, the optional error message is always empty on Android.
*/
void shareText (const String& text,
std::function<void (bool /*success*/, const String& /*error*/)> callback);
/** A convenience function to share an image. This is useful when you have images
loaded in memory. The images will be written to temporary files first, so if
you have the images in question stored on disk already call shareFiles() instead.
By default, images will be saved to PNG files, but you can supply a custom
ImageFileFormat to override this. The custom file format will be owned and
deleted by the sharer. e.g.
@code
Graphics g (myImage);
g.setColour (Colours::green);
g.fillEllipse (20, 20, 300, 200);
Array<Image> images;
images.add (myImage);
ContentSharer::getInstance()->shareImages (images, myCallback);
@endcode
Upon completion you will receive a callback with a sharing result. Note:
Sadly on Android the returned success flag may be wrong as there is no
standard way the sharing targets report if the sharing operation
succeeded. Also, the optional error message is always empty on Android.
*/
void shareImages (const Array<Image>& images,
std::function<void (bool /*success*/, const String& /*error*/)> callback,
ImageFileFormat* imageFileFormatToUse = nullptr);
/** A convenience function to share arbitrary data. The data will be written
to a temporary file and then that file will be shared. If you have
your data stored on disk already, call shareFiles() instead.
Upon completion you will receive a callback with a sharing result. Note:
Sadly on Android the returned success flag may be wrong as there is no
standard way the sharing targets report if the sharing operation
succeeded. Also, the optional error message is always empty on Android.
*/
void shareData (const MemoryBlock& mb,
std::function<void (bool /*success*/, const String& /*error*/)> callback);
private:
ContentSharer();
~ContentSharer();
Array<File> temporaryFiles;
std::function<void (bool, String)> callback;
#if JUCE_CONTENT_SHARING
struct Pimpl
{
virtual ~Pimpl() {}
virtual void shareFiles (const Array<URL>& files) = 0;
virtual void shareText (const String& text) = 0;
};
std::unique_ptr<Pimpl> pimpl;
Pimpl* createPimpl();
void startNewShare (std::function<void (bool, const String&)>);
class ContentSharerNativeImpl;
friend class ContentSharerNativeImpl;
class PrepareImagesThread;
friend class PrepareImagesThread;
std::unique_ptr<PrepareImagesThread> prepareImagesThread;
class PrepareDataThread;
friend class PrepareDataThread;
std::unique_ptr<PrepareDataThread> prepareDataThread;
void filesToSharePrepared();
#endif
void deleteTemporaryFiles();
void sharingFinished (bool, const String&);
};
} // namespace juce

View File

@ -0,0 +1,70 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
DirectoryContentsDisplayComponent::DirectoryContentsDisplayComponent (DirectoryContentsList& l)
: directoryContentsList (l)
{
}
DirectoryContentsDisplayComponent::~DirectoryContentsDisplayComponent()
{
}
//==============================================================================
FileBrowserListener::~FileBrowserListener()
{
}
void DirectoryContentsDisplayComponent::addListener (FileBrowserListener* l) { listeners.add (l); }
void DirectoryContentsDisplayComponent::removeListener (FileBrowserListener* l) { listeners.remove (l); }
void DirectoryContentsDisplayComponent::sendSelectionChangeMessage()
{
Component::BailOutChecker checker (dynamic_cast<Component*> (this));
listeners.callChecked (checker, [] (FileBrowserListener& l) { l.selectionChanged(); });
}
void DirectoryContentsDisplayComponent::sendMouseClickMessage (const File& file, const MouseEvent& e)
{
if (directoryContentsList.getDirectory().exists())
{
Component::BailOutChecker checker (dynamic_cast<Component*> (this));
listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.fileClicked (file, e); });
}
}
void DirectoryContentsDisplayComponent::sendDoubleClickMessage (const File& file)
{
if (directoryContentsList.getDirectory().exists())
{
Component::BailOutChecker checker (dynamic_cast<Component*> (this));
listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.fileDoubleClicked (file); });
}
}
} // namespace juce

View File

@ -0,0 +1,116 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A base class for components that display a list of the files in a directory.
@see DirectoryContentsList
@tags{GUI}
*/
class JUCE_API DirectoryContentsDisplayComponent
{
public:
//==============================================================================
/** Creates a DirectoryContentsDisplayComponent for a given list of files. */
DirectoryContentsDisplayComponent (DirectoryContentsList& listToShow);
/** Destructor. */
virtual ~DirectoryContentsDisplayComponent();
//==============================================================================
/** The list that this component is displaying */
DirectoryContentsList& directoryContentsList;
//==============================================================================
/** 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. */
highlightedTextColourId = 0x1000542 /**< The colour with which to draw the text in highlighted sections. */
};
//==============================================================================
/** @internal */
void sendSelectionChangeMessage();
/** @internal */
void sendDoubleClickMessage (const File&);
/** @internal */
void sendMouseClickMessage (const File&, const MouseEvent&);
protected:
//==============================================================================
ListenerList<FileBrowserListener> listeners;
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectoryContentsDisplayComponent)
};
} // namespace juce

View File

@ -0,0 +1,275 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
DirectoryContentsList::DirectoryContentsList (const FileFilter* f, TimeSliceThread& t)
: fileFilter (f), thread (t)
{
}
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 specify 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);
}
auto 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.isEmpty())
{
files.clear();
changed();
}
}
void DirectoryContentsList::refresh()
{
stopSearching();
wasEmpty = files.isEmpty();
files.clear();
if (root.isDirectory())
{
fileFindHandle = std::make_unique<RangedDirectoryIterator> (root, false, "*", fileTypeFlags);
shouldStop = false;
thread.addTimeSliceClient (this);
}
}
void DirectoryContentsList::setFileFilter (const FileFilter* newFileFilter)
{
const ScopedLock sl (fileListLock);
fileFilter = newFileFilter;
}
//==============================================================================
int DirectoryContentsList::getNumFiles() const noexcept
{
const ScopedLock sl (fileListLock);
return files.size();
}
bool DirectoryContentsList::getFileInfo (const int index, FileInfo& result) const
{
const ScopedLock sl (fileListLock);
if (auto* info = files [index])
{
result = *info;
return true;
}
return false;
}
File DirectoryContentsList::getFile (const int index) const
{
const ScopedLock sl (fileListLock);
if (auto* info = files [index])
return root.getChildFile (info->filename);
return {};
}
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()
{
auto 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)
{
if (*fileFindHandle != RangedDirectoryIterator())
{
const auto entry = *(*fileFindHandle)++;
if (addFile (entry.getFile(),
entry.isDirectory(),
entry.getFileSize(),
entry.getModificationTime(),
entry.getCreationTime(),
entry.isReadOnly()))
{
hasChanged = true;
}
return true;
}
fileFindHandle = nullptr;
if (! wasEmpty && files.isEmpty())
hasChanged = true;
}
return false;
}
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)))
{
auto info = std::make_unique<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;
files.add (std::move (info));
std::sort (files.begin(), files.end(), [] (const FileInfo* a, const FileInfo* b)
{
// #if JUCE_WINDOWS
if (a->isDirectory != b->isDirectory)
return a->isDirectory;
// #endif
return a->filename.compareNatural (b->filename) < 0;
});
return true;
}
return false;
}
} // namespace juce

View File

@ -0,0 +1,226 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
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() override;
//==============================================================================
/** 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;
/** 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 a default File() object
@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 = nullptr;
TimeSliceThread& thread;
int fileTypeFlags = File::ignoreHiddenFiles | File::findFiles;
CriticalSection fileListLock;
OwnedArray<FileInfo> files;
std::unique_ptr<RangedDirectoryIterator> fileFindHandle;
std::atomic<bool> shouldStop { true };
bool wasEmpty = true;
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)
};
} // namespace juce

View File

@ -0,0 +1,625 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
FileBrowserComponent::FileBrowserComponent (int flags_,
const File& initialFileOrDirectory,
const FileFilter* fileFilter_,
FilePreviewComponent* previewComp_)
: FileFilter ({}),
fileFilter (fileFilter_),
flags (flags_),
previewComp (previewComp_),
currentPathBox ("path"),
fileLabel ("f", TRANS ("file:")),
thread ("JUCE FileBrowser"),
wasProcessActive (true)
{
// 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())
{
currentRoot = File::getCurrentWorkingDirectory();
}
else if (initialFileOrDirectory.isDirectory())
{
currentRoot = initialFileOrDirectory;
}
else
{
chosenFiles.add (initialFileOrDirectory);
currentRoot = initialFileOrDirectory.getParentDirectory();
filename = initialFileOrDirectory.getFileName();
}
fileList.reset (new DirectoryContentsList (this, thread));
fileList->setDirectory (currentRoot, true, true);
if ((flags & useTreeView) != 0)
{
auto tree = new FileTreeComponent (*fileList);
fileListComponent.reset (tree);
if ((flags & canSelectMultipleItems) != 0)
tree->setMultiSelectEnabled (true);
addAndMakeVisible (tree);
}
else
{
auto list = new FileListComponent (*fileList);
fileListComponent.reset (list);
list->setOutlineThickness (1);
if ((flags & canSelectMultipleItems) != 0)
list->setMultipleSelectionEnabled (true);
addAndMakeVisible (list);
}
fileListComponent->addListener (this);
addAndMakeVisible (currentPathBox);
currentPathBox.setEditableText (true);
resetRecentPaths();
currentPathBox.onChange = [this] { updateSelectedPath(); };
addAndMakeVisible (filenameBox);
filenameBox.setMultiLine (false);
filenameBox.setSelectAllWhenFocused (true);
filenameBox.setText (filename, false);
filenameBox.onTextChange = [this] { sendListenerChangeMessage(); };
filenameBox.onReturnKey = [this] { changeFilename(); };
filenameBox.onFocusLost = [this]
{
if (! isSaveMode())
selectionChanged();
};
filenameBox.setReadOnly ((flags & (filenameBoxIsReadOnly | canSelectMultipleItems)) != 0);
addAndMakeVisible (fileLabel);
fileLabel.attachToComponent (&filenameBox, true);
if (previewComp != nullptr)
addAndMakeVisible (previewComp);
lookAndFeelChanged();
setRoot (currentRoot);
if (filename.isNotEmpty())
setFileName (filename);
thread.startThread (4);
startTimer (2000);
}
FileBrowserComponent::~FileBrowserComponent()
{
fileListComponent.reset();
fileList.reset();
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.isEmpty() && 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
{
auto f = getSelectedFile (0);
if ((flags & canSelectDirectories) == 0 && f.isDirectory())
return false;
return isSaveMode() || 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::getSeparatorString();
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);
if (auto* tree = dynamic_cast<FileTreeComponent*> (fileListComponent.get()))
tree->refresh();
auto currentRootName = currentRoot.getFullPathName();
if (currentRootName.isEmpty())
currentRootName = File::getSeparatorString();
currentPathBox.setText (currentRootName, dontSendNotification);
goUpButton->setEnabled (currentRoot.getParentDirectory().isDirectory()
&& currentRoot.getParentDirectory() != currentRoot);
if (callListeners)
{
Component::BailOutChecker checker (this);
listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.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.get();
}
//==============================================================================
void FileBrowserComponent::resized()
{
getLookAndFeel()
.layoutFileBrowserComponent (*this, fileListComponent.get(), previewComp,
&currentPathBox, &filenameBox, goUpButton.get());
}
//==============================================================================
void FileBrowserComponent::lookAndFeelChanged()
{
goUpButton.reset (getLookAndFeel().createFileBrowserGoUpButton());
if (auto* buttonPtr = goUpButton.get())
{
addAndMakeVisible (*buttonPtr);
buttonPtr->onClick = [this] { goUp(); };
buttonPtr->setTooltip (TRANS ("Go up to parent directory"));
}
currentPathBox.setColour (ComboBox::backgroundColourId, findColour (currentPathBoxBackgroundColourId));
currentPathBox.setColour (ComboBox::textColourId, findColour (currentPathBoxTextColourId));
currentPathBox.setColour (ComboBox::arrowColourId, findColour (currentPathBoxArrowColourId));
filenameBox.setColour (TextEditor::backgroundColourId, findColour (filenameBoxBackgroundColourId));
filenameBox.applyColourToAllText (findColour (filenameBoxTextColourId));
resized();
repaint();
}
//==============================================================================
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& l) { l.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& l) { l.fileClicked (f, e); });
}
void FileBrowserComponent::fileDoubleClicked (const File& f)
{
if (f.isDirectory())
{
setRoot (f);
if ((flags & canSelectDirectories) != 0 && (flags & doNotClearFileNameOnRootChange) == 0)
filenameBox.setText ({});
}
else
{
Component::BailOutChecker checker (this);
listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.fileDoubleClicked (f); });
}
}
void FileBrowserComponent::browserRootChanged (const File&) {}
bool FileBrowserComponent::keyPressed (const KeyPress& key)
{
#if JUCE_LINUX || JUCE_BSD || JUCE_WINDOWS
if (key.getModifiers().isCommandDown()
&& (key.getKeyCode() == 'H' || key.getKeyCode() == 'h'))
{
fileList->setIgnoresHiddenFiles (! fileList->ignoresHiddenFiles());
fileList->refresh();
return true;
}
#endif
ignoreUnused (key);
return false;
}
//==============================================================================
void FileBrowserComponent::changeFilename()
{
if (filenameBox.getText().containsChar (File::getSeparatorChar()))
{
auto f = currentRoot.getChildFile (filenameBox.getText());
if (f.isDirectory())
{
setRoot (f);
chosenFiles.clear();
if ((flags & doNotClearFileNameOnRootChange) == 0)
filenameBox.setText ({});
}
else
{
setRoot (f.getParentDirectory());
chosenFiles.clear();
chosenFiles.add (f);
filenameBox.setText (f.getFileName());
}
}
else
{
fileDoubleClicked (getSelectedFile (0));
}
}
//==============================================================================
void FileBrowserComponent::updateSelectedPath()
{
auto newText = currentPathBox.getText().trim().unquoted();
if (newText.isNotEmpty())
{
auto 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 ({});
rootNames.add ({});
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 ({});
rootNames.add ({});
for (auto& volume : File ("/Volumes").findChildFiles (File::findDirectories, false))
{
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);
}
void FileBrowserComponent::timerCallback()
{
const auto isProcessActive = isForegroundOrEmbeddedProcess (this);
if (wasProcessActive != isProcessActive)
{
wasProcessActive = isProcessActive;
if (isProcessActive && fileList != nullptr)
refresh();
}
}
//==============================================================================
std::unique_ptr<AccessibilityHandler> FileBrowserComponent::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
}
} // namespace juce

View File

@ -0,0 +1,296 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
class JUCE_API FileBrowserComponent : public Component,
private FileBrowserListener,
private FileFilter,
private Timer
{
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
conjunction 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). */
doNotClearFileNameOnRootChange = 256 /**< specifies that the file name should not be cleared upon root change. */
};
//==============================================================================
/** 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(), 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() override;
//==============================================================================
/** 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() = default;
// 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 File& file,
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;
};
/** A set of colour IDs to use to change the colour of various aspects of the FileBrowserComponent.
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
{
currentPathBoxBackgroundColourId = 0x1000640, /**< The colour to use to fill the background of the current path ComboBox. */
currentPathBoxTextColourId = 0x1000641, /**< The colour to use for the text of the current path ComboBox. */
currentPathBoxArrowColourId = 0x1000642, /**< The colour to use to draw the arrow of the current path ComboBox. */
filenameBoxBackgroundColourId = 0x1000643, /**< The colour to use to fill the background of the filename TextEditor. */
filenameBoxTextColourId = 0x1000644 /**< The colour to use for the text of the filename TextEditor. */
};
//==============================================================================
/** @internal */
void resized() override;
/** @internal */
void lookAndFeelChanged() 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:
//==============================================================================
std::unique_ptr<DirectoryContentsList> fileList;
const FileFilter* fileFilter;
int flags;
File currentRoot;
Array<File> chosenFiles;
ListenerList<FileBrowserListener> listeners;
std::unique_ptr<DirectoryContentsDisplayComponent> fileListComponent;
FilePreviewComponent* previewComp;
ComboBox currentPathBox;
TextEditor filenameBox;
Label fileLabel;
std::unique_ptr<Button> goUpButton;
TimeSliceThread thread;
bool wasProcessActive;
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
void timerCallback() override;
void sendListenerChangeMessage();
bool isFileOrDirSuitable (const File&) const;
void updateSelectedPath();
void changeFilename();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileBrowserComponent)
};
} // namespace juce

View File

@ -0,0 +1,58 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A listener for user selection events in a file browser.
This is used by a FileBrowserComponent or FileListComponent.
@tags{GUI}
*/
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;
};
} // namespace juce

View File

@ -0,0 +1,268 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class FileChooser::NonNative : public FileChooser::Pimpl
{
public:
NonNative (FileChooser& fileChooser, int flags, FilePreviewComponent* preview)
: owner (fileChooser),
selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories) != 0),
selectsFiles ((flags & FileBrowserComponent::canSelectFiles) != 0),
warnAboutOverwrite ((flags & FileBrowserComponent::warnAboutOverwriting) != 0),
filter (selectsFiles ? owner.filters : String(), selectsDirectories ? "*" : String(), {}),
browserComponent (flags, owner.startingFile, &filter, preview),
dialogBox (owner.title, {}, browserComponent, warnAboutOverwrite,
browserComponent.findColour (AlertWindow::backgroundColourId), owner.parent)
{}
~NonNative() override
{
dialogBox.exitModalState (0);
}
void launch() override
{
dialogBox.centreWithDefaultSize (nullptr);
dialogBox.enterModalState (true, ModalCallbackFunction::create ([this] (int r) { modalStateFinished (r); }), true);
}
void runModally() override
{
#if JUCE_MODAL_LOOPS_PERMITTED
modalStateFinished (dialogBox.show() ? 1 : 0);
#else
jassertfalse;
#endif
}
private:
void modalStateFinished (int returnValue)
{
Array<URL> result;
if (returnValue != 0)
{
for (int i = 0; i < browserComponent.getNumSelectedFiles(); ++i)
result.add (URL (browserComponent.getSelectedFile (i)));
}
owner.finished (result);
}
//==============================================================================
FileChooser& owner;
bool selectsDirectories, selectsFiles, warnAboutOverwrite;
WildcardFileFilter filter;
FileBrowserComponent browserComponent;
FileChooserDialogBox dialogBox;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NonNative)
};
//==============================================================================
FileChooser::FileChooser (const String& chooserBoxTitle,
const File& currentFileOrDirectory,
const String& fileFilters,
const bool useNativeBox,
const bool treatFilePackagesAsDirectories,
Component* parentComponentToUse)
: title (chooserBoxTitle),
filters (fileFilters),
startingFile (currentFileOrDirectory),
parent (parentComponentToUse),
useNativeDialogBox (useNativeBox && isPlatformDialogAvailable()),
treatFilePackagesAsDirs (treatFilePackagesAsDirectories)
{
#ifndef JUCE_MAC
ignoreUnused (treatFilePackagesAsDirs);
#endif
if (! fileFilters.containsNonWhitespaceChars())
filters = "*";
}
FileChooser::~FileChooser()
{
asyncCallback = nullptr;
}
#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;
pimpl = createPimpl (flags, previewComp);
pimpl->runModally();
// ensure that the finished function was invoked
jassert (pimpl == nullptr);
return (results.size() > 0);
}
#endif
void FileChooser::launchAsync (int flags, std::function<void (const FileChooser&)> callback,
FilePreviewComponent* previewComp)
{
// You must specify a callback when using launchAsync
jassert (callback);
// you cannot run two file chooser dialog boxes at the same time
jassert (asyncCallback == nullptr);
asyncCallback = std::move (callback);
pimpl = createPimpl (flags, previewComp);
pimpl->launch();
}
std::shared_ptr<FileChooser::Pimpl> FileChooser::createPimpl (int flags, FilePreviewComponent* previewComp)
{
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));
if (pimpl != nullptr)
{
// you cannot run two file chooser dialog boxes at the same time
jassertfalse;
pimpl.reset();
}
// You've set the flags for both saveMode and openMode!
jassert (! (((flags & FileBrowserComponent::saveMode) != 0)
&& ((flags & FileBrowserComponent::openMode) != 0)));
#if JUCE_WINDOWS
const bool selectsFiles = (flags & FileBrowserComponent::canSelectFiles) != 0;
const bool selectsDirectories = (flags & FileBrowserComponent::canSelectDirectories) != 0;
if (useNativeDialogBox && ! (selectsFiles && selectsDirectories))
#else
if (useNativeDialogBox)
#endif
{
return showPlatformDialog (*this, flags, previewComp);
}
return std::make_unique<NonNative> (*this, flags, previewComp);
}
Array<File> FileChooser::getResults() const noexcept
{
Array<File> files;
for (auto url : getURLResults())
if (url.isLocalFile())
files.add (url.getLocalFile());
return files;
}
File FileChooser::getResult() const
{
auto fileResults = getResults();
// if you've used a multiple-file select, you should use the getResults() method
// to retrieve all the files that were chosen.
jassert (fileResults.size() <= 1);
return fileResults.getFirst();
}
URL FileChooser::getURLResult() 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();
}
void FileChooser::finished (const Array<URL>& asyncResults)
{
std::function<void (const FileChooser&)> callback;
std::swap (callback, asyncCallback);
results = asyncResults;
pimpl.reset();
if (callback)
callback (*this);
}
//==============================================================================
FilePreviewComponent::FilePreviewComponent() {}
FilePreviewComponent::~FilePreviewComponent() {}
} // namespace juce

View File

@ -0,0 +1,336 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Creates a dialog box to choose a file or directory to load or save.
@code
std::unique_ptr<FileChooser> myChooser;
void loadMooseFile()
{
myChooser = std::make_unique<FileChooser> ("Please select the moose you want to load...",
File::getSpecialLocation (File::userHomeDirectory),
"*.moose");
auto folderChooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
myChooser->launchAsync (folderChooserFlags, [this] (const FileChooser& chooser)
{
File mooseFile (chooser.getResult());
loadMoose (mooseFile);
}
}
@endcode
@tags{GUI}
*/
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(), a sensible default directory will
be used instead. When using native dialogs, not
all platforms will actually select the file. For
example, on macOS, when initialFileOrDirectory is
a file, only the parent directory of
initialFileOrDirectory will be used as the initial
directory of the native file chooser.
Note: On iOS when saving a file, a user will not
be able to change a file name, so it may be a good
idea to include at least a valid file name in
initialFileOrDirectory. When no filename is found,
"Untitled" will be used.
Also, if you pass an already existing file on iOS,
that file will be automatically copied to the
destination chosen by user and if it can be previewed,
its preview will be presented in the dialog too. You
will still be able to write into this file copy, since
its URL will be returned by getURLResult(). This can be
useful when you want to save e.g. an image, so that
you can pass a (temporary) file with low quality
preview and after the user picks the destination,
you can write a high quality image into the copied
file. If you create such a temporary file, you need
to delete it yourself, once it is not needed anymore.
@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". The native MacOS
file browser only supports wildcard that specify
extensions, so "*.jpg" is OK but "myfilename*" will not
work. 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
@param treatFilePackagesAsDirectories if true, then the file chooser will allow the
selection of files inside packages when
invoked on OS X and when using native dialog
boxes.
@param parentComponent An optional component which should be the parent
for the file chooser. If this is a nullptr then the
FileChooser will be a top-level window. AUv3s on iOS
must specify this parameter as opening a top-level window
in an AUv3 is forbidden due to sandbox restrictions.
@see browseForFileToOpen, browseForFileToSave, browseForDirectory
*/
FileChooser (const String& dialogBoxTitle,
const File& initialFileOrDirectory = File(),
const String& filePatternsAllowed = String(),
bool useOSNativeDialogBox = true,
bool treatFilePackagesAsDirectories = false,
Component* parentComponent = nullptr);
/** Destructor. */
~FileChooser();
//==============================================================================
#if JUCE_MODAL_LOOPS_PERMITTED
/** Shows a dialog box to choose a file to open.
This will display the dialog box modally, using an "open file" mode, so that
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);
#endif
/** Use this method to launch the file browser window asynchronously.
This will create a file browser dialog based on the settings in this
structure and will launch it modally, returning immediately.
You must specify a callback which is called when the file browser is
cancelled or a file is selected. To abort the file selection, simply
delete the FileChooser object.
You must ensure that the lifetime of the callback object is longer than
the lifetime of the file-chooser.
*/
void launchAsync (int flags,
std::function<void (const FileChooser&)>,
FilePreviewComponent* previewComponent = nullptr);
//==============================================================================
/** 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).
On mobile platforms, the file browser may return a URL instead of a local file.
Therefore, on mobile platforms, you should call getURLResult() instead.
If you're using a multiple-file select, then use the getResults() method instead,
to obtain the list of all files chosen.
@see getURLResult, getResults
*/
File getResult() const;
/** Returns a list of all the files that were chosen during the last call to a
browse method.
On mobile platforms, the file browser may return a URL instead of a local file.
Therefore, on mobile platforms, you should call getURLResults() instead.
This array may be empty if no files were chosen, or can contain multiple entries
if multiple files were chosen.
@see getURLResults, getResult
*/
Array<File> getResults() const noexcept;
//==============================================================================
/** Returns the last document that was chosen by one of the browseFor methods.
Use this method if you are using the FileChooser on a mobile platform which
may return a URL to a remote document. If a local file is chosen then you can
convert this file to a JUCE File class via the URL::getLocalFile method.
Note: On iOS you must use the returned URL object directly (you are also
allowed to copy- or move-construct another URL from the returned URL), rather
than just storing the path as a String and then creating a new URL from that
String. This is because the returned URL contains internally a security
bookmark that is required to access the files pointed by it. Then, once you stop
dealing with the file pointed by the URL, you should dispose that URL object,
so that the security bookmark can be released by the system (only a limited
number of such URLs is allowed).
@see getResult, URL::getLocalFile
*/
URL getURLResult() const;
/** Returns a list of all the files that were chosen during the last call to a
browse method.
Use this method if you are using the FileChooser on a mobile platform which
may return a URL to a remote document. If a local file is chosen then you can
convert this file to a JUCE File class via the URL::getLocalFile method.
This array may be empty if no files were chosen, or can contain multiple entries
if multiple files were chosen.
Note: On iOS you must use the returned URL object directly (you are also
allowed to copy- or move-construct another URL from the returned URL), rather
than just storing the path as a String and then creating a new URL from that
String. This is because the returned URL contains internally a security
bookmark that is required to access the files pointed by it. Then, once you stop
dealing with the file pointed by the URL, you should dispose that URL object,
so that the security bookmark can be released by the system (only a limited
number of such URLs is allowed).
@see getResults, URL::getLocalFile
*/
const Array<URL>& getURLResults() const noexcept { return results; }
//==============================================================================
/** Returns if a native filechooser is currently available on this platform.
Note: On iOS this will only return true if you have iCloud permissions
and code-signing enabled in the Projucer and have added iCloud containers
to your app in Apple's online developer portal. Additionally, the user must
have installed the iCloud app on their device and used the app at least once.
*/
static bool isPlatformDialogAvailable();
//==============================================================================
#ifndef DOXYGEN
class Native;
#endif
private:
//==============================================================================
String title, filters;
File startingFile;
Component* parent;
Array<URL> results;
const bool useNativeDialogBox;
const bool treatFilePackagesAsDirs;
std::function<void (const FileChooser&)> asyncCallback;
//==============================================================================
void finished (const Array<URL>&);
//==============================================================================
struct Pimpl
{
virtual ~Pimpl() = default;
virtual void launch() = 0;
virtual void runModally() = 0;
};
std::shared_ptr<Pimpl> pimpl;
//==============================================================================
std::shared_ptr<Pimpl> createPimpl (int, FilePreviewComponent*);
static std::shared_ptr<Pimpl> showPlatformDialog (FileChooser&, int, FilePreviewComponent*);
class NonNative;
friend class NonNative;
friend class Native;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileChooser)
};
} // namespace juce

View File

@ -0,0 +1,262 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
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;
auto area = getLocalBounds();
text.createLayout (getLookAndFeel().createFileChooserHeaderText (getName(), instructions),
(float) getWidth() - 12.0f);
area.removeFromTop (roundToInt (text.getHeight()) + 10);
chooserComponent.setBounds (area.removeFromTop (area.getHeight() - buttonHeight - 20));
auto 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;
String instructions;
TextLayout text;
};
//==============================================================================
FileChooserDialogBox::FileChooserDialogBox (const String& name,
const String& instructions,
FileBrowserComponent& chooserComponent,
bool shouldWarn,
Colour backgroundColour,
Component* parentComp)
: ResizableWindow (name, backgroundColour, parentComp == nullptr),
warnAboutOverwritingExistingFiles (shouldWarn)
{
content = new ContentComponent (name, instructions, chooserComponent);
setContentOwned (content, false);
setResizable (true, true);
setResizeLimits (300, 300, 1200, 1000);
content->okButton.onClick = [this] { okButtonPressed(); };
content->cancelButton.onClick = [this] { closeButtonPressed(); };
content->newFolderButton.onClick = [this] { createNewFolder(); };
content->chooserComponent.addListener (this);
FileChooserDialogBox::selectionChanged();
if (parentComp != nullptr)
parentComp->addAndMakeVisible (this);
else
setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
}
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 (auto* previewComp = content->chooserComponent.getPreviewComponent())
return 400 + previewComp->getWidth();
return 600;
}
//==============================================================================
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 (MessageBoxIconType::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()
{
auto parent = content->chooserComponent.getRoot();
if (parent.isDirectory())
{
auto* aw = new AlertWindow (TRANS("New Folder"),
TRANS("Please enter the name for the folder"),
MessageBoxIconType::NoIcon, this);
aw->addTextEditor ("Folder Name", String(), String(), 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)
{
auto name = File::createLegalFileName (nameFromDialog);
if (! name.isEmpty())
{
auto parent = content->chooserComponent.getRoot();
if (! parent.getChildFile (name).createDirectory())
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
TRANS ("New Folder"),
TRANS ("Couldn't create the folder!"));
content->chooserComponent.refresh();
}
}
} // namespace juce

View File

@ -0,0 +1,167 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A file open/save dialog box.
This is a Juce-based file dialog box; to use a native file chooser, see the
FileChooser class.
@code
{
wildcardFilter = std::make_unique<WildcardFileFilter> ("*.foo", String(), "Foo files");
browser = std::make_unique<FileBrowserComponent> (FileBrowserComponent::canSelectFiles,
File(),
wildcardFilter.get(),
nullptr);
dialogBox = std::make_unique<FileChooserDialogBox> ("Open some kind of file",
"Please choose some kind of file that you want to open...",
*browser,
false,
Colours::lightgrey);
auto onFileSelected = [this] (int r)
{
modalStateFinished (r);
auto selectedFile = browser->getSelectedFile (0);
...etc...
};
dialogBox->centreWithDefaultSize (nullptr);
dialogBox->enterModalState (true,
ModalCallbackFunction::create (onFileSelected),
true);
}
@endcode
@see FileChooser
@tags{GUI}
*/
class JUCE_API FileChooserDialogBox : public ResizableWindow,
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
@param parentComponent an optional component which should be the parent
for the file chooser. If this is a nullptr then the
dialog box will be a top-level window. AUv3s on iOS
must specify this parameter as opening a top-level window
in an AUv3 is forbidden due to sandbox restrictions.
@see FileBrowserComponent, FilePreviewComponent
*/
FileChooserDialogBox (const String& title,
const String& instructions,
FileBrowserComponent& browserComponent,
bool warnAboutOverwritingExistingFiles,
Colour backgroundColour,
Component* parentComponent = nullptr);
/** Destructor. */
~FileChooserDialogBox() override;
//==============================================================================
#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 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)
};
} // namespace juce

View File

@ -0,0 +1,320 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
Image juce_createIconForFile (const File& file);
//==============================================================================
FileListComponent::FileListComponent (DirectoryContentsList& listToShow)
: ListBox ({}, nullptr),
DirectoryContentsDisplayComponent (listToShow),
lastDirectory (listToShow.getDirectory())
{
setTitle ("Files");
setModel (this);
directoryContentsList.addChangeListener (this);
}
FileListComponent::~FileListComponent()
{
directoryContentsList.removeChangeListener (this);
}
int FileListComponent::getNumSelectedFiles() const
{
return getNumSelectedRows();
}
File FileListComponent::getSelectedFile (int index) const
{
return directoryContentsList.getFile (getSelectedRow (index));
}
void FileListComponent::deselectAllFiles()
{
deselectAllRows();
}
void FileListComponent::scrollToTop()
{
getVerticalScrollBar().setCurrentRangeStart (0);
}
void FileListComponent::setSelectedFile (const File& f)
{
for (int i = directoryContentsList.getNumFiles(); --i >= 0;)
{
if (directoryContentsList.getFile (i) == f)
{
fileWaitingToBeSelected = File();
selectRow (i);
return;
}
}
deselectAllRows();
fileWaitingToBeSelected = f;
}
//==============================================================================
void FileListComponent::changeListenerCallback (ChangeBroadcaster*)
{
updateContent();
if (lastDirectory != directoryContentsList.getDirectory())
{
fileWaitingToBeSelected = File();
lastDirectory = directoryContentsList.getDirectory();
deselectAllRows();
}
if (fileWaitingToBeSelected != File())
setSelectedFile (fileWaitingToBeSelected);
}
//==============================================================================
class FileListComponent::ItemComponent : public Component,
private TimeSliceClient,
private AsyncUpdater
{
public:
ItemComponent (FileListComponent& fc, TimeSliceThread& t)
: owner (fc), thread (t)
{
}
~ItemComponent() override
{
thread.removeTimeSliceClient (this);
}
//==============================================================================
void paint (Graphics& g) override
{
getLookAndFeel().drawFileBrowserRow (g, getWidth(), getHeight(),
file, file.getFileName(),
&icon, fileSize, modTime,
isDirectory, highlighted,
index, owner);
}
bool isInDragToScrollViewport() const noexcept
{
if (auto* vp = owner.getViewport())
return vp->isScrollOnDragEnabled() && (vp->canScrollVertically() || vp->canScrollHorizontally());
return false;
}
void mouseDown (const MouseEvent& e) override
{
selectRowOnMouseUp = false;
isDraggingToScroll = false;
if (isEnabled())
{
if (owner.getRowSelectedOnMouseDown() && ! (owner.isRowSelected(index) || isInDragToScrollViewport())) {
//performSelection (e, false);
owner.selectRowsBasedOnModifierKeys (index, e.mods, true);
owner.sendMouseClickMessage (file, e);
}
else
selectRowOnMouseUp = true;
}
}
void mouseDrag (const MouseEvent& e) override
{
if (! isDraggingToScroll)
if (auto* vp = owner.getViewport())
isDraggingToScroll = vp->isCurrentlyScrollingOnDrag();
}
void mouseUp (const MouseEvent& e) override
{
if (isEnabled() && selectRowOnMouseUp && ! (/*isDragging || */ isDraggingToScroll)) {
owner.selectRowsBasedOnModifierKeys (index, e.mods, true);
owner.sendMouseClickMessage (file, e);
//performSelection (e, true);
}
}
void mouseDoubleClick (const MouseEvent&) override
{
owner.sendDoubleClickMessage (file);
}
void update (const File& root, const DirectoryContentsList::FileInfo* fileInfo,
int newIndex, bool nowHighlighted)
{
thread.removeTimeSliceClient (this);
if (nowHighlighted != highlighted || newIndex != index)
{
index = newIndex;
highlighted = nowHighlighted;
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();
isDirectory = fileInfo != nullptr && fileInfo->isDirectory;
repaint();
}
if (file != File() && 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 = 0;
bool highlighted = false, isDirectory = false;
bool selectRowOnMouseUp = false;
bool isDraggingToScroll = false;
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
{
return createIgnoredAccessibilityHandler (*this);
}
void updateIcon (const bool onlyUpdateIfCached)
{
if (icon.isNull())
{
auto hashCode = (file.getFullPathName() + "_iconCacheSalt").hashCode();
auto 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 directoryContentsList.getNumFiles();
}
String FileListComponent::getNameForRow (int rowNumber)
{
return directoryContentsList.getFile (rowNumber).getFileName();
}
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);
auto comp = static_cast<ItemComponent*> (existingComponentToUpdate);
if (comp == nullptr)
comp = new ItemComponent (*this, directoryContentsList.getTimeSliceThread());
DirectoryContentsList::FileInfo fileInfo;
comp->update (directoryContentsList.getDirectory(),
directoryContentsList.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 (directoryContentsList.getFile (currentSelectedRow));
}
} // namespace juce

View File

@ -0,0 +1,95 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
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() override;
//==============================================================================
/** Returns the number of files the user has got selected.
@see getSelectedFile
*/
int getNumSelectedFiles() const override;
/** 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 override;
/** Deselects any files that are currently selected. */
void deselectAllFiles() override;
/** Scrolls to the top of the list. */
void scrollToTop() override;
/** 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&) override;
private:
//==============================================================================
File lastDirectory, fileWaitingToBeSelected;
class ItemComponent;
void changeListenerCallback (ChangeBroadcaster*) override;
int getNumRows() override;
String getNameForRow (int rowNumber) override;
void paintListBoxItem (int, Graphics&, int, int, bool) override;
Component* refreshComponentForRow (int rowNumber, bool isRowSelected, Component*) override;
void selectedRowsChanged (int row) override;
void deleteKeyPressed (int currentSelectedRow) override;
void returnKeyPressed (int currentSelectedRow) override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileListComponent)
};
} // namespace juce

View File

@ -0,0 +1,66 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
class JUCE_API FilePreviewComponent : public Component
{
public:
//==============================================================================
/** Creates a FilePreviewComponent. */
FilePreviewComponent();
/** Destructor. */
~FilePreviewComponent() override;
/** Called to indicate that the user's currently selected file has changed.
@param newSelectedFile the newly selected file or directory, which may be
a default File() object if none is selected.
*/
virtual void selectedFileChanged (const File& newSelectedFile) = 0;
private:
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilePreviewComponent)
};
} // namespace juce

View File

@ -0,0 +1,275 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
FileSearchPathListComponent::FileSearchPathListComponent()
: addButton ("+"),
removeButton ("-"),
changeButton (TRANS ("change...")),
upButton ({}, DrawableButton::ImageOnButtonBackground),
downButton ({}, 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.onClick = [this] { addPath(); };
addButton.setConnectedEdges (Button::ConnectedOnLeft | Button::ConnectedOnRight | Button::ConnectedOnBottom | Button::ConnectedOnTop);
addAndMakeVisible (removeButton);
removeButton.onClick = [this] { deleteSelected(); };
removeButton.setConnectedEdges (Button::ConnectedOnLeft | Button::ConnectedOnRight | Button::ConnectedOnBottom | Button::ConnectedOnTop);
addAndMakeVisible (changeButton);
changeButton.onClick = [this] { editSelected(); };
addAndMakeVisible (upButton);
upButton.onClick = [this] { moveSelection (-1); };
auto arrowColour = findColour (ListBox::textColourId);
{
Path arrowPath;
arrowPath.addArrow ({ 50.0f, 100.0f, 50.0f, 0.0f }, 40.0f, 100.0f, 50.0f);
DrawablePath arrowImage;
arrowImage.setFill (arrowColour);
arrowImage.setPath (arrowPath);
upButton.setImages (&arrowImage);
}
addAndMakeVisible (downButton);
downButton.onClick = [this] { moveSelection (1); };
{
Path arrowPath;
arrowPath.addArrow ({ 50.0f, 0.0f, 50.0f, 100.0f }, 40.0f, 100.0f, 50.0f);
DrawablePath arrowImage;
arrowImage.setFill (arrowColour);
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 ((float) 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)
{
chooser = std::make_unique<FileChooser> (TRANS("Change folder..."), path[row], "*");
auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
chooser->launchAsync (chooserFlags, [this, row] (const FileChooser& fc)
{
if (fc.getResult() == File{})
return;
path.remove (row);
path.add (fc.getResult(), row);
changed();
});
}
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())
{
auto row = listBox.getRowContainingPosition (0, mouseY - listBox.getY());
path.add (f, row);
changed();
}
}
}
void FileSearchPathListComponent::addPath()
{
auto start = defaultBrowseTarget;
if (start == File())
start = path[0];
if (start == File())
start = File::getCurrentWorkingDirectory();
chooser = std::make_unique<FileChooser> (TRANS("Add a folder..."), start, "*");
auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
chooser->launchAsync (chooserFlags, [this] (const FileChooser& fc)
{
if (fc.getResult() == File{})
return;
path.add (fc.getResult(), listBox.getSelectedRow());
changed();
});
}
void FileSearchPathListComponent::deleteSelected()
{
deleteKeyPressed (listBox.getSelectedRow());
changed();
}
void FileSearchPathListComponent::editSelected()
{
returnKeyPressed (listBox.getSelectedRow());
changed();
}
void FileSearchPathListComponent::moveSelection (int delta)
{
jassert (delta == -1 || delta == 1);
auto currentRow = listBox.getSelectedRow();
if (isPositiveAndBelow (currentRow, path.getNumPaths()))
{
auto newRow = jlimit (0, path.getNumPaths() - 1, currentRow + delta);
if (currentRow != newRow)
{
auto f = path[currentRow];
path.remove (currentRow);
path.add (f, newRow);
listBox.selectRow (newRow);
changed();
}
}
}
} // namespace juce

View File

@ -0,0 +1,120 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Shows a set of file paths in a list, allowing them to be added, removed or
re-ordered.
@see FileSearchPath
@tags{GUI}
*/
class JUCE_API FileSearchPathListComponent : public Component,
public SettableTooltipClient,
public FileDragAndDropTarget,
private ListBoxModel
{
public:
//==============================================================================
/** Creates an empty FileSearchPathListComponent. */
FileSearchPathListComponent();
/** Destructor. */
~FileSearchPathListComponent() override;
//==============================================================================
/** 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;
private:
//==============================================================================
FileSearchPath path;
File defaultBrowseTarget;
std::unique_ptr<FileChooser> chooser;
ListBox listBox;
TextButton addButton, removeButton, changeButton;
DrawableButton upButton, downButton;
void changed();
void updateButtons();
void addPath();
void deleteSelected();
void editSelected();
void moveSelection (int);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileSearchPathListComponent)
};
} // namespace juce

View File

@ -0,0 +1,334 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class FileListTreeItem : public TreeViewItem,
private TimeSliceClient,
private AsyncUpdater,
private ChangeListener
{
public:
FileListTreeItem (FileTreeComponent& treeComp,
DirectoryContentsList* parentContents,
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() override
{
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 && parentContentsList != nullptr)
{
auto 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.reset();
}
}
void setSubContentsList (DirectoryContentsList* newList, const bool canDeleteList)
{
removeSubContentsList();
subContentsList = OptionalScopedPointer<DirectoryContentsList> (newList, canDeleteList);
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 (auto* 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
{
ScopedLock lock (iconUpdate);
if (file != File())
{
updateIcon (true);
if (icon.isNull())
thread.addTimeSliceClient (this);
}
owner.getLookAndFeel().drawFileBrowserRow (g, width, height,
file, file.getFileName(),
&icon, fileSize, modTime,
isDirectory, isSelected(),
indexInContentsList, owner);
}
String getAccessibilityName() override
{
return file.getFileName();
}
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;
CriticalSection iconUpdate;
Image icon;
String fileSize, modTime;
void updateIcon (const bool onlyUpdateIfCached)
{
if (icon.isNull())
{
auto hashCode = (file.getFullPathName() + "_iconCacheSalt").hashCode();
auto im = ImageCache::getFromHashCode (hashCode);
if (im.isNull() && ! onlyUpdateIfCached)
{
im = juce_createIconForFile (file);
if (im.isValid())
ImageCache::addImageToCache (im, hashCode);
}
if (im.isValid())
{
{
ScopedLock lock (iconUpdate);
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();
auto root = new FileListTreeItem (*this, nullptr, 0, directoryContentsList.getDirectory(),
directoryContentsList.getTimeSliceThread());
root->setSubContentsList (&directoryContentsList, false);
setRootItem (root);
}
//==============================================================================
File FileTreeComponent::getSelectedFile (const int index) const
{
if (auto* item = dynamic_cast<const FileListTreeItem*> (getSelectedItem (index)))
return item->file;
return {};
}
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 (auto* t = dynamic_cast<FileListTreeItem*> (getRootItem()))
if (! t->selectFile (target))
clearSelectedItems();
}
void FileTreeComponent::setItemHeight (int newHeight)
{
if (itemHeight != newHeight)
{
itemHeight = newHeight;
if (auto* root = getRootItem())
root->treeHasChanged();
}
}
} // namespace juce

View File

@ -0,0 +1,105 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
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() override;
//==============================================================================
/** Returns the number of files the user has got selected.
@see getSelectedFile
*/
int getNumSelectedFiles() const override { 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 override;
/** Deselects any files that are currently selected. */
void deselectAllFiles() override;
/** Scrolls the list to the top. */
void scrollToTop() override;
/** 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&) override;
/** 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)
};
} // namespace juce

View File

@ -0,0 +1,268 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
FilenameComponent::FilenameComponent (const String& name,
const File& currentFile,
bool canEditFilename,
bool isDirectory,
bool isForSaving,
const String& fileBrowserWildcard,
const String& suffix,
const String& textWhenNothingSelected)
: Component (name),
isDir (isDirectory),
isSaving (isForSaving),
wildcard (fileBrowserWildcard),
enforcedSuffix (suffix)
{
addAndMakeVisible (filenameBox);
filenameBox.setEditableText (canEditFilename);
filenameBox.setTextWhenNothingSelected (textWhenNothingSelected);
filenameBox.setTextWhenNoChoicesAvailable (TRANS ("(no recently selected files)"));
filenameBox.onChange = [this] { setCurrentFile (getCurrentFile(), true); };
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.get());
}
std::unique_ptr<ComponentTraverser> FilenameComponent::createKeyboardFocusTraverser()
{
// This prevents the sub-components from grabbing focus if the
// FilenameComponent has been set to refuse focus.
return getWantsKeyboardFocus() ? Component::createKeyboardFocusTraverser() : nullptr;
}
void FilenameComponent::setBrowseButtonText (const String& newBrowseButtonText)
{
browseButtonText = newBrowseButtonText;
lookAndFeelChanged();
}
void FilenameComponent::lookAndFeelChanged()
{
browseButton.reset();
browseButton.reset (getLookAndFeel().createFilenameComponentBrowseButton (browseButtonText));
addAndMakeVisible (browseButton.get());
browseButton->setConnectedEdges (Button::ConnectedOnLeft);
browseButton->onClick = [this] { showChooser(); };
resized();
}
void FilenameComponent::setTooltip (const String& newTooltip)
{
SettableTooltipClient::setTooltip (newTooltip);
filenameBox.setTooltip (newTooltip);
}
void FilenameComponent::setDefaultBrowseTarget (const File& newDefaultDirectory)
{
defaultBrowseFile = newDefaultDirectory;
}
File FilenameComponent::getLocationToBrowse()
{
if (lastFilename.isEmpty() && defaultBrowseFile != File())
return defaultBrowseFile;
return getCurrentFile();
}
void FilenameComponent::showChooser()
{
chooser = std::make_unique<FileChooser> (isDir ? TRANS ("Choose a new directory")
: TRANS ("Choose a new file"),
getLocationToBrowse(),
wildcard);
auto chooserFlags = isDir ? FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories
: FileBrowserComponent::canSelectFiles | (isSaving ? FileBrowserComponent::saveMode
: FileBrowserComponent::openMode);
chooser->launchAsync (chooserFlags, [this] (const FileChooser&)
{
if (chooser->getResult() == File{})
return;
setCurrentFile (chooser->getResult(), 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
{
auto 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)
{
auto 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, [this] (FilenameComponentListener& l) { l.filenameComponentChanged (this); });
}
} // namespace juce

View File

@ -0,0 +1,236 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
class JUCE_API FilenameComponentListener
{
public:
/** Destructor. */
virtual ~FilenameComponentListener() = default;
/** 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
@tags{GUI}
*/
class JUCE_API FilenameComponent : public Component,
public SettableTooltipClient,
public FileDragAndDropTarget,
private AsyncUpdater
{
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() override;
//==============================================================================
/** 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.
A notification will only be sent if the filename has changed.
*/
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() = default;
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 */
std::unique_ptr<ComponentTraverser> createKeyboardFocusTraverser() override;
private:
//==============================================================================
void handleAsyncUpdate() override;
void showChooser();
ComboBox filenameBox;
String lastFilename;
std::unique_ptr<Button> browseButton;
int maxRecentFiles = 30;
bool isDir, isSaving, isFileDragOver = false;
String wildcard, enforcedSuffix, browseButtonText;
ListenerList <FilenameComponentListener> listeners;
File defaultBrowseFile;
std::unique_ptr<FileChooser> chooser;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilenameComponent)
};
} // namespace juce

View File

@ -0,0 +1,126 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
ImagePreviewComponent::ImagePreviewComponent()
{
}
ImagePreviewComponent::~ImagePreviewComponent()
{
}
//==============================================================================
void ImagePreviewComponent::getThumbSize (int& w, int& h) const
{
auto availableW = proportionOfWidth (0.97f);
auto availableH = getHeight() - 13 * 4;
auto 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();
currentDetails.clear();
repaint();
FileInputStream in (fileToLoad);
if (in.openedOk() && fileToLoad.existsAsFile())
{
if (auto format = ImageFileFormat::findImageFormatForStream (in))
{
currentThumbnail = format->decodeImage (in);
if (currentThumbnail.isValid())
{
auto w = currentThumbnail.getWidth();
auto 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);
auto w = currentThumbnail.getWidth();
auto h = currentThumbnail.getHeight();
getThumbSize (w, h);
const int numLines = 4;
auto totalH = 13 * numLines + h + 4;
auto 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);
}
}
//==============================================================================
std::unique_ptr<AccessibilityHandler> ImagePreviewComponent::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::image);
}
} // namespace juce

View File

@ -0,0 +1,67 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
A simple preview component that shows thumbnails of image files.
@see FileChooserDialogBox, FilePreviewComponent
@tags{GUI}
*/
class JUCE_API ImagePreviewComponent : public FilePreviewComponent,
private Timer
{
public:
//==============================================================================
/** Creates an ImagePreviewComponent. */
ImagePreviewComponent();
/** Destructor. */
~ImagePreviewComponent() override;
//==============================================================================
/** @internal */
void selectedFileChanged (const File& newSelectedFile) override;
/** @internal */
void paint (Graphics&) override;
/** @internal */
void timerCallback() override;
private:
File fileToLoad;
Image currentThumbnail;
String currentDetails;
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
void getThumbSize (int& w, int& h) const;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImagePreviewComponent)
};
} // namespace juce

View File

@ -0,0 +1,379 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#ifdef JUCE_GUI_BASICS_H_INCLUDED
/* 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
#define NS_FORMAT_FUNCTION(F,A) // To avoid spurious warnings from GCC
#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1
#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1
#define JUCE_CORE_INCLUDE_JNI_HELPERS 1
#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1
#define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1
#define JUCE_GRAPHICS_INCLUDE_COREGRAPHICS_HELPERS 1
#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1
#define JUCE_GUI_BASICS_INCLUDE_SCOPED_THREAD_DPI_AWARENESS_SETTER 1
#include "juce_gui_basics.h"
//==============================================================================
#if JUCE_MAC
#import <WebKit/WebKit.h>
#import <IOKit/pwr_mgt/IOPMLib.h>
#if JUCE_SUPPORT_CARBON
#import <Carbon/Carbon.h> // still needed for SetSystemUIMode()
#endif
#elif JUCE_IOS
#if JUCE_PUSH_NOTIFICATIONS && defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
#import <UserNotifications/UserNotifications.h>
#endif
#import <UIKit/UIActivityViewController.h>
//==============================================================================
#elif JUCE_WINDOWS
#include <windowsx.h>
#include <vfw.h>
#include <commdlg.h>
#include <commctrl.h>
#if ! JUCE_MINGW
#include <UIAutomation.h>
#include <sapi.h>
#endif
#if JUCE_WEB_BROWSER
#include <exdisp.h>
#include <exdispid.h>
#endif
#if JUCE_MINGW
#include <imm.h>
#elif ! JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES
#pragma comment(lib, "vfw32.lib")
#pragma comment(lib, "imm32.lib")
#pragma comment(lib, "comctl32.lib")
#if JUCE_OPENGL
#pragma comment(lib, "OpenGL32.Lib")
#pragma comment(lib, "GlU32.Lib")
#endif
#if JUCE_DIRECT2D
#pragma comment (lib, "Dwrite.lib")
#pragma comment (lib, "D2d1.lib")
#endif
#endif
#endif
#include <set>
//==============================================================================
#define JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN \
jassert ((MessageManager::getInstanceWithoutCreating() != nullptr \
&& MessageManager::getInstanceWithoutCreating()->currentThreadHasLockedMessageManager()) \
|| getPeer() == nullptr);
namespace juce
{
bool juce_areThereAnyAlwaysOnTopWindows();
bool isEmbeddedInForegroundProcess (Component* c);
#if ! JUCE_WINDOWS
bool isEmbeddedInForegroundProcess (Component*) { return false; }
#endif
/* Returns true if this process is in the foreground, or if the viewComponent
is embedded into a window owned by the foreground process.
*/
static bool isForegroundOrEmbeddedProcess (Component* viewComponent)
{
return Process::isForegroundProcess() || isEmbeddedInForegroundProcess (viewComponent);
}
}
#include "accessibility/juce_AccessibilityHandler.cpp"
#include "components/juce_Component.cpp"
#include "components/juce_ComponentListener.cpp"
#include "components/juce_FocusTraverser.cpp"
#include "mouse/juce_MouseInputSource.cpp"
#include "desktop/juce_Displays.cpp"
#include "desktop/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 "filebrowser/juce_ContentSharer.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_SidePanel.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 "lookandfeel/juce_LookAndFeel_V4.cpp"
#include "menus/juce_MenuBarComponent.cpp"
#include "menus/juce_BurgerMenuComponent.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 "properties/juce_MultiChoicePropertyComponent.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"
#include "misc/juce_JUCESplashScreen.cpp"
#include "layout/juce_FlexBox.cpp"
#include "layout/juce_GridItem.cpp"
#include "layout/juce_Grid.cpp"
#if JUCE_IOS || JUCE_WINDOWS
#include "native/juce_MultiTouchMapper.h"
#endif
#if JUCE_ANDROID || JUCE_WINDOWS
#include "native/accessibility/juce_AccessibilityTextHelpers.h"
#endif
namespace juce
{
static const juce::Identifier disableAsyncLayerBackedViewIdentifier { "disableAsyncLayerBackedView" };
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes")
/** Used by the macOS and iOS peers. */
void setComponentAsyncLayerBackedViewDisabled (juce::Component& comp, bool shouldDisableAsyncLayerBackedView)
{
comp.getProperties().set (disableAsyncLayerBackedViewIdentifier, shouldDisableAsyncLayerBackedView);
}
/** Used by the macOS and iOS peers. */
bool getComponentAsyncLayerBackedViewDisabled (juce::Component& comp)
{
return comp.getProperties()[disableAsyncLayerBackedViewIdentifier];
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
} // namespace juce
#if JUCE_MAC || JUCE_IOS
#include "native/accessibility/juce_mac_AccessibilitySharedCode.mm"
#if JUCE_IOS
#include "native/accessibility/juce_ios_Accessibility.mm"
#include "native/juce_ios_UIViewComponentPeer.mm"
#include "native/juce_ios_Windowing.mm"
#include "native/juce_ios_FileChooser.mm"
#if JUCE_CONTENT_SHARING
#include "native/juce_ios_ContentSharer.cpp"
#endif
#else
#include "native/accessibility/juce_mac_Accessibility.mm"
#include "native/juce_mac_NSViewComponentPeer.mm"
#include "native/juce_mac_Windowing.mm"
#include "native/juce_mac_MainMenu.mm"
#include "native/juce_mac_FileChooser.mm"
#endif
#include "native/juce_mac_MouseCursor.mm"
#elif JUCE_WINDOWS
#if ! JUCE_MINGW
#include "native/accessibility/juce_win32_WindowsUIAWrapper.h"
#include "native/accessibility/juce_win32_AccessibilityElement.h"
#include "native/accessibility/juce_win32_UIAHelpers.h"
#include "native/accessibility/juce_win32_UIAProviders.h"
#include "native/accessibility/juce_win32_AccessibilityElement.cpp"
#include "native/accessibility/juce_win32_Accessibility.cpp"
#else
namespace juce
{
namespace WindowsAccessibility
{
long getUiaRootObjectId() { return -1; }
bool handleWmGetObject (AccessibilityHandler*, WPARAM, LPARAM, LRESULT*) { return false; }
void revokeUIAMapEntriesForWindow (HWND) {}
}
}
#endif
#include "native/juce_win32_Windowing.cpp"
#include "native/juce_win32_DragAndDrop.cpp"
#include "native/juce_win32_FileChooser.cpp"
#elif JUCE_LINUX || JUCE_BSD
#include "native/x11/juce_linux_X11_Symbols.cpp"
#include "native/x11/juce_linux_X11_DragAndDrop.cpp"
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wzero-as-null-pointer-constant")
#include "native/juce_linux_Windowing.cpp"
#include "native/x11/juce_linux_XWindowSystem.cpp"
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
#include "native/juce_linux_FileChooser.cpp"
#elif JUCE_ANDROID
#include "native/accessibility/juce_android_Accessibility.cpp"
#include "native/juce_android_Windowing.cpp"
#include "native/juce_common_MimeTypes.cpp"
#include "native/juce_android_FileChooser.cpp"
#if JUCE_CONTENT_SHARING
#include "native/juce_android_ContentSharer.cpp"
#endif
#endif
namespace juce
{
#if ! JUCE_NATIVE_ACCESSIBILITY_INCLUDED
class AccessibilityHandler::AccessibilityNativeImpl { public: AccessibilityNativeImpl (AccessibilityHandler&) {} };
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent) const {}
void AccessibilityHandler::postAnnouncement (const String&, AnnouncementPriority) {}
AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const { return nullptr; }
void notifyAccessibilityEventInternal (const AccessibilityHandler&, InternalAccessibilityEvent) {}
std::unique_ptr<AccessibilityHandler::AccessibilityNativeImpl> AccessibilityHandler::createNativeImpl (AccessibilityHandler&)
{
return nullptr;
}
#else
std::unique_ptr<AccessibilityHandler::AccessibilityNativeImpl> AccessibilityHandler::createNativeImpl (AccessibilityHandler& handler)
{
return std::make_unique<AccessibilityNativeImpl> (handler);
}
#endif
}
//==============================================================================
#if ! JUCE_WINDOWS
juce::ScopedDPIAwarenessDisabler::ScopedDPIAwarenessDisabler() { ignoreUnused (previousContext); }
juce::ScopedDPIAwarenessDisabler::~ScopedDPIAwarenessDisabler() {}
#endif

View File

@ -0,0 +1,373 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this module, and is read by
the Projucer to automatically generate project code that uses it.
For details about the syntax and how to create or use a module, see the
JUCE Module Format.md file.
BEGIN_JUCE_MODULE_DECLARATION
ID: juce_gui_basics
vendor: juce
version: 6.1.2
name: JUCE GUI core classes
description: Basic user-interface components and related classes.
website: http://www.juce.com/juce
license: GPL/Commercial
minimumCppStandard: 14
dependencies: juce_graphics juce_data_structures
OSXFrameworks: Cocoa Carbon QuartzCore
iOSFrameworks: UIKit CoreServices
END_JUCE_MODULE_DECLARATION
*******************************************************************************/
#pragma once
#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
/** Config: JUCE_USE_XRANDR
Enables Xrandr multi-monitor support (Linux only).
Unless you specifically want to disable this, it's best to leave this option turned on.
Note that your users do not need to have Xrandr installed for your JUCE app to run, as
the availability of Xrandr is queried during runtime.
*/
#ifndef JUCE_USE_XRANDR
#define JUCE_USE_XRANDR 1
#endif
/** Config: 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.
This will be used as a fallback if JUCE_USE_XRANDR not set or libxrandr cannot be found.
Note that your users do not need to have Xinerama installed for your JUCE app to run, as
the availability of Xinerama is queried during runtime.
*/
#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
/** Config: JUCE_WIN_PER_MONITOR_DPI_AWARE
Enables per-monitor DPI awareness on Windows 8.1 and above.
*/
#ifndef JUCE_WIN_PER_MONITOR_DPI_AWARE
#define JUCE_WIN_PER_MONITOR_DPI_AWARE 1
#endif
//==============================================================================
namespace juce
{
class Component;
class LookAndFeel;
class MouseInputSource;
class MouseInputSourceInternal;
class ComponentPeer;
class MouseEvent;
struct MouseWheelDetails;
struct PenDetails;
class ToggleButton;
class TextButton;
class AlertWindow;
class TextLayout;
class ScrollBar;
class ComboBox;
class Button;
class FilenameComponent;
class ResizableWindow;
class MenuBarComponent;
class GlyphArrangement;
class TableHeaderComponent;
class Toolbar;
class PopupMenu;
class ProgressBar;
class FileBrowserComponent;
class DirectoryContentsDisplayComponent;
class FilePreviewComponent;
class CallOutBox;
class Drawable;
class DrawablePath;
class DrawableComposite;
class CaretComponent;
class KeyPressMappingSet;
class ApplicationCommandManagerListener;
class DrawableButton;
class Displays;
class AccessibilityHandler;
class KeyboardFocusTraverser;
class FlexBox;
class Grid;
#if JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX
Image createSnapshotOfNativeWindow (void* nativeWindowHandle);
#endif
}
#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 "components/juce_ComponentTraverser.h"
#include "components/juce_FocusTraverser.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 "desktop/juce_Desktop.h"
#include "desktop/juce_Displays.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_MouseInactivityDetector.h"
#include "mouse/juce_TextDragAndDropTarget.h"
#include "mouse/juce_TooltipClient.h"
#include "keyboard/juce_CaretComponent.h"
#include "keyboard/juce_KeyboardFocusTraverser.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_ImageComponent.h"
#include "widgets/juce_ComboBox.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 "menus/juce_BurgerMenuComponent.h"
#include "buttons/juce_ToolbarButton.h"
#include "misc/juce_DropShadower.h"
#include "misc/juce_JUCESplashScreen.h"
#include "widgets/juce_TreeView.h"
#include "windows/juce_TopLevelWindow.h"
#include "windows/juce_MessageBoxOptions.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 "layout/juce_SidePanel.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 "filebrowser/juce_ContentSharer.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 "properties/juce_MultiChoicePropertyComponent.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"
#include "lookandfeel/juce_LookAndFeel_V4.h"
#include "mouse/juce_LassoComponent.h"
#include "accessibility/interfaces/juce_AccessibilityCellInterface.h"
#include "accessibility/interfaces/juce_AccessibilityTableInterface.h"
#include "accessibility/interfaces/juce_AccessibilityTextInterface.h"
#include "accessibility/interfaces/juce_AccessibilityValueInterface.h"
#include "accessibility/enums/juce_AccessibilityActions.h"
#include "accessibility/enums/juce_AccessibilityEvent.h"
#include "accessibility/enums/juce_AccessibilityRole.h"
#include "accessibility/juce_AccessibilityState.h"
#include "accessibility/juce_AccessibilityHandler.h"
#if JUCE_LINUX || JUCE_BSD
#if JUCE_GUI_BASICS_INCLUDE_XHEADERS
// If you're missing these headers, you need to install the libx11-dev package
#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_XRANDR
// If you're missing this header, you need to install the libxrandr-dev package
#include <X11/extensions/Xrandr.h>
#endif
#if JUCE_USE_XINERAMA
// If you're missing this header, you 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, you need to install the libxrender-dev and libxcomposite-dev packages
#include <X11/extensions/Xrender.h>
#include <X11/extensions/Xcomposite.h>
#endif
#if JUCE_USE_XCURSOR
// If you're missing this header, you need to install the libxcursor-dev package
#include <X11/Xcursor/Xcursor.h>
#endif
#undef SIZEOF
#undef KeyPress
#include "native/x11/juce_linux_XWindowSystem.h"
#include "native/x11/juce_linux_X11_Symbols.h"
#endif
#endif
#if JUCE_GUI_BASICS_INCLUDE_SCOPED_THREAD_DPI_AWARENESS_SETTER && JUCE_WINDOWS
#include "native/juce_win32_ScopedThreadDPIAwarenessSetter.h"
#endif
#include "layout/juce_FlexItem.h"
#include "layout/juce_FlexBox.h"
#include "layout/juce_GridItem.h"
#include "layout/juce_Grid.h"
#include "native/juce_ScopedDPIAwarenessDisabler.h"

View File

@ -0,0 +1,26 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "juce_gui_basics.cpp"

View File

@ -0,0 +1,64 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
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());
}
} // namespace juce

View File

@ -0,0 +1,81 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
@tags{GUI}
*/
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() override;
//==============================================================================
/** 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)
};
} // namespace juce

View File

@ -0,0 +1,34 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
bool KeyListener::keyStateChanged (const bool, Component*)
{
return false;
}
} // namespace juce

View File

@ -0,0 +1,77 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
@tags{GUI}
*/
class JUCE_API KeyListener
{
public:
/** Destructor. */
virtual ~KeyListener() = default;
//==============================================================================
/** 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);
};
} // namespace juce

View File

@ -0,0 +1,289 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
KeyPress::KeyPress (int code, ModifierKeys m, juce_wchar textChar) noexcept
: keyCode (code), mods (m), textCharacter (textChar)
{
}
KeyPress::KeyPress (const int code) noexcept : keyCode (code)
{
}
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::currentModifiers.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()))
{
auto 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 + (int) 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 || JUCE_IOS
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 <= 35; ++i)
{
if (desc.containsWholeWordIgnoreCase ("f" + String (i)))
{
if (i <= 16) key = F1Key + i - 1;
else if (i <= 24) key = F17Key + i - 17;
else if (i <= 35) key = F25Key + i - 25;
}
}
}
if (key == 0)
{
// give up and use the hex code..
auto 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 == '/' && keyCode != numberPadDivide)
return "/";
if (mods.isCtrlDown()) desc << "ctrl + ";
if (mods.isShiftDown()) desc << "shift + ";
#if JUCE_MAC || JUCE_IOS
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;
// not all F keys have consecutive key codes on all platforms
if (keyCode >= F1Key && keyCode <= F16Key) desc << 'F' << (1 + keyCode - F1Key);
else if (keyCode >= F17Key && keyCode <= F24Key) desc << 'F' << (17 + keyCode - F17Key);
else if (keyCode >= F25Key && keyCode <= F35Key) desc << 'F' << (25 + keyCode - F25Key);
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 == numberPadEquals) 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 || JUCE_IOS
auto 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
}
} // namespace juce

Some files were not shown because too many files have changed in this diff Show More