migrating to the latest JUCE version

This commit is contained in:
2022-11-04 23:11:33 +01:00
committed by Nikolai Rodionov
parent 4257a0f8ba
commit faf8f18333
2796 changed files with 888518 additions and 784244 deletions

View File

@ -1,72 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

@ -1,38 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

@ -1,122 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

@ -1,72 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

@ -1,360 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

@ -1,93 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

@ -1,292 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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

@ -1,324 +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
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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