mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Accessibility: Added VoiceOver (macOS) and Narrator (Windows) accessibility screen reader support to juce_gui_basics
This commit is contained in:
parent
1df59f7469
commit
ec990202b1
133 changed files with 10158 additions and 1297 deletions
|
|
@ -4,6 +4,32 @@ JUCE breaking changes
|
|||
Develop
|
||||
=======
|
||||
|
||||
Change
|
||||
------
|
||||
`Component::createFocusTraverser()` has been renamed to
|
||||
`Component::createKeyboardFocusTraverser()` and now returns a `std::unique_ptr`
|
||||
instead of a raw pointer. `Component::createFocusTraverser()` is a new method
|
||||
for controlling basic focus traversal and not keyboard focus traversal.
|
||||
|
||||
Possible Issues
|
||||
---------------
|
||||
Derived Components that override the old method will no longer compile.
|
||||
|
||||
Workaround
|
||||
----------
|
||||
Override the new method. Be careful to override
|
||||
`createKeyboardFocusTraverser()` and not `createFocusTraverser()` to ensure
|
||||
that the behaviour is the same.
|
||||
|
||||
Rationale
|
||||
---------
|
||||
The ownership of this method is now clearer as the previous code relied on the
|
||||
caller deleting the object. The name has changed to accomodate the new
|
||||
`Component::createFocusTraverser()` method that returns an object for
|
||||
determining basic focus traversal, of which keyboard focus is generally a
|
||||
subset.
|
||||
|
||||
|
||||
Change
|
||||
------
|
||||
PluginDescription::uid has been deprecated and replaced with a new 'uniqueId'
|
||||
|
|
|
|||
51
docs/Accessibility.md
Normal file
51
docs/Accessibility.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# JUCE Accessibility
|
||||
|
||||
## What is supported?
|
||||
|
||||
Currently JUCE supports VoiceOver on macOS and Narrator on Windows. The JUCE
|
||||
accessibility API exposes the following to these clients:
|
||||
|
||||
- Title, description, and help text for UI elements
|
||||
- Programmatic access to UI elements and text
|
||||
- Interaction with UI elements
|
||||
- Full UI keyboard navigation
|
||||
- Posting notifications to listening clients
|
||||
|
||||
## Customising Behaviour
|
||||
|
||||
By default any visible and enabled `Component` is accessible to screen reader
|
||||
clients and exposes some basic information such as title, description, help
|
||||
text and its position in the hierarchy of UI elements.
|
||||
|
||||
The `setTitle()`, `setDescription()` and `setHelpText()` methods can be used
|
||||
to customise the text that will be read out by accessibility clients when
|
||||
interacting with UI elements and the `setExplicitFocusOrder()`,
|
||||
`setFocusContainer()` and `createFocusTraverser()` methods can be used to
|
||||
control the parent/child relationships and the order of navigation between UI
|
||||
elements.
|
||||
|
||||
## Custom Components
|
||||
|
||||
For further customisation of accessibility behaviours the `AccessibilityHandler`
|
||||
class provides a unified API to the underlying native accessibility libraries.
|
||||
|
||||
This class wraps a component with a given role specified by the
|
||||
`AccessibilityRole` enum and takes a list of optional actions and interfaces to
|
||||
provide programmatic access and control over the UI element. Its state is used
|
||||
to convey further information to accessibility clients via the
|
||||
`getCurrentState()` method.
|
||||
|
||||
To implement the desired behaviours for a custom component, subclass
|
||||
`AccessibilityHandler` and return an instance of this from the
|
||||
`Component::createAccessibilityHandler()` method.
|
||||
|
||||
Examples of some common UI element handlers for existing JUCE widgets can be
|
||||
found in the [`widget_handlers`](/modules/juce_gui_basics/accessibility/widget_handlers) directory.
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [NSAccessibility protocol](https://developer.apple.com/documentation/appkit/nsaccessibility?language=objc)
|
||||
- [UI Automation for Win32 applications](https://docs.microsoft.com/en-us/windows/win32/winauto/entry-uiauto-win32)
|
||||
- A talk giving an overview of this feature from ADC 2020 can be found on
|
||||
YouTube at https://youtu.be/BqrEv4ApH3U
|
||||
|
||||
|
|
@ -29,6 +29,16 @@ namespace juce
|
|||
{
|
||||
|
||||
//==============================================================================
|
||||
inline Range<int> nsRangeToJuce (NSRange range)
|
||||
{
|
||||
return { (int) range.location, (int) (range.location + range.length) };
|
||||
}
|
||||
|
||||
inline NSRange juceRangeToNS (Range<int> range)
|
||||
{
|
||||
return NSMakeRange ((NSUInteger) range.getStart(), (NSUInteger) range.getLength());
|
||||
}
|
||||
|
||||
inline String nsStringToJuce (NSString* s)
|
||||
{
|
||||
return CharPointer_UTF8 ([s UTF8String]);
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@
|
|||
#include <numeric>
|
||||
#include <queue>
|
||||
#include <sstream>
|
||||
#include <typeindex>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,34 @@ namespace
|
|||
{
|
||||
return CGPointMake ((CGFloat) p.x, (CGFloat) p.y);
|
||||
}
|
||||
|
||||
#if JUCE_MAC
|
||||
inline CGFloat getMainScreenHeight() noexcept
|
||||
{
|
||||
if ([[NSScreen screens] count] == 0)
|
||||
return 0.0f;
|
||||
|
||||
return [[[NSScreen screens] objectAtIndex: 0] frame].size.height;
|
||||
}
|
||||
|
||||
inline NSRect flippedScreenRect (NSRect r) noexcept
|
||||
{
|
||||
r.origin.y = getMainScreenHeight() - (r.origin.y + r.size.height);
|
||||
return r;
|
||||
}
|
||||
|
||||
inline NSPoint flippedScreenPoint (NSPoint p) noexcept
|
||||
{
|
||||
p.y = getMainScreenHeight() - p.y;
|
||||
return p;
|
||||
}
|
||||
|
||||
template <class PointType>
|
||||
Point<int> convertToIntPoint (PointType p) noexcept
|
||||
{
|
||||
return Point<int> (roundToInt (p.x), roundToInt (p.y));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
CGImageRef juce_createCoreGraphicsImage (const Image&, CGColorSpaceRef, bool mustOutliveSource);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 structure of the UI elements has changed in a
|
||||
significant way.
|
||||
|
||||
This should be posted 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
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
|
||||
/** 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,
|
||||
staticText,
|
||||
editableText,
|
||||
menuItem,
|
||||
menuBar,
|
||||
popupMenu,
|
||||
table,
|
||||
tableHeader,
|
||||
column,
|
||||
row,
|
||||
cell,
|
||||
hyperlink,
|
||||
list,
|
||||
listItem,
|
||||
tree,
|
||||
treeItem,
|
||||
progressBar,
|
||||
group,
|
||||
dialogWindow,
|
||||
window,
|
||||
scrollBar,
|
||||
tooltip,
|
||||
splashScreen,
|
||||
ignored,
|
||||
unspecified
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,346 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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,
|
||||
focusChanged,
|
||||
windowOpened,
|
||||
windowClosed
|
||||
};
|
||||
|
||||
void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent event);
|
||||
|
||||
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
|
||||
{
|
||||
auto state = AccessibleState().withFocusable();
|
||||
|
||||
return hasFocus (false) ? state.withFocused() : state;
|
||||
}
|
||||
|
||||
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::isIgnored() const
|
||||
{
|
||||
const auto state = getCurrentState();
|
||||
|
||||
return role == AccessibilityRole::ignored
|
||||
|| state.isIgnored()
|
||||
|| ! component.isShowing()
|
||||
|| (! state.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->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(); });
|
||||
|
||||
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())
|
||||
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 {};
|
||||
|
||||
std::vector<AccessibilityHandler*> children;
|
||||
|
||||
if (auto traverser = component.createFocusTraverser())
|
||||
{
|
||||
for (auto* focusableChild : traverser->getAllComponents (&component))
|
||||
{
|
||||
if (auto* handler = findEnclosingHandler (focusableChild))
|
||||
{
|
||||
if (! isParentOf (handler))
|
||||
continue;
|
||||
|
||||
if (auto* unignored = getFirstUnignoredDescendant (handler))
|
||||
if (std::find (children.cbegin(), children.cend(), unignored) == children.cend())
|
||||
children.push_back (unignored);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 (isParentOf (comp->getAccessibilityHandler()))
|
||||
return getUnignoredAncestor (findEnclosingHandler (comp));
|
||||
|
||||
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) && ! currentlyFocusedHandler->isIgnored())
|
||||
return;
|
||||
|
||||
if (component.isFocusContainer() || component.getParentComponent() == nullptr)
|
||||
{
|
||||
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);
|
||||
|
||||
if (auto* focusedComponent = Component::getCurrentlyFocusedComponent())
|
||||
if (auto* handler = focusedComponent->getAccessibilityHandler())
|
||||
handler->grabFocus();
|
||||
}
|
||||
|
||||
void AccessibilityHandler::takeFocus()
|
||||
{
|
||||
currentlyFocusedHandler = this;
|
||||
|
||||
WeakReference<Component> weakComponent (&component);
|
||||
actions.invoke (AccessibilityActionType::focus);
|
||||
|
||||
if (weakComponent != nullptr
|
||||
&& component.getWantsKeyboardFocus()
|
||||
&& ! component.hasKeyboardFocus (true))
|
||||
{
|
||||
component.grabKeyboardFocus();
|
||||
}
|
||||
|
||||
notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::focusChanged);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if ! (JUCE_MAC || JUCE_WINDOWS)
|
||||
class AccessibilityHandler::AccessibilityNativeImpl { public: AccessibilityNativeImpl (AccessibilityHandler&) {} };
|
||||
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent) const {}
|
||||
void AccessibilityHandler::postAnnouncement (const String&, AnnouncementPriority) {}
|
||||
AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const { return nullptr; }
|
||||
AccessibilityHandler::AccessibilityNativeImpl* AccessibilityHandler::createNativeImpl (AccessibilityHandler&) { return nullptr; }
|
||||
void AccessibilityHandler::DestroyNativeImpl::operator() (AccessibilityHandler::AccessibilityNativeImpl*) const noexcept {}
|
||||
void notifyAccessibilityEventInternal (const AccessibilityHandler&, InternalAccessibilityEvent) {}
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -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 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, as indicated by AccessibleState::isFocusable(),
|
||||
this will perform its AccessibilityActionType::focus action, 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;
|
||||
|
||||
struct DestroyNativeImpl
|
||||
{
|
||||
void operator() (AccessibilityNativeImpl*) const noexcept;
|
||||
};
|
||||
|
||||
static AccessibilityNativeImpl* createNativeImpl (AccessibilityHandler&);
|
||||
|
||||
std::unique_ptr<AccessibilityNativeImpl, DestroyNativeImpl> nativeImpl;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityHandler)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
227
modules/juce_gui_basics/accessibility/juce_AccessibilityState.h
Normal file
227
modules/juce_gui_basics/accessibility/juce_AccessibilityState.h
Normal 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
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
/** Basic accessible interface for a Button that can be clicked or toggled.
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
class JUCE_API ButtonAccessibilityHandler : public AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
explicit ButtonAccessibilityHandler (Button& buttonToWrap)
|
||||
: AccessibilityHandler (buttonToWrap,
|
||||
getButtonRole (buttonToWrap),
|
||||
getAccessibilityActions (buttonToWrap)),
|
||||
button (buttonToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
AccessibleState getCurrentState() const override
|
||||
{
|
||||
auto state = AccessibilityHandler::getCurrentState();
|
||||
|
||||
if (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;
|
||||
}
|
||||
|
||||
private:
|
||||
static AccessibilityRole getButtonRole (const Button& b)
|
||||
{
|
||||
if (b.getRadioGroupId() != 0) return AccessibilityRole::radioButton;
|
||||
if (b.getClickingTogglesState()) return AccessibilityRole::toggleButton;
|
||||
|
||||
return AccessibilityRole::button;
|
||||
}
|
||||
|
||||
static AccessibilityActions getAccessibilityActions (Button& button)
|
||||
{
|
||||
auto actions = AccessibilityActions().addAction (AccessibilityActionType::press,
|
||||
[&button] { button.triggerClick(); });
|
||||
|
||||
if (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)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -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
|
||||
{
|
||||
|
||||
/** Basic accessible interface for a ComboBox that can show a menu.
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
class JUCE_API ComboBoxAccessibilityHandler : public AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
explicit ComboBoxAccessibilityHandler (ComboBox& comboBoxToWrap)
|
||||
: AccessibilityHandler (comboBoxToWrap,
|
||||
AccessibilityRole::comboBox,
|
||||
getAccessibilityActions (comboBoxToWrap)),
|
||||
comboBox (comboBoxToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
AccessibleState getCurrentState() const override
|
||||
{
|
||||
auto state = AccessibilityHandler::getCurrentState().withExpandable();
|
||||
|
||||
return comboBox.isPopupActive() ? state.withExpanded() : state.withCollapsed();
|
||||
}
|
||||
|
||||
String getTitle() const override { return comboBox.getText(); }
|
||||
|
||||
private:
|
||||
static AccessibilityActions getAccessibilityActions (ComboBox& comboBox)
|
||||
{
|
||||
return AccessibilityActions().addAction (AccessibilityActionType::press, [&comboBox] { comboBox.showPopup(); })
|
||||
.addAction (AccessibilityActionType::showMenu, [&comboBox] { comboBox.showPopup(); });
|
||||
}
|
||||
|
||||
ComboBox& comboBox;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComboBoxAccessibilityHandler)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
/** Basic accessible interface for a Label that can also show a TextEditor
|
||||
when clicked.
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
class JUCE_API LabelAccessibilityHandler : public AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
explicit LabelAccessibilityHandler (Label& labelToWrap)
|
||||
: AccessibilityHandler (labelToWrap,
|
||||
AccessibilityRole::staticText,
|
||||
getAccessibilityActions (labelToWrap)),
|
||||
label (labelToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
String getTitle() const override { return label.getText(); }
|
||||
|
||||
private:
|
||||
static AccessibilityActions getAccessibilityActions (Label& label)
|
||||
{
|
||||
if (label.isEditable())
|
||||
return AccessibilityActions().addAction (AccessibilityActionType::press, [&label] { label.showEditor(); });
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Label& label;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LabelAccessibilityHandler)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
/** Basic accessible interface for a Slider.
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
class JUCE_API SliderAccessibilityHandler : public AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
explicit SliderAccessibilityHandler (Slider& sliderToWrap)
|
||||
: AccessibilityHandler (sliderToWrap,
|
||||
AccessibilityRole::slider,
|
||||
{},
|
||||
{ std::make_unique<SliderValueInterface> (sliderToWrap) })
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
class SliderValueInterface : public AccessibilityValueInterface
|
||||
{
|
||||
public:
|
||||
explicit SliderValueInterface (Slider& sliderToWrap)
|
||||
: slider (sliderToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
bool isReadOnly() const override { return false; }
|
||||
|
||||
double getCurrentValue() const override
|
||||
{
|
||||
return slider.isTwoValue() ? slider.getMaxValue() : slider.getValue();
|
||||
}
|
||||
|
||||
void setValue (double newValue) override
|
||||
{
|
||||
if (slider.isTwoValue())
|
||||
slider.setMaxValue (newValue, sendNotification);
|
||||
else
|
||||
slider.setValue (newValue, sendNotification);
|
||||
}
|
||||
|
||||
String getCurrentValueAsString() const override
|
||||
{
|
||||
return slider.getTextFromValue (getCurrentValue());
|
||||
}
|
||||
|
||||
void setValueAsString (const String& newValue) override
|
||||
{
|
||||
setValue (slider.getValueFromText (newValue));
|
||||
}
|
||||
|
||||
AccessibleValueRange getRange() const override
|
||||
{
|
||||
return { { slider.getMinimum(), slider.getMaximum() },
|
||||
getStepSize() };
|
||||
}
|
||||
|
||||
private:
|
||||
double getStepSize() const
|
||||
{
|
||||
auto interval = slider.getInterval();
|
||||
|
||||
return interval != 0.0 ? interval
|
||||
: slider.proportionOfLengthToValue (0.01);
|
||||
}
|
||||
|
||||
Slider& slider;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SliderAccessibilityHandler)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
/** Basic accessible interface for a TableListBox.
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
class JUCE_API TableListBoxAccessibilityHandler : public AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
explicit TableListBoxAccessibilityHandler (TableListBox& tableListBoxToWrap)
|
||||
: AccessibilityHandler (tableListBoxToWrap,
|
||||
AccessibilityRole::list,
|
||||
{},
|
||||
{ std::make_unique<TableListBoxTableInterface> (tableListBoxToWrap) })
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
class TableListBoxTableInterface : public AccessibilityTableInterface
|
||||
{
|
||||
public:
|
||||
explicit TableListBoxTableInterface (TableListBox& tableListBoxToWrap)
|
||||
: tableListBox (tableListBoxToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
int getNumRows() const override
|
||||
{
|
||||
if (auto* model = tableListBox.getModel())
|
||||
return model->getNumRows();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getNumColumns() const override
|
||||
{
|
||||
return tableListBox.getHeader().getNumColumns (false);
|
||||
}
|
||||
|
||||
const AccessibilityHandler* getCellHandler (int row, int column) const override
|
||||
{
|
||||
if (isPositiveAndBelow (row, getNumRows()) && isPositiveAndBelow (column, getNumColumns()))
|
||||
if (auto* cellComponent = tableListBox.getCellComponent (tableListBox.getHeader().getColumnIdOfIndex (column, false), row))
|
||||
return cellComponent->getAccessibilityHandler();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
TableListBox& tableListBox;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableListBoxAccessibilityHandler)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
/** Basic accessible interface for a TextEditor.
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
class JUCE_API TextEditorAccessibilityHandler : public AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
explicit TextEditorAccessibilityHandler (TextEditor& textEditorToWrap)
|
||||
: AccessibilityHandler (textEditorToWrap,
|
||||
textEditorToWrap.isReadOnly() ? AccessibilityRole::staticText : AccessibilityRole::editableText,
|
||||
{},
|
||||
{ textEditorToWrap.isReadOnly() ? nullptr : std::make_unique<TextEditorTextInterface> (textEditorToWrap) }),
|
||||
textEditor (textEditorToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
String getTitle() const override
|
||||
{
|
||||
return textEditor.isReadOnly() ? textEditor.getText() : textEditor.getTitle();
|
||||
}
|
||||
|
||||
private:
|
||||
class TextEditorTextInterface : public AccessibilityTextInterface
|
||||
{
|
||||
public:
|
||||
explicit TextEditorTextInterface (TextEditor& editor)
|
||||
: textEditor (editor)
|
||||
{
|
||||
}
|
||||
|
||||
bool isDisplayingProtectedText() const override { return textEditor.getPasswordCharacter() != 0; }
|
||||
|
||||
int getTotalNumCharacters() const override { return textEditor.getText().length(); }
|
||||
Range<int> getSelection() const override { return textEditor.getHighlightedRegion(); }
|
||||
void setSelection (Range<int> r) override { textEditor.setHighlightedRegion (r); }
|
||||
|
||||
String getText (Range<int> r) const override
|
||||
{
|
||||
if (isDisplayingProtectedText())
|
||||
return String::repeatedString (String::charToString (textEditor.getPasswordCharacter()),
|
||||
getTotalNumCharacters());
|
||||
|
||||
return textEditor.getTextInRange (r);
|
||||
}
|
||||
|
||||
void setText (const String& newText) override
|
||||
{
|
||||
textEditor.setText (newText);
|
||||
}
|
||||
|
||||
int getTextInsertionOffset() const override { return textEditor.getCaretPosition(); }
|
||||
|
||||
RectangleList<int> getTextBounds (Range<int> textRange) const override
|
||||
{
|
||||
auto localRects = textEditor.getTextBounds (textRange);
|
||||
RectangleList<int> globalRects;
|
||||
|
||||
std::for_each (localRects.begin(), localRects.end(),
|
||||
[&] (const Rectangle<int>& r) { globalRects.add (textEditor.localAreaToGlobal (r)); });
|
||||
|
||||
return globalRects;
|
||||
}
|
||||
|
||||
int getOffsetAtPoint (Point<int> point) const override
|
||||
{
|
||||
auto localPoint = textEditor.getLocalPoint (nullptr, point);
|
||||
return textEditor.getTextIndexAt (localPoint.x, localPoint.y);
|
||||
}
|
||||
|
||||
private:
|
||||
TextEditor& textEditor;
|
||||
};
|
||||
|
||||
TextEditor& textEditor;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TextEditorAccessibilityHandler)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
/** Basic accessible interface for a TreeView.
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
class JUCE_API TreeViewAccessibilityHandler : public AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
explicit TreeViewAccessibilityHandler (TreeView& treeViewToWrap)
|
||||
: AccessibilityHandler (treeViewToWrap,
|
||||
AccessibilityRole::tree,
|
||||
{},
|
||||
{ std::make_unique<TreeViewTableInterface> (treeViewToWrap) })
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
class TreeViewTableInterface : public AccessibilityTableInterface
|
||||
{
|
||||
public:
|
||||
explicit TreeViewTableInterface (TreeView& treeViewToWrap)
|
||||
: treeView (treeViewToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
int getNumRows() const override
|
||||
{
|
||||
return treeView.getNumRowsInTree();
|
||||
}
|
||||
|
||||
int getNumColumns() const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
const AccessibilityHandler* getCellHandler (int row, int) const override
|
||||
{
|
||||
if (auto* itemComp = treeView.getItemComponent (treeView.getItemOnRow (row)))
|
||||
return itemComp->getAccessibilityHandler();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
TreeView& treeView;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TreeViewAccessibilityHandler)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -188,6 +188,9 @@ void Button::setToggleState (bool shouldBeOn, NotificationType clickNotification
|
|||
sendStateMessage();
|
||||
else
|
||||
buttonStateChanged();
|
||||
|
||||
if (auto* handler = getAccessibilityHandler())
|
||||
handler->notifyAccessibilityEvent (AccessibilityEvent::valueChanged);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -205,6 +208,8 @@ void Button::setClickingTogglesState (bool shouldToggle) noexcept
|
|||
// it is that this button represents, and the button will update its state to reflect this
|
||||
// in the applicationCommandListChanged() method.
|
||||
jassert (commandManagerToUse == nullptr || ! clickTogglesState);
|
||||
|
||||
invalidateAccessibilityHandler();
|
||||
}
|
||||
|
||||
bool Button::getClickingTogglesState() const noexcept
|
||||
|
|
@ -220,6 +225,8 @@ void Button::setRadioGroupId (int newGroupId, NotificationType notification)
|
|||
|
||||
if (lastToggleState)
|
||||
turnOffOtherButtonsInGroup (notification, notification);
|
||||
|
||||
invalidateAccessibilityHandler();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -692,4 +699,10 @@ void Button::repeatTimerCallback()
|
|||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> Button::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<ButtonAccessibilityHandler> (*this);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -470,6 +470,8 @@ protected:
|
|||
void focusLost (FocusChangeType) override;
|
||||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -482,15 +482,15 @@ Component::~Component()
|
|||
|
||||
componentListeners.call ([this] (ComponentListener& l) { l.componentBeingDeleted (*this); });
|
||||
|
||||
masterReference.clear();
|
||||
|
||||
while (childComponentList.size() > 0)
|
||||
removeChildComponent (childComponentList.size() - 1, false, true);
|
||||
|
||||
masterReference.clear();
|
||||
|
||||
if (parentComponent != nullptr)
|
||||
parentComponent->removeChildComponent (parentComponent->childComponentList.indexOf (this), true, false);
|
||||
else if (hasKeyboardFocus (true))
|
||||
giveAwayFocus (currentlyFocusedComponent != this);
|
||||
else
|
||||
giveAwayKeyboardFocusInternal (isParentOf (currentlyFocusedComponent));
|
||||
|
||||
if (flags.hasHeavyweightPeerFlag)
|
||||
removeFromDesktop();
|
||||
|
|
@ -551,8 +551,8 @@ void Component::setVisible (bool shouldBeVisible)
|
|||
if (parentComponent != nullptr)
|
||||
parentComponent->grabKeyboardFocus();
|
||||
|
||||
if (hasKeyboardFocus (true))
|
||||
giveAwayFocus (true);
|
||||
// ensure that keyboard focus is given away if it wasn't taken by parent
|
||||
giveAwayKeyboardFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -704,6 +704,9 @@ void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo)
|
|||
|
||||
repaint();
|
||||
internalHierarchyChanged();
|
||||
|
||||
if (auto* handler = getAccessibilityHandler())
|
||||
notifyAccessibilityEventInternal (*handler, InternalAccessibilityEvent::windowOpened);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -716,6 +719,9 @@ void Component::removeFromDesktop()
|
|||
|
||||
if (flags.hasHeavyweightPeerFlag)
|
||||
{
|
||||
if (auto* handler = getAccessibilityHandler())
|
||||
notifyAccessibilityEventInternal (*handler, InternalAccessibilityEvent::windowClosed);
|
||||
|
||||
ComponentHelpers::releaseAllCachedImageResources (*this);
|
||||
|
||||
auto* peer = ComponentPeer::getPeerFor (this);
|
||||
|
|
@ -886,7 +892,7 @@ void Component::reorderChildInternal (int sourceIndex, int destIndex)
|
|||
}
|
||||
}
|
||||
|
||||
void Component::toFront (bool setAsForeground)
|
||||
void Component::toFront (bool shouldGrabKeyboardFocus)
|
||||
{
|
||||
// if component methods are being called from threads other than the message
|
||||
// thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
|
||||
|
|
@ -896,9 +902,9 @@ void Component::toFront (bool setAsForeground)
|
|||
{
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
peer->toFront (setAsForeground);
|
||||
peer->toFront (shouldGrabKeyboardFocus);
|
||||
|
||||
if (setAsForeground && ! hasKeyboardFocus (true))
|
||||
if (shouldGrabKeyboardFocus && ! hasKeyboardFocus (true))
|
||||
grabKeyboardFocus();
|
||||
}
|
||||
}
|
||||
|
|
@ -926,7 +932,7 @@ void Component::toFront (bool setAsForeground)
|
|||
}
|
||||
}
|
||||
|
||||
if (setAsForeground)
|
||||
if (shouldGrabKeyboardFocus)
|
||||
{
|
||||
internalBroughtToFront();
|
||||
|
||||
|
|
@ -1498,9 +1504,7 @@ Component* Component::removeChildComponent (int index, bool sendParentEvents, bo
|
|||
// thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
|
||||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN
|
||||
|
||||
auto* child = childComponentList [index];
|
||||
|
||||
if (child != nullptr)
|
||||
if (auto* child = childComponentList [index])
|
||||
{
|
||||
sendParentEvents = sendParentEvents && child->isShowing();
|
||||
|
||||
|
|
@ -1518,23 +1522,19 @@ Component* Component::removeChildComponent (int index, bool sendParentEvents, bo
|
|||
ComponentHelpers::releaseAllCachedImageResources (*child);
|
||||
|
||||
// (NB: there are obscure situations where child->isShowing() = false, but it still has the focus)
|
||||
if (currentlyFocusedComponent == child || child->isParentOf (currentlyFocusedComponent))
|
||||
if (child->hasKeyboardFocus (true))
|
||||
{
|
||||
const WeakReference<Component> safeThis (this);
|
||||
|
||||
child->giveAwayKeyboardFocusInternal (sendChildEvents || currentlyFocusedComponent != child);
|
||||
|
||||
if (sendParentEvents)
|
||||
{
|
||||
const WeakReference<Component> thisPointer (this);
|
||||
|
||||
giveAwayFocus (sendChildEvents || currentlyFocusedComponent != child);
|
||||
|
||||
if (thisPointer == nullptr)
|
||||
if (safeThis == nullptr)
|
||||
return child;
|
||||
|
||||
grabKeyboardFocus();
|
||||
}
|
||||
else
|
||||
{
|
||||
giveAwayFocus (sendChildEvents || currentlyFocusedComponent != child);
|
||||
}
|
||||
}
|
||||
|
||||
if (sendChildEvents)
|
||||
|
|
@ -1542,9 +1542,11 @@ Component* Component::removeChildComponent (int index, bool sendParentEvents, bo
|
|||
|
||||
if (sendParentEvents)
|
||||
internalChildrenChanged();
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -1656,6 +1658,10 @@ void Component::internalHierarchyChanged()
|
|||
|
||||
i = jmin (i, childComponentList.size());
|
||||
}
|
||||
|
||||
if (flags.hasHeavyweightPeerFlag)
|
||||
if (auto* handler = getAccessibilityHandler())
|
||||
handler->notifyAccessibilityEvent (AccessibilityEvent::structureChanged);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -2416,7 +2422,7 @@ void Component::internalMouseDown (MouseInputSource source, Point<float> relativ
|
|||
|
||||
if (! flags.dontFocusOnMouseClickFlag)
|
||||
{
|
||||
grabFocusInternal (focusChangedByMouseClick, true);
|
||||
grabKeyboardFocusInternal (focusChangedByMouseClick, true);
|
||||
|
||||
if (checker.shouldBailOut())
|
||||
return;
|
||||
|
|
@ -2644,36 +2650,48 @@ void Component::focusGained (FocusChangeType) {}
|
|||
void Component::focusLost (FocusChangeType) {}
|
||||
void Component::focusOfChildComponentChanged (FocusChangeType) {}
|
||||
|
||||
void Component::internalFocusGain (FocusChangeType cause)
|
||||
void Component::internalKeyboardFocusGain (FocusChangeType cause)
|
||||
{
|
||||
internalFocusGain (cause, WeakReference<Component> (this));
|
||||
internalKeyboardFocusGain (cause, WeakReference<Component> (this));
|
||||
}
|
||||
|
||||
void Component::internalFocusGain (FocusChangeType cause, const WeakReference<Component>& safePointer)
|
||||
void Component::internalKeyboardFocusGain (FocusChangeType cause,
|
||||
const WeakReference<Component>& safePointer)
|
||||
{
|
||||
focusGained (cause);
|
||||
|
||||
if (safePointer != nullptr)
|
||||
internalChildFocusChange (cause, safePointer);
|
||||
{
|
||||
if (auto* handler = getAccessibilityHandler())
|
||||
handler->grabFocus();
|
||||
|
||||
internalChildKeyboardFocusChange (cause, safePointer);
|
||||
}
|
||||
}
|
||||
|
||||
void Component::internalFocusLoss (FocusChangeType cause)
|
||||
void Component::internalKeyboardFocusLoss (FocusChangeType cause)
|
||||
{
|
||||
const WeakReference<Component> safePointer (this);
|
||||
|
||||
focusLost (cause);
|
||||
|
||||
if (safePointer != nullptr)
|
||||
internalChildFocusChange (cause, safePointer);
|
||||
{
|
||||
if (auto* handler = getAccessibilityHandler())
|
||||
handler->giveAwayFocus();
|
||||
|
||||
internalChildKeyboardFocusChange (cause, safePointer);
|
||||
}
|
||||
}
|
||||
|
||||
void Component::internalChildFocusChange (FocusChangeType cause, const WeakReference<Component>& safePointer)
|
||||
void Component::internalChildKeyboardFocusChange (FocusChangeType cause,
|
||||
const WeakReference<Component>& safePointer)
|
||||
{
|
||||
const bool childIsNowFocused = hasKeyboardFocus (true);
|
||||
const bool childIsNowKeyboardFocused = hasKeyboardFocus (true);
|
||||
|
||||
if (flags.childCompFocusedFlag != childIsNowFocused)
|
||||
if (flags.childKeyboardFocusedFlag != childIsNowKeyboardFocused)
|
||||
{
|
||||
flags.childCompFocusedFlag = childIsNowFocused;
|
||||
flags.childKeyboardFocusedFlag = childIsNowKeyboardFocused;
|
||||
|
||||
focusOfChildComponentChanged (cause);
|
||||
|
||||
|
|
@ -2682,12 +2700,12 @@ void Component::internalChildFocusChange (FocusChangeType cause, const WeakRefer
|
|||
}
|
||||
|
||||
if (parentComponent != nullptr)
|
||||
parentComponent->internalChildFocusChange (cause, WeakReference<Component> (parentComponent));
|
||||
parentComponent->internalChildKeyboardFocusChange (cause, parentComponent);
|
||||
}
|
||||
|
||||
void Component::setWantsKeyboardFocus (bool wantsFocus) noexcept
|
||||
{
|
||||
flags.wantsFocusFlag = wantsFocus;
|
||||
flags.wantsKeyboardFocusFlag = wantsFocus;
|
||||
}
|
||||
|
||||
void Component::setMouseClickGrabsKeyboardFocus (bool shouldGrabFocus)
|
||||
|
|
@ -2702,12 +2720,15 @@ bool Component::getMouseClickGrabsKeyboardFocus() const noexcept
|
|||
|
||||
bool Component::getWantsKeyboardFocus() const noexcept
|
||||
{
|
||||
return flags.wantsFocusFlag && ! flags.isDisabledFlag;
|
||||
return flags.wantsKeyboardFocusFlag && ! flags.isDisabledFlag;
|
||||
}
|
||||
|
||||
void Component::setFocusContainer (bool shouldBeFocusContainer) noexcept
|
||||
void Component::setFocusContainerType (FocusContainerType containerType) noexcept
|
||||
{
|
||||
flags.isFocusContainerFlag = shouldBeFocusContainer;
|
||||
flags.isFocusContainerFlag = (containerType == FocusContainerType::focusContainer
|
||||
|| containerType == FocusContainerType::keyboardFocusContainer);
|
||||
|
||||
flags.isKeyboardFocusContainerFlag = (containerType == FocusContainerType::keyboardFocusContainer);
|
||||
}
|
||||
|
||||
bool Component::isFocusContainer() const noexcept
|
||||
|
|
@ -2715,6 +2736,35 @@ bool Component::isFocusContainer() const noexcept
|
|||
return flags.isFocusContainerFlag;
|
||||
}
|
||||
|
||||
bool Component::isKeyboardFocusContainer() const noexcept
|
||||
{
|
||||
return flags.isKeyboardFocusContainerFlag;
|
||||
}
|
||||
|
||||
template <typename FocusContainerFn>
|
||||
static Component* findContainer (const Component* child, FocusContainerFn isFocusContainer)
|
||||
{
|
||||
if (auto* parent = child->getParentComponent())
|
||||
{
|
||||
if ((parent->*isFocusContainer)() || parent->getParentComponent() == nullptr)
|
||||
return parent;
|
||||
|
||||
return findContainer (parent, isFocusContainer);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Component* Component::findFocusContainer() const
|
||||
{
|
||||
return findContainer (this, &Component::isFocusContainer);
|
||||
}
|
||||
|
||||
Component* Component::findKeyboardFocusContainer() const
|
||||
{
|
||||
return findContainer (this, &Component::isKeyboardFocusContainer);
|
||||
}
|
||||
|
||||
static const Identifier juce_explicitFocusOrderId ("_jexfo");
|
||||
|
||||
int Component::getExplicitFocusOrder() const
|
||||
|
|
@ -2727,27 +2777,35 @@ void Component::setExplicitFocusOrder (int newFocusOrderIndex)
|
|||
properties.set (juce_explicitFocusOrderId, newFocusOrderIndex);
|
||||
}
|
||||
|
||||
KeyboardFocusTraverser* Component::createFocusTraverser()
|
||||
std::unique_ptr<ComponentTraverser> Component::createFocusTraverser()
|
||||
{
|
||||
if (flags.isFocusContainerFlag || parentComponent == nullptr)
|
||||
return new KeyboardFocusTraverser();
|
||||
return std::make_unique<FocusTraverser>();
|
||||
|
||||
return parentComponent->createFocusTraverser();
|
||||
}
|
||||
|
||||
std::unique_ptr<ComponentTraverser> Component::createKeyboardFocusTraverser()
|
||||
{
|
||||
if (flags.isKeyboardFocusContainerFlag || parentComponent == nullptr)
|
||||
return std::make_unique<KeyboardFocusTraverser>();
|
||||
|
||||
return parentComponent->createKeyboardFocusTraverser();
|
||||
}
|
||||
|
||||
void Component::takeKeyboardFocus (FocusChangeType cause)
|
||||
{
|
||||
// give the focus to this component
|
||||
if (currentlyFocusedComponent != this)
|
||||
{
|
||||
// get the focus onto our desktop window
|
||||
if (currentlyFocusedComponent == this)
|
||||
return;
|
||||
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
const WeakReference<Component> safePointer (this);
|
||||
peer->grabFocus();
|
||||
|
||||
if (peer->isFocused() && currentlyFocusedComponent != this)
|
||||
{
|
||||
if (! peer->isFocused() || currentlyFocusedComponent == this)
|
||||
return;
|
||||
|
||||
WeakReference<Component> componentLosingFocus (currentlyFocusedComponent);
|
||||
currentlyFocusedComponent = this;
|
||||
|
||||
|
|
@ -2756,56 +2814,41 @@ void Component::takeKeyboardFocus (FocusChangeType cause)
|
|||
// call this after setting currentlyFocusedComponent so that the one that's
|
||||
// losing it has a chance to see where focus is going
|
||||
if (componentLosingFocus != nullptr)
|
||||
componentLosingFocus->internalFocusLoss (cause);
|
||||
componentLosingFocus->internalKeyboardFocusLoss (cause);
|
||||
|
||||
if (currentlyFocusedComponent == this)
|
||||
internalFocusGain (cause, safePointer);
|
||||
}
|
||||
}
|
||||
internalKeyboardFocusGain (cause, safePointer);
|
||||
}
|
||||
}
|
||||
|
||||
void Component::grabFocusInternal (FocusChangeType cause, bool canTryParent)
|
||||
void Component::grabKeyboardFocusInternal (FocusChangeType cause, bool canTryParent)
|
||||
{
|
||||
if (isShowing())
|
||||
{
|
||||
if (flags.wantsFocusFlag && (isEnabled() || parentComponent == nullptr))
|
||||
if (! isShowing())
|
||||
return;
|
||||
|
||||
if (flags.wantsKeyboardFocusFlag
|
||||
&& (isEnabled() || parentComponent == nullptr))
|
||||
{
|
||||
takeKeyboardFocus (cause);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isParentOf (currentlyFocusedComponent)
|
||||
&& currentlyFocusedComponent->isShowing())
|
||||
{
|
||||
// do nothing if the focused component is actually a child of ours..
|
||||
}
|
||||
else
|
||||
{
|
||||
// find the default child component..
|
||||
std::unique_ptr<KeyboardFocusTraverser> traverser (createFocusTraverser());
|
||||
|
||||
if (traverser != nullptr)
|
||||
{
|
||||
auto* defaultComp = traverser->getDefaultComponent (this);
|
||||
traverser.reset();
|
||||
if (isParentOf (currentlyFocusedComponent) && currentlyFocusedComponent->isShowing())
|
||||
return;
|
||||
|
||||
if (defaultComp != nullptr)
|
||||
if (auto traverser = createKeyboardFocusTraverser())
|
||||
{
|
||||
defaultComp->grabFocusInternal (cause, false);
|
||||
if (auto* defaultComp = traverser->getDefaultComponent (this))
|
||||
{
|
||||
defaultComp->grabKeyboardFocusInternal (cause, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (canTryParent && parentComponent != nullptr)
|
||||
{
|
||||
// if no children want it and we're allowed to try our parent comp,
|
||||
// then pass up to parent, which will try our siblings.
|
||||
parentComponent->grabFocusInternal (cause, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (canTryParent && parentComponent != nullptr)
|
||||
parentComponent->grabKeyboardFocusInternal (cause, true);
|
||||
}
|
||||
|
||||
void Component::grabKeyboardFocus()
|
||||
|
|
@ -2814,7 +2857,7 @@ void Component::grabKeyboardFocus()
|
|||
// thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
|
||||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
|
||||
grabFocusInternal (focusChangedDirectly, true);
|
||||
grabKeyboardFocusInternal (focusChangedDirectly, true);
|
||||
|
||||
// A component can only be focused when it's actually on the screen!
|
||||
// If this fails then you're probably trying to grab the focus before you've
|
||||
|
|
@ -2823,6 +2866,31 @@ void Component::grabKeyboardFocus()
|
|||
jassert (isShowing() || isOnDesktop());
|
||||
}
|
||||
|
||||
void Component::giveAwayKeyboardFocusInternal (bool sendFocusLossEvent)
|
||||
{
|
||||
if (hasKeyboardFocus (true))
|
||||
{
|
||||
if (auto* componentLosingFocus = currentlyFocusedComponent)
|
||||
{
|
||||
currentlyFocusedComponent = nullptr;
|
||||
|
||||
if (sendFocusLossEvent && componentLosingFocus != nullptr)
|
||||
componentLosingFocus->internalKeyboardFocusLoss (focusChangedDirectly);
|
||||
|
||||
Desktop::getInstance().triggerFocusCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Component::giveAwayKeyboardFocus()
|
||||
{
|
||||
// if component methods are being called from threads other than the message
|
||||
// thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
|
||||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
|
||||
giveAwayKeyboardFocusInternal (true);
|
||||
}
|
||||
|
||||
void Component::moveKeyboardFocusToSibling (bool moveToNext)
|
||||
{
|
||||
// if component methods are being called from threads other than the message
|
||||
|
|
@ -2831,15 +2899,27 @@ void Component::moveKeyboardFocusToSibling (bool moveToNext)
|
|||
|
||||
if (parentComponent != nullptr)
|
||||
{
|
||||
std::unique_ptr<KeyboardFocusTraverser> traverser (createFocusTraverser());
|
||||
|
||||
if (traverser != nullptr)
|
||||
if (auto traverser = createKeyboardFocusTraverser())
|
||||
{
|
||||
auto* nextComp = moveToNext ? traverser->getNextComponent (this)
|
||||
: traverser->getPreviousComponent (this);
|
||||
traverser.reset();
|
||||
auto findComponentToFocus = [&]() -> Component*
|
||||
{
|
||||
if (auto* comp = (moveToNext ? traverser->getNextComponent (this)
|
||||
: traverser->getPreviousComponent (this)))
|
||||
return comp;
|
||||
|
||||
if (nextComp != nullptr)
|
||||
if (auto* focusContainer = findKeyboardFocusContainer())
|
||||
{
|
||||
auto allFocusableComponents = traverser->getAllComponents (focusContainer);
|
||||
|
||||
if (! allFocusableComponents.empty())
|
||||
return moveToNext ? allFocusableComponents.front()
|
||||
: allFocusableComponents.back();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
if (auto* nextComp = findComponentToFocus())
|
||||
{
|
||||
if (nextComp->isCurrentlyBlockedByAnotherModalComponent())
|
||||
{
|
||||
|
|
@ -2850,7 +2930,7 @@ void Component::moveKeyboardFocusToSibling (bool moveToNext)
|
|||
return;
|
||||
}
|
||||
|
||||
nextComp->grabFocusInternal (focusChangedByTabKey, true);
|
||||
nextComp->grabKeyboardFocusInternal (focusChangedByTabKey, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -2872,19 +2952,8 @@ Component* JUCE_CALLTYPE Component::getCurrentlyFocusedComponent() noexcept
|
|||
|
||||
void JUCE_CALLTYPE Component::unfocusAllComponents()
|
||||
{
|
||||
if (auto* c = getCurrentlyFocusedComponent())
|
||||
c->giveAwayFocus (true);
|
||||
}
|
||||
|
||||
void Component::giveAwayFocus (bool sendFocusLossEvent)
|
||||
{
|
||||
auto* componentLosingFocus = currentlyFocusedComponent;
|
||||
currentlyFocusedComponent = nullptr;
|
||||
|
||||
if (sendFocusLossEvent && componentLosingFocus != nullptr)
|
||||
componentLosingFocus->internalFocusLoss (focusChangedDirectly);
|
||||
|
||||
Desktop::getInstance().triggerFocusCallback();
|
||||
if (currentlyFocusedComponent != nullptr)
|
||||
currentlyFocusedComponent->giveAwayKeyboardFocus();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -3029,4 +3098,52 @@ bool Component::BailOutChecker::shouldBailOut() const noexcept
|
|||
return safePointer == nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Component::setTitle (const String& newTitle)
|
||||
{
|
||||
componentTitle = newTitle;
|
||||
}
|
||||
|
||||
void Component::setDescription (const String& newDescription)
|
||||
{
|
||||
componentDescription = newDescription;
|
||||
}
|
||||
|
||||
void Component::setHelpText (const String& newHelpText)
|
||||
{
|
||||
componentHelpText = newHelpText;
|
||||
}
|
||||
|
||||
void Component::setAccessible (bool shouldBeAccessible)
|
||||
{
|
||||
flags.accessibilityIgnoredFlag = ! shouldBeAccessible;
|
||||
|
||||
if (flags.accessibilityIgnoredFlag)
|
||||
invalidateAccessibilityHandler();
|
||||
}
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> Component::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::unspecified);
|
||||
}
|
||||
|
||||
void Component::invalidateAccessibilityHandler()
|
||||
{
|
||||
accessibilityHandler = nullptr;
|
||||
}
|
||||
|
||||
AccessibilityHandler* Component::getAccessibilityHandler()
|
||||
{
|
||||
if (flags.accessibilityIgnoredFlag)
|
||||
return nullptr;
|
||||
|
||||
if (accessibilityHandler == nullptr
|
||||
|| accessibilityHandler->getTypeIndex() != std::type_index (typeid (*this)))
|
||||
{
|
||||
accessibilityHandler = createAccessibilityHandler();
|
||||
}
|
||||
|
||||
return accessibilityHandler.get();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ public:
|
|||
/** Returns the name of this component.
|
||||
@see setName
|
||||
*/
|
||||
const String& getName() const noexcept { return componentName; }
|
||||
String getName() const noexcept { return componentName; }
|
||||
|
||||
/** Sets the name of this component.
|
||||
|
||||
|
|
@ -87,7 +87,7 @@ public:
|
|||
/** Returns the ID string that was set by setComponentID().
|
||||
@see setComponentID, findChildWithID
|
||||
*/
|
||||
const String& getComponentID() const noexcept { return componentID; }
|
||||
String getComponentID() const noexcept { return componentID; }
|
||||
|
||||
/** Sets the component's ID string.
|
||||
You can retrieve the ID using getComponentID().
|
||||
|
|
@ -217,11 +217,12 @@ public:
|
|||
then they will still be kept in front of this one (unless of course this
|
||||
one is also 'always-on-top').
|
||||
|
||||
@param shouldAlsoGainFocus if true, this will also try to assign keyboard focus
|
||||
to the component (see grabKeyboardFocus() for more details)
|
||||
@param shouldAlsoGainKeyboardFocus if true, this will also try to assign
|
||||
keyboard focus to the component (see
|
||||
grabKeyboardFocus() for more details)
|
||||
@see toBack, toBehind, setAlwaysOnTop
|
||||
*/
|
||||
void toFront (bool shouldAlsoGainFocus);
|
||||
void toFront (bool shouldAlsoGainKeyboardFocus);
|
||||
|
||||
/** Changes this component's z-order to be at the back of all its siblings.
|
||||
|
||||
|
|
@ -1203,55 +1204,156 @@ public:
|
|||
bool isBroughtToFrontOnMouseClick() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
// Keyboard focus methods
|
||||
// Focus methods
|
||||
|
||||
/** Sets a flag to indicate whether this component needs keyboard focus or not.
|
||||
/** Sets the focus order of this component.
|
||||
|
||||
By default components aren't actually interested in gaining the
|
||||
The focus order is used by the default traverser implementation returned by
|
||||
createFocusTraverser() as part of its algorithm for deciding the order in
|
||||
which components should be traversed. A value of 0 or less is taken to mean
|
||||
that no explicit order is wanted, and that traversal should use other
|
||||
factors, like the component's position.
|
||||
|
||||
@see getExplicitFocusOrder, FocusTraverser, createFocusTraverser
|
||||
*/
|
||||
void setExplicitFocusOrder (int newFocusOrderIndex);
|
||||
|
||||
/** Returns the focus order of this component, if one has been specified.
|
||||
|
||||
By default components don't have a focus order - in that case, this will
|
||||
return 0.
|
||||
|
||||
@see setExplicitFocusOrder
|
||||
*/
|
||||
int getExplicitFocusOrder() const;
|
||||
|
||||
/** A focus container type that can be passed to setFocusContainer().
|
||||
|
||||
If a component is marked as a focus container or keyboard focus container then
|
||||
it will act as the top-level component within which focus or keyboard focus is
|
||||
passed around. By default components are considered "focusable" if they are visible
|
||||
and enabled and "keyboard focusable" if `getWantsKeyboardFocus() == true`.
|
||||
|
||||
The order of traversal within a focus container is determined by the objects
|
||||
returned by createFocusTraverser() and createKeyboardFocusTraverser(),
|
||||
respectively - see the documentation of the default FocusContainer and
|
||||
KeyboardFocusContainer implementations for more information.
|
||||
*/
|
||||
enum class FocusContainerType
|
||||
{
|
||||
/** The component will not act as a focus container.
|
||||
|
||||
This is the default setting for non top-level components and means that it and any
|
||||
sub-components are navigable within their containing focus container.
|
||||
*/
|
||||
none,
|
||||
|
||||
/** The component will act as a top-level component within which focus is passed around.
|
||||
|
||||
The default traverser implementation returned by createFocusTraverser() will use this
|
||||
flag to find the first parent component (of the currently focused one) that wants to
|
||||
be a focus container.
|
||||
|
||||
This is currently used when determining the hierarchy of accessible UI elements presented
|
||||
to screen reader clients on supported platforms. See the AccessibilityHandler class for
|
||||
more information.
|
||||
*/
|
||||
focusContainer,
|
||||
|
||||
/** The component will act as a top-level component within which keyboard focus is passed around.
|
||||
|
||||
The default traverser implementation returned by createKeyboardFocusTraverser() will
|
||||
use this flag to find the first parent component (of the currently focused one) that
|
||||
wants to be a keyboard focus container.
|
||||
|
||||
This is currently used when determining how keyboard focus is passed between components
|
||||
that have been marked as keyboard focusable with setWantsKeyboardFocus() when clicking
|
||||
on components and navigating with the tab key.
|
||||
*/
|
||||
keyboardFocusContainer
|
||||
};
|
||||
|
||||
/** Sets whether this component is a container for components that can have
|
||||
their focus traversed, and the type of focus traversal that it supports.
|
||||
|
||||
@see FocusContainerType, isFocusContainer, isKeyboardFocusContainer,
|
||||
FocusTraverser, createFocusTraverser,
|
||||
KeyboardFocusTraverser, createKeyboardFocusTraverser
|
||||
*/
|
||||
void setFocusContainerType (FocusContainerType containerType) noexcept;
|
||||
|
||||
/** Returns true if this component has been marked as a focus container.
|
||||
|
||||
@see setFocusContainer
|
||||
*/
|
||||
bool isFocusContainer() const noexcept;
|
||||
|
||||
/** Returns true if this component has been marked as a keyboard focus container.
|
||||
|
||||
@see setFocusContainer
|
||||
*/
|
||||
bool isKeyboardFocusContainer() const noexcept;
|
||||
|
||||
/** Returns the focus container for this component.
|
||||
|
||||
@see isFocusContainer, setFocusContainer
|
||||
*/
|
||||
Component* findFocusContainer() const;
|
||||
|
||||
/** Returns the keyboard focus container for this component.
|
||||
|
||||
@see isFocusContainer, setFocusContainer
|
||||
*/
|
||||
Component* findKeyboardFocusContainer() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets a flag to indicate whether this component wants keyboard focus or not.
|
||||
|
||||
By default components aren't actually interested in gaining the keyboard
|
||||
focus, but this method can be used to turn this on.
|
||||
|
||||
See the grabKeyboardFocus() method for details about the way a component
|
||||
is chosen to receive the focus.
|
||||
|
||||
@see grabKeyboardFocus, getWantsKeyboardFocus
|
||||
@see grabKeyboardFocus, giveAwayKeyboardFocus, getWantsKeyboardFocus
|
||||
*/
|
||||
void setWantsKeyboardFocus (bool wantsFocus) noexcept;
|
||||
|
||||
/** Returns true if the component is interested in getting keyboard focus.
|
||||
|
||||
This returns the flag set by setWantsKeyboardFocus(). The default
|
||||
setting is false.
|
||||
This returns the flag set by setWantsKeyboardFocus(). The default setting
|
||||
is false.
|
||||
|
||||
@see setWantsKeyboardFocus
|
||||
*/
|
||||
bool getWantsKeyboardFocus() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Chooses whether a click on this component automatically grabs the focus.
|
||||
|
||||
By default this is set to true, but you might want a component which can
|
||||
be focused, but where you don't want the user to be able to affect it directly
|
||||
by clicking.
|
||||
be focused, but where you don't want the user to be able to affect it
|
||||
directly by clicking.
|
||||
*/
|
||||
void setMouseClickGrabsKeyboardFocus (bool shouldGrabFocus);
|
||||
|
||||
/** Returns the last value set with setMouseClickGrabsKeyboardFocus().
|
||||
See setMouseClickGrabsKeyboardFocus() for more info.
|
||||
|
||||
@see setMouseClickGrabsKeyboardFocus
|
||||
*/
|
||||
bool getMouseClickGrabsKeyboardFocus() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Tries to give keyboard focus to this component.
|
||||
|
||||
When the user clicks on a component or its grabKeyboardFocus()
|
||||
method is called, the following procedure is used to work out which
|
||||
component should get it:
|
||||
When the user clicks on a component or its grabKeyboardFocus() method is
|
||||
called, the following procedure is used to work out which component should
|
||||
get it:
|
||||
|
||||
- if the component that was clicked on actually wants focus (as indicated
|
||||
by calling getWantsKeyboardFocus), it gets it.
|
||||
- if the component itself doesn't want focus, it will try to pass it
|
||||
on to whichever of its children is the default component, as determined by
|
||||
KeyboardFocusTraverser::getDefaultComponent()
|
||||
the getDefaultComponent() implemetation of the ComponentTraverser returned
|
||||
by createKeyboardFocusTraverser().
|
||||
- if none of its children want focus at all, it will pass it up to its
|
||||
parent instead, unless it's a top-level component without a parent,
|
||||
in which case it just takes the focus itself.
|
||||
|
|
@ -1261,12 +1363,21 @@ public:
|
|||
visible. So there's no point trying to call this in the component's own
|
||||
constructor or before all of its parent hierarchy has been fully instantiated.
|
||||
|
||||
@see setWantsKeyboardFocus, getWantsKeyboardFocus, hasKeyboardFocus,
|
||||
getCurrentlyFocusedComponent, focusGained, focusLost,
|
||||
@see giveAwayKeyboardFocus, setWantsKeyboardFocus, getWantsKeyboardFocus,
|
||||
hasKeyboardFocus, getCurrentlyFocusedComponent, focusGained, focusLost,
|
||||
keyPressed, keyStateChanged
|
||||
*/
|
||||
void grabKeyboardFocus();
|
||||
|
||||
/** If this component or any of its children currently have the keyboard focus,
|
||||
this will defocus it, send a focus change notification, and try to pass the
|
||||
focus to the next component.
|
||||
|
||||
@see grabKeyboardFocus, setWantsKeyboardFocus, getCurrentlyFocusedComponent,
|
||||
focusGained, focusLost
|
||||
*/
|
||||
void giveAwayKeyboardFocus();
|
||||
|
||||
/** Returns true if this component currently has the keyboard focus.
|
||||
|
||||
@param trueIfChildIsFocused if this is true, then the method returns true if
|
||||
|
|
@ -1274,13 +1385,28 @@ public:
|
|||
have the focus. If false, the method only returns true if
|
||||
this component has the focus.
|
||||
|
||||
@see grabKeyboardFocus, setWantsKeyboardFocus, getCurrentlyFocusedComponent,
|
||||
focusGained, focusLost
|
||||
@see grabKeyboardFocus, giveAwayKeyboardFocus, setWantsKeyboardFocus,
|
||||
getCurrentlyFocusedComponent, focusGained, focusLost
|
||||
*/
|
||||
bool hasKeyboardFocus (bool trueIfChildIsFocused) const;
|
||||
|
||||
/** Tries to move the keyboard focus to one of this component's siblings.
|
||||
|
||||
This will try to move focus to either the next or previous component, as
|
||||
determined by the getNextComponent() and getPreviousComponent() implemetations
|
||||
of the ComponentTraverser returned by createKeyboardFocusTraverser().
|
||||
|
||||
This is the method that is used when shifting focus by pressing the tab key.
|
||||
|
||||
@param moveToNext if true, the focus will move forwards; if false, it will
|
||||
move backwards
|
||||
@see grabKeyboardFocus, giveAwayKeyboardFocus, setFocusContainer, setWantsKeyboardFocus
|
||||
*/
|
||||
void moveKeyboardFocusToSibling (bool moveToNext);
|
||||
|
||||
/** Returns the component that currently has the keyboard focus.
|
||||
@returns the focused component, or null if nothing is focused.
|
||||
|
||||
@returns the focused component, or nullptr if nothing is focused.
|
||||
*/
|
||||
static Component* JUCE_CALLTYPE getCurrentlyFocusedComponent() noexcept;
|
||||
|
||||
|
|
@ -1288,83 +1414,32 @@ public:
|
|||
static void JUCE_CALLTYPE unfocusAllComponents();
|
||||
|
||||
//==============================================================================
|
||||
/** Tries to move the keyboard focus to one of this component's siblings.
|
||||
/** Creates a ComponentTraverser object to determine the logic by which focus should be
|
||||
passed from this component.
|
||||
|
||||
This will try to move focus to either the next or previous component. (This
|
||||
is the method that is used when shifting focus by pressing the tab key).
|
||||
The default implementation of this method will return an instance of FocusTraverser
|
||||
if this component is a focus container (as determined by the setFocusContainer() method).
|
||||
If the component isn't a focus container, then it will recursively call
|
||||
createFocusTraverser() on its parents.
|
||||
|
||||
Components for which getWantsKeyboardFocus() returns false are not looked at.
|
||||
|
||||
@param moveToNext if true, the focus will move forwards; if false, it will
|
||||
move backwards
|
||||
@see grabKeyboardFocus, setFocusContainer, setWantsKeyboardFocus
|
||||
If you override this to return a custom traverser object, then this component and
|
||||
all its sub-components will use the new object to make their focusing decisions.
|
||||
*/
|
||||
void moveKeyboardFocusToSibling (bool moveToNext);
|
||||
virtual std::unique_ptr<ComponentTraverser> createFocusTraverser();
|
||||
|
||||
/** Creates a KeyboardFocusTraverser object to use to determine the logic by
|
||||
which focus should be passed from this component.
|
||||
/** Creates a ComponentTraverser object to use to determine the logic by which keyboard
|
||||
focus should be passed from this component.
|
||||
|
||||
The default implementation of this method will return a default
|
||||
KeyboardFocusTraverser if this component is a focus container (as determined
|
||||
by the setFocusContainer() method). If the component isn't a focus
|
||||
container, then it will recursively ask its parents for a KeyboardFocusTraverser.
|
||||
The default implementation of this method will return an instance of
|
||||
KeyboardFocusTraverser if this component is a keyboard focus container (as determined by
|
||||
the setFocusContainer() method). If the component isn't a keyboard focus container, then
|
||||
it will recursively call createKeyboardFocusTraverser() on its parents.
|
||||
|
||||
If you override this to return a custom KeyboardFocusTraverser, then
|
||||
this component and all its sub-components will use the new object to
|
||||
make their focusing decisions.
|
||||
|
||||
The method should return a new object, which the caller is required to
|
||||
delete when no longer needed.
|
||||
If you override this to return a custom traverser object, then this component and
|
||||
all its sub-components will use the new object to make their keyboard focusing
|
||||
decisions.
|
||||
*/
|
||||
virtual KeyboardFocusTraverser* createFocusTraverser();
|
||||
|
||||
/** Returns the focus order of this component, if one has been specified.
|
||||
|
||||
By default components don't have a focus order - in that case, this
|
||||
will return 0. Lower numbers indicate that the component will be
|
||||
earlier in the focus traversal order.
|
||||
|
||||
To change the order, call setExplicitFocusOrder().
|
||||
|
||||
The focus order may be used by the KeyboardFocusTraverser class as part of
|
||||
its algorithm for deciding the order in which components should be traversed.
|
||||
See the KeyboardFocusTraverser class for more details on this.
|
||||
|
||||
@see moveKeyboardFocusToSibling, createFocusTraverser, KeyboardFocusTraverser
|
||||
*/
|
||||
int getExplicitFocusOrder() const;
|
||||
|
||||
/** Sets the index used in determining the order in which focusable components
|
||||
should be traversed.
|
||||
|
||||
A value of 0 or less is taken to mean that no explicit order is wanted, and
|
||||
that traversal should use other factors, like the component's position.
|
||||
|
||||
@see getExplicitFocusOrder, moveKeyboardFocusToSibling
|
||||
*/
|
||||
void setExplicitFocusOrder (int newFocusOrderIndex);
|
||||
|
||||
/** Indicates whether this component is a parent for components that can have
|
||||
their focus traversed.
|
||||
|
||||
This flag is used by the default implementation of the createFocusTraverser()
|
||||
method, which uses the flag to find the first parent component (of the currently
|
||||
focused one) which wants to be a focus container.
|
||||
|
||||
So using this method to set the flag to 'true' causes this component to
|
||||
act as the top level within which focus is passed around.
|
||||
|
||||
@see isFocusContainer, createFocusTraverser, moveKeyboardFocusToSibling
|
||||
*/
|
||||
void setFocusContainer (bool shouldBeFocusContainer) noexcept;
|
||||
|
||||
/** Returns true if this component has been marked as a focus container.
|
||||
|
||||
See setFocusContainer() for more details.
|
||||
|
||||
@see setFocusContainer, moveKeyboardFocusToSibling, createFocusTraverser
|
||||
*/
|
||||
bool isFocusContainer() const noexcept;
|
||||
virtual std::unique_ptr<ComponentTraverser> createKeyboardFocusTraverser();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the component (and all its parents) are enabled.
|
||||
|
|
@ -2284,7 +2359,109 @@ public:
|
|||
*/
|
||||
bool getViewportIgnoreDragFlag() const noexcept { return flags.viewportIgnoreDragFlag; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the title text for this component.
|
||||
|
||||
@see setTitle
|
||||
*/
|
||||
String getTitle() const noexcept { return componentTitle; }
|
||||
|
||||
/** Sets the title for this component.
|
||||
|
||||
If this component supports accessibility using the default AccessibilityHandler
|
||||
implementation, this string will be passed to accessibility clients requesting a
|
||||
title and may be read out by a screen reader.
|
||||
|
||||
@see getTitle, getAccessibilityHandler
|
||||
*/
|
||||
void setTitle (const String& newTitle);
|
||||
|
||||
/** Returns the description for this component.
|
||||
|
||||
@see setDescription
|
||||
*/
|
||||
String getDescription() const noexcept { return componentDescription; }
|
||||
|
||||
/** Sets the description for this component.
|
||||
|
||||
If this component supports accessibility using the default AccessibilityHandler
|
||||
implementation, this string will be passed to accessibility clients requesting a
|
||||
description and may be read out by a screen reader.
|
||||
|
||||
@see getDescription, getAccessibilityHandler
|
||||
*/
|
||||
void setDescription (const String& newDescription);
|
||||
|
||||
/** Returns the help text for this component.
|
||||
|
||||
@see setHelpText
|
||||
*/
|
||||
String getHelpText() const noexcept { return componentHelpText; }
|
||||
|
||||
/** Sets the help text for this component.
|
||||
|
||||
If this component supports accessibility using the default AccessibilityHandler
|
||||
implementation, this string will be passed to accessibility clients requesting help text
|
||||
and may be read out by a screen reader.
|
||||
|
||||
@see getHelpText, getAccessibilityHandler
|
||||
*/
|
||||
void setHelpText (const String& newHelpText);
|
||||
|
||||
/** Sets whether this component is visible to accessibility clients.
|
||||
|
||||
If this flag is set to false then the getAccessibilityHandler() method will return nullptr
|
||||
and this component will not be visible to any accessibility clients.
|
||||
|
||||
By default this is set to true.
|
||||
|
||||
@see getAccessibilityHandler, createAccessibilityHandler
|
||||
*/
|
||||
void setAccessible (bool shouldBeAccessible);
|
||||
|
||||
/** Returns the accessibility handler for this component, or nullptr if this component is not
|
||||
accessible.
|
||||
|
||||
@see createAccessibilityHandler, setAccessible
|
||||
*/
|
||||
AccessibilityHandler* getAccessibilityHandler();
|
||||
|
||||
/** Invalidates the AccessibilityHandler that is currently being used for this component.
|
||||
|
||||
Use this to indicate that something in the accessible component has changed
|
||||
and its handler needs to be updated. This will trigger a call to
|
||||
createAccessibilityHandler().
|
||||
*/
|
||||
void invalidateAccessibilityHandler();
|
||||
|
||||
//==============================================================================
|
||||
// This method has been deprecated in favour of the setFocusContainerType() method
|
||||
// that takes a more descriptive enum.
|
||||
JUCE_DEPRECATED_WITH_BODY (void setFocusContainer (bool shouldBeFocusContainer) noexcept,
|
||||
{
|
||||
setFocusContainerType (shouldBeFocusContainer ? FocusContainerType::keyboardFocusContainer
|
||||
: FocusContainerType::none);
|
||||
})
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
/** Override this method to return a custom AccessibilityHandler for this component.
|
||||
|
||||
The default implementation creates and returns a AccessibilityHandler object with an
|
||||
unspecified role, meaning that it will be visible to accessibility clients but
|
||||
without a specific role, action callbacks or interfaces. To control how accessibility
|
||||
clients see and interact with your component subclass AccessibilityHandler, implement
|
||||
the desired behaviours, and return an instance of it from this method in your
|
||||
component subclass.
|
||||
|
||||
The accessibility handler you return here is guaranteed to be destroyed before
|
||||
its Component, so it's safe to store and use a reference back to the Component
|
||||
inside the AccessibilityHandler if necessary.
|
||||
|
||||
@see getAccessibilityHandler
|
||||
*/
|
||||
virtual std::unique_ptr<AccessibilityHandler> createAccessibilityHandler();
|
||||
|
||||
//==============================================================================
|
||||
friend class ComponentPeer;
|
||||
friend class MouseInputSource;
|
||||
|
|
@ -2294,7 +2471,7 @@ private:
|
|||
static Component* currentlyFocusedComponent;
|
||||
|
||||
//==============================================================================
|
||||
String componentName, componentID;
|
||||
String componentName, componentID, componentTitle, componentDescription, componentHelpText;
|
||||
Component* parentComponent = nullptr;
|
||||
Rectangle<int> boundsRelativeToParent;
|
||||
std::unique_ptr<Positioner> positioner;
|
||||
|
|
@ -2314,6 +2491,8 @@ private:
|
|||
friend class WeakReference<Component>;
|
||||
WeakReference<Component>::Master masterReference;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> accessibilityHandler;
|
||||
|
||||
struct ComponentFlags
|
||||
{
|
||||
bool hasHeavyweightPeerFlag : 1;
|
||||
|
|
@ -2321,20 +2500,22 @@ private:
|
|||
bool opaqueFlag : 1;
|
||||
bool ignoresMouseClicksFlag : 1;
|
||||
bool allowChildMouseClicksFlag : 1;
|
||||
bool wantsFocusFlag : 1;
|
||||
bool wantsKeyboardFocusFlag : 1;
|
||||
bool isFocusContainerFlag : 1;
|
||||
bool isKeyboardFocusContainerFlag : 1;
|
||||
bool childKeyboardFocusedFlag : 1;
|
||||
bool dontFocusOnMouseClickFlag : 1;
|
||||
bool alwaysOnTopFlag : 1;
|
||||
bool bufferToImageFlag : 1;
|
||||
bool bringToFrontOnClickFlag : 1;
|
||||
bool repaintOnMouseActivityFlag : 1;
|
||||
bool isDisabledFlag : 1;
|
||||
bool childCompFocusedFlag : 1;
|
||||
bool dontClipGraphicsFlag : 1;
|
||||
bool mouseDownWasBlocked : 1;
|
||||
bool isMoveCallbackPending : 1;
|
||||
bool isResizeCallbackPending : 1;
|
||||
bool viewportIgnoreDragFlag : 1;
|
||||
bool accessibilityIgnoredFlag : 1;
|
||||
#if JUCE_DEBUG
|
||||
bool isInsidePaintCall : 1;
|
||||
#endif
|
||||
|
|
@ -2358,10 +2539,10 @@ private:
|
|||
void internalMouseWheel (MouseInputSource, Point<float>, Time, const MouseWheelDetails&);
|
||||
void internalMagnifyGesture (MouseInputSource, Point<float>, Time, float);
|
||||
void internalBroughtToFront();
|
||||
void internalFocusGain (FocusChangeType, const WeakReference<Component>&);
|
||||
void internalFocusGain (FocusChangeType);
|
||||
void internalFocusLoss (FocusChangeType);
|
||||
void internalChildFocusChange (FocusChangeType, const WeakReference<Component>&);
|
||||
void internalKeyboardFocusGain (FocusChangeType, const WeakReference<Component>&);
|
||||
void internalKeyboardFocusGain (FocusChangeType);
|
||||
void internalKeyboardFocusLoss (FocusChangeType);
|
||||
void internalChildKeyboardFocusChange (FocusChangeType, const WeakReference<Component>&);
|
||||
void internalModalInputAttempt();
|
||||
void internalModifierKeysChanged();
|
||||
void internalChildrenChanged();
|
||||
|
|
@ -2377,8 +2558,8 @@ private:
|
|||
void repaintParent();
|
||||
void sendFakeMouseMove() const;
|
||||
void takeKeyboardFocus (FocusChangeType);
|
||||
void grabFocusInternal (FocusChangeType, bool canTryParent);
|
||||
static void giveAwayFocus (bool sendFocusLossEvent);
|
||||
void grabKeyboardFocusInternal (FocusChangeType, bool canTryParent);
|
||||
void giveAwayKeyboardFocusInternal (bool sendFocusLossEvent);
|
||||
void sendEnablementChangeMessage();
|
||||
void sendVisibilityChangeMessage();
|
||||
|
||||
|
|
|
|||
72
modules/juce_gui_basics/components/juce_ComponentTraverser.h
Normal file
72
modules/juce_gui_basics/components/juce_ComponentTraverser.h
Normal 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
|
||||
359
modules/juce_gui_basics/components/juce_FocusTraverser.cpp
Normal file
359
modules/juce_gui_basics/components/juce_FocusTraverser.cpp
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
93
modules/juce_gui_basics/components/juce_FocusTraverser.h
Normal file
93
modules/juce_gui_basics/components/juce_FocusTraverser.h
Normal 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
|
||||
|
|
@ -204,4 +204,10 @@ std::unique_ptr<Drawable> Drawable::createFromImageFile (const File& file)
|
|||
return {};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> Drawable::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::ignored);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -199,6 +199,8 @@ protected:
|
|||
void setBoundsToEnclose (Rectangle<float>);
|
||||
/** @internal */
|
||||
void applyDrawableClipPath (Graphics&);
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
Point<int> originRelativeToComponent;
|
||||
std::unique_ptr<Drawable> drawableClipPath;
|
||||
|
|
|
|||
|
|
@ -133,4 +133,10 @@ Path DrawableImage::getOutlineAsPath() const
|
|||
return {}; // not applicable for images
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> DrawableImage::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::image);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -94,6 +94,8 @@ public:
|
|||
Rectangle<float> getDrawableBounds() const override;
|
||||
/** @internal */
|
||||
Path getOutlineAsPath() const override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -208,4 +208,25 @@ bool DrawableText::replaceColour (Colour originalColour, Colour replacementColou
|
|||
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
|
||||
|
|
|
|||
|
|
@ -98,6 +98,8 @@ public:
|
|||
Path getOutlineAsPath() const override;
|
||||
/** @internal */
|
||||
bool replaceColour (Colour originalColour, Colour replacementColour) override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -616,4 +616,10 @@ void FileBrowserComponent::timerCallback()
|
|||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> FileBrowserComponent::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -252,6 +252,8 @@ public:
|
|||
FilePreviewComponent* getPreviewComponent() const noexcept;
|
||||
/** @internal */
|
||||
DirectoryContentsDisplayComponent* getDisplayComponent() const noexcept;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
protected:
|
||||
/** Returns a list of names and paths for the default places the user might want to look.
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ FileListComponent::FileListComponent (DirectoryContentsList& listToShow)
|
|||
DirectoryContentsDisplayComponent (listToShow),
|
||||
lastDirectory (listToShow.getDirectory())
|
||||
{
|
||||
setTitle ("Files");
|
||||
setModel (this);
|
||||
directoryContentsList.addChangeListener (this);
|
||||
}
|
||||
|
|
@ -68,7 +69,7 @@ void FileListComponent::setSelectedFile (const File& f)
|
|||
{
|
||||
for (int i = directoryContentsList.getNumFiles(); --i >= 0;)
|
||||
{
|
||||
if (directoryContentsList.getFile(i) == f)
|
||||
if (directoryContentsList.getFile (i) == f)
|
||||
{
|
||||
fileWaitingToBeSelected = File();
|
||||
|
||||
|
|
@ -189,6 +190,11 @@ public:
|
|||
repaint();
|
||||
}
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
FileListComponent& owner;
|
||||
|
|
@ -231,6 +237,11 @@ int FileListComponent::getNumRows()
|
|||
return directoryContentsList.getNumFiles();
|
||||
}
|
||||
|
||||
String FileListComponent::getNameForRow (int rowNumber)
|
||||
{
|
||||
return directoryContentsList.getFile (rowNumber).getFileName();
|
||||
}
|
||||
|
||||
void FileListComponent::paintListBoxItem (int, Graphics&, int, int, bool)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ private:
|
|||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -190,6 +190,11 @@ public:
|
|||
indexInContentsList, owner);
|
||||
}
|
||||
|
||||
String getAccessibilityName() override
|
||||
{
|
||||
return file.getFileName();
|
||||
}
|
||||
|
||||
void itemClicked (const MouseEvent& e) override
|
||||
{
|
||||
owner.sendMouseClickMessage (file, e);
|
||||
|
|
|
|||
|
|
@ -70,11 +70,11 @@ void FilenameComponent::resized()
|
|||
getLookAndFeel().layoutFilenameComponent (*this, &filenameBox, browseButton.get());
|
||||
}
|
||||
|
||||
KeyboardFocusTraverser* FilenameComponent::createFocusTraverser()
|
||||
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::createFocusTraverser() : nullptr;
|
||||
return getWantsKeyboardFocus() ? Component::createKeyboardFocusTraverser() : nullptr;
|
||||
}
|
||||
|
||||
void FilenameComponent::setBrowseButtonText (const String& newBrowseButtonText)
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ public:
|
|||
/** @internal */
|
||||
void fileDragExit (const StringArray&) override;
|
||||
/** @internal */
|
||||
KeyboardFocusTraverser* createFocusTraverser() override;
|
||||
std::unique_ptr<ComponentTraverser> createKeyboardFocusTraverser() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -117,4 +117,10 @@ void ImagePreviewComponent::paint (Graphics& g)
|
|||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> ImagePreviewComponent::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::image);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ public:
|
|||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void timerCallback() override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
File fileToLoad;
|
||||
|
|
|
|||
|
|
@ -66,6 +66,8 @@
|
|||
#include <windowsx.h>
|
||||
#include <vfw.h>
|
||||
#include <commdlg.h>
|
||||
#include <UIAutomation.h>
|
||||
#include <sapi.h>
|
||||
|
||||
#if JUCE_WEB_BROWSER
|
||||
#include <exdisp.h>
|
||||
|
|
@ -103,8 +105,10 @@ namespace juce
|
|||
extern bool juce_areThereAnyAlwaysOnTopWindows();
|
||||
}
|
||||
|
||||
#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"
|
||||
|
|
@ -240,6 +244,7 @@ namespace juce
|
|||
#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"
|
||||
|
|
@ -249,6 +254,12 @@ namespace juce
|
|||
#include "native/juce_mac_MouseCursor.mm"
|
||||
|
||||
#elif JUCE_WINDOWS
|
||||
#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"
|
||||
#include "native/juce_win32_Windowing.cpp"
|
||||
#include "native/juce_win32_DragAndDrop.cpp"
|
||||
#include "native/juce_win32_FileChooser.cpp"
|
||||
|
|
|
|||
|
|
@ -155,6 +155,8 @@ namespace juce
|
|||
class ApplicationCommandManagerListener;
|
||||
class DrawableButton;
|
||||
class Displays;
|
||||
class AccessibilityHandler;
|
||||
class KeyboardFocusTraverser;
|
||||
|
||||
class FlexBox;
|
||||
class Grid;
|
||||
|
|
@ -167,7 +169,8 @@ namespace juce
|
|||
#include "mouse/juce_MouseEvent.h"
|
||||
#include "keyboard/juce_KeyPress.h"
|
||||
#include "keyboard/juce_KeyListener.h"
|
||||
#include "keyboard/juce_KeyboardFocusTraverser.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"
|
||||
|
|
@ -185,6 +188,7 @@ namespace juce
|
|||
#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"
|
||||
|
|
@ -293,6 +297,22 @@ namespace juce
|
|||
#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"
|
||||
#include "accessibility/widget_handlers/juce_ButtonAccessibilityHandler.h"
|
||||
#include "accessibility/widget_handlers/juce_ComboBoxAccessibilityHandler.h"
|
||||
#include "accessibility/widget_handlers/juce_LabelAccessibilityHandler.h"
|
||||
#include "accessibility/widget_handlers/juce_SliderAccessibilityHandler.h"
|
||||
#include "accessibility/widget_handlers/juce_TableListBoxAccessibilityHandler.h"
|
||||
#include "accessibility/widget_handlers/juce_TextEditorAccessibilityHandler.h"
|
||||
#include "accessibility/widget_handlers/juce_TreeViewAccessibilityHandler.h"
|
||||
|
||||
#if JUCE_LINUX || JUCE_BSD
|
||||
#if JUCE_GUI_BASICS_INCLUDE_XHEADERS
|
||||
|
|
|
|||
|
|
@ -26,105 +26,248 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
namespace KeyboardFocusHelpers
|
||||
//==============================================================================
|
||||
namespace KeyboardFocusTraverserHelpers
|
||||
{
|
||||
static int getOrder (const Component* c)
|
||||
static bool isKeyboardFocusable (const Component* comp, const Component* container)
|
||||
{
|
||||
auto order = c->getExplicitFocusOrder();
|
||||
return order > 0 ? order : (std::numeric_limits<int>::max() / 2);
|
||||
return comp->getWantsKeyboardFocus() && container->isParentOf (comp);
|
||||
}
|
||||
|
||||
static void findAllFocusableComponents (Component* parent, Array<Component*>& comps)
|
||||
static Component* traverse (Component* current, Component* container,
|
||||
FocusHelpers::NavigationDirection direction)
|
||||
{
|
||||
if (parent->getNumChildComponents() != 0)
|
||||
if (auto* comp = FocusHelpers::navigateFocus (current, container, direction,
|
||||
&Component::isKeyboardFocusContainer))
|
||||
{
|
||||
Array<Component*> localComps;
|
||||
if (isKeyboardFocusable (comp, container))
|
||||
return comp;
|
||||
|
||||
for (auto* c : parent->getChildren())
|
||||
if (c->isVisible() && c->isEnabled())
|
||||
localComps.add (c);
|
||||
|
||||
// This will sort so that they are ordered in terms of left-to-right
|
||||
// and then top-to-bottom.
|
||||
std::stable_sort (localComps.begin(), localComps.end(),
|
||||
[] (const Component* a, const Component* b)
|
||||
{
|
||||
auto explicitOrder1 = getOrder (a);
|
||||
auto explicitOrder2 = getOrder (b);
|
||||
|
||||
if (explicitOrder1 != explicitOrder2)
|
||||
return explicitOrder1 < explicitOrder2;
|
||||
|
||||
if (a->getY() != b->getY())
|
||||
return a->getY() < b->getY();
|
||||
|
||||
return a->getX() < b->getX();
|
||||
});
|
||||
|
||||
for (auto* c : localComps)
|
||||
{
|
||||
if (c->getWantsKeyboardFocus())
|
||||
comps.add (c);
|
||||
|
||||
if (! c->isFocusContainer())
|
||||
findAllFocusableComponents (c, comps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Component* findFocusContainer (Component* c)
|
||||
{
|
||||
c = c->getParentComponent();
|
||||
|
||||
if (c != nullptr)
|
||||
while (c->getParentComponent() != nullptr && ! c->isFocusContainer())
|
||||
c = c->getParentComponent();
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static Component* getIncrementedComponent (Component* current, int delta)
|
||||
{
|
||||
if (auto* focusContainer = findFocusContainer (current))
|
||||
{
|
||||
Array<Component*> comps;
|
||||
KeyboardFocusHelpers::findAllFocusableComponents (focusContainer, comps);
|
||||
|
||||
if (! comps.isEmpty())
|
||||
{
|
||||
auto index = comps.indexOf (current);
|
||||
return comps [(index + comps.size() + delta) % comps.size()];
|
||||
}
|
||||
return traverse (comp, container, direction);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
KeyboardFocusTraverser::KeyboardFocusTraverser() {}
|
||||
KeyboardFocusTraverser::~KeyboardFocusTraverser() {}
|
||||
|
||||
Component* KeyboardFocusTraverser::getNextComponent (Component* current)
|
||||
{
|
||||
jassert (current != nullptr);
|
||||
return KeyboardFocusHelpers::getIncrementedComponent (current, 1);
|
||||
return KeyboardFocusTraverserHelpers::traverse (current, current->findKeyboardFocusContainer(),
|
||||
FocusHelpers::NavigationDirection::forwards);
|
||||
}
|
||||
|
||||
Component* KeyboardFocusTraverser::getPreviousComponent (Component* current)
|
||||
{
|
||||
jassert (current != nullptr);
|
||||
return KeyboardFocusHelpers::getIncrementedComponent (current, -1);
|
||||
return KeyboardFocusTraverserHelpers::traverse (current, current->findKeyboardFocusContainer(),
|
||||
FocusHelpers::NavigationDirection::backwards);
|
||||
}
|
||||
|
||||
Component* KeyboardFocusTraverser::getDefaultComponent (Component* parentComponent)
|
||||
{
|
||||
Array<Component*> comps;
|
||||
for (auto* comp : getAllComponents (parentComponent))
|
||||
if (KeyboardFocusTraverserHelpers::isKeyboardFocusable (comp, parentComponent))
|
||||
return comp;
|
||||
|
||||
if (parentComponent != nullptr)
|
||||
KeyboardFocusHelpers::findAllFocusableComponents (parentComponent, comps);
|
||||
|
||||
return comps.getFirst();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<Component*> KeyboardFocusTraverser::getAllComponents (Component* parentComponent)
|
||||
{
|
||||
std::vector<Component*> components;
|
||||
FocusHelpers::findAllComponents (parentComponent,
|
||||
components,
|
||||
&Component::isKeyboardFocusContainer);
|
||||
|
||||
auto removePredicate = [parentComponent] (const Component* comp)
|
||||
{
|
||||
return ! KeyboardFocusTraverserHelpers::isKeyboardFocusable (comp, parentComponent);
|
||||
};
|
||||
|
||||
components.erase (std::remove_if (std::begin (components), std::end (components), std::move (removePredicate)),
|
||||
std::end (components));
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
struct KeyboardFocusTraverserTests : public UnitTest
|
||||
{
|
||||
KeyboardFocusTraverserTests()
|
||||
: UnitTest ("KeyboardFocusTraverser", UnitTestCategories::gui)
|
||||
{}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
ScopedJuceInitialiser_GUI libraryInitialiser;
|
||||
|
||||
beginTest ("No child wants keyboard focus");
|
||||
{
|
||||
TestComponent parent;
|
||||
|
||||
expect (traverser.getDefaultComponent (&parent) == nullptr);
|
||||
expect (traverser.getAllComponents (&parent).empty());
|
||||
}
|
||||
|
||||
beginTest ("Single child wants keyboard focus");
|
||||
{
|
||||
TestComponent parent;
|
||||
|
||||
parent.children[5].setWantsKeyboardFocus (true);
|
||||
|
||||
auto* defaultComponent = traverser.getDefaultComponent (&parent);
|
||||
|
||||
expect (defaultComponent == &parent.children[5]);
|
||||
expect (defaultComponent->getWantsKeyboardFocus());
|
||||
|
||||
expect (traverser.getNextComponent (defaultComponent) == nullptr);
|
||||
expect (traverser.getPreviousComponent (defaultComponent) == nullptr);
|
||||
expect (traverser.getAllComponents (&parent).size() == 1);
|
||||
}
|
||||
|
||||
beginTest ("Multiple children want keyboard focus");
|
||||
{
|
||||
TestComponent parent;
|
||||
|
||||
Component* focusChildren[]
|
||||
{
|
||||
&parent.children[1],
|
||||
&parent.children[9],
|
||||
&parent.children[3],
|
||||
&parent.children[5],
|
||||
&parent.children[8],
|
||||
&parent.children[0]
|
||||
};
|
||||
|
||||
for (auto* focusChild : focusChildren)
|
||||
focusChild->setWantsKeyboardFocus (true);
|
||||
|
||||
auto allComponents = traverser.getAllComponents (&parent);
|
||||
|
||||
for (auto* focusChild : focusChildren)
|
||||
expect (std::find (allComponents.cbegin(), allComponents.cend(), focusChild) != allComponents.cend());
|
||||
|
||||
auto* componentToTest = traverser.getDefaultComponent (&parent);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
expect (componentToTest->getWantsKeyboardFocus());
|
||||
expect (std::find (std::begin (focusChildren), std::end (focusChildren), componentToTest) != std::end (focusChildren));
|
||||
|
||||
componentToTest = traverser.getNextComponent (componentToTest);
|
||||
|
||||
if (componentToTest == nullptr)
|
||||
break;
|
||||
}
|
||||
|
||||
int focusOrder = 1;
|
||||
for (auto* focusChild : focusChildren)
|
||||
focusChild->setExplicitFocusOrder (focusOrder++);
|
||||
|
||||
componentToTest = traverser.getDefaultComponent (&parent);
|
||||
|
||||
for (auto* focusChild : focusChildren)
|
||||
{
|
||||
expect (componentToTest == focusChild);
|
||||
expect (componentToTest->getWantsKeyboardFocus());
|
||||
|
||||
componentToTest = traverser.getNextComponent (componentToTest);
|
||||
}
|
||||
}
|
||||
|
||||
beginTest ("Single nested child wants keyboard focus");
|
||||
{
|
||||
TestComponent parent;
|
||||
Component grandparent;
|
||||
|
||||
grandparent.addAndMakeVisible (parent);
|
||||
|
||||
auto& focusChild = parent.children[5];
|
||||
|
||||
focusChild.setWantsKeyboardFocus (true);
|
||||
|
||||
expect (traverser.getDefaultComponent (&grandparent) == &focusChild);
|
||||
expect (traverser.getDefaultComponent (&parent) == &focusChild);
|
||||
expect (traverser.getNextComponent (&focusChild) == nullptr);
|
||||
expect (traverser.getPreviousComponent (&focusChild) == nullptr);
|
||||
expect (traverser.getAllComponents (&parent).size() == 1);
|
||||
}
|
||||
|
||||
beginTest ("Multiple nested children want keyboard focus");
|
||||
{
|
||||
TestComponent parent;
|
||||
Component grandparent;
|
||||
|
||||
grandparent.addAndMakeVisible (parent);
|
||||
|
||||
Component* focusChildren[]
|
||||
{
|
||||
&parent.children[1],
|
||||
&parent.children[4],
|
||||
&parent.children[5]
|
||||
};
|
||||
|
||||
for (auto* focusChild : focusChildren)
|
||||
focusChild->setWantsKeyboardFocus (true);
|
||||
|
||||
auto allComponents = traverser.getAllComponents (&parent);
|
||||
|
||||
expect (std::equal (allComponents.cbegin(), allComponents.cend(), focusChildren,
|
||||
[] (const Component* c1, const Component* c2) { return c1 == c2; }));
|
||||
|
||||
const auto front = *focusChildren;
|
||||
const auto back = *std::prev (std::end (focusChildren));
|
||||
|
||||
expect (traverser.getDefaultComponent (&grandparent) == front);
|
||||
expect (traverser.getDefaultComponent (&parent) == front);
|
||||
expect (traverser.getNextComponent (front) == *std::next (std::begin (focusChildren)));
|
||||
expect (traverser.getPreviousComponent (back) == *std::prev (std::end (focusChildren), 2));
|
||||
|
||||
std::array<Component, 3> otherParents;
|
||||
|
||||
for (auto& p : otherParents)
|
||||
{
|
||||
grandparent.addAndMakeVisible (p);
|
||||
p.setWantsKeyboardFocus (true);
|
||||
}
|
||||
|
||||
expect (traverser.getDefaultComponent (&grandparent) == front);
|
||||
expect (traverser.getDefaultComponent (&parent) == front);
|
||||
expect (traverser.getNextComponent (back) == &otherParents.front());
|
||||
expect (traverser.getNextComponent (&otherParents.back()) == nullptr);
|
||||
expect (traverser.getAllComponents (&grandparent).size() == numElementsInArray (focusChildren) + otherParents.size());
|
||||
expect (traverser.getAllComponents (&parent).size() == (size_t) numElementsInArray (focusChildren));
|
||||
|
||||
for (auto* focusChild : focusChildren)
|
||||
focusChild->setWantsKeyboardFocus (false);
|
||||
|
||||
expect (traverser.getDefaultComponent (&grandparent) == &otherParents.front());
|
||||
expect (traverser.getDefaultComponent (&parent) == nullptr);
|
||||
expect (traverser.getAllComponents (&grandparent).size() == otherParents.size());
|
||||
expect (traverser.getAllComponents (&parent).empty());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct TestComponent : public Component
|
||||
{
|
||||
TestComponent()
|
||||
{
|
||||
for (auto& child : children)
|
||||
addAndMakeVisible (child);
|
||||
}
|
||||
|
||||
std::array<Component, 10> children;
|
||||
};
|
||||
|
||||
KeyboardFocusTraverser traverser;
|
||||
};
|
||||
|
||||
static KeyboardFocusTraverserTests keyboardFocusTraverserTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -28,63 +28,60 @@ namespace juce
|
|||
|
||||
//==============================================================================
|
||||
/**
|
||||
Controls the order in which focus moves between components.
|
||||
Controls the order in which keyboard focus moves between components.
|
||||
|
||||
The default algorithm used by this class to work out the order of traversal
|
||||
is as follows:
|
||||
- if two components both have an explicit focus order specified, then the
|
||||
one with the lowest number comes first (see the Component::setExplicitFocusOrder()
|
||||
method).
|
||||
- any component with an explicit focus order greater than 0 comes before ones
|
||||
that don't have an order specified.
|
||||
- any unspecified components are traversed in a left-to-right, then top-to-bottom
|
||||
order.
|
||||
The default behaviour of this class uses a FocusTraverser object internally to
|
||||
determine the default/next/previous component until it finds one which wants
|
||||
keyboard focus, as set by the Component::setWantsKeyboardFocus() method.
|
||||
|
||||
If you need traversal in a more customised way, you can create a subclass
|
||||
of KeyboardFocusTraverser that uses your own algorithm, and use
|
||||
Component::createFocusTraverser() to create it.
|
||||
If you need keyboard focus traversal in a more customised way, you can create
|
||||
a subclass of ComponentTraverser that uses your own algorithm, and use
|
||||
Component::createKeyboardFocusTraverser() to create it.
|
||||
|
||||
@see Component::setExplicitFocusOrder, Component::createFocusTraverser
|
||||
@see FocusTraverser, ComponentTraverser, Component::createKeyboardFocusTraverser
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API KeyboardFocusTraverser
|
||||
class JUCE_API KeyboardFocusTraverser : public ComponentTraverser
|
||||
{
|
||||
public:
|
||||
KeyboardFocusTraverser();
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~KeyboardFocusTraverser();
|
||||
~KeyboardFocusTraverser() override = default;
|
||||
|
||||
/** Returns the component that should be given focus after the specified one
|
||||
when moving "forwards".
|
||||
/** Returns the component that should receive keyboard focus by default within the
|
||||
given parent component.
|
||||
|
||||
The default implementation will return the next component which is to the
|
||||
right of or below this one.
|
||||
|
||||
This may return nullptr if there's no suitable candidate.
|
||||
The default implementation will return the foremost focusable component (as
|
||||
determined by FocusTraverser) that also wants keyboard focus, or nullptr if
|
||||
there is no suitable component.
|
||||
*/
|
||||
virtual Component* getNextComponent (Component* current);
|
||||
Component* getDefaultComponent (Component* parentComponent) override;
|
||||
|
||||
/** Returns the component that should be given focus after the specified one
|
||||
when moving "backwards".
|
||||
/** Returns the component that should be given keyboard focus after the specified
|
||||
one when moving "forwards".
|
||||
|
||||
The default implementation will return the next component which is to the
|
||||
left of or above this one.
|
||||
|
||||
This may return nullptr if there's no suitable candidate.
|
||||
The default implementation will return the next focusable component (as
|
||||
determined by FocusTraverser) that also wants keyboard focus, or nullptr if
|
||||
there is no suitable component.
|
||||
*/
|
||||
virtual Component* getPreviousComponent (Component* current);
|
||||
Component* getNextComponent (Component* current) override;
|
||||
|
||||
/** Returns the component that should receive focus be default within the given
|
||||
parent component.
|
||||
/** Returns the component that should be given keyboard focus after the specified
|
||||
one when moving "backwards".
|
||||
|
||||
The default implementation will just return the foremost child component that
|
||||
wants focus.
|
||||
|
||||
This may return nullptr if there's no suitable candidate.
|
||||
The default implementation will return the previous focusable component (as
|
||||
determined by FocusTraverser) that also wants keyboard focus, or nullptr if
|
||||
there is no suitable component.
|
||||
*/
|
||||
virtual Component* getDefaultComponent (Component* parentComponent);
|
||||
Component* getPreviousComponent (Component* current) override;
|
||||
|
||||
/** Returns all of the components that can receive keyboard focus within the given
|
||||
parent component in traversal order.
|
||||
|
||||
The default implementation will return all focusable child components (as
|
||||
determined by FocusTraverser) that also wants keyboard focus.
|
||||
*/
|
||||
std::vector<Component*> getAllComponents (Component* parentComponent) override;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@ public:
|
|||
{
|
||||
ProxyComponent (Component& c)
|
||||
{
|
||||
setAccessible (false);
|
||||
setWantsKeyboardFocus (false);
|
||||
setBounds (c.getBounds());
|
||||
setTransform (c.getTransform());
|
||||
|
|
|
|||
|
|
@ -459,4 +459,10 @@ void ConcertinaPanel::panelHeaderDoubleClicked (Component* component)
|
|||
setPanelSize (component, 0, true);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> ConcertinaPanel::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -119,6 +119,10 @@ public:
|
|||
ConcertinaPanel&, Component&) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
void resized() override;
|
||||
|
||||
|
|
|
|||
|
|
@ -69,4 +69,10 @@ void GroupComponent::paint (Graphics& g)
|
|||
void GroupComponent::enablementChanged() { repaint(); }
|
||||
void GroupComponent::colourChanged() { repaint(); }
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> GroupComponent::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -98,6 +98,8 @@ public:
|
|||
void enablementChanged() override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
String text;
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ private:
|
|||
ScrollBar::ScrollBar (bool shouldBeVertical) : vertical (shouldBeVertical)
|
||||
{
|
||||
setRepaintsOnMouseActivity (true);
|
||||
setFocusContainer (true);
|
||||
setFocusContainerType (FocusContainerType::keyboardFocusContainer);
|
||||
}
|
||||
|
||||
ScrollBar::~ScrollBar()
|
||||
|
|
@ -440,4 +440,46 @@ bool ScrollBar::getVisibility() const noexcept
|
|||
&& visibleRange.getLength() > 0.0);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> ScrollBar::createAccessibilityHandler()
|
||||
{
|
||||
class ScrollBarValueInterface : public AccessibilityRangedNumericValueInterface
|
||||
{
|
||||
public:
|
||||
explicit ScrollBarValueInterface (ScrollBar& scrollBarToWrap)
|
||||
: scrollBar (scrollBarToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
bool isReadOnly() const override { return false; }
|
||||
|
||||
double getCurrentValue() const override
|
||||
{
|
||||
return scrollBar.getCurrentRangeStart();
|
||||
}
|
||||
|
||||
void setValue (double newValue) override
|
||||
{
|
||||
scrollBar.setCurrentRangeStart (newValue);
|
||||
}
|
||||
|
||||
AccessibleValueRange getRange() const override
|
||||
{
|
||||
if (scrollBar.getRangeLimit().isEmpty())
|
||||
return {};
|
||||
|
||||
return { { scrollBar.getMinimumRangeLimit(), scrollBar.getMaximumRangeLimit() },
|
||||
scrollBar.getSingleStepSize() };
|
||||
}
|
||||
|
||||
private:
|
||||
ScrollBar& scrollBar;
|
||||
};
|
||||
|
||||
return std::make_unique<AccessibilityHandler> (*this,
|
||||
AccessibilityRole::scrollBar,
|
||||
AccessibilityActions{},
|
||||
AccessibilityHandler::Interfaces { std::make_unique<ScrollBarValueInterface> (*this) });
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -211,6 +211,11 @@ public:
|
|||
*/
|
||||
void setSingleStepSize (double newSingleStepSize) noexcept;
|
||||
|
||||
/** Returns the current step size.
|
||||
@see setSingleStepSize
|
||||
*/
|
||||
double getSingleStepSize() const noexcept { return singleStepSize; }
|
||||
|
||||
/** Moves the scrollbar by a number of single-steps.
|
||||
|
||||
This will move the bar by a multiple of its single-step interval (as
|
||||
|
|
@ -409,6 +414,8 @@ public:
|
|||
void parentHierarchyChanged() override;
|
||||
/** @internal */
|
||||
void setVisible (bool) override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -294,4 +294,10 @@ bool SidePanel::isMouseEventInThisOrChildren (Component* eventComponent)
|
|||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> SidePanel::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -195,6 +195,8 @@ public:
|
|||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ TabbedButtonBar::TabbedButtonBar (Orientation orientationToUse)
|
|||
setInterceptsMouseClicks (false, true);
|
||||
behindFrontTab.reset (new BehindFrontTabComp (*this));
|
||||
addAndMakeVisible (behindFrontTab.get());
|
||||
setFocusContainer (true);
|
||||
setFocusContainerType (FocusContainerType::keyboardFocusContainer);
|
||||
}
|
||||
|
||||
TabbedButtonBar::~TabbedButtonBar()
|
||||
|
|
@ -574,4 +574,10 @@ void TabbedButtonBar::showExtraItemsMenu()
|
|||
void TabbedButtonBar::currentTabChanged (int, const String&) {}
|
||||
void TabbedButtonBar::popupMenuClickOnTab (int, const String&) {}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> TabbedButtonBar::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -334,6 +334,8 @@ public:
|
|||
void resized() override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -310,4 +310,10 @@ void TabbedComponent::changeCallback (int newCurrentTabIndex, const String& newT
|
|||
void TabbedComponent::currentTabChanged (int, const String&) {}
|
||||
void TabbedComponent::popupMenuClickOnTab (int, const String&) {}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> TabbedComponent::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -197,6 +197,8 @@ public:
|
|||
void resized() override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -293,4 +293,10 @@ void BurgerMenuComponent::lookAndFeelChanged()
|
|||
listBox.setRowHeight (roundToInt (getLookAndFeel().getPopupMenuFont().getHeight() * 2.0f));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> BurgerMenuComponent::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::menuBar);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ public:
|
|||
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -26,11 +26,63 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
class MenuBarComponent::AccessibleItemComponent : public Component
|
||||
{
|
||||
public:
|
||||
AccessibleItemComponent (MenuBarComponent& comp, const String& menuItemName)
|
||||
: owner (comp),
|
||||
name (menuItemName)
|
||||
{
|
||||
setInterceptsMouseClicks (false, false);
|
||||
}
|
||||
|
||||
const String& getName() const noexcept { return name; }
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
|
||||
{
|
||||
class ComponentHandler : public AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
explicit ComponentHandler (AccessibleItemComponent& item)
|
||||
: AccessibilityHandler (item,
|
||||
AccessibilityRole::menuItem,
|
||||
getAccessibilityActions (item)),
|
||||
itemComponent (item)
|
||||
{
|
||||
}
|
||||
|
||||
AccessibleState getCurrentState() const override
|
||||
{
|
||||
auto state = AccessibilityHandler::getCurrentState().withSelectable();
|
||||
|
||||
return state.isFocused() ? state.withSelected() : state;
|
||||
}
|
||||
|
||||
String getTitle() const override { return itemComponent.name; }
|
||||
|
||||
private:
|
||||
static AccessibilityActions getAccessibilityActions (AccessibleItemComponent& item)
|
||||
{
|
||||
auto showMenu = [&item] { item.owner.showMenu (item.owner.indexOfItemComponent (&item)); };
|
||||
|
||||
return AccessibilityActions().addAction (AccessibilityActionType::focus,
|
||||
[&item] { item.owner.setItemUnderMouse (item.owner.indexOfItemComponent (&item)); })
|
||||
.addAction (AccessibilityActionType::press, showMenu)
|
||||
.addAction (AccessibilityActionType::showMenu, showMenu);
|
||||
}
|
||||
|
||||
AccessibleItemComponent& itemComponent;
|
||||
};
|
||||
|
||||
return std::make_unique<ComponentHandler> (*this);
|
||||
}
|
||||
|
||||
private:
|
||||
MenuBarComponent& owner;
|
||||
const String name;
|
||||
};
|
||||
|
||||
MenuBarComponent::MenuBarComponent (MenuBarModel* m)
|
||||
: model (nullptr),
|
||||
itemUnderMouse (-1),
|
||||
currentPopupIndex (-1),
|
||||
topLevelIndexClicked (0)
|
||||
{
|
||||
setRepaintsOnMouseActivity (true);
|
||||
setWantsKeyboardFocus (false);
|
||||
|
|
@ -70,77 +122,83 @@ void MenuBarComponent::setModel (MenuBarModel* const newModel)
|
|||
//==============================================================================
|
||||
void MenuBarComponent::paint (Graphics& g)
|
||||
{
|
||||
const bool isMouseOverBar = currentPopupIndex >= 0 || itemUnderMouse >= 0 || isMouseOver();
|
||||
const auto isMouseOverBar = (currentPopupIndex >= 0 || itemUnderMouse >= 0 || isMouseOver());
|
||||
|
||||
getLookAndFeel().drawMenuBarBackground (g,
|
||||
getWidth(),
|
||||
getHeight(),
|
||||
isMouseOverBar,
|
||||
*this);
|
||||
getLookAndFeel().drawMenuBarBackground (g, getWidth(), getHeight(), isMouseOverBar, *this);
|
||||
|
||||
if (model != nullptr)
|
||||
{
|
||||
for (int i = 0; i < menuNames.size(); ++i)
|
||||
if (model == nullptr)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < itemComponents.size(); ++i)
|
||||
{
|
||||
const auto& itemComponent = itemComponents[i];
|
||||
const auto itemBounds = itemComponent->getBounds();
|
||||
|
||||
Graphics::ScopedSaveState ss (g);
|
||||
|
||||
g.setOrigin (xPositions [i], 0);
|
||||
g.reduceClipRegion (0, 0, xPositions[i + 1] - xPositions[i], getHeight());
|
||||
g.setOrigin (itemBounds.getX(), 0);
|
||||
g.reduceClipRegion (0, 0, itemBounds.getWidth(), itemBounds.getHeight());
|
||||
|
||||
getLookAndFeel().drawMenuBarItem (g,
|
||||
xPositions[i + 1] - xPositions[i],
|
||||
getHeight(),
|
||||
i,
|
||||
menuNames[i],
|
||||
i == itemUnderMouse,
|
||||
i == currentPopupIndex,
|
||||
itemBounds.getWidth(),
|
||||
itemBounds.getHeight(),
|
||||
(int) i,
|
||||
itemComponent->getName(),
|
||||
(int) i == itemUnderMouse,
|
||||
(int) i == currentPopupIndex,
|
||||
isMouseOverBar,
|
||||
*this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBarComponent::resized()
|
||||
{
|
||||
xPositions.clear();
|
||||
int x = 0;
|
||||
xPositions.add (x);
|
||||
|
||||
for (int i = 0; i < menuNames.size(); ++i)
|
||||
for (size_t i = 0; i < itemComponents.size(); ++i)
|
||||
{
|
||||
x += getLookAndFeel().getMenuBarItemWidth (*this, i, menuNames[i]);
|
||||
xPositions.add (x);
|
||||
auto& itemComponent = itemComponents[i];
|
||||
|
||||
auto w = getLookAndFeel().getMenuBarItemWidth (*this, (int) i, itemComponent->getName());
|
||||
itemComponent->setBounds (x, 0, w, getHeight());
|
||||
x += w;
|
||||
}
|
||||
}
|
||||
|
||||
int MenuBarComponent::getItemAt (Point<int> p)
|
||||
{
|
||||
for (int i = 0; i < xPositions.size(); ++i)
|
||||
if (p.x >= xPositions[i] && p.x < xPositions[i + 1])
|
||||
return reallyContains (p, true) ? i : -1;
|
||||
for (size_t i = 0; i < itemComponents.size(); ++i)
|
||||
if (itemComponents[i]->getBounds().contains (p) && reallyContains (p, true))
|
||||
return (int) i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void MenuBarComponent::repaintMenuItem (int index)
|
||||
{
|
||||
if (isPositiveAndBelow (index, xPositions.size()))
|
||||
if (isPositiveAndBelow (index, (int) itemComponents.size()))
|
||||
{
|
||||
const int x1 = xPositions [index];
|
||||
const int x2 = xPositions [index + 1];
|
||||
auto itemBounds = itemComponents[(size_t) index]->getBounds();
|
||||
|
||||
repaint (x1 - 2, 0, x2 - x1 + 4, getHeight());
|
||||
repaint (itemBounds.getX() - 2,
|
||||
0,
|
||||
itemBounds.getWidth() + 4,
|
||||
itemBounds.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBarComponent::setItemUnderMouse (const int index)
|
||||
void MenuBarComponent::setItemUnderMouse (int index)
|
||||
{
|
||||
if (itemUnderMouse != index)
|
||||
{
|
||||
if (itemUnderMouse == index)
|
||||
return;
|
||||
|
||||
repaintMenuItem (itemUnderMouse);
|
||||
itemUnderMouse = index;
|
||||
repaintMenuItem (itemUnderMouse);
|
||||
}
|
||||
|
||||
if (isPositiveAndBelow (itemUnderMouse, (int) itemComponents.size()))
|
||||
if (auto* handler = itemComponents[(size_t) itemUnderMouse]->getAccessibilityHandler())
|
||||
handler->grabFocus();
|
||||
}
|
||||
|
||||
void MenuBarComponent::setOpenItem (int index)
|
||||
|
|
@ -156,7 +214,7 @@ void MenuBarComponent::setOpenItem (int index)
|
|||
currentPopupIndex = index;
|
||||
repaintMenuItem (currentPopupIndex);
|
||||
|
||||
Desktop& desktop = Desktop::getInstance();
|
||||
auto& desktop = Desktop::getInstance();
|
||||
|
||||
if (index >= 0)
|
||||
desktop.addGlobalMouseListener (this);
|
||||
|
|
@ -180,30 +238,24 @@ void MenuBarComponent::showMenu (int index)
|
|||
setOpenItem (index);
|
||||
setItemUnderMouse (index);
|
||||
|
||||
if (index >= 0)
|
||||
if (isPositiveAndBelow (index, (int) itemComponents.size()))
|
||||
{
|
||||
PopupMenu m (model->getMenuForIndex (itemUnderMouse,
|
||||
menuNames [itemUnderMouse]));
|
||||
const auto& itemComponent = itemComponents[(size_t) index];
|
||||
auto m = model->getMenuForIndex (itemUnderMouse, itemComponent->getName());
|
||||
|
||||
if (m.lookAndFeel == nullptr)
|
||||
m.setLookAndFeel (&getLookAndFeel());
|
||||
|
||||
const Rectangle<int> itemPos (xPositions [index], 0, xPositions [index + 1] - xPositions [index], getHeight());
|
||||
auto itemBounds = itemComponent->getBounds();
|
||||
|
||||
m.showMenuAsync (PopupMenu::Options().withTargetComponent (this)
|
||||
.withTargetScreenArea (localAreaToGlobal (itemPos))
|
||||
.withMinimumWidth (itemPos.getWidth()),
|
||||
ModalCallbackFunction::forComponent (menuBarMenuDismissedCallback, this, index));
|
||||
.withTargetScreenArea (localAreaToGlobal (itemBounds))
|
||||
.withMinimumWidth (itemBounds.getWidth()),
|
||||
[this, index] (int result) { menuDismissed (index, result); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBarComponent::menuBarMenuDismissedCallback (int result, MenuBarComponent* bar, int topLevelIndex)
|
||||
{
|
||||
if (bar != nullptr)
|
||||
bar->menuDismissed (topLevelIndex, result);
|
||||
}
|
||||
|
||||
void MenuBarComponent::menuDismissed (int topLevelIndex, int itemId)
|
||||
{
|
||||
topLevelIndexClicked = topLevelIndex;
|
||||
|
|
@ -212,8 +264,7 @@ void MenuBarComponent::menuDismissed (int topLevelIndex, int itemId)
|
|||
|
||||
void MenuBarComponent::handleCommandMessage (int commandId)
|
||||
{
|
||||
const Point<int> mousePos (getMouseXYRelative());
|
||||
updateItemUnderMouse (mousePos);
|
||||
updateItemUnderMouse (getMouseXYRelative());
|
||||
|
||||
if (currentPopupIndex == topLevelIndexClicked)
|
||||
setOpenItem (-1);
|
||||
|
|
@ -239,8 +290,7 @@ void MenuBarComponent::mouseDown (const MouseEvent& e)
|
|||
{
|
||||
if (currentPopupIndex < 0)
|
||||
{
|
||||
const MouseEvent e2 (e.getEventRelativeTo (this));
|
||||
updateItemUnderMouse (e2.getPosition());
|
||||
updateItemUnderMouse (e.getEventRelativeTo (this).getPosition());
|
||||
|
||||
currentPopupIndex = -2;
|
||||
showMenu (itemUnderMouse);
|
||||
|
|
@ -249,8 +299,7 @@ void MenuBarComponent::mouseDown (const MouseEvent& e)
|
|||
|
||||
void MenuBarComponent::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
const MouseEvent e2 (e.getEventRelativeTo (this));
|
||||
const int item = getItemAt (e2.getPosition());
|
||||
const auto item = getItemAt (e.getEventRelativeTo (this).getPosition());
|
||||
|
||||
if (item >= 0)
|
||||
showMenu (item);
|
||||
|
|
@ -258,7 +307,7 @@ void MenuBarComponent::mouseDrag (const MouseEvent& e)
|
|||
|
||||
void MenuBarComponent::mouseUp (const MouseEvent& e)
|
||||
{
|
||||
const MouseEvent e2 (e.getEventRelativeTo (this));
|
||||
const auto e2 = e.getEventRelativeTo (this);
|
||||
|
||||
updateItemUnderMouse (e2.getPosition());
|
||||
|
||||
|
|
@ -271,13 +320,13 @@ void MenuBarComponent::mouseUp (const MouseEvent& e)
|
|||
|
||||
void MenuBarComponent::mouseMove (const MouseEvent& e)
|
||||
{
|
||||
const MouseEvent e2 (e.getEventRelativeTo (this));
|
||||
const auto e2 = e.getEventRelativeTo (this);
|
||||
|
||||
if (lastMousePos != e2.getPosition())
|
||||
{
|
||||
if (currentPopupIndex >= 0)
|
||||
{
|
||||
const int item = getItemAt (e2.getPosition());
|
||||
const auto item = getItemAt (e2.getPosition());
|
||||
|
||||
if (item >= 0)
|
||||
showMenu (item);
|
||||
|
|
@ -293,11 +342,11 @@ void MenuBarComponent::mouseMove (const MouseEvent& e)
|
|||
|
||||
bool MenuBarComponent::keyPressed (const KeyPress& key)
|
||||
{
|
||||
const int numMenus = menuNames.size();
|
||||
const auto numMenus = (int) itemComponents.size();
|
||||
|
||||
if (numMenus > 0)
|
||||
{
|
||||
const int currentIndex = jlimit (0, numMenus - 1, currentPopupIndex);
|
||||
const auto currentIndex = jlimit (0, numMenus - 1, currentPopupIndex);
|
||||
|
||||
if (key.isKeyCode (KeyPress::leftKey))
|
||||
{
|
||||
|
|
@ -315,34 +364,69 @@ bool MenuBarComponent::keyPressed (const KeyPress& key)
|
|||
return false;
|
||||
}
|
||||
|
||||
void MenuBarComponent::menuBarItemsChanged (MenuBarModel* /*menuBarModel*/)
|
||||
void MenuBarComponent::menuBarItemsChanged (MenuBarModel*)
|
||||
{
|
||||
StringArray newNames;
|
||||
|
||||
if (model != nullptr)
|
||||
newNames = model->getMenuBarNames();
|
||||
|
||||
if (newNames != menuNames)
|
||||
auto itemsHaveChanged = [this, &newNames]
|
||||
{
|
||||
menuNames = newNames;
|
||||
if ((int) itemComponents.size() != newNames.size())
|
||||
return true;
|
||||
|
||||
for (size_t i = 0; i < itemComponents.size(); ++i)
|
||||
if (itemComponents[i]->getName() != newNames[(int) i])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}();
|
||||
|
||||
if (itemsHaveChanged)
|
||||
{
|
||||
updateItemComponents (newNames);
|
||||
|
||||
repaint();
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBarComponent::menuCommandInvoked (MenuBarModel* /*menuBarModel*/,
|
||||
const ApplicationCommandTarget::InvocationInfo& info)
|
||||
void MenuBarComponent::updateItemComponents (const StringArray& menuNames)
|
||||
{
|
||||
itemComponents.clear();
|
||||
|
||||
for (const auto& name : menuNames)
|
||||
{
|
||||
itemComponents.push_back (std::make_unique<AccessibleItemComponent> (*this, name));
|
||||
addAndMakeVisible (*itemComponents.back());
|
||||
}
|
||||
}
|
||||
|
||||
int MenuBarComponent::indexOfItemComponent (AccessibleItemComponent* itemComponent) const
|
||||
{
|
||||
const auto iter = std::find_if (itemComponents.cbegin(), itemComponents.cend(),
|
||||
[itemComponent] (const std::unique_ptr<AccessibleItemComponent>& c) { return c.get() == itemComponent; });
|
||||
|
||||
if (iter != itemComponents.cend())
|
||||
return (int) std::distance (itemComponents.cbegin(), iter);
|
||||
|
||||
jassertfalse;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void MenuBarComponent::menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo& info)
|
||||
{
|
||||
if (model == nullptr || (info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) != 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < menuNames.size(); ++i)
|
||||
for (size_t i = 0; i < itemComponents.size(); ++i)
|
||||
{
|
||||
const PopupMenu menu (model->getMenuForIndex (i, menuNames [i]));
|
||||
const auto menu = model->getMenuForIndex ((int) i, itemComponents[i]->getName());
|
||||
|
||||
if (menu.containsCommandItem (info.commandID))
|
||||
{
|
||||
setItemUnderMouse (i);
|
||||
setItemUnderMouse ((int) i);
|
||||
startTimer (200);
|
||||
break;
|
||||
}
|
||||
|
|
@ -355,4 +439,20 @@ void MenuBarComponent::timerCallback()
|
|||
updateItemUnderMouse (getMouseXYRelative());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> MenuBarComponent::createAccessibilityHandler()
|
||||
{
|
||||
struct MenuBarComponentAccessibilityHandler : public AccessibilityHandler
|
||||
{
|
||||
explicit MenuBarComponentAccessibilityHandler (MenuBarComponent& menuBarComponent)
|
||||
: AccessibilityHandler (menuBarComponent, AccessibilityRole::menuBar)
|
||||
{
|
||||
}
|
||||
|
||||
AccessibleState getCurrentState() const override { return AccessibleState().withIgnored(); }
|
||||
};
|
||||
|
||||
return std::make_unique<MenuBarComponentAccessibilityHandler> (*this);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -95,24 +95,32 @@ public:
|
|||
void menuBarItemsChanged (MenuBarModel*) override;
|
||||
/** @internal */
|
||||
void menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo&) override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
MenuBarModel* model;
|
||||
class AccessibleItemComponent;
|
||||
|
||||
StringArray menuNames;
|
||||
Array<int> xPositions;
|
||||
Point<int> lastMousePos;
|
||||
int itemUnderMouse, currentPopupIndex, topLevelIndexClicked;
|
||||
//==============================================================================
|
||||
void timerCallback() override;
|
||||
|
||||
int getItemAt (Point<int>);
|
||||
void setItemUnderMouse (int index);
|
||||
void setOpenItem (int index);
|
||||
void setItemUnderMouse (int);
|
||||
void setOpenItem (int);
|
||||
void updateItemUnderMouse (Point<int>);
|
||||
void timerCallback() override;
|
||||
void repaintMenuItem (int index);
|
||||
void menuDismissed (int topLevelIndex, int itemId);
|
||||
static void menuBarMenuDismissedCallback (int, MenuBarComponent*, int);
|
||||
void repaintMenuItem (int);
|
||||
void menuDismissed (int, int);
|
||||
|
||||
void updateItemComponents (const StringArray&);
|
||||
int indexOfItemComponent (AccessibleItemComponent*) const;
|
||||
|
||||
//==============================================================================
|
||||
MenuBarModel* model = nullptr;
|
||||
std::vector<std::unique_ptr<AccessibleItemComponent>> itemComponents;
|
||||
|
||||
Point<int> lastMousePos;
|
||||
int itemUnderMouse = -1, currentPopupIndex = -1, topLevelIndexClicked = 0;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MenuBarComponent)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ struct ItemComponent : public Component
|
|||
ItemComponent (const PopupMenu::Item& i,
|
||||
const PopupMenu::Options& o,
|
||||
MenuWindow& parent)
|
||||
: item (i), options (o), customComp (i.customComponent)
|
||||
: item (i), parentWindow (parent), options (o), customComp (i.customComponent)
|
||||
{
|
||||
if (item.isSectionHeader)
|
||||
customComp = *new HeaderItemComponent (item.text, options);
|
||||
|
|
@ -156,13 +156,99 @@ struct ItemComponent : public Component
|
|||
if (customComp != nullptr)
|
||||
customComp->setHighlighted (shouldBeHighlighted);
|
||||
|
||||
if (isHighlighted)
|
||||
if (auto* handler = getAccessibilityHandler())
|
||||
handler->grabFocus();
|
||||
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
|
||||
{
|
||||
return item.isSeparator ? nullptr : std::make_unique<ItemAccessibilityHandler> (*this);
|
||||
}
|
||||
|
||||
PopupMenu::Item item;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class ItemAccessibilityHandler : public AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
explicit ItemAccessibilityHandler (ItemComponent& itemComponentToWrap)
|
||||
: AccessibilityHandler (itemComponentToWrap,
|
||||
AccessibilityRole::menuItem,
|
||||
getAccessibilityActions (*this, itemComponentToWrap)),
|
||||
itemComponent (itemComponentToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
String getTitle() const override
|
||||
{
|
||||
return itemComponent.item.text;
|
||||
}
|
||||
|
||||
AccessibleState getCurrentState() const override
|
||||
{
|
||||
auto state = AccessibilityHandler::getCurrentState().withSelectable()
|
||||
.withAccessibleOffscreen();
|
||||
|
||||
if (hasActiveSubMenu (itemComponent.item))
|
||||
{
|
||||
state = itemComponent.parentWindow.isSubMenuVisible() ? state.withExpandable().withExpanded()
|
||||
: state.withExpandable().withCollapsed();
|
||||
}
|
||||
|
||||
return state.isFocused() ? state.withSelected() : state;
|
||||
}
|
||||
|
||||
private:
|
||||
static AccessibilityActions getAccessibilityActions (ItemAccessibilityHandler& handler,
|
||||
ItemComponent& item)
|
||||
{
|
||||
auto onFocus = [&item]
|
||||
{
|
||||
item.parentWindow.disableTimerUntilMouseMoves();
|
||||
item.parentWindow.ensureItemComponentIsVisible (item, -1);
|
||||
item.parentWindow.setCurrentlyHighlightedChild (&item);
|
||||
};
|
||||
|
||||
auto onPress = [&item]
|
||||
{
|
||||
item.parentWindow.setCurrentlyHighlightedChild (&item);
|
||||
item.parentWindow.triggerCurrentlyHighlightedItem();
|
||||
};
|
||||
|
||||
auto onToggle = [&handler, &item, onFocus]
|
||||
{
|
||||
if (handler.getCurrentState().isSelected())
|
||||
item.parentWindow.setCurrentlyHighlightedChild (nullptr);
|
||||
else
|
||||
onFocus();
|
||||
};
|
||||
|
||||
auto actions = AccessibilityActions().addAction (AccessibilityActionType::focus, std::move (onFocus))
|
||||
.addAction (AccessibilityActionType::press, std::move (onPress))
|
||||
.addAction (AccessibilityActionType::toggle, std::move (onToggle));
|
||||
|
||||
if (hasActiveSubMenu (item.item))
|
||||
actions.addAction (AccessibilityActionType::showMenu, [&item]
|
||||
{
|
||||
item.parentWindow.showSubMenuFor (&item);
|
||||
|
||||
if (auto* subMenu = item.parentWindow.activeSubMenu.get())
|
||||
subMenu->setCurrentlyHighlightedChild (subMenu->items.getFirst());
|
||||
});
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
ItemComponent& itemComponent;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
MenuWindow& parentWindow;
|
||||
const PopupMenu::Options& options;
|
||||
// NB: we use a copy of the one from the item info in case we're using our own section comp
|
||||
ReferenceCountedObjectPtr<CustomComponent> customComp;
|
||||
|
|
@ -223,6 +309,7 @@ struct MenuWindow : public Component
|
|||
setWantsKeyboardFocus (false);
|
||||
setMouseClickGrabsKeyboardFocus (false);
|
||||
setAlwaysOnTop (true);
|
||||
setFocusContainerType (FocusContainerType::focusContainer);
|
||||
|
||||
setLookAndFeel (parent != nullptr ? &(parent->getLookAndFeel())
|
||||
: menu.lookAndFeel.get());
|
||||
|
|
@ -274,12 +361,20 @@ struct MenuWindow : public Component
|
|||
updateYPositions();
|
||||
|
||||
if (auto visibleID = options.getItemThatMustBeVisible())
|
||||
{
|
||||
for (auto* item : items)
|
||||
{
|
||||
if (item->item.itemID == visibleID)
|
||||
{
|
||||
auto targetPosition = parentComponent != nullptr ? parentComponent->getLocalPoint (nullptr, targetArea.getTopLeft())
|
||||
: targetArea.getTopLeft();
|
||||
|
||||
auto y = targetPosition.getY() - windowPos.getY();
|
||||
ensureItemIsVisible (visibleID, isPositiveAndBelow (y, windowPos.getHeight()) ? y : -1);
|
||||
ensureItemComponentIsVisible (*item, isPositiveAndBelow (y, windowPos.getHeight()) ? y : -1);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resizeToBestWindowPos();
|
||||
|
|
@ -887,25 +982,18 @@ struct MenuWindow : public Component
|
|||
return correctColumnWidths (maxMenuW);
|
||||
}
|
||||
|
||||
void ensureItemIsVisible (const int itemID, int wantedY)
|
||||
void ensureItemComponentIsVisible (const ItemComponent& itemComp, int wantedY)
|
||||
{
|
||||
jassert (itemID != 0);
|
||||
if (windowPos.getHeight() > PopupMenuSettings::scrollZone * 4)
|
||||
{
|
||||
auto currentY = itemComp.getY();
|
||||
|
||||
for (int i = items.size(); --i >= 0;)
|
||||
{
|
||||
if (auto* m = items.getUnchecked (i))
|
||||
{
|
||||
if (m->item.itemID == itemID
|
||||
&& windowPos.getHeight() > PopupMenuSettings::scrollZone * 4)
|
||||
{
|
||||
auto currentY = m->getY();
|
||||
|
||||
if (wantedY > 0 || currentY < 0 || m->getBottom() > windowPos.getHeight())
|
||||
if (wantedY > 0 || currentY < 0 || itemComp.getBottom() > windowPos.getHeight())
|
||||
{
|
||||
if (wantedY < 0)
|
||||
wantedY = jlimit (PopupMenuSettings::scrollZone,
|
||||
jmax (PopupMenuSettings::scrollZone,
|
||||
windowPos.getHeight() - (PopupMenuSettings::scrollZone + m->getHeight())),
|
||||
windowPos.getHeight() - (PopupMenuSettings::scrollZone + itemComp.getHeight())),
|
||||
currentY);
|
||||
|
||||
auto parentArea = getParentArea (windowPos.getPosition(), parentComponent) / scaleFactor;
|
||||
|
|
@ -925,10 +1013,6 @@ struct MenuWindow : public Component
|
|||
|
||||
updateYPositions();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1016,6 +1100,9 @@ struct MenuWindow : public Component
|
|||
|
||||
void setCurrentlyHighlightedChild (ItemComponent* child)
|
||||
{
|
||||
if (currentChild == child)
|
||||
return;
|
||||
|
||||
if (currentChild != nullptr)
|
||||
currentChild->setHighlighted (false);
|
||||
|
||||
|
|
@ -1026,6 +1113,9 @@ struct MenuWindow : public Component
|
|||
currentChild->setHighlighted (true);
|
||||
timeEnteredCurrentChildComp = Time::getApproximateMillisecondCounter();
|
||||
}
|
||||
|
||||
if (auto* handler = getAccessibilityHandler())
|
||||
handler->notifyAccessibilityEvent (AccessibilityEvent::rowSelectionChanged);
|
||||
}
|
||||
|
||||
bool isSubMenuVisible() const noexcept { return activeSubMenu != nullptr && activeSubMenu->isVisible(); }
|
||||
|
|
@ -1119,6 +1209,19 @@ struct MenuWindow : public Component
|
|||
bool isTopScrollZoneActive() const noexcept { return canScroll() && childYOffset > 0; }
|
||||
bool isBottomScrollZoneActive() const noexcept { return canScroll() && childYOffset < contentHeight - windowPos.getHeight(); }
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this,
|
||||
AccessibilityRole::popupMenu,
|
||||
AccessibilityActions().addAction (AccessibilityActionType::focus, [this]
|
||||
{
|
||||
if (currentChild != nullptr)
|
||||
if (auto* handler = currentChild->getAccessibilityHandler())
|
||||
handler->grabFocus();
|
||||
}));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MenuWindow* parent;
|
||||
const Options options;
|
||||
|
|
@ -1925,6 +2028,9 @@ int PopupMenu::showWithOptionalCallback (const Options& options,
|
|||
window->toFront (false); // need to do this after making it modal, or it could
|
||||
// be stuck behind other comps that are already modal..
|
||||
|
||||
if (auto* handler = window->getAccessibilityHandler())
|
||||
handler->grabFocus();
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
if (userCallback == nullptr && canBeModal)
|
||||
return window->runModalLoop();
|
||||
|
|
|
|||
|
|
@ -156,6 +156,10 @@ public:
|
|||
const Rectangle<float>& body) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** Subclasses should override this to return the size of the content they
|
||||
|
|
@ -170,10 +174,6 @@ protected:
|
|||
*/
|
||||
virtual void paintContent (Graphics& g, int width, int height) = 0;
|
||||
|
||||
public:
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
|
||||
private:
|
||||
Rectangle<int> content;
|
||||
Point<int> arrowTip;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ public:
|
|||
: target (comp), shadow (ds)
|
||||
{
|
||||
setVisible (true);
|
||||
setAccessible (false);
|
||||
setInterceptsMouseClicks (false, false);
|
||||
|
||||
if (comp->isOnDesktop())
|
||||
|
|
|
|||
|
|
@ -188,6 +188,12 @@ void JUCESplashScreen::mouseUp (const MouseEvent&)
|
|||
juceWebsite.launchInDefaultBrowser();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> JUCESplashScreen::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::splashScreen);
|
||||
}
|
||||
|
||||
// END SECTION A
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -55,6 +55,10 @@ public:
|
|||
|
||||
static std::unique_ptr<Drawable> getSplashScreenLogo();
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
void paint (Graphics&) override;
|
||||
void timerCallback() override;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,270 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
static bool isStartingUpOrShuttingDown()
|
||||
{
|
||||
if (auto* app = JUCEApplicationBase::getInstance())
|
||||
if (app->isInitialising())
|
||||
return true;
|
||||
|
||||
if (auto* mm = MessageManager::getInstanceWithoutCreating())
|
||||
if (mm->hasStopMessageBeenSent())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isHandlerValid (const AccessibilityHandler& handler)
|
||||
{
|
||||
if (auto* provider = handler.getNativeImplementation())
|
||||
return provider->isElementValid();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class AccessibilityHandler::AccessibilityNativeImpl
|
||||
{
|
||||
public:
|
||||
explicit AccessibilityNativeImpl (AccessibilityHandler& owner)
|
||||
: accessibilityElement (new AccessibilityNativeHandle (owner))
|
||||
{
|
||||
++providerCount;
|
||||
}
|
||||
|
||||
~AccessibilityNativeImpl()
|
||||
{
|
||||
accessibilityElement->invalidateElement();
|
||||
|
||||
if (auto* wrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
|
||||
{
|
||||
ComSmartPtr<IRawElementProviderSimple> provider;
|
||||
accessibilityElement->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
|
||||
|
||||
wrapper->disconnectProvider (provider);
|
||||
|
||||
if (--providerCount == 0)
|
||||
wrapper->disconnectAllProviders();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ComSmartPtr<AccessibilityNativeHandle> accessibilityElement;
|
||||
static int providerCount;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeImpl)
|
||||
};
|
||||
|
||||
int AccessibilityHandler::AccessibilityNativeImpl::providerCount = 0;
|
||||
|
||||
//==============================================================================
|
||||
AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const
|
||||
{
|
||||
return nativeImpl->accessibilityElement;
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
void getProviderWithCheckedWrapper (const AccessibilityHandler& handler, Callback&& callback)
|
||||
{
|
||||
if (isStartingUpOrShuttingDown() || ! isHandlerValid (handler))
|
||||
return;
|
||||
|
||||
if (auto* wrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
|
||||
{
|
||||
if (! wrapper->clientsAreListening())
|
||||
return;
|
||||
|
||||
ComSmartPtr<IRawElementProviderSimple> provider;
|
||||
handler.getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
|
||||
|
||||
callback (wrapper, provider);
|
||||
}
|
||||
}
|
||||
|
||||
void sendAccessibilityAutomationEvent (const AccessibilityHandler& handler, EVENTID event)
|
||||
{
|
||||
jassert (event != EVENTID{});
|
||||
|
||||
getProviderWithCheckedWrapper (handler, [event] (WindowsUIAWrapper* wrapper, ComSmartPtr<IRawElementProviderSimple>& provider)
|
||||
{
|
||||
wrapper->raiseAutomationEvent (provider, event);
|
||||
});
|
||||
}
|
||||
|
||||
void sendAccessibilityPropertyChangedEvent (const AccessibilityHandler& handler, PROPERTYID property, VARIANT newValue)
|
||||
{
|
||||
jassert (property != PROPERTYID{});
|
||||
|
||||
getProviderWithCheckedWrapper (handler, [property, newValue] (WindowsUIAWrapper* wrapper, ComSmartPtr<IRawElementProviderSimple>& provider)
|
||||
{
|
||||
VARIANT oldValue;
|
||||
VariantHelpers::clear (&oldValue);
|
||||
|
||||
wrapper->raiseAutomationPropertyChangedEvent (provider, property, oldValue, newValue);
|
||||
});
|
||||
}
|
||||
|
||||
void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent eventType)
|
||||
{
|
||||
auto event = [eventType]() -> EVENTID
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case InternalAccessibilityEvent::elementCreated:
|
||||
case InternalAccessibilityEvent::elementDestroyed: return UIA_StructureChangedEventId;
|
||||
case InternalAccessibilityEvent::focusChanged: return UIA_AutomationFocusChangedEventId;
|
||||
case InternalAccessibilityEvent::windowOpened: return UIA_Window_WindowOpenedEventId;
|
||||
case InternalAccessibilityEvent::windowClosed: return UIA_Window_WindowClosedEventId;
|
||||
}
|
||||
|
||||
return {};
|
||||
}();
|
||||
|
||||
if (event != EVENTID{})
|
||||
sendAccessibilityAutomationEvent (handler, event);
|
||||
}
|
||||
|
||||
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const
|
||||
{
|
||||
auto event = [eventType] () -> EVENTID
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case AccessibilityEvent::textSelectionChanged: return UIA_Text_TextSelectionChangedEventId;
|
||||
case AccessibilityEvent::textChanged: return UIA_Text_TextChangedEventId;
|
||||
case AccessibilityEvent::structureChanged: return UIA_StructureChangedEventId;
|
||||
case AccessibilityEvent::rowSelectionChanged: return UIA_SelectionItem_ElementSelectedEventId;
|
||||
case AccessibilityEvent::valueChanged: break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}();
|
||||
|
||||
if (event != EVENTID{})
|
||||
sendAccessibilityAutomationEvent (*this, event);
|
||||
}
|
||||
|
||||
struct SpVoiceWrapper : public DeletedAtShutdown
|
||||
{
|
||||
SpVoiceWrapper()
|
||||
{
|
||||
auto hr = voice.CoCreateInstance (CLSID_SpVoice);
|
||||
|
||||
jassert (SUCCEEDED (hr));
|
||||
ignoreUnused (hr);
|
||||
}
|
||||
|
||||
~SpVoiceWrapper() override
|
||||
{
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
ComSmartPtr<ISpVoice> voice;
|
||||
|
||||
JUCE_DECLARE_SINGLETON (SpVoiceWrapper, false)
|
||||
};
|
||||
|
||||
JUCE_IMPLEMENT_SINGLETON (SpVoiceWrapper)
|
||||
|
||||
|
||||
void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority priority)
|
||||
{
|
||||
if (auto* sharedVoice = SpVoiceWrapper::getInstance())
|
||||
{
|
||||
auto voicePriority = [priority]
|
||||
{
|
||||
switch (priority)
|
||||
{
|
||||
case AnnouncementPriority::low: return SPVPRI_OVER;
|
||||
case AnnouncementPriority::medium: return SPVPRI_NORMAL;
|
||||
case AnnouncementPriority::high: return SPVPRI_ALERT;
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return SPVPRI_OVER;
|
||||
}();
|
||||
|
||||
sharedVoice->voice->SetPriority (voicePriority);
|
||||
sharedVoice->voice->Speak (announcementString.toWideCharPointer(), SPF_ASYNC, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
AccessibilityHandler::AccessibilityNativeImpl* AccessibilityHandler::createNativeImpl (AccessibilityHandler& handler)
|
||||
{
|
||||
return new AccessibilityHandler::AccessibilityNativeImpl (handler);
|
||||
}
|
||||
|
||||
void AccessibilityHandler::DestroyNativeImpl::operator() (AccessibilityHandler::AccessibilityNativeImpl* impl) const noexcept
|
||||
{
|
||||
delete impl;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
namespace WindowsAccessibility
|
||||
{
|
||||
void initialiseUIAWrapper()
|
||||
{
|
||||
WindowsUIAWrapper::getInstance();
|
||||
}
|
||||
|
||||
long getUiaRootObjectId()
|
||||
{
|
||||
return static_cast<long> (UiaRootObjectId);
|
||||
}
|
||||
|
||||
bool handleWmGetObject (AccessibilityHandler* handler, WPARAM wParam, LPARAM lParam, LRESULT* res)
|
||||
{
|
||||
if (isStartingUpOrShuttingDown() || (handler == nullptr || ! isHandlerValid (*handler)))
|
||||
return false;
|
||||
|
||||
if (auto* wrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
|
||||
{
|
||||
ComSmartPtr<IRawElementProviderSimple> provider;
|
||||
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
|
||||
|
||||
if (! wrapper->isProviderDisconnecting (provider))
|
||||
*res = wrapper->returnRawElementProvider ((HWND) handler->getComponent().getWindowHandle(), wParam, lParam, provider);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void revokeUIAMapEntriesForWindow (HWND hwnd)
|
||||
{
|
||||
if (auto* wrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
|
||||
wrapper->returnRawElementProvider (hwnd, 0, 0, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
JUCE_IMPLEMENT_SINGLETON (WindowsUIAWrapper)
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,558 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
int AccessibilityNativeHandle::idCounter = 0;
|
||||
|
||||
//==============================================================================
|
||||
static String getAutomationId (const AccessibilityHandler& handler)
|
||||
{
|
||||
auto result = handler.getTitle();
|
||||
auto* parentComponent = handler.getComponent().getParentComponent();
|
||||
|
||||
while (parentComponent != nullptr)
|
||||
{
|
||||
if (auto* parentHandler = parentComponent->getAccessibilityHandler())
|
||||
{
|
||||
auto parentTitle = parentHandler->getTitle();
|
||||
result << "." << (parentTitle.isNotEmpty() ? parentTitle : "<empty>");
|
||||
}
|
||||
|
||||
parentComponent = parentComponent->getParentComponent();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static long roleToControlTypeId (AccessibilityRole roleType)
|
||||
{
|
||||
switch (roleType)
|
||||
{
|
||||
case AccessibilityRole::button: return UIA_ButtonControlTypeId;
|
||||
case AccessibilityRole::toggleButton: return UIA_CheckBoxControlTypeId;
|
||||
case AccessibilityRole::radioButton: return UIA_RadioButtonControlTypeId;
|
||||
case AccessibilityRole::comboBox: return UIA_ComboBoxControlTypeId;
|
||||
case AccessibilityRole::image: return UIA_ImageControlTypeId;
|
||||
case AccessibilityRole::slider: return UIA_SliderControlTypeId;
|
||||
case AccessibilityRole::staticText: return UIA_TextControlTypeId;
|
||||
case AccessibilityRole::editableText: return UIA_EditControlTypeId;
|
||||
case AccessibilityRole::menuItem: return UIA_MenuItemControlTypeId;
|
||||
case AccessibilityRole::menuBar: return UIA_MenuBarControlTypeId;
|
||||
case AccessibilityRole::popupMenu: return UIA_WindowControlTypeId;
|
||||
case AccessibilityRole::table: return UIA_TableControlTypeId;
|
||||
case AccessibilityRole::tableHeader: return UIA_HeaderControlTypeId;
|
||||
case AccessibilityRole::column: return UIA_HeaderItemControlTypeId;
|
||||
case AccessibilityRole::row: return UIA_HeaderItemControlTypeId;
|
||||
case AccessibilityRole::cell: return UIA_DataItemControlTypeId;
|
||||
case AccessibilityRole::hyperlink: return UIA_HyperlinkControlTypeId;
|
||||
case AccessibilityRole::list: return UIA_ListControlTypeId;
|
||||
case AccessibilityRole::listItem: return UIA_ListItemControlTypeId;
|
||||
case AccessibilityRole::tree: return UIA_TreeControlTypeId;
|
||||
case AccessibilityRole::treeItem: return UIA_TreeItemControlTypeId;
|
||||
case AccessibilityRole::progressBar: return UIA_ProgressBarControlTypeId;
|
||||
case AccessibilityRole::group: return UIA_GroupControlTypeId;
|
||||
case AccessibilityRole::dialogWindow: return UIA_WindowControlTypeId;
|
||||
case AccessibilityRole::window: return UIA_WindowControlTypeId;
|
||||
case AccessibilityRole::scrollBar: return UIA_ScrollBarControlTypeId;
|
||||
case AccessibilityRole::tooltip: return UIA_ToolTipControlTypeId;
|
||||
case AccessibilityRole::splashScreen: return UIA_WindowControlTypeId;
|
||||
case AccessibilityRole::ignored: return UIA_CustomControlTypeId;
|
||||
case AccessibilityRole::unspecified: return UIA_CustomControlTypeId;
|
||||
};
|
||||
|
||||
return UIA_CustomControlTypeId;
|
||||
}
|
||||
|
||||
static bool isEditableText (const AccessibilityHandler& handler)
|
||||
{
|
||||
return handler.getRole() == AccessibilityRole::editableText
|
||||
&& handler.getTextInterface() != nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
AccessibilityNativeHandle::AccessibilityNativeHandle (AccessibilityHandler& handler)
|
||||
: ComBaseClassHelper (0),
|
||||
accessibilityHandler (handler)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::QueryInterface (REFIID refId, void** result)
|
||||
{
|
||||
*result = nullptr;
|
||||
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if ((refId == __uuidof (IRawElementProviderFragmentRoot) && ! isFragmentRoot()))
|
||||
return E_NOINTERFACE;
|
||||
|
||||
return ComBaseClassHelper::QueryInterface (refId, result);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::get_HostRawElementProvider (IRawElementProviderSimple** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
if (isFragmentRoot())
|
||||
if (auto* wrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
|
||||
return wrapper->hostProviderFromHwnd ((HWND) accessibilityHandler.getComponent().getWindowHandle(), pRetVal);
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::get_ProviderOptions (ProviderOptions* options)
|
||||
{
|
||||
if (options == nullptr)
|
||||
return E_INVALIDARG;
|
||||
|
||||
*options = ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::GetPatternProvider (PATTERNID pId, IUnknown** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = [&]() -> IUnknown*
|
||||
{
|
||||
const auto role = accessibilityHandler.getRole();
|
||||
const auto fragmentRoot = isFragmentRoot();
|
||||
|
||||
switch (pId)
|
||||
{
|
||||
case UIA_WindowPatternId:
|
||||
{
|
||||
if (fragmentRoot)
|
||||
return new UIAWindowProvider (this);
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_TransformPatternId:
|
||||
{
|
||||
if (fragmentRoot)
|
||||
return new UIATransformProvider (this);
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_TextPatternId:
|
||||
case UIA_TextPattern2Id:
|
||||
{
|
||||
if (accessibilityHandler.getTextInterface() != nullptr)
|
||||
return new UIATextProvider (this);
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_ValuePatternId:
|
||||
{
|
||||
auto editableText = isEditableText (accessibilityHandler);
|
||||
|
||||
if (accessibilityHandler.getValueInterface() != nullptr || editableText)
|
||||
return new UIAValueProvider (this, editableText);
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_RangeValuePatternId:
|
||||
{
|
||||
if (accessibilityHandler.getValueInterface() != nullptr
|
||||
&& accessibilityHandler.getValueInterface()->getRange().isValid())
|
||||
{
|
||||
return new UIARangeValueProvider (this);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_TogglePatternId:
|
||||
{
|
||||
if (accessibilityHandler.getActions().contains (AccessibilityActionType::toggle)
|
||||
&& accessibilityHandler.getCurrentState().isCheckable())
|
||||
{
|
||||
return new UIAToggleProvider (this);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_SelectionPatternId:
|
||||
{
|
||||
if (role == AccessibilityRole::list
|
||||
|| role == AccessibilityRole::popupMenu
|
||||
|| role == AccessibilityRole::tree)
|
||||
{
|
||||
return new UIASelectionProvider (this);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_SelectionItemPatternId:
|
||||
{
|
||||
auto state = accessibilityHandler.getCurrentState();
|
||||
|
||||
if (state.isSelectable() || state.isMultiSelectable()
|
||||
|| role == AccessibilityRole::radioButton)
|
||||
{
|
||||
return new UIASelectionItemProvider (this);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_GridPatternId:
|
||||
{
|
||||
if ((role == AccessibilityRole::table || role == AccessibilityRole::tree)
|
||||
&& accessibilityHandler.getTableInterface() != nullptr)
|
||||
{
|
||||
return new UIAGridProvider (this);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_GridItemPatternId:
|
||||
{
|
||||
if ((role == AccessibilityRole::cell || role == AccessibilityRole::treeItem)
|
||||
&& accessibilityHandler.getCellInterface() != nullptr)
|
||||
{
|
||||
return new UIAGridItemProvider (this);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_InvokePatternId:
|
||||
{
|
||||
if (accessibilityHandler.getActions().contains (AccessibilityActionType::press))
|
||||
return new UIAInvokeProvider (this);
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_ExpandCollapsePatternId:
|
||||
{
|
||||
if (role == AccessibilityRole::menuItem
|
||||
&& accessibilityHandler.getActions().contains (AccessibilityActionType::showMenu))
|
||||
{
|
||||
return new UIAExpandCollapseProvider (this);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}();
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::GetPropertyValue (PROPERTYID propertyId, VARIANT* pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
VariantHelpers::clear (pRetVal);
|
||||
|
||||
const auto fragmentRoot = isFragmentRoot();
|
||||
|
||||
switch (propertyId)
|
||||
{
|
||||
case UIA_AutomationIdPropertyId:
|
||||
VariantHelpers::setString (getAutomationId (accessibilityHandler), pRetVal);
|
||||
break;
|
||||
case UIA_ControlTypePropertyId:
|
||||
VariantHelpers::setInt (roleToControlTypeId (accessibilityHandler.getRole()),
|
||||
pRetVal);
|
||||
break;
|
||||
case UIA_FrameworkIdPropertyId:
|
||||
VariantHelpers::setString ("JUCE", pRetVal);
|
||||
break;
|
||||
case UIA_FullDescriptionPropertyId:
|
||||
VariantHelpers::setString (accessibilityHandler.getDescription(), pRetVal);
|
||||
break;
|
||||
case UIA_HelpTextPropertyId:
|
||||
VariantHelpers::setString (accessibilityHandler.getHelp(), pRetVal);
|
||||
break;
|
||||
case UIA_IsContentElementPropertyId:
|
||||
VariantHelpers::setBool (! accessibilityHandler.isIgnored(), pRetVal);
|
||||
break;
|
||||
case UIA_IsControlElementPropertyId:
|
||||
VariantHelpers::setBool (true, pRetVal);
|
||||
break;
|
||||
case UIA_IsDialogPropertyId:
|
||||
VariantHelpers::setBool (accessibilityHandler.getRole() == AccessibilityRole::dialogWindow, pRetVal);
|
||||
break;
|
||||
case UIA_IsEnabledPropertyId:
|
||||
VariantHelpers::setBool (accessibilityHandler.getComponent().isEnabled(), pRetVal);
|
||||
break;
|
||||
case UIA_IsKeyboardFocusablePropertyId:
|
||||
VariantHelpers::setBool (accessibilityHandler.getCurrentState().isFocusable(), pRetVal);
|
||||
break;
|
||||
case UIA_HasKeyboardFocusPropertyId:
|
||||
VariantHelpers::setBool (accessibilityHandler.hasFocus (true), pRetVal);
|
||||
break;
|
||||
case UIA_IsOffscreenPropertyId:
|
||||
VariantHelpers::setBool (false, pRetVal);
|
||||
break;
|
||||
case UIA_IsPasswordPropertyId:
|
||||
if (auto* textInterface = accessibilityHandler.getTextInterface())
|
||||
VariantHelpers::setBool (textInterface->isDisplayingProtectedText(), pRetVal);
|
||||
|
||||
break;
|
||||
case UIA_IsPeripheralPropertyId:
|
||||
VariantHelpers::setBool (accessibilityHandler.getRole() == AccessibilityRole::tooltip
|
||||
|| accessibilityHandler.getRole() == AccessibilityRole::popupMenu
|
||||
|| accessibilityHandler.getRole() == AccessibilityRole::splashScreen,
|
||||
pRetVal);
|
||||
break;
|
||||
case UIA_NamePropertyId:
|
||||
VariantHelpers::setString (getElementName(), pRetVal);
|
||||
break;
|
||||
case UIA_ProcessIdPropertyId:
|
||||
VariantHelpers::setInt ((int) GetCurrentProcessId(), pRetVal);
|
||||
break;
|
||||
case UIA_NativeWindowHandlePropertyId:
|
||||
if (fragmentRoot)
|
||||
VariantHelpers::setInt ((int) (pointer_sized_int) accessibilityHandler.getComponent().getWindowHandle(), pRetVal);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::Navigate (NavigateDirection direction, IRawElementProviderFragment** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
auto* handler = [&]() -> AccessibilityHandler*
|
||||
{
|
||||
if (direction == NavigateDirection_Parent)
|
||||
return accessibilityHandler.getParent();
|
||||
|
||||
if (direction == NavigateDirection_FirstChild
|
||||
|| direction == NavigateDirection_LastChild)
|
||||
{
|
||||
auto children = accessibilityHandler.getChildren();
|
||||
|
||||
return children.empty() ? nullptr
|
||||
: (direction == NavigateDirection_FirstChild ? children.front()
|
||||
: children.back());
|
||||
}
|
||||
|
||||
if (direction == NavigateDirection_NextSibling
|
||||
|| direction == NavigateDirection_PreviousSibling)
|
||||
{
|
||||
if (auto* parent = accessibilityHandler.getParent())
|
||||
{
|
||||
const auto siblings = parent->getChildren();
|
||||
const auto iter = std::find (siblings.cbegin(), siblings.cend(), &accessibilityHandler);
|
||||
|
||||
if (iter == siblings.end())
|
||||
return nullptr;
|
||||
|
||||
if (direction == NavigateDirection_NextSibling && iter != std::prev (siblings.cend()))
|
||||
return *std::next (iter);
|
||||
|
||||
if (direction == NavigateDirection_PreviousSibling && iter != siblings.cbegin())
|
||||
return *std::prev (iter);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}();
|
||||
|
||||
if (handler != nullptr)
|
||||
if (auto* provider = handler->getNativeImplementation())
|
||||
if (provider->isElementValid())
|
||||
provider->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::GetRuntimeId (SAFEARRAY** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
if (! isFragmentRoot())
|
||||
{
|
||||
*pRetVal = SafeArrayCreateVector (VT_I4, 0, 2);
|
||||
|
||||
if (*pRetVal == nullptr)
|
||||
return E_OUTOFMEMORY;
|
||||
|
||||
for (LONG i = 0; i < 2; ++i)
|
||||
{
|
||||
auto hr = SafeArrayPutElement (*pRetVal, &i, &rtid[i]);
|
||||
|
||||
if (FAILED (hr))
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::get_BoundingRectangle (UiaRect* pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
auto bounds = Desktop::getInstance().getDisplays()
|
||||
.logicalToPhysical (accessibilityHandler.getComponent().getScreenBounds());
|
||||
|
||||
pRetVal->left = bounds.getX();
|
||||
pRetVal->top = bounds.getY();
|
||||
pRetVal->width = bounds.getWidth();
|
||||
pRetVal->height = bounds.getHeight();
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::GetEmbeddedFragmentRoots (SAFEARRAY** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, []
|
||||
{
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::SetFocus()
|
||||
{
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
accessibilityHandler.grabFocus();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::get_FragmentRoot (IRawElementProviderFragmentRoot** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
auto* handler = [&]() -> AccessibilityHandler*
|
||||
{
|
||||
if (isFragmentRoot())
|
||||
return &accessibilityHandler;
|
||||
|
||||
if (auto* peer = accessibilityHandler.getComponent().getPeer())
|
||||
if (auto* handler = peer->getComponent().getAccessibilityHandler())
|
||||
return handler;
|
||||
|
||||
return nullptr;
|
||||
}();
|
||||
|
||||
if (handler != nullptr)
|
||||
{
|
||||
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::ElementProviderFromPoint (double x, double y, IRawElementProviderFragment** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
auto* handler = [&]
|
||||
{
|
||||
auto logicalScreenPoint = Desktop::getInstance().getDisplays()
|
||||
.physicalToLogical (Point<int> (roundToInt (x),
|
||||
roundToInt (y)));
|
||||
|
||||
if (auto* child = accessibilityHandler.getChildAt (logicalScreenPoint))
|
||||
return child;
|
||||
|
||||
return &accessibilityHandler;
|
||||
}();
|
||||
|
||||
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::GetFocus (IRawElementProviderFragment** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
const auto getFocusHandler = [this]() -> AccessibilityHandler*
|
||||
{
|
||||
if (auto* modal = Component::getCurrentlyModalComponent())
|
||||
{
|
||||
const auto& component = accessibilityHandler.getComponent();
|
||||
|
||||
if (! component.isParentOf (modal)
|
||||
&& component.isCurrentlyBlockedByAnotherModalComponent())
|
||||
{
|
||||
if (auto* modalHandler = modal->getAccessibilityHandler())
|
||||
{
|
||||
if (auto* focusChild = modalHandler->getChildFocus())
|
||||
return focusChild;
|
||||
|
||||
return modalHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (auto* focusChild = accessibilityHandler.getChildFocus())
|
||||
return focusChild;
|
||||
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
if (auto* focusHandler = getFocusHandler())
|
||||
focusHandler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
String AccessibilityNativeHandle::getElementName() const
|
||||
{
|
||||
if (accessibilityHandler.getRole() == AccessibilityRole::tooltip)
|
||||
return accessibilityHandler.getDescription();
|
||||
|
||||
auto name = accessibilityHandler.getTitle();
|
||||
|
||||
if (name.isEmpty() && isFragmentRoot())
|
||||
return getAccessibleApplicationOrPluginName();
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
#define UIA_FullDescriptionPropertyId 30159
|
||||
#define UIA_IsDialogPropertyId 30174
|
||||
|
||||
class AccessibilityNativeHandle : public ComBaseClassHelper<IRawElementProviderSimple,
|
||||
IRawElementProviderFragment,
|
||||
IRawElementProviderFragmentRoot>
|
||||
{
|
||||
public:
|
||||
explicit AccessibilityNativeHandle (AccessibilityHandler& handler);
|
||||
|
||||
//==============================================================================
|
||||
void invalidateElement() noexcept { valid = false; }
|
||||
bool isElementValid() const noexcept { return valid; }
|
||||
|
||||
const AccessibilityHandler& getHandler() { return accessibilityHandler; }
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT QueryInterface (REFIID refId, void** result) override;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT get_HostRawElementProvider (IRawElementProviderSimple** provider) override;
|
||||
JUCE_COMRESULT get_ProviderOptions (ProviderOptions* options) override;
|
||||
JUCE_COMRESULT GetPatternProvider (PATTERNID pId, IUnknown** provider) override;
|
||||
JUCE_COMRESULT GetPropertyValue (PROPERTYID propertyId, VARIANT* pRetVal) override;
|
||||
|
||||
JUCE_COMRESULT Navigate (NavigateDirection direction, IRawElementProviderFragment** pRetVal) override;
|
||||
JUCE_COMRESULT GetRuntimeId (SAFEARRAY** pRetVal) override;
|
||||
JUCE_COMRESULT get_BoundingRectangle (UiaRect* pRetVal) override;
|
||||
JUCE_COMRESULT GetEmbeddedFragmentRoots (SAFEARRAY** pRetVal) override;
|
||||
JUCE_COMRESULT SetFocus() override;
|
||||
JUCE_COMRESULT get_FragmentRoot (IRawElementProviderFragmentRoot** pRetVal) override;
|
||||
|
||||
JUCE_COMRESULT ElementProviderFromPoint (double x, double y, IRawElementProviderFragment** pRetVal) override;
|
||||
JUCE_COMRESULT GetFocus (IRawElementProviderFragment** pRetVal) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
String getElementName() const;
|
||||
bool isFragmentRoot() const { return accessibilityHandler.getComponent().isOnDesktop(); }
|
||||
|
||||
//==============================================================================
|
||||
AccessibilityHandler& accessibilityHandler;
|
||||
|
||||
static int idCounter;
|
||||
std::array<int, 2> rtid { UiaAppendRuntimeId, ++idCounter };
|
||||
bool valid = true;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeHandle)
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 UIAExpandCollapseProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<IExpandCollapseProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIAExpandCollapseProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT Expand() override
|
||||
{
|
||||
return invokeShowMenu();
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Collapse() override
|
||||
{
|
||||
return invokeShowMenu();
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_ExpandCollapseState (ExpandCollapseState* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = getHandler().getCurrentState().isExpanded()
|
||||
? ExpandCollapseState_Expanded
|
||||
: ExpandCollapseState_Collapsed;
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
JUCE_COMRESULT invokeShowMenu()
|
||||
{
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
const auto& handler = getHandler();
|
||||
|
||||
if (handler.getActions().invoke (AccessibilityActionType::showMenu))
|
||||
{
|
||||
sendAccessibilityAutomationEvent (handler, handler.getCurrentState().isExpanded()
|
||||
? UIA_MenuOpenedEventId
|
||||
: UIA_MenuClosedEventId);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAExpandCollapseProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 UIAGridItemProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<IGridItemProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIAGridItemProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT get_Row (int* pRetVal) override
|
||||
{
|
||||
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
|
||||
{
|
||||
*pRetVal = cellInterface.getRowIndex();
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_Column (int* pRetVal) override
|
||||
{
|
||||
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
|
||||
{
|
||||
*pRetVal = cellInterface.getColumnIndex();
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_RowSpan (int* pRetVal) override
|
||||
{
|
||||
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
|
||||
{
|
||||
*pRetVal = cellInterface.getRowSpan();
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_ColumnSpan (int* pRetVal) override
|
||||
{
|
||||
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
|
||||
{
|
||||
*pRetVal = cellInterface.getColumnSpan();
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_ContainingGrid (IRawElementProviderSimple** pRetVal) override
|
||||
{
|
||||
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
|
||||
{
|
||||
if (auto* handler = cellInterface.getTableHandler())
|
||||
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Value, typename Callback>
|
||||
JUCE_COMRESULT withCellInterface (Value* pRetVal, Callback&& callback) const
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* cellInterface = getHandler().getCellInterface())
|
||||
{
|
||||
callback (*cellInterface);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAGridItemProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class UIAGridProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<IGridProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIAGridProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT GetItem (int row, int column, IRawElementProviderSimple** pRetVal) override
|
||||
{
|
||||
return withTableInterface (pRetVal, [&] (const AccessibilityTableInterface& tableInterface)
|
||||
{
|
||||
if (! isPositiveAndBelow (row, tableInterface.getNumRows())
|
||||
|| ! isPositiveAndBelow (column, tableInterface.getNumColumns()))
|
||||
return E_INVALIDARG;
|
||||
|
||||
if (auto* handler = tableInterface.getCellHandler (row, column))
|
||||
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_RowCount (int* pRetVal) override
|
||||
{
|
||||
return withTableInterface (pRetVal, [&] (const AccessibilityTableInterface& tableInterface)
|
||||
{
|
||||
*pRetVal = tableInterface.getNumRows();
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_ColumnCount (int* pRetVal) override
|
||||
{
|
||||
return withTableInterface (pRetVal, [&] (const AccessibilityTableInterface& tableInterface)
|
||||
{
|
||||
*pRetVal = tableInterface.getNumColumns();
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Value, typename Callback>
|
||||
JUCE_COMRESULT withTableInterface (Value* pRetVal, Callback&& callback) const
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* tableInterface = getHandler().getTableInterface())
|
||||
return callback (*tableInterface);
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAGridProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 VariantHelpers
|
||||
{
|
||||
inline void clear (VARIANT* variant)
|
||||
{
|
||||
variant->vt = VT_EMPTY;
|
||||
}
|
||||
|
||||
inline void setInt (int value, VARIANT* variant)
|
||||
{
|
||||
variant->vt = VT_I4;
|
||||
variant->lVal = value;
|
||||
}
|
||||
|
||||
inline void setBool (bool value, VARIANT* variant)
|
||||
{
|
||||
variant->vt = VT_BOOL;
|
||||
variant->boolVal = value ? -1 : 0;
|
||||
}
|
||||
|
||||
inline void setString (const String& value, VARIANT* variant)
|
||||
{
|
||||
variant->vt = VT_BSTR;
|
||||
variant->bstrVal = SysAllocString ((const OLECHAR*) value.toWideCharPointer());
|
||||
}
|
||||
|
||||
inline void setDouble (double value, VARIANT* variant)
|
||||
{
|
||||
variant->vt = VT_R8;
|
||||
variant->dblVal = value;
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_COMRESULT addHandlersToArray (const std::vector<const AccessibilityHandler*>& handlers, SAFEARRAY** pRetVal)
|
||||
{
|
||||
auto numHandlers = handlers.size();
|
||||
|
||||
*pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, (ULONG) numHandlers);
|
||||
|
||||
if (pRetVal != nullptr)
|
||||
{
|
||||
for (LONG i = 0; i < (LONG) numHandlers; ++i)
|
||||
{
|
||||
auto* handler = handlers[i];
|
||||
|
||||
if (handler == nullptr)
|
||||
continue;
|
||||
|
||||
ComSmartPtr<IRawElementProviderSimple> provider;
|
||||
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
|
||||
|
||||
auto hr = SafeArrayPutElement (*pRetVal, &i, provider);
|
||||
|
||||
if (FAILED (hr))
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
template <typename Value, typename Object, typename Callback>
|
||||
JUCE_COMRESULT withCheckedComArgs (Value* pRetVal, Object& handle, Callback&& callback)
|
||||
{
|
||||
if (pRetVal == nullptr)
|
||||
return E_INVALIDARG;
|
||||
|
||||
*pRetVal = Value{};
|
||||
|
||||
if (! handle.isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
return callback();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 UIAInvokeProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<IInvokeProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIAInvokeProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT Invoke() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
const auto& handler = getHandler();
|
||||
|
||||
if (handler.getActions().invoke (AccessibilityActionType::press))
|
||||
{
|
||||
if (isElementValid())
|
||||
sendAccessibilityAutomationEvent (handler, UIA_Invoke_InvokedEventId);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAInvokeProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class UIAProviderBase
|
||||
{
|
||||
public:
|
||||
explicit UIAProviderBase (AccessibilityNativeHandle* nativeHandleIn)
|
||||
: nativeHandle (nativeHandleIn)
|
||||
{
|
||||
}
|
||||
|
||||
bool isElementValid() const
|
||||
{
|
||||
if (nativeHandle != nullptr)
|
||||
return nativeHandle->isElementValid();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const AccessibilityHandler& getHandler() const
|
||||
{
|
||||
return nativeHandle->getHandler();
|
||||
}
|
||||
|
||||
private:
|
||||
ComSmartPtr<AccessibilityNativeHandle> nativeHandle;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAProviderBase)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 sendAccessibilityAutomationEvent (const AccessibilityHandler&, EVENTID);
|
||||
void sendAccessibilityPropertyChangedEvent (const AccessibilityHandler&, PROPERTYID, VARIANT);
|
||||
}
|
||||
|
||||
#include "juce_win32_UIAProviderBase.h"
|
||||
#include "juce_win32_UIAExpandCollapseProvider.h"
|
||||
#include "juce_win32_UIAGridItemProvider.h"
|
||||
#include "juce_win32_UIAGridProvider.h"
|
||||
#include "juce_win32_UIAInvokeProvider.h"
|
||||
#include "juce_win32_UIARangeValueProvider.h"
|
||||
#include "juce_win32_UIASelectionProvider.h"
|
||||
#include "juce_win32_UIATextProvider.h"
|
||||
#include "juce_win32_UIAToggleProvider.h"
|
||||
#include "juce_win32_UIATransformProvider.h"
|
||||
#include "juce_win32_UIAValueProvider.h"
|
||||
#include "juce_win32_UIAWindowProvider.h"
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 UIARangeValueProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<IRangeValueProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIARangeValueProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT SetValue (double val) override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
const auto& handler = getHandler();
|
||||
|
||||
if (auto* valueInterface = handler.getValueInterface())
|
||||
{
|
||||
auto range = valueInterface->getRange();
|
||||
|
||||
if (range.isValid())
|
||||
{
|
||||
if (val < range.getMinimumValue() || val > range.getMaximumValue())
|
||||
return E_INVALIDARG;
|
||||
|
||||
if (! valueInterface->isReadOnly())
|
||||
{
|
||||
valueInterface->setValue (val);
|
||||
|
||||
VARIANT newValue;
|
||||
VariantHelpers::setDouble (valueInterface->getCurrentValue(), &newValue);
|
||||
sendAccessibilityPropertyChangedEvent (handler, UIA_RangeValueValuePropertyId, newValue);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_Value (double* pRetVal) override
|
||||
{
|
||||
return withValueInterface (pRetVal, [] (const AccessibilityValueInterface& valueInterface)
|
||||
{
|
||||
return valueInterface.getCurrentValue();
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_IsReadOnly (BOOL* pRetVal) override
|
||||
{
|
||||
return withValueInterface (pRetVal, [] (const AccessibilityValueInterface& valueInterface)
|
||||
{
|
||||
return valueInterface.isReadOnly();
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_Maximum (double* pRetVal) override
|
||||
{
|
||||
return withValueInterface (pRetVal, [] (const AccessibilityValueInterface& valueInterface)
|
||||
{
|
||||
return valueInterface.getRange().getMaximumValue();
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_Minimum (double* pRetVal) override
|
||||
{
|
||||
return withValueInterface (pRetVal, [] (const AccessibilityValueInterface& valueInterface)
|
||||
{
|
||||
return valueInterface.getRange().getMinimumValue();
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_LargeChange (double* pRetVal) override
|
||||
{
|
||||
return get_SmallChange (pRetVal);
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_SmallChange (double* pRetVal) override
|
||||
{
|
||||
return withValueInterface (pRetVal, [] (const AccessibilityValueInterface& valueInterface)
|
||||
{
|
||||
return valueInterface.getRange().getInterval();
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Value, typename Callback>
|
||||
JUCE_COMRESULT withValueInterface (Value* pRetVal, Callback&& callback) const
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* valueInterface = getHandler().getValueInterface())
|
||||
{
|
||||
if (valueInterface->getRange().isValid())
|
||||
{
|
||||
*pRetVal = callback (*valueInterface);
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIARangeValueProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
JUCE_COMCLASS (ISelectionProvider2, "14f68475-ee1c-44f6-a869-d239381f0fe7") : public ISelectionProvider
|
||||
{
|
||||
JUCE_COMCALL get_FirstSelectedItem (IRawElementProviderSimple** retVal) = 0;
|
||||
JUCE_COMCALL get_LastSelectedItem (IRawElementProviderSimple** retVal) = 0;
|
||||
JUCE_COMCALL get_CurrentSelectedItem (IRawElementProviderSimple** retVal) = 0;
|
||||
JUCE_COMCALL get_ItemCount (int* retVal) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class UIASelectionItemProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ISelectionItemProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIASelectionItemProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle),
|
||||
isRadioButton (getHandler().getRole() == AccessibilityRole::radioButton)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT AddToSelection() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
const auto& handler = getHandler();
|
||||
|
||||
if (isRadioButton)
|
||||
{
|
||||
handler.getActions().invoke (AccessibilityActionType::press);
|
||||
sendAccessibilityAutomationEvent (handler, UIA_SelectionItem_ElementSelectedEventId);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
handler.getActions().invoke (AccessibilityActionType::toggle);
|
||||
handler.getActions().invoke (AccessibilityActionType::press);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_IsSelected (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
const auto state = getHandler().getCurrentState();
|
||||
*pRetVal = isRadioButton ? state.isChecked() : state.isSelected();
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_SelectionContainer (IRawElementProviderSimple** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
if (! isRadioButton)
|
||||
if (auto* parent = getHandler().getParent())
|
||||
parent->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT RemoveFromSelection() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (! isRadioButton)
|
||||
{
|
||||
const auto& handler = getHandler();
|
||||
|
||||
if (handler.getCurrentState().isSelected())
|
||||
getHandler().getActions().invoke (AccessibilityActionType::toggle);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Select() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
AddToSelection();
|
||||
|
||||
if (! isRadioButton)
|
||||
{
|
||||
const auto& handler = getHandler();
|
||||
|
||||
if (auto* parent = handler.getParent())
|
||||
for (auto* child : parent->getChildren())
|
||||
if (child != &handler && child->getCurrentState().isSelected())
|
||||
child->getActions().invoke (AccessibilityActionType::toggle);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
const bool isRadioButton;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIASelectionItemProvider)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class UIASelectionProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ISelectionProvider2>
|
||||
{
|
||||
public:
|
||||
explicit UIASelectionProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT QueryInterface (REFIID iid, void** result) override
|
||||
{
|
||||
if (iid == _uuidof (IUnknown) || iid == _uuidof (ISelectionProvider))
|
||||
return castToType<ISelectionProvider> (result);
|
||||
|
||||
if (iid == _uuidof (ISelectionProvider2))
|
||||
return castToType<ISelectionProvider2> (result);
|
||||
|
||||
*result = nullptr;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT get_CanSelectMultiple (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = isMultiSelectable();
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_IsSelectionRequired (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = getSelectedChildren().size() > 0 && ! isMultiSelectable();
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetSelection (SAFEARRAY** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
return addHandlersToArray (getSelectedChildren(), pRetVal);
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT get_FirstSelectedItem (IRawElementProviderSimple** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
auto selectedChildren = getSelectedChildren();
|
||||
|
||||
if (! selectedChildren.empty())
|
||||
selectedChildren.front()->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_LastSelectedItem (IRawElementProviderSimple** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
auto selectedChildren = getSelectedChildren();
|
||||
|
||||
if (! selectedChildren.empty())
|
||||
selectedChildren.back()->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_CurrentSelectedItem (IRawElementProviderSimple** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
get_FirstSelectedItem (pRetVal);
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_ItemCount (int* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = (int) getSelectedChildren().size();
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
bool isMultiSelectable() const noexcept
|
||||
{
|
||||
return getHandler().getCurrentState().isMultiSelectable();
|
||||
}
|
||||
|
||||
std::vector<const AccessibilityHandler*> getSelectedChildren() const
|
||||
{
|
||||
std::vector<const AccessibilityHandler*> selectedHandlers;
|
||||
|
||||
for (auto* child : getHandler().getComponent().getChildren())
|
||||
if (auto* handler = child->getAccessibilityHandler())
|
||||
if (handler->getCurrentState().isSelected())
|
||||
selectedHandlers.push_back (handler);
|
||||
|
||||
return selectedHandlers;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIASelectionProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,664 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 UIATextProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ITextProvider2>
|
||||
{
|
||||
public:
|
||||
explicit UIATextProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT QueryInterface (REFIID iid, void** result) override
|
||||
{
|
||||
if (iid == _uuidof (IUnknown) || iid == _uuidof (ITextProvider))
|
||||
return castToType<ITextProvider> (result);
|
||||
|
||||
if (iid == _uuidof (ITextProvider2))
|
||||
return castToType<ITextProvider2> (result);
|
||||
|
||||
*result = nullptr;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
JUCE_COMRESULT get_DocumentRange (ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
*pRetVal = new UIATextRangeProvider (*this, { 0, textInterface.getTotalNumCharacters() });
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_SupportedTextSelection (SupportedTextSelection* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = SupportedTextSelection_Single;
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetSelection (SAFEARRAY** pRetVal) override
|
||||
{
|
||||
return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
*pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 1);
|
||||
|
||||
if (pRetVal != nullptr)
|
||||
{
|
||||
auto selection = textInterface.getSelection();
|
||||
auto hasSelection = ! selection.isEmpty();
|
||||
auto cursorPos = textInterface.getTextInsertionOffset();
|
||||
|
||||
auto* rangeProvider = new UIATextRangeProvider (*this,
|
||||
{ hasSelection ? selection.getStart() : cursorPos,
|
||||
hasSelection ? selection.getEnd() : cursorPos });
|
||||
|
||||
LONG pos = 0;
|
||||
auto hr = SafeArrayPutElement (*pRetVal, &pos, static_cast<IUnknown*> (rangeProvider));
|
||||
|
||||
if (FAILED (hr))
|
||||
return E_FAIL;
|
||||
|
||||
rangeProvider->Release();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetVisibleRanges (SAFEARRAY** pRetVal) override
|
||||
{
|
||||
return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
*pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 1);
|
||||
|
||||
if (pRetVal != nullptr)
|
||||
{
|
||||
auto* rangeProvider = new UIATextRangeProvider (*this, { 0, textInterface.getTotalNumCharacters() });
|
||||
|
||||
LONG pos = 0;
|
||||
auto hr = SafeArrayPutElement (*pRetVal, &pos, static_cast<IUnknown*> (rangeProvider));
|
||||
|
||||
if (FAILED (hr))
|
||||
return E_FAIL;
|
||||
|
||||
rangeProvider->Release();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT RangeFromChild (IRawElementProviderSimple*, ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, []
|
||||
{
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT RangeFromPoint (UiaPoint point, ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
auto offset = textInterface.getOffsetAtPoint ({ roundToInt (point.x), roundToInt (point.y) });
|
||||
|
||||
if (offset > 0)
|
||||
*pRetVal = new UIATextRangeProvider (*this, { offset, offset });
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT GetCaretRange (BOOL* isActive, ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
*isActive = getHandler().hasFocus (false);
|
||||
|
||||
auto cursorPos = textInterface.getTextInsertionOffset();
|
||||
*pRetVal = new UIATextRangeProvider (*this, { cursorPos, cursorPos });
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT RangeFromAnnotation (IRawElementProviderSimple*, ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, []
|
||||
{
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
template <typename Value, typename Callback>
|
||||
JUCE_COMRESULT withTextInterface (Value* pRetVal, Callback&& callback) const
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* textInterface = getHandler().getTextInterface())
|
||||
return callback (*textInterface);
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class UIATextRangeProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ITextRangeProvider>
|
||||
{
|
||||
public:
|
||||
UIATextRangeProvider (UIATextProvider& textProvider, Range<int> range)
|
||||
: UIAProviderBase (textProvider.getHandler().getNativeImplementation()),
|
||||
owner (&textProvider),
|
||||
selectionRange (range)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Range<int> getSelectionRange() const noexcept { return selectionRange; }
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT AddToSelection() override
|
||||
{
|
||||
return Select();
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Clone (ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = new UIATextRangeProvider (*owner, selectionRange);
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Compare (ITextRangeProvider* range, BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = (selectionRange == static_cast<UIATextRangeProvider*> (range)->getSelectionRange());
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT CompareEndpoints (TextPatternRangeEndpoint endpoint,
|
||||
ITextRangeProvider* targetRange,
|
||||
TextPatternRangeEndpoint targetEndpoint,
|
||||
int* pRetVal) override
|
||||
{
|
||||
if (targetRange == nullptr)
|
||||
return E_INVALIDARG;
|
||||
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
auto offset = (endpoint == TextPatternRangeEndpoint_Start ? selectionRange.getStart()
|
||||
: selectionRange.getEnd());
|
||||
|
||||
auto otherRange = static_cast<UIATextRangeProvider*> (targetRange)->getSelectionRange();
|
||||
auto otherOffset = (targetEndpoint == TextPatternRangeEndpoint_Start ? otherRange.getStart()
|
||||
: otherRange.getEnd());
|
||||
|
||||
*pRetVal = offset - otherOffset;
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT ExpandToEnclosingUnit (TextUnit unit) override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (auto* textInterface = getHandler().getTextInterface())
|
||||
{
|
||||
auto numCharacters = textInterface->getTotalNumCharacters();
|
||||
|
||||
if (numCharacters == 0)
|
||||
{
|
||||
selectionRange = {};
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
if (unit == TextUnit_Character)
|
||||
{
|
||||
selectionRange.setStart (jlimit (0, numCharacters - 1, selectionRange.getStart()));
|
||||
selectionRange.setEnd (selectionRange.getStart() + 1);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
if (unit == TextUnit_Paragraph
|
||||
|| unit == TextUnit_Page
|
||||
|| unit == TextUnit_Document)
|
||||
{
|
||||
selectionRange = { 0, textInterface->getTotalNumCharacters() };
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
auto start = getNextEndpointPosition (*textInterface,
|
||||
selectionRange.getStart(),
|
||||
unit,
|
||||
NextEndpointDirection::backwards);
|
||||
|
||||
if (start >= 0)
|
||||
{
|
||||
auto end = getNextEndpointPosition (*textInterface,
|
||||
start,
|
||||
unit,
|
||||
NextEndpointDirection::forwards);
|
||||
|
||||
if (end >= 0)
|
||||
selectionRange = Range<int> (start, end);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT FindAttribute (TEXTATTRIBUTEID, VARIANT, BOOL, ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, []
|
||||
{
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT FindText (BSTR text, BOOL backward, BOOL ignoreCase,
|
||||
ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
auto selectionText = textInterface.getText (selectionRange);
|
||||
String textToSearchFor (text);
|
||||
|
||||
auto offset = (backward ? (ignoreCase ? selectionText.lastIndexOfIgnoreCase (textToSearchFor) : selectionText.lastIndexOf (textToSearchFor))
|
||||
: (ignoreCase ? selectionText.indexOfIgnoreCase (textToSearchFor) : selectionText.indexOf (textToSearchFor)));
|
||||
|
||||
if (offset != -1)
|
||||
*pRetVal = new UIATextRangeProvider (*owner, { offset, offset + textToSearchFor.length() });
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetAttributeValue (TEXTATTRIBUTEID attributeId, VARIANT* pRetVal) override
|
||||
{
|
||||
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
VariantHelpers::clear (pRetVal);
|
||||
|
||||
const auto& handler = getHandler();
|
||||
|
||||
switch (attributeId)
|
||||
{
|
||||
case UIA_IsReadOnlyAttributeId:
|
||||
{
|
||||
const auto readOnly = [&]
|
||||
{
|
||||
if (auto* valueInterface = handler.getValueInterface())
|
||||
return valueInterface->isReadOnly();
|
||||
|
||||
return false;
|
||||
}();
|
||||
|
||||
VariantHelpers::setBool (readOnly, pRetVal);
|
||||
break;
|
||||
}
|
||||
case UIA_CaretPositionAttributeId:
|
||||
{
|
||||
auto cursorPos = textInterface.getTextInsertionOffset();
|
||||
|
||||
auto caretPos = [&]
|
||||
{
|
||||
if (cursorPos == 0)
|
||||
return CaretPosition_BeginningOfLine;
|
||||
|
||||
if (cursorPos == textInterface.getTotalNumCharacters())
|
||||
return CaretPosition_EndOfLine;
|
||||
|
||||
return CaretPosition_Unknown;
|
||||
}();
|
||||
|
||||
VariantHelpers::setInt (caretPos, pRetVal);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetBoundingRectangles (SAFEARRAY** pRetVal) override
|
||||
{
|
||||
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
auto rectangleList = textInterface.getTextBounds (selectionRange);
|
||||
auto numRectangles = rectangleList.getNumRectangles();
|
||||
|
||||
*pRetVal = SafeArrayCreateVector (VT_R8, 0, 4 * numRectangles);
|
||||
|
||||
if (*pRetVal == nullptr)
|
||||
return E_FAIL;
|
||||
|
||||
if (numRectangles > 0)
|
||||
{
|
||||
double* doubleArr = nullptr;
|
||||
|
||||
if (FAILED (SafeArrayAccessData (*pRetVal, reinterpret_cast<void**> (&doubleArr))))
|
||||
{
|
||||
SafeArrayDestroy (*pRetVal);
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
for (int i = 0; i < numRectangles; ++i)
|
||||
{
|
||||
auto r = Desktop::getInstance().getDisplays().logicalToPhysical (rectangleList.getRectangle (i));
|
||||
|
||||
doubleArr[i * 4] = r.getX();
|
||||
doubleArr[i * 4 + 1] = r.getY();
|
||||
doubleArr[i * 4 + 2] = r.getWidth();
|
||||
doubleArr[i * 4 + 3] = r.getHeight();
|
||||
}
|
||||
|
||||
if (FAILED (SafeArrayUnaccessData (*pRetVal)))
|
||||
{
|
||||
SafeArrayDestroy (*pRetVal);
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetChildren (SAFEARRAY** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 0);
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetEnclosingElement (IRawElementProviderSimple** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
getHandler().getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetText (int maxLength, BSTR* pRetVal) override
|
||||
{
|
||||
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
auto text = textInterface.getText (selectionRange);
|
||||
|
||||
if (maxLength >= 0 && text.length() > maxLength)
|
||||
text = text.substring (0, maxLength);
|
||||
|
||||
*pRetVal = SysAllocString ((const OLECHAR*) text.toWideCharPointer());
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Move (TextUnit unit, int count, int* pRetVal) override
|
||||
{
|
||||
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface&)
|
||||
{
|
||||
if (count > 0)
|
||||
{
|
||||
MoveEndpointByUnit (TextPatternRangeEndpoint_End, unit, count, pRetVal);
|
||||
MoveEndpointByUnit (TextPatternRangeEndpoint_Start, unit, count, pRetVal);
|
||||
}
|
||||
else if (count < 0)
|
||||
{
|
||||
MoveEndpointByUnit (TextPatternRangeEndpoint_Start, unit, count, pRetVal);
|
||||
MoveEndpointByUnit (TextPatternRangeEndpoint_End, unit, count, pRetVal);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT MoveEndpointByRange (TextPatternRangeEndpoint endpoint,
|
||||
ITextRangeProvider* targetRange,
|
||||
TextPatternRangeEndpoint targetEndpoint) override
|
||||
{
|
||||
if (targetRange == nullptr)
|
||||
return E_INVALIDARG;
|
||||
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (auto* textInterface = getHandler().getTextInterface())
|
||||
{
|
||||
auto otherRange = static_cast<UIATextRangeProvider*> (targetRange)->getSelectionRange();
|
||||
auto targetPoint = (targetEndpoint == TextPatternRangeEndpoint_Start ? otherRange.getStart()
|
||||
: otherRange.getEnd());
|
||||
|
||||
setEndpointChecked (endpoint, targetPoint);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT MoveEndpointByUnit (TextPatternRangeEndpoint endpoint,
|
||||
TextUnit unit,
|
||||
int count,
|
||||
int* pRetVal) override
|
||||
{
|
||||
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
auto numCharacters = textInterface.getTotalNumCharacters();
|
||||
|
||||
if (count == 0 || numCharacters == 0)
|
||||
return S_OK;
|
||||
|
||||
auto isStart = (endpoint == TextPatternRangeEndpoint_Start);
|
||||
auto endpointToMove = (isStart ? selectionRange.getStart() : selectionRange.getEnd());
|
||||
|
||||
if (unit == TextUnit_Character)
|
||||
{
|
||||
auto targetPoint = jlimit (0, numCharacters, endpointToMove + count);
|
||||
|
||||
*pRetVal = targetPoint - endpointToMove;
|
||||
setEndpointChecked (endpoint, targetPoint);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
auto direction = (count > 0 ? NextEndpointDirection::forwards
|
||||
: NextEndpointDirection::backwards);
|
||||
|
||||
if (unit == TextUnit_Paragraph
|
||||
|| unit == TextUnit_Page
|
||||
|| unit == TextUnit_Document)
|
||||
{
|
||||
*pRetVal = (direction == NextEndpointDirection::forwards ? 1 : -1);
|
||||
setEndpointChecked (endpoint, numCharacters);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
for (int i = 0; i < std::abs (count); ++i)
|
||||
{
|
||||
auto nextEndpoint = getNextEndpointPosition (textInterface,
|
||||
endpointToMove,
|
||||
unit,
|
||||
direction);
|
||||
|
||||
if (nextEndpoint < 0)
|
||||
{
|
||||
*pRetVal = (direction == NextEndpointDirection::forwards ? i : -i);
|
||||
setEndpointChecked (endpoint, endpointToMove);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
endpointToMove = nextEndpoint;
|
||||
}
|
||||
|
||||
*pRetVal = count;
|
||||
setEndpointChecked (endpoint, endpointToMove);
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT RemoveFromSelection() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (auto* textInterface = getHandler().getTextInterface())
|
||||
{
|
||||
textInterface->setSelection ({});
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT ScrollIntoView (BOOL) override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Select() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (auto* textInterface = getHandler().getTextInterface())
|
||||
{
|
||||
textInterface->setSelection ({});
|
||||
textInterface->setSelection (selectionRange);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
private:
|
||||
enum class NextEndpointDirection { forwards, backwards };
|
||||
|
||||
static int getNextEndpointPosition (const AccessibilityTextInterface& textInterface,
|
||||
int currentPosition,
|
||||
TextUnit unit,
|
||||
NextEndpointDirection direction)
|
||||
{
|
||||
auto isTextUnitSeparator = [unit] (const juce_wchar c)
|
||||
{
|
||||
return ((unit == TextUnit_Word || unit == TextUnit_Format) && CharacterFunctions::isWhitespace (c))
|
||||
|| (unit == TextUnit_Line && (c == '\r' || c == '\n'));
|
||||
};
|
||||
|
||||
constexpr int textBufferSize = 1024;
|
||||
int numChars = 0;
|
||||
|
||||
if (direction == NextEndpointDirection::forwards)
|
||||
{
|
||||
auto textBuffer = textInterface.getText ({ currentPosition,
|
||||
jmin (textInterface.getTotalNumCharacters(), currentPosition + textBufferSize) });
|
||||
|
||||
for (auto charPtr = textBuffer.getCharPointer(); ! charPtr.isEmpty();)
|
||||
{
|
||||
auto character = charPtr.getAndAdvance();
|
||||
++numChars;
|
||||
|
||||
if (isTextUnitSeparator (character))
|
||||
return currentPosition + numChars;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto textBuffer = textInterface.getText ({ jmax (0, currentPosition - textBufferSize),
|
||||
currentPosition });
|
||||
|
||||
for (auto charPtr = textBuffer.end() - 1; charPtr != textBuffer.begin(); --charPtr)
|
||||
{
|
||||
auto character = *charPtr;
|
||||
|
||||
if (isTextUnitSeparator (character))
|
||||
return currentPosition - numChars;
|
||||
|
||||
++numChars;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void setEndpointChecked (TextPatternRangeEndpoint endpoint, int newEndpoint)
|
||||
{
|
||||
if (endpoint == TextPatternRangeEndpoint_Start)
|
||||
{
|
||||
if (selectionRange.getEnd() < newEndpoint)
|
||||
selectionRange.setEnd (newEndpoint);
|
||||
|
||||
selectionRange.setStart (newEndpoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (selectionRange.getStart() > newEndpoint)
|
||||
selectionRange.setStart (newEndpoint);
|
||||
|
||||
selectionRange.setEnd (newEndpoint);
|
||||
}
|
||||
}
|
||||
|
||||
ComSmartPtr<UIATextProvider> owner;
|
||||
Range<int> selectionRange;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATextRangeProvider)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATextProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 UIAToggleProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<IToggleProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIAToggleProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT Toggle() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
const auto& handler = getHandler();
|
||||
|
||||
if (handler.getActions().invoke (AccessibilityActionType::toggle))
|
||||
{
|
||||
VARIANT newValue;
|
||||
VariantHelpers::setInt (getCurrentToggleState(), &newValue);
|
||||
|
||||
sendAccessibilityPropertyChangedEvent (handler, UIA_ToggleToggleStatePropertyId, newValue);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_ToggleState (ToggleState* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = getCurrentToggleState();
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
ToggleState getCurrentToggleState() const
|
||||
{
|
||||
return getHandler().getCurrentState().isChecked() ? ToggleState_On
|
||||
: ToggleState_Off;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAToggleProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 UIATransformProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ITransformProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIATransformProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT Move (double x, double y) override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
RECT rect;
|
||||
GetWindowRect ((HWND) peer->getNativeHandle(), &rect);
|
||||
|
||||
rect.left = roundToInt (x);
|
||||
rect.top = roundToInt (y);
|
||||
|
||||
auto bounds = Rectangle<int>::leftTopRightBottom (rect.left, rect.top, rect.right, rect.bottom);
|
||||
|
||||
peer->setBounds (Desktop::getInstance().getDisplays().physicalToLogical (bounds),
|
||||
peer->isFullScreen());
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Resize (double width, double height) override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
auto scale = peer->getPlatformScaleFactor();
|
||||
|
||||
peer->getComponent().setSize (roundToInt (width / scale),
|
||||
roundToInt (height / scale));
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Rotate (double) override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_CanMove (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = true;
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_CanResize (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
if (auto* peer = getPeer())
|
||||
*pRetVal = ((peer->getStyleFlags() & ComponentPeer::windowIsResizable) != 0);
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_CanRotate (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = false;
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
ComponentPeer* getPeer() const
|
||||
{
|
||||
return getHandler().getComponent().getPeer();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATransformProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 UIAValueProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<IValueProvider>
|
||||
{
|
||||
public:
|
||||
UIAValueProvider (AccessibilityNativeHandle* nativeHandle, bool editableText)
|
||||
: UIAProviderBase (nativeHandle),
|
||||
isEditableText (editableText)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT SetValue (LPCWSTR val) override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
const auto& handler = getHandler();
|
||||
|
||||
const auto sendValuePropertyChangeMessage = [&]()
|
||||
{
|
||||
VARIANT newValue;
|
||||
VariantHelpers::setString (getCurrentValueString(), &newValue);
|
||||
|
||||
sendAccessibilityPropertyChangedEvent (handler, UIA_ValueValuePropertyId, newValue);
|
||||
};
|
||||
|
||||
if (isEditableText)
|
||||
{
|
||||
handler.getTextInterface()->setText (String (val));
|
||||
sendValuePropertyChangeMessage();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
if (auto* valueInterface = handler.getValueInterface())
|
||||
{
|
||||
if (! valueInterface->isReadOnly())
|
||||
{
|
||||
valueInterface->setValueAsString (String (val));
|
||||
sendValuePropertyChangeMessage();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_Value (BSTR* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
auto currentValue = getCurrentValueString();
|
||||
|
||||
*pRetVal = SysAllocString ((const OLECHAR*) currentValue.toWideCharPointer());
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_IsReadOnly (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
if (! isEditableText)
|
||||
if (auto* valueInterface = getHandler().getValueInterface())
|
||||
*pRetVal = valueInterface->isReadOnly();
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
String getCurrentValueString() const
|
||||
{
|
||||
if (isEditableText)
|
||||
if (auto* textInterface = getHandler().getTextInterface())
|
||||
return textInterface->getText ({ 0, textInterface->getTotalNumCharacters() });
|
||||
|
||||
if (auto* valueInterface = getHandler().getValueInterface())
|
||||
return valueInterface->getCurrentValueAsString();
|
||||
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
const bool isEditableText;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAValueProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 UIAWindowProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<IWindowProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIAWindowProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT SetVisualState (WindowVisualState state) override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case WindowVisualState_Maximized:
|
||||
peer->setFullScreen (true);
|
||||
break;
|
||||
|
||||
case WindowVisualState_Minimized:
|
||||
peer->setMinimised (true);
|
||||
break;
|
||||
|
||||
case WindowVisualState_Normal:
|
||||
peer->setFullScreen (false);
|
||||
peer->setMinimised (false);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Close() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
peer->handleUserClosingWindow();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT WaitForInputIdle (int, BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, []
|
||||
{
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_CanMaximize (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
*pRetVal = (peer->getStyleFlags() & ComponentPeer::windowHasMaximiseButton) != 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_CanMinimize (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
*pRetVal = (peer->getStyleFlags() & ComponentPeer::windowHasMinimiseButton) != 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_IsModal (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
*pRetVal = peer->getComponent().isCurrentlyModal();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_WindowVisualState (WindowVisualState* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
if (peer->isFullScreen())
|
||||
*pRetVal = WindowVisualState_Maximized;
|
||||
else if (peer->isMinimised())
|
||||
*pRetVal = WindowVisualState_Minimized;
|
||||
else
|
||||
*pRetVal = WindowVisualState_Normal;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_WindowInteractionState (WindowInteractionState* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
*pRetVal = peer->getComponent().isCurrentlyBlockedByAnotherModalComponent()
|
||||
? WindowInteractionState::WindowInteractionState_BlockedByModalWindow
|
||||
: WindowInteractionState::WindowInteractionState_Running;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_IsTopmost (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
*pRetVal = peer->isFocused();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
ComponentPeer* getPeer() const
|
||||
{
|
||||
return getHandler().getComponent().getPeer();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAWindowProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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 WindowsUIAWrapper : public DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
bool isLoaded() const noexcept
|
||||
{
|
||||
return uiaReturnRawElementProvider != nullptr
|
||||
&& uiaHostProviderFromHwnd != nullptr
|
||||
&& uiaRaiseAutomationPropertyChangedEvent != nullptr
|
||||
&& uiaRaiseAutomationEvent != nullptr
|
||||
&& uiaClientsAreListening != nullptr
|
||||
&& uiaDisconnectProvider != nullptr
|
||||
&& uiaDisconnectAllProviders != nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
LRESULT returnRawElementProvider (HWND hwnd, WPARAM wParam, LPARAM lParam, IRawElementProviderSimple* provider)
|
||||
{
|
||||
return uiaReturnRawElementProvider != nullptr ? uiaReturnRawElementProvider (hwnd, wParam, lParam, provider)
|
||||
: (LRESULT) nullptr;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT hostProviderFromHwnd (HWND hwnd, IRawElementProviderSimple** provider)
|
||||
{
|
||||
return uiaHostProviderFromHwnd != nullptr ? uiaHostProviderFromHwnd (hwnd, provider)
|
||||
: UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT raiseAutomationPropertyChangedEvent (IRawElementProviderSimple* provider, PROPERTYID propID, VARIANT oldValue, VARIANT newValue)
|
||||
{
|
||||
return uiaRaiseAutomationPropertyChangedEvent != nullptr ? uiaRaiseAutomationPropertyChangedEvent (provider, propID, oldValue, newValue)
|
||||
: UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT raiseAutomationEvent (IRawElementProviderSimple* provider, EVENTID eventID)
|
||||
{
|
||||
return uiaRaiseAutomationEvent != nullptr ? uiaRaiseAutomationEvent (provider, eventID)
|
||||
: UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
BOOL clientsAreListening()
|
||||
{
|
||||
return uiaClientsAreListening != nullptr ? uiaClientsAreListening()
|
||||
: false;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT disconnectProvider (IRawElementProviderSimple* provider)
|
||||
{
|
||||
if (uiaDisconnectProvider != nullptr)
|
||||
{
|
||||
const ScopedValueSetter<IRawElementProviderSimple*> disconnectingProviderSetter (disconnectingProvider, provider);
|
||||
return uiaDisconnectProvider (provider);
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT disconnectAllProviders()
|
||||
{
|
||||
if (uiaDisconnectAllProviders != nullptr)
|
||||
{
|
||||
const ScopedValueSetter<bool> disconnectingAllProvidersSetter (disconnectingAllProviders, true);
|
||||
return uiaDisconnectAllProviders();
|
||||
}
|
||||
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool isProviderDisconnecting (IRawElementProviderSimple* provider)
|
||||
{
|
||||
return disconnectingProvider == provider || disconnectingAllProviders;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (WindowsUIAWrapper)
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
WindowsUIAWrapper()
|
||||
{
|
||||
// force UIA COM library initialisation here to prevent an exception when calling methods from SendMessage()
|
||||
if (isLoaded())
|
||||
returnRawElementProvider (nullptr, 0, 0, nullptr);
|
||||
else
|
||||
jassertfalse; // UIAutomationCore could not be loaded!
|
||||
}
|
||||
|
||||
~WindowsUIAWrapper()
|
||||
{
|
||||
disconnectAllProviders();
|
||||
|
||||
if (uiaHandle != nullptr)
|
||||
::FreeLibrary (uiaHandle);
|
||||
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template<typename FuncType>
|
||||
static FuncType getUiaFunction (HMODULE module, StringRef funcName)
|
||||
{
|
||||
return (FuncType) GetProcAddress (module, funcName);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
using UiaReturnRawElementProviderFunc = LRESULT (WINAPI*) (HWND, WPARAM, LPARAM, IRawElementProviderSimple*);
|
||||
using UiaHostProviderFromHwndFunc = HRESULT (WINAPI*) (HWND, IRawElementProviderSimple**);
|
||||
using UiaRaiseAutomationPropertyChangedEventFunc = HRESULT (WINAPI*) (IRawElementProviderSimple*, PROPERTYID, VARIANT, VARIANT);
|
||||
using UiaRaiseAutomationEventFunc = HRESULT (WINAPI*) (IRawElementProviderSimple*, EVENTID);
|
||||
using UiaClientsAreListeningFunc = BOOL (WINAPI*) ();
|
||||
using UiaDisconnectProviderFunc = HRESULT (WINAPI*) (IRawElementProviderSimple*);
|
||||
using UiaDisconnectAllProvidersFunc = HRESULT (WINAPI*) ();
|
||||
|
||||
HMODULE uiaHandle = ::LoadLibraryA ("UIAutomationCore.dll");
|
||||
UiaReturnRawElementProviderFunc uiaReturnRawElementProvider = getUiaFunction<UiaReturnRawElementProviderFunc> (uiaHandle, "UiaReturnRawElementProvider");
|
||||
UiaHostProviderFromHwndFunc uiaHostProviderFromHwnd = getUiaFunction<UiaHostProviderFromHwndFunc> (uiaHandle, "UiaHostProviderFromHwnd");
|
||||
UiaRaiseAutomationPropertyChangedEventFunc uiaRaiseAutomationPropertyChangedEvent = getUiaFunction<UiaRaiseAutomationPropertyChangedEventFunc> (uiaHandle, "UiaRaiseAutomationPropertyChangedEvent");
|
||||
UiaRaiseAutomationEventFunc uiaRaiseAutomationEvent = getUiaFunction<UiaRaiseAutomationEventFunc> (uiaHandle, "UiaRaiseAutomationEvent");
|
||||
UiaClientsAreListeningFunc uiaClientsAreListening = getUiaFunction<UiaClientsAreListeningFunc> (uiaHandle, "UiaClientsAreListening");
|
||||
UiaDisconnectProviderFunc uiaDisconnectProvider = getUiaFunction<UiaDisconnectProviderFunc> (uiaHandle, "UiaDisconnectProvider");
|
||||
UiaDisconnectAllProvidersFunc uiaDisconnectAllProviders = getUiaFunction<UiaDisconnectAllProvidersFunc> (uiaHandle, "UiaDisconnectAllProviders");
|
||||
|
||||
IRawElementProviderSimple* disconnectingProvider = nullptr;
|
||||
bool disconnectingAllProviders = false;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsUIAWrapper)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -56,26 +56,6 @@ namespace juce
|
|||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
static CGFloat getMainScreenHeight() noexcept
|
||||
{
|
||||
if ([[NSScreen screens] count] == 0)
|
||||
return 0.0f;
|
||||
|
||||
return [[[NSScreen screens] objectAtIndex: 0] frame].size.height;
|
||||
}
|
||||
|
||||
static void flipScreenRect (NSRect& r) noexcept
|
||||
{
|
||||
r.origin.y = getMainScreenHeight() - (r.origin.y + r.size.height);
|
||||
}
|
||||
|
||||
static NSRect flippedScreenRect (NSRect r) noexcept
|
||||
{
|
||||
flipScreenRect (r);
|
||||
return r;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class NSViewComponentPeer : public ComponentPeer,
|
||||
private Timer
|
||||
|
|
@ -124,7 +104,7 @@ public:
|
|||
{
|
||||
r.origin.x = (CGFloat) component.getX();
|
||||
r.origin.y = (CGFloat) component.getY();
|
||||
flipScreenRect (r);
|
||||
r = flippedScreenRect (r);
|
||||
|
||||
window = [createWindowInstance() initWithContentRect: r
|
||||
styleMask: getNSWindowStyleMask (windowStyleFlags)
|
||||
|
|
@ -323,7 +303,7 @@ public:
|
|||
r = [[view superview] convertRect: r toView: nil];
|
||||
r = [viewWindow convertRectToScreen: r];
|
||||
|
||||
flipScreenRect (r);
|
||||
r = flippedScreenRect (r);
|
||||
}
|
||||
|
||||
return convertToRectInt (r);
|
||||
|
|
@ -1669,12 +1649,34 @@ const SEL NSViewComponentPeer::becomeKeySelector = @selector (becomeKey:);
|
|||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
//==============================================================================
|
||||
struct JuceNSViewClass : public ObjCClass<NSView>
|
||||
template <typename Base>
|
||||
struct NSViewComponentPeerWrapper : public Base
|
||||
{
|
||||
JuceNSViewClass() : ObjCClass<NSView> ("JUCEView_")
|
||||
explicit NSViewComponentPeerWrapper (const char* baseName)
|
||||
: Base (baseName)
|
||||
{
|
||||
addIvar<NSViewComponentPeer*> ("owner");
|
||||
Base::template addIvar<NSViewComponentPeer*> ("owner");
|
||||
}
|
||||
|
||||
static NSViewComponentPeer* getOwner (id self)
|
||||
{
|
||||
return Base::template getIvar<NSViewComponentPeer*> (self, "owner");
|
||||
}
|
||||
|
||||
static id getAccessibleChild (id self)
|
||||
{
|
||||
if (auto* owner = getOwner (self))
|
||||
if (auto* handler = owner->getComponent().getAccessibilityHandler())
|
||||
return (id) handler->getNativeImplementation();
|
||||
|
||||
return nil;
|
||||
}
|
||||
};
|
||||
|
||||
struct JuceNSViewClass : public NSViewComponentPeerWrapper<ObjCClass<NSView>>
|
||||
{
|
||||
JuceNSViewClass() : NSViewComponentPeerWrapper ("JUCEView_")
|
||||
{
|
||||
addMethod (@selector (isOpaque), isOpaque, "c@:");
|
||||
addMethod (@selector (drawRect:), drawRect, "v@:", @encode (NSRect));
|
||||
addMethod (@selector (mouseDown:), mouseDown, "v@:@");
|
||||
|
|
@ -1696,7 +1698,6 @@ struct JuceNSViewClass : public ObjCClass<NSView>
|
|||
addMethod (@selector (windowDidDeminiaturize:), windowDidDeminiaturize, "v@:@");
|
||||
addMethod (@selector (wantsDefaultClipping), wantsDefaultClipping, "c@:");
|
||||
addMethod (@selector (worksWhenModal), worksWhenModal, "c@:");
|
||||
addMethod (@selector (viewWillMoveToWindow:), willMoveToWindow, "v@:@");
|
||||
addMethod (@selector (viewDidMoveToWindow), viewDidMoveToWindow, "v@:");
|
||||
addMethod (@selector (viewWillDraw), viewWillDraw, "v@:");
|
||||
addMethod (@selector (keyDown:), keyDown, "v@:@");
|
||||
|
|
@ -1732,6 +1733,17 @@ struct JuceNSViewClass : public ObjCClass<NSView>
|
|||
addMethod (@selector (cut:), cut, "v@:@");
|
||||
addMethod (@selector (selectAll:), selectAll, "v@:@");
|
||||
|
||||
addMethod (@selector (viewWillMoveToWindow:), willMoveToWindow, "v@:@");
|
||||
|
||||
addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement, "c@:");
|
||||
addMethod (@selector (accessibilityChildren), getAccessibilityChildren, "@@:");
|
||||
addMethod (@selector (accessibilityHitTest:), accessibilityHitTest, "@@:", @encode (NSPoint));
|
||||
addMethod (@selector (accessibilityFocusedUIElement), getAccessibilityFocusedUIElement, "@@:");
|
||||
|
||||
// deprecated methods required for backwards compatibility
|
||||
addMethod (@selector (accessibilityIsIgnored), getAccessibilityIsIgnored, "c@:");
|
||||
addMethod (@selector (accessibilityAttributeValue:), getAccessibilityAttributeValue, "@@:@");
|
||||
|
||||
addMethod (@selector (isFlipped), isFlipped, "c@:");
|
||||
|
||||
addMethod (NSViewComponentPeer::dismissModalsSelector, dismissModals, "v@:");
|
||||
|
|
@ -1746,11 +1758,6 @@ struct JuceNSViewClass : public ObjCClass<NSView>
|
|||
}
|
||||
|
||||
private:
|
||||
static NSViewComponentPeer* getOwner (id self)
|
||||
{
|
||||
return getIvar<NSViewComponentPeer*> (self, "owner");
|
||||
}
|
||||
|
||||
static void mouseDown (id self, SEL s, NSEvent* ev)
|
||||
{
|
||||
if (JUCEApplicationBase::isStandaloneApp())
|
||||
|
|
@ -2072,15 +2079,47 @@ private:
|
|||
}
|
||||
|
||||
static void concludeDragOperation (id, SEL, id<NSDraggingInfo>) {}
|
||||
|
||||
//==============================================================================
|
||||
static BOOL getIsAccessibilityElement (id, SEL)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
static NSArray* getAccessibilityChildren (id self, SEL)
|
||||
{
|
||||
return NSAccessibilityUnignoredChildrenForOnlyChild (getAccessibleChild (self));
|
||||
}
|
||||
|
||||
static id accessibilityHitTest (id self, SEL, NSPoint point)
|
||||
{
|
||||
return [getAccessibleChild (self) accessibilityHitTest: point];
|
||||
}
|
||||
|
||||
static id getAccessibilityFocusedUIElement (id self, SEL)
|
||||
{
|
||||
return [getAccessibleChild (self) accessibilityFocusedUIElement];
|
||||
}
|
||||
|
||||
static BOOL getAccessibilityIsIgnored (id self, SEL)
|
||||
{
|
||||
return ! [self isAccessibilityElement];
|
||||
}
|
||||
|
||||
static id getAccessibilityAttributeValue (id self, SEL, NSString* attribute)
|
||||
{
|
||||
if ([attribute isEqualToString: NSAccessibilityChildrenAttribute])
|
||||
return getAccessibilityChildren (self, {});
|
||||
|
||||
return sendSuperclassMessage<id> (self, @selector (accessibilityAttributeValue:), attribute);
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct JuceNSWindowClass : public ObjCClass<NSWindow>
|
||||
struct JuceNSWindowClass : public NSViewComponentPeerWrapper<ObjCClass<NSWindow>>
|
||||
{
|
||||
JuceNSWindowClass() : ObjCClass<NSWindow> ("JUCEWindow_")
|
||||
JuceNSWindowClass() : NSViewComponentPeerWrapper ("JUCEWindow_")
|
||||
{
|
||||
addIvar<NSViewComponentPeer*> ("owner");
|
||||
|
||||
addMethod (@selector (canBecomeKeyWindow), canBecomeKeyWindow, "c@:");
|
||||
addMethod (@selector (canBecomeMainWindow), canBecomeMainWindow, "c@:");
|
||||
addMethod (@selector (becomeKeyWindow), becomeKeyWindow, "v@:");
|
||||
|
|
@ -2096,6 +2135,12 @@ struct JuceNSWindowClass : public ObjCClass<NSWindow>
|
|||
addMethod (@selector (window:shouldPopUpDocumentPathMenu:), shouldPopUpPathMenu, "B@:@", @encode (NSMenu*));
|
||||
addMethod (@selector (isFlipped), isFlipped, "c@:");
|
||||
|
||||
addMethod (@selector (accessibilityLabel), getAccessibilityLabel, "@@:");
|
||||
addMethod (@selector (accessibilityTopLevelUIElement), getAccessibilityWindow, "@@:");
|
||||
addMethod (@selector (accessibilityWindow), getAccessibilityWindow, "@@:");
|
||||
addMethod (@selector (accessibilityRole), getAccessibilityRole, "@@:");
|
||||
addMethod (@selector (accessibilitySubrole), getAccessibilitySubrole, "@@:");
|
||||
|
||||
addMethod (@selector (window:shouldDragDocumentWithEvent:from:withPasteboard:),
|
||||
shouldAllowIconDrag, "B@:@", @encode (NSEvent*), @encode (NSPoint), @encode (NSPasteboard*));
|
||||
|
||||
|
|
@ -2105,11 +2150,6 @@ struct JuceNSWindowClass : public ObjCClass<NSWindow>
|
|||
}
|
||||
|
||||
private:
|
||||
static NSViewComponentPeer* getOwner (id self)
|
||||
{
|
||||
return getIvar<NSViewComponentPeer*> (self, "owner");
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static BOOL isFlipped (id, SEL) { return true; }
|
||||
|
||||
|
|
@ -2249,6 +2289,26 @@ private:
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
static NSString* getAccessibilityLabel (id self, SEL)
|
||||
{
|
||||
return [getAccessibleChild (self) accessibilityLabel];
|
||||
}
|
||||
|
||||
static id getAccessibilityWindow (id self, SEL)
|
||||
{
|
||||
return self;
|
||||
}
|
||||
|
||||
static NSAccessibilityRole getAccessibilityRole (id, SEL)
|
||||
{
|
||||
return NSAccessibilityWindowRole;
|
||||
}
|
||||
|
||||
static NSAccessibilityRole getAccessibilitySubrole (id self, SEL)
|
||||
{
|
||||
return [getAccessibleChild (self) accessibilitySubrole];
|
||||
}
|
||||
};
|
||||
|
||||
NSView* NSViewComponentPeer::createViewInstance()
|
||||
|
|
|
|||
|
|
@ -63,6 +63,14 @@ static bool shouldDeactivateTitleBar = true;
|
|||
|
||||
void* getUser32Function (const char*);
|
||||
|
||||
namespace WindowsAccessibility
|
||||
{
|
||||
void initialiseUIAWrapper();
|
||||
long getUiaRootObjectId();
|
||||
bool handleWmGetObject (AccessibilityHandler*, WPARAM, LPARAM, LRESULT*);
|
||||
void revokeUIAMapEntriesForWindow (HWND);
|
||||
}
|
||||
|
||||
#if JUCE_DEBUG
|
||||
int numActiveScopedDpiAwarenessDisablers = 0;
|
||||
bool isInScopedDPIAwarenessDisabler() { return numActiveScopedDpiAwarenessDisablers > 0; }
|
||||
|
|
@ -1372,12 +1380,14 @@ public:
|
|||
parentToAddTo (parent),
|
||||
currentRenderingEngine (softwareRenderingEngine)
|
||||
{
|
||||
// make sure that the UIA wrapper singleton is loaded
|
||||
WindowsAccessibility::initialiseUIAWrapper();
|
||||
|
||||
callFunctionIfNotLocked (&createWindowCallback, this);
|
||||
|
||||
setTitle (component.getName());
|
||||
updateShadower();
|
||||
|
||||
// make sure that the on-screen keyboard code is loaded
|
||||
OnScreenKeyboard::getInstance();
|
||||
|
||||
getNativeRealtimeModifiers = []
|
||||
|
|
@ -1397,13 +1407,15 @@ public:
|
|||
|
||||
~HWNDComponentPeer()
|
||||
{
|
||||
// do this first to avoid messages arriving for this window before it's destroyed
|
||||
JuceWindowIdentifier::setAsJUCEWindow (hwnd, false);
|
||||
|
||||
if (isAccessibilityActive)
|
||||
WindowsAccessibility::revokeUIAMapEntriesForWindow (hwnd);
|
||||
|
||||
shadower = nullptr;
|
||||
currentTouches.deleteAllTouchesForPeer (this);
|
||||
|
||||
// do this before the next bit to avoid messages arriving for this window
|
||||
// before it's destroyed
|
||||
JuceWindowIdentifier::setAsJUCEWindow (hwnd, false);
|
||||
|
||||
callFunctionIfNotLocked (&destroyWindowCallback, (void*) hwnd);
|
||||
|
||||
if (currentWindowIcon != nullptr)
|
||||
|
|
@ -1989,6 +2001,8 @@ private:
|
|||
double scaleFactor = 1.0;
|
||||
bool isInDPIChange = false;
|
||||
|
||||
bool isAccessibilityActive = false;
|
||||
|
||||
//==============================================================================
|
||||
static MultiTouchMapper<DWORD> currentTouches;
|
||||
|
||||
|
|
@ -3907,6 +3921,24 @@ private:
|
|||
case WM_GETDLGCODE:
|
||||
return DLGC_WANTALLKEYS;
|
||||
|
||||
case WM_GETOBJECT:
|
||||
{
|
||||
if (static_cast<long> (lParam) == WindowsAccessibility::getUiaRootObjectId())
|
||||
{
|
||||
if (auto* handler = component.getAccessibilityHandler())
|
||||
{
|
||||
LRESULT res = 0;
|
||||
|
||||
if (WindowsAccessibility::handleWmGetObject (handler, wParam, lParam, &res))
|
||||
{
|
||||
isAccessibilityActive = true;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ void PropertyPanel::init()
|
|||
|
||||
addAndMakeVisible (viewport);
|
||||
viewport.setViewedComponent (propertyHolderComponent = new PropertyHolderComponent());
|
||||
viewport.setFocusContainer (true);
|
||||
viewport.setFocusContainerType (FocusContainerType::keyboardFocusContainer);
|
||||
}
|
||||
|
||||
PropertyPanel::~PropertyPanel()
|
||||
|
|
|
|||
|
|
@ -424,6 +424,7 @@ void ComboBox::lookAndFeelChanged()
|
|||
|
||||
label->onTextChange = [this] { triggerAsyncUpdate(); };
|
||||
label->addMouseListener (this, false);
|
||||
label->setAccessible (labelEditableState == labelIsEditable);
|
||||
|
||||
label->setColour (Label::backgroundColourId, Colours::transparentBlack);
|
||||
label->setColour (Label::textColourId, findColour (ComboBox::textColourId));
|
||||
|
|
@ -641,4 +642,10 @@ void ComboBox::setSelectedItemIndex (const int index, const bool dontSendChange)
|
|||
void ComboBox::setSelectedId (const int newItemId, const bool dontSendChange) { setSelectedId (newItemId, dontSendChange ? dontSendNotification : sendNotification); }
|
||||
void ComboBox::setText (const String& newText, const bool dontSendChange) { setText (newText, dontSendChange ? dontSendNotification : sendNotification); }
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> ComboBox::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<ComboBoxAccessibilityHandler> (*this);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -419,6 +419,8 @@ public:
|
|||
void valueChanged (Value&) override;
|
||||
/** @internal */
|
||||
void parentHierarchyChanged() override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
// These methods' bool parameters have changed: see their new method signatures.
|
||||
JUCE_DEPRECATED (void clear (bool));
|
||||
|
|
|
|||
|
|
@ -80,4 +80,10 @@ void ImageComponent::paint (Graphics& g)
|
|||
g.drawImage (image, getLocalBounds().toFloat(), placement);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> ImageComponent::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::image);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ public:
|
|||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
Image image;
|
||||
|
|
|
|||
|
|
@ -105,8 +105,13 @@ void Label::setEditable (bool editOnSingleClick,
|
|||
editDoubleClick = editOnDoubleClick;
|
||||
lossOfFocusDiscardsChanges = lossOfFocusDiscards;
|
||||
|
||||
setWantsKeyboardFocus (editOnSingleClick || editOnDoubleClick);
|
||||
setFocusContainer (editOnSingleClick || editOnDoubleClick);
|
||||
const auto isKeybordFocusable = (editOnSingleClick || editOnDoubleClick);
|
||||
|
||||
setWantsKeyboardFocus (isKeybordFocusable);
|
||||
setFocusContainerType (isKeybordFocusable ? FocusContainerType::keyboardFocusContainer
|
||||
: FocusContainerType::none);
|
||||
|
||||
invalidateAccessibilityHandler();
|
||||
}
|
||||
|
||||
void Label::setJustificationType (Justification newJustification)
|
||||
|
|
@ -221,6 +226,7 @@ void Label::showEditor()
|
|||
if (editor == nullptr)
|
||||
{
|
||||
editor.reset (createEditorComponent());
|
||||
editor->setSize (10, 10);
|
||||
addAndMakeVisible (editor.get());
|
||||
editor->setText (getText(), false);
|
||||
editor->setKeyboardType (keyboardType);
|
||||
|
|
@ -351,7 +357,9 @@ void Label::mouseDoubleClick (const MouseEvent& e)
|
|||
if (editDoubleClick
|
||||
&& isEnabled()
|
||||
&& ! e.mods.isPopupMenu())
|
||||
{
|
||||
showEditor();
|
||||
}
|
||||
}
|
||||
|
||||
void Label::resized()
|
||||
|
|
@ -364,8 +372,11 @@ void Label::focusGained (FocusChangeType cause)
|
|||
{
|
||||
if (editSingleClick
|
||||
&& isEnabled()
|
||||
&& cause == focusChangedByTabKey)
|
||||
&& (cause == focusChangedByTabKey
|
||||
|| (cause == focusChangedDirectly && ! isCurrentlyModal())))
|
||||
{
|
||||
showEditor();
|
||||
}
|
||||
}
|
||||
|
||||
void Label::enablementChanged()
|
||||
|
|
@ -393,21 +404,45 @@ void Label::setMinimumHorizontalScale (const float newScale)
|
|||
class LabelKeyboardFocusTraverser : public KeyboardFocusTraverser
|
||||
{
|
||||
public:
|
||||
LabelKeyboardFocusTraverser() {}
|
||||
explicit LabelKeyboardFocusTraverser (Label& l) : owner (l) {}
|
||||
|
||||
Component* getDefaultComponent (Component* parent) override
|
||||
{
|
||||
auto getContainer = [&]
|
||||
{
|
||||
if (owner.getCurrentTextEditor() != nullptr && parent == &owner)
|
||||
return owner.findKeyboardFocusContainer();
|
||||
|
||||
return parent;
|
||||
};
|
||||
|
||||
if (auto* container = getContainer())
|
||||
KeyboardFocusTraverser::getDefaultComponent (container);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Component* getNextComponent (Component* c) override { return KeyboardFocusTraverser::getNextComponent (getComp (c)); }
|
||||
Component* getPreviousComponent (Component* c) override { return KeyboardFocusTraverser::getPreviousComponent (getComp (c)); }
|
||||
|
||||
static Component* getComp (Component* current)
|
||||
private:
|
||||
Component* getComp (Component* current) const
|
||||
{
|
||||
return dynamic_cast<TextEditor*> (current) != nullptr
|
||||
? current->getParentComponent() : current;
|
||||
if (auto* ed = owner.getCurrentTextEditor())
|
||||
if (current == ed)
|
||||
return current->getParentComponent();
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
Label& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LabelKeyboardFocusTraverser)
|
||||
};
|
||||
|
||||
KeyboardFocusTraverser* Label::createFocusTraverser()
|
||||
std::unique_ptr<ComponentTraverser> Label::createKeyboardFocusTraverser()
|
||||
{
|
||||
return new LabelKeyboardFocusTraverser();
|
||||
return std::make_unique<LabelKeyboardFocusTraverser> (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -480,4 +515,9 @@ void Label::textEditorFocusLost (TextEditor& ed)
|
|||
textEditorTextChanged (ed);
|
||||
}
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> Label::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<LabelAccessibilityHandler> (*this);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -323,7 +323,7 @@ protected:
|
|||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
/** @internal */
|
||||
KeyboardFocusTraverser* createFocusTraverser() override;
|
||||
std::unique_ptr<ComponentTraverser> createKeyboardFocusTraverser() override;
|
||||
/** @internal */
|
||||
void textEditorTextChanged (TextEditor&) override;
|
||||
/** @internal */
|
||||
|
|
@ -338,6 +338,8 @@ protected:
|
|||
void valueChanged (Value&) override;
|
||||
/** @internal */
|
||||
void callChangeListeners();
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -26,6 +26,34 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
template<typename RowHandlerType, typename RowComponent>
|
||||
static AccessibilityActions getListRowAccessibilityActions (RowHandlerType& handler, RowComponent& rowComponent)
|
||||
{
|
||||
auto onFocus = [&rowComponent]
|
||||
{
|
||||
rowComponent.owner.scrollToEnsureRowIsOnscreen (rowComponent.row);
|
||||
rowComponent.owner.selectRow (rowComponent.row);
|
||||
};
|
||||
|
||||
auto onPress = [&rowComponent, onFocus]
|
||||
{
|
||||
onFocus();
|
||||
rowComponent.owner.keyPressed (KeyPress (KeyPress::returnKey));
|
||||
};
|
||||
|
||||
auto onToggle = [&handler, &rowComponent, onFocus]
|
||||
{
|
||||
if (handler.getCurrentState().isSelected())
|
||||
rowComponent.owner.deselectRow (rowComponent.row);
|
||||
else
|
||||
onFocus();
|
||||
};
|
||||
|
||||
return AccessibilityActions().addAction (AccessibilityActionType::focus, std::move (onFocus))
|
||||
.addAction (AccessibilityActionType::press, std::move (onPress))
|
||||
.addAction (AccessibilityActionType::toggle, std::move (onToggle));
|
||||
}
|
||||
|
||||
class ListBox::RowComponent : public Component,
|
||||
public TooltipClient
|
||||
{
|
||||
|
|
@ -35,16 +63,28 @@ public:
|
|||
void paint (Graphics& g) override
|
||||
{
|
||||
if (auto* m = owner.getModel())
|
||||
m->paintListBoxItem (row, g, getWidth(), getHeight(), selected);
|
||||
m->paintListBoxItem (row, g, getWidth(), getHeight(), isSelected);
|
||||
}
|
||||
|
||||
void update (const int newRow, const bool nowSelected)
|
||||
{
|
||||
if (row != newRow || selected != nowSelected)
|
||||
const auto rowHasChanged = (row != newRow);
|
||||
const auto selectionHasChanged = (isSelected != nowSelected);
|
||||
|
||||
if (rowHasChanged || selectionHasChanged)
|
||||
{
|
||||
repaint();
|
||||
|
||||
if (rowHasChanged)
|
||||
row = newRow;
|
||||
selected = nowSelected;
|
||||
|
||||
if (selectionHasChanged)
|
||||
{
|
||||
isSelected = nowSelected;
|
||||
|
||||
if (auto* handler = getAccessibilityHandler())
|
||||
isSelected ? handler->grabFocus() : handler->giveAwayFocus();
|
||||
}
|
||||
}
|
||||
|
||||
if (auto* m = owner.getModel())
|
||||
|
|
@ -57,6 +97,9 @@ public:
|
|||
{
|
||||
addAndMakeVisible (customComponent.get());
|
||||
customComponent->setBounds (getLocalBounds());
|
||||
|
||||
if (customComponent->getAccessibilityHandler() != nullptr)
|
||||
invalidateAccessibilityHandler();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -85,7 +128,7 @@ public:
|
|||
|
||||
if (isEnabled())
|
||||
{
|
||||
if (owner.selectOnMouseDown && ! (selected || isInDragToScrollViewport()))
|
||||
if (owner.selectOnMouseDown && ! (isSelected || isInDragToScrollViewport()))
|
||||
performSelection (e, false);
|
||||
else
|
||||
selectRowOnMouseUp = true;
|
||||
|
|
@ -150,10 +193,62 @@ public:
|
|||
return {};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class RowAccessibilityHandler : public AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
explicit RowAccessibilityHandler (RowComponent& rowComponentToWrap)
|
||||
: AccessibilityHandler (rowComponentToWrap,
|
||||
AccessibilityRole::listItem,
|
||||
getListRowAccessibilityActions (*this, rowComponentToWrap)),
|
||||
rowComponent (rowComponentToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
String getTitle() const override
|
||||
{
|
||||
if (auto* m = rowComponent.owner.getModel())
|
||||
return m->getNameForRow (rowComponent.row);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
AccessibleState getCurrentState() const override
|
||||
{
|
||||
if (auto* m = rowComponent.owner.getModel())
|
||||
if (rowComponent.row >= m->getNumRows())
|
||||
return AccessibleState().withIgnored();
|
||||
|
||||
auto state = AccessibilityHandler::getCurrentState().withAccessibleOffscreen();
|
||||
|
||||
if (rowComponent.owner.multipleSelection)
|
||||
state = state.withMultiSelectable();
|
||||
else
|
||||
state = state.withSelectable();
|
||||
|
||||
if (rowComponent.isSelected)
|
||||
state = state.withSelected();
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
private:
|
||||
RowComponent& rowComponent;
|
||||
};
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
|
||||
{
|
||||
if (customComponent != nullptr && customComponent->getAccessibilityHandler() != nullptr)
|
||||
return nullptr;
|
||||
|
||||
return std::make_unique<RowAccessibilityHandler> (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ListBox& owner;
|
||||
std::unique_ptr<Component> customComponent;
|
||||
int row = -1;
|
||||
bool selected = false, isDragging = false, isDraggingToScroll = false, selectRowOnMouseUp = false;
|
||||
bool isSelected = false, isDragging = false, isDraggingToScroll = false, selectRowOnMouseUp = false;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RowComponent)
|
||||
};
|
||||
|
|
@ -166,10 +261,13 @@ public:
|
|||
ListViewport (ListBox& lb) : owner (lb)
|
||||
{
|
||||
setWantsKeyboardFocus (false);
|
||||
setAccessible (false);
|
||||
|
||||
auto content = new Component();
|
||||
setViewedComponent (content);
|
||||
auto content = std::make_unique<Component>();
|
||||
content->setWantsKeyboardFocus (false);
|
||||
content->setAccessible (false);
|
||||
|
||||
setViewedComponent (content.release());
|
||||
}
|
||||
|
||||
RowComponent* getComponentForRow (const int row) const noexcept
|
||||
|
|
@ -233,13 +331,12 @@ public:
|
|||
auto y = getViewPositionY();
|
||||
auto w = content.getWidth();
|
||||
|
||||
const int numNeeded = 2 + getMaximumVisibleHeight() / rowH;
|
||||
const int numNeeded = 4 + getMaximumVisibleHeight() / rowH;
|
||||
rows.removeRange (numNeeded, rows.size());
|
||||
|
||||
while (numNeeded > rows.size())
|
||||
{
|
||||
auto newRow = new RowComponent (owner);
|
||||
rows.add (newRow);
|
||||
auto* newRow = rows.add (new RowComponent (owner));
|
||||
content.addAndMakeVisible (newRow);
|
||||
}
|
||||
|
||||
|
|
@ -247,9 +344,11 @@ public:
|
|||
firstWholeIndex = (y + rowH - 1) / rowH;
|
||||
lastWholeIndex = (y + getMaximumVisibleHeight() - 1) / rowH;
|
||||
|
||||
auto startIndex = jmax (0, firstIndex - 1);
|
||||
|
||||
for (int i = 0; i < numNeeded; ++i)
|
||||
{
|
||||
const int row = i + firstIndex;
|
||||
const int row = i + startIndex;
|
||||
|
||||
if (auto* rowComp = getComponentForRow (row))
|
||||
{
|
||||
|
|
@ -379,8 +478,9 @@ ListBox::ListBox (const String& name, ListBoxModel* const m)
|
|||
viewport.reset (new ListViewport (*this));
|
||||
addAndMakeVisible (viewport.get());
|
||||
|
||||
ListBox::setWantsKeyboardFocus (true);
|
||||
ListBox::colourChanged();
|
||||
setWantsKeyboardFocus (true);
|
||||
setFocusContainerType (FocusContainerType::focusContainer);
|
||||
colourChanged();
|
||||
}
|
||||
|
||||
ListBox::~ListBox()
|
||||
|
|
@ -938,6 +1038,11 @@ void ListBox::startDragAndDrop (const MouseEvent& e, const SparseSet<int>& rowsT
|
|||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> ListBox::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::list);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Component* ListBoxModel::refreshComponentForRow (int, bool, Component* existingComponentToUpdate)
|
||||
{
|
||||
|
|
@ -946,6 +1051,7 @@ Component* ListBoxModel::refreshComponentForRow (int, bool, Component* existingC
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
String ListBoxModel::getNameForRow (int rowNumber) { return "Row " + String (rowNumber + 1); }
|
||||
void ListBoxModel::listBoxItemClicked (int, const MouseEvent&) {}
|
||||
void ListBoxModel::listBoxItemDoubleClicked (int, const MouseEvent&) {}
|
||||
void ListBoxModel::backgroundClicked (const MouseEvent&) {}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,12 @@ public:
|
|||
virtual Component* refreshComponentForRow (int rowNumber, bool isRowSelected,
|
||||
Component* existingComponentToUpdate);
|
||||
|
||||
/** This can be overridden to return a name for the specified row.
|
||||
|
||||
By default this will just return a string containing the row number.
|
||||
*/
|
||||
virtual String getNameForRow (int rowNumber);
|
||||
|
||||
/** This can be overridden to react to the user clicking on a row.
|
||||
@see listBoxItemDoubleClicked
|
||||
*/
|
||||
|
|
@ -565,6 +571,8 @@ public:
|
|||
/** @internal */
|
||||
void startDragAndDrop (const MouseEvent&, const SparseSet<int>& rowsToDrag,
|
||||
const var& dragDescription, bool allowDraggingToOtherWindows);
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue