Browse Source

Accessibility: Added VoiceOver (macOS) and Narrator (Windows) accessibility screen reader support to juce_gui_basics

v6.1.6
ed 4 years ago
parent
commit
ec990202b1
100 changed files with 8833 additions and 610 deletions
  1. +26
    -0
      BREAKING-CHANGES.txt
  2. +51
    -0
      docs/Accessibility.md
  3. +10
    -0
      modules/juce_core/native/juce_mac_ObjCHelpers.h
  4. +1
    -0
      modules/juce_core/system/juce_StandardHeader.h
  5. +28
    -0
      modules/juce_graphics/native/juce_mac_CoreGraphicsHelpers.h
  6. +119
    -0
      modules/juce_gui_basics/accessibility/enums/juce_AccessibilityActions.h
  7. +75
    -0
      modules/juce_gui_basics/accessibility/enums/juce_AccessibilityEvent.h
  8. +70
    -0
      modules/juce_gui_basics/accessibility/enums/juce_AccessibilityRole.h
  9. +61
    -0
      modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityCellInterface.h
  10. +54
    -0
      modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityTableInterface.h
  11. +78
    -0
      modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityTextInterface.h
  12. +222
    -0
      modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityValueInterface.h
  13. +346
    -0
      modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp
  14. +325
    -0
      modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.h
  15. +227
    -0
      modules/juce_gui_basics/accessibility/juce_AccessibilityState.h
  16. +96
    -0
      modules/juce_gui_basics/accessibility/widget_handlers/juce_ButtonAccessibilityHandler.h
  17. +66
    -0
      modules/juce_gui_basics/accessibility/widget_handlers/juce_ComboBoxAccessibilityHandler.h
  18. +62
    -0
      modules/juce_gui_basics/accessibility/widget_handlers/juce_LabelAccessibilityHandler.h
  19. +100
    -0
      modules/juce_gui_basics/accessibility/widget_handlers/juce_SliderAccessibilityHandler.h
  20. +83
    -0
      modules/juce_gui_basics/accessibility/widget_handlers/juce_TableListBoxAccessibilityHandler.h
  21. +108
    -0
      modules/juce_gui_basics/accessibility/widget_handlers/juce_TextEditorAccessibilityHandler.h
  22. +79
    -0
      modules/juce_gui_basics/accessibility/widget_handlers/juce_TreeViewAccessibilityHandler.h
  23. +13
    -0
      modules/juce_gui_basics/buttons/juce_Button.cpp
  24. +2
    -0
      modules/juce_gui_basics/buttons/juce_Button.h
  25. +236
    -119
      modules/juce_gui_basics/components/juce_Component.cpp
  26. +298
    -117
      modules/juce_gui_basics/components/juce_Component.h
  27. +72
    -0
      modules/juce_gui_basics/components/juce_ComponentTraverser.h
  28. +359
    -0
      modules/juce_gui_basics/components/juce_FocusTraverser.cpp
  29. +93
    -0
      modules/juce_gui_basics/components/juce_FocusTraverser.h
  30. +6
    -0
      modules/juce_gui_basics/drawables/juce_Drawable.cpp
  31. +2
    -0
      modules/juce_gui_basics/drawables/juce_Drawable.h
  32. +6
    -0
      modules/juce_gui_basics/drawables/juce_DrawableImage.cpp
  33. +2
    -0
      modules/juce_gui_basics/drawables/juce_DrawableImage.h
  34. +21
    -0
      modules/juce_gui_basics/drawables/juce_DrawableText.cpp
  35. +2
    -0
      modules/juce_gui_basics/drawables/juce_DrawableText.h
  36. +6
    -0
      modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp
  37. +2
    -0
      modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h
  38. +12
    -1
      modules/juce_gui_basics/filebrowser/juce_FileListComponent.cpp
  39. +1
    -0
      modules/juce_gui_basics/filebrowser/juce_FileListComponent.h
  40. +5
    -0
      modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp
  41. +2
    -2
      modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp
  42. +1
    -1
      modules/juce_gui_basics/filebrowser/juce_FilenameComponent.h
  43. +6
    -0
      modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.cpp
  44. +2
    -0
      modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.h
  45. +11
    -0
      modules/juce_gui_basics/juce_gui_basics.cpp
  46. +21
    -1
      modules/juce_gui_basics/juce_gui_basics.h
  47. +211
    -68
      modules/juce_gui_basics/keyboard/juce_KeyboardFocusTraverser.cpp
  48. +35
    -38
      modules/juce_gui_basics/keyboard/juce_KeyboardFocusTraverser.h
  49. +1
    -0
      modules/juce_gui_basics/layout/juce_ComponentAnimator.cpp
  50. +6
    -0
      modules/juce_gui_basics/layout/juce_ConcertinaPanel.cpp
  51. +4
    -0
      modules/juce_gui_basics/layout/juce_ConcertinaPanel.h
  52. +6
    -0
      modules/juce_gui_basics/layout/juce_GroupComponent.cpp
  53. +2
    -0
      modules/juce_gui_basics/layout/juce_GroupComponent.h
  54. +43
    -1
      modules/juce_gui_basics/layout/juce_ScrollBar.cpp
  55. +7
    -0
      modules/juce_gui_basics/layout/juce_ScrollBar.h
  56. +6
    -0
      modules/juce_gui_basics/layout/juce_SidePanel.cpp
  57. +2
    -0
      modules/juce_gui_basics/layout/juce_SidePanel.h
  58. +7
    -1
      modules/juce_gui_basics/layout/juce_TabbedButtonBar.cpp
  59. +2
    -0
      modules/juce_gui_basics/layout/juce_TabbedButtonBar.h
  60. +6
    -0
      modules/juce_gui_basics/layout/juce_TabbedComponent.cpp
  61. +2
    -0
      modules/juce_gui_basics/layout/juce_TabbedComponent.h
  62. +6
    -0
      modules/juce_gui_basics/menus/juce_BurgerMenuComponent.cpp
  63. +2
    -0
      modules/juce_gui_basics/menus/juce_BurgerMenuComponent.h
  64. +180
    -80
      modules/juce_gui_basics/menus/juce_MenuBarComponent.cpp
  65. +19
    -11
      modules/juce_gui_basics/menus/juce_MenuBarComponent.h
  66. +143
    -37
      modules/juce_gui_basics/menus/juce_PopupMenu.cpp
  67. +4
    -4
      modules/juce_gui_basics/misc/juce_BubbleComponent.h
  68. +1
    -0
      modules/juce_gui_basics/misc/juce_DropShadower.cpp
  69. +6
    -0
      modules/juce_gui_basics/misc/juce_JUCESplashScreen.cpp
  70. +4
    -0
      modules/juce_gui_basics/misc/juce_JUCESplashScreen.h
  71. +1078
    -0
      modules/juce_gui_basics/native/accessibility/juce_mac_Accessibility.mm
  72. +270
    -0
      modules/juce_gui_basics/native/accessibility/juce_win32_Accessibility.cpp
  73. +558
    -0
      modules/juce_gui_basics/native/accessibility/juce_win32_AccessibilityElement.cpp
  74. +80
    -0
      modules/juce_gui_basics/native/accessibility/juce_win32_AccessibilityElement.h
  75. +86
    -0
      modules/juce_gui_basics/native/accessibility/juce_win32_UIAExpandCollapseProvider.h
  76. +101
    -0
      modules/juce_gui_basics/native/accessibility/juce_win32_UIAGridItemProvider.h
  77. +90
    -0
      modules/juce_gui_basics/native/accessibility/juce_win32_UIAGridProvider.h
  78. +103
    -0
      modules/juce_gui_basics/native/accessibility/juce_win32_UIAHelpers.h
  79. +62
    -0
      modules/juce_gui_basics/native/accessibility/juce_win32_UIAInvokeProvider.h
  80. +58
    -0
      modules/juce_gui_basics/native/accessibility/juce_win32_UIAProviderBase.h
  81. +43
    -0
      modules/juce_gui_basics/native/accessibility/juce_win32_UIAProviders.h
  82. +140
    -0
      modules/juce_gui_basics/native/accessibility/juce_win32_UIARangeValueProvider.h
  83. +252
    -0
      modules/juce_gui_basics/native/accessibility/juce_win32_UIASelectionProvider.h
  84. +664
    -0
      modules/juce_gui_basics/native/accessibility/juce_win32_UIATextProvider.h
  85. +80
    -0
      modules/juce_gui_basics/native/accessibility/juce_win32_UIAToggleProvider.h
  86. +125
    -0
      modules/juce_gui_basics/native/accessibility/juce_win32_UIATransformProvider.h
  87. +121
    -0
      modules/juce_gui_basics/native/accessibility/juce_win32_UIAValueProvider.h
  88. +197
    -0
      modules/juce_gui_basics/native/accessibility/juce_win32_UIAWindowProvider.h
  89. +158
    -0
      modules/juce_gui_basics/native/accessibility/juce_win32_WindowsUIAWrapper.h
  90. +157
    -97
      modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm
  91. +37
    -5
      modules/juce_gui_basics/native/juce_win32_Windowing.cpp
  92. +1
    -1
      modules/juce_gui_basics/properties/juce_PropertyPanel.cpp
  93. +7
    -0
      modules/juce_gui_basics/widgets/juce_ComboBox.cpp
  94. +2
    -0
      modules/juce_gui_basics/widgets/juce_ComboBox.h
  95. +6
    -0
      modules/juce_gui_basics/widgets/juce_ImageComponent.cpp
  96. +2
    -0
      modules/juce_gui_basics/widgets/juce_ImageComponent.h
  97. +51
    -11
      modules/juce_gui_basics/widgets/juce_Label.cpp
  98. +3
    -1
      modules/juce_gui_basics/widgets/juce_Label.h
  99. +120
    -14
      modules/juce_gui_basics/widgets/juce_ListBox.cpp
  100. +8
    -0
      modules/juce_gui_basics/widgets/juce_ListBox.h

+ 26
- 0
BREAKING-CHANGES.txt View File

@@ -4,6 +4,32 @@ JUCE breaking changes
Develop 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 Change
------ ------
PluginDescription::uid has been deprecated and replaced with a new 'uniqueId' PluginDescription::uid has been deprecated and replaced with a new 'uniqueId'


+ 51
- 0
docs/Accessibility.md View 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


+ 10
- 0
modules/juce_core/native/juce_mac_ObjCHelpers.h View File

@@ -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) inline String nsStringToJuce (NSString* s)
{ {
return CharPointer_UTF8 ([s UTF8String]); return CharPointer_UTF8 ([s UTF8String]);


+ 1
- 0
modules/juce_core/system/juce_StandardHeader.h View File

@@ -60,6 +60,7 @@
#include <numeric> #include <numeric>
#include <queue> #include <queue>
#include <sstream> #include <sstream>
#include <typeindex>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>


+ 28
- 0
modules/juce_graphics/native/juce_mac_CoreGraphicsHelpers.h View File

@@ -64,6 +64,34 @@ namespace
{ {
return CGPointMake ((CGFloat) p.x, (CGFloat) p.y); 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); CGImageRef juce_createCoreGraphicsImage (const Image&, CGColorSpaceRef, bool mustOutliveSource);


+ 119
- 0
modules/juce_gui_basics/accessibility/enums/juce_AccessibilityActions.h View File

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

+ 75
- 0
modules/juce_gui_basics/accessibility/enums/juce_AccessibilityEvent.h View File

@@ -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
};
}

+ 70
- 0
modules/juce_gui_basics/accessibility/enums/juce_AccessibilityRole.h View File

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

+ 61
- 0
modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityCellInterface.h View File

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

+ 54
- 0
modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityTableInterface.h View File

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

+ 78
- 0
modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityTextInterface.h View File

@@ -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

+ 222
- 0
modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityValueInterface.h View File

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

+ 346
- 0
modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp View File

@@ -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

+ 325
- 0
modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.h View File

@@ -0,0 +1,325 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
class AccessibilityNativeHandle;
/** Base class for accessible Components.
This class wraps a Component and provides methods that allow an accessibility client,
such as VoiceOver on macOS, or Narrator on Windows, to control it.
It handles hierarchical navigation, properties, state, and various interfaces.
@tags{Accessibility}
*/
class JUCE_API AccessibilityHandler
{
public:
/** Utility struct which holds one or more accessibility interfaces.
The main purpose of this class is to provide convenience constructors from each
of the four types of accessibility interface.
*/
struct JUCE_API Interfaces
{
Interfaces() = default;
Interfaces (std::unique_ptr<AccessibilityValueInterface> ptr) : value (std::move (ptr)) {}
Interfaces (std::unique_ptr<AccessibilityTextInterface> ptr) : text (std::move (ptr)) {}
Interfaces (std::unique_ptr<AccessibilityTableInterface> ptr) : table (std::move (ptr)) {}
Interfaces (std::unique_ptr<AccessibilityCellInterface> ptr) : cell (std::move (ptr)) {}
Interfaces (std::unique_ptr<AccessibilityValueInterface> valueIn,
std::unique_ptr<AccessibilityTextInterface> textIn,
std::unique_ptr<AccessibilityTableInterface> tableIn,
std::unique_ptr<AccessibilityCellInterface> cellIn)
: value (std::move (valueIn)),
text (std::move (textIn)),
table (std::move (tableIn)),
cell (std::move (cellIn))
{
}
std::unique_ptr<AccessibilityValueInterface> value;
std::unique_ptr<AccessibilityTextInterface> text;
std::unique_ptr<AccessibilityTableInterface> table;
std::unique_ptr<AccessibilityCellInterface> cell;
};
/** Constructor.
This will create a AccessibilityHandler which wraps the provided Component and makes
it visible to accessibility clients. You must also specify a role for the UI element
from the `AccessibilityRole` list which best describes it.
To enable users to interact with the UI element you should provide the set of supported
actions and their associated callbacks via the `accessibilityActions` parameter.
For UI elements that support more complex interaction the value, text, table, and cell
interfaces should be implemented as required and passed as the final argument of this
constructor. See the documentation of these classes for more information about the
types of control they represent and which methods need to be implemented.
*/
AccessibilityHandler (Component& componentToWrap,
AccessibilityRole accessibilityRole,
AccessibilityActions actions = {},
Interfaces interfaces = {});
/** Destructor. */
virtual ~AccessibilityHandler();
//==============================================================================
/** Returns the Component that this handler represents. */
const Component& getComponent() const noexcept { return component; }
/** Returns the Component that this handler represents. */
Component& getComponent() noexcept { return component; }
//==============================================================================
/** The type of UI element that this accessibility handler represents.
@see AccessibilityRole
*/
AccessibilityRole getRole() const noexcept { return role; }
/** The title of the UI element.
This will be read out by the system and should be concise, preferably matching
the visible title of the UI element (if any). For example, this might be the
text of a button or a simple label.
The default implementation will call `Component::getTitle()`, but you can override
this to return a different string if required.
If neither a name nor a description is provided then the UI element may be
ignored by accessibility clients.
This must be a localised string.
*/
virtual String getTitle() const { return component.getTitle(); }
/** A short description of the UI element.
This may be read out by the system. It should not include the type of the UI
element and should ideally be a single word, for example "Open" for a button
that opens a window.
The default implementation will call `Component::getDescription()`, but you
can override this to return a different string if required.
If neither a name nor a description is provided then the UI element may be
ignored by accessibility clients.
This must be a localised string.
*/
virtual String getDescription() const { return component.getDescription(); }
/** Some help text for the UI element (if required).
This may be read out by the system. This string functions in a similar way to
a tooltip, for example "Click to open window." for a button which opens a window.
The default implementation will call `Component::getHelpText()`, but you can
override this to return a different string if required.
This must be a localised string.
*/
virtual String getHelp() const { return component.getHelpText(); }
/** Returns the current state of the UI element.
The default implementation of this method will set the focusable flag and, if
this UI element is currently focused, will also set the focused flag.
*/
virtual AccessibleState getCurrentState() const;
/** Returns true if this UI element should be ignored by accessibility clients. */
bool isIgnored() const;
//==============================================================================
/** Returns 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
- 0
modules/juce_gui_basics/accessibility/juce_AccessibilityState.h View File

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

+ 96
- 0
modules/juce_gui_basics/accessibility/widget_handlers/juce_ButtonAccessibilityHandler.h View File

@@ -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

+ 66
- 0
modules/juce_gui_basics/accessibility/widget_handlers/juce_ComboBoxAccessibilityHandler.h View File

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

+ 62
- 0
modules/juce_gui_basics/accessibility/widget_handlers/juce_LabelAccessibilityHandler.h View File

@@ -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

+ 100
- 0
modules/juce_gui_basics/accessibility/widget_handlers/juce_SliderAccessibilityHandler.h View File

@@ -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

+ 83
- 0
modules/juce_gui_basics/accessibility/widget_handlers/juce_TableListBoxAccessibilityHandler.h View File

@@ -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

+ 108
- 0
modules/juce_gui_basics/accessibility/widget_handlers/juce_TextEditorAccessibilityHandler.h View File

@@ -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

+ 79
- 0
modules/juce_gui_basics/accessibility/widget_handlers/juce_TreeViewAccessibilityHandler.h View File

@@ -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

+ 13
- 0
modules/juce_gui_basics/buttons/juce_Button.cpp View File

@@ -188,6 +188,9 @@ void Button::setToggleState (bool shouldBeOn, NotificationType clickNotification
sendStateMessage(); sendStateMessage();
else else
buttonStateChanged(); 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 // it is that this button represents, and the button will update its state to reflect this
// in the applicationCommandListChanged() method. // in the applicationCommandListChanged() method.
jassert (commandManagerToUse == nullptr || ! clickTogglesState); jassert (commandManagerToUse == nullptr || ! clickTogglesState);
invalidateAccessibilityHandler();
} }
bool Button::getClickingTogglesState() const noexcept bool Button::getClickingTogglesState() const noexcept
@@ -220,6 +225,8 @@ void Button::setRadioGroupId (int newGroupId, NotificationType notification)
if (lastToggleState) if (lastToggleState)
turnOffOtherButtonsInGroup (notification, notification); 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 } // namespace juce

+ 2
- 0
modules/juce_gui_basics/buttons/juce_Button.h View File

@@ -470,6 +470,8 @@ protected:
void focusLost (FocusChangeType) override; void focusLost (FocusChangeType) override;
/** @internal */ /** @internal */
void enablementChanged() override; void enablementChanged() override;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
private: private:
//============================================================================== //==============================================================================


+ 236
- 119
modules/juce_gui_basics/components/juce_Component.cpp View File

@@ -482,15 +482,15 @@ Component::~Component()
componentListeners.call ([this] (ComponentListener& l) { l.componentBeingDeleted (*this); }); componentListeners.call ([this] (ComponentListener& l) { l.componentBeingDeleted (*this); });
masterReference.clear();
while (childComponentList.size() > 0) while (childComponentList.size() > 0)
removeChildComponent (childComponentList.size() - 1, false, true); removeChildComponent (childComponentList.size() - 1, false, true);
masterReference.clear();
if (parentComponent != nullptr) if (parentComponent != nullptr)
parentComponent->removeChildComponent (parentComponent->childComponentList.indexOf (this), true, false); parentComponent->removeChildComponent (parentComponent->childComponentList.indexOf (this), true, false);
else if (hasKeyboardFocus (true))
giveAwayFocus (currentlyFocusedComponent != this);
else
giveAwayKeyboardFocusInternal (isParentOf (currentlyFocusedComponent));
if (flags.hasHeavyweightPeerFlag) if (flags.hasHeavyweightPeerFlag)
removeFromDesktop(); removeFromDesktop();
@@ -551,8 +551,8 @@ void Component::setVisible (bool shouldBeVisible)
if (parentComponent != nullptr) if (parentComponent != nullptr)
parentComponent->grabKeyboardFocus(); 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(); repaint();
internalHierarchyChanged(); internalHierarchyChanged();
if (auto* handler = getAccessibilityHandler())
notifyAccessibilityEventInternal (*handler, InternalAccessibilityEvent::windowOpened);
} }
} }
} }
@@ -716,6 +719,9 @@ void Component::removeFromDesktop()
if (flags.hasHeavyweightPeerFlag) if (flags.hasHeavyweightPeerFlag)
{ {
if (auto* handler = getAccessibilityHandler())
notifyAccessibilityEventInternal (*handler, InternalAccessibilityEvent::windowClosed);
ComponentHelpers::releaseAllCachedImageResources (*this); ComponentHelpers::releaseAllCachedImageResources (*this);
auto* peer = ComponentPeer::getPeerFor (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 // 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. // 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()) if (auto* peer = getPeer())
{ {
peer->toFront (setAsForeground);
peer->toFront (shouldGrabKeyboardFocus);
if (setAsForeground && ! hasKeyboardFocus (true))
if (shouldGrabKeyboardFocus && ! hasKeyboardFocus (true))
grabKeyboardFocus(); grabKeyboardFocus();
} }
} }
@@ -926,7 +932,7 @@ void Component::toFront (bool setAsForeground)
} }
} }
if (setAsForeground)
if (shouldGrabKeyboardFocus)
{ {
internalBroughtToFront(); 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. // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN
auto* child = childComponentList [index];
if (child != nullptr)
if (auto* child = childComponentList [index])
{ {
sendParentEvents = sendParentEvents && child->isShowing(); sendParentEvents = sendParentEvents && child->isShowing();
@@ -1518,23 +1522,19 @@ Component* Component::removeChildComponent (int index, bool sendParentEvents, bo
ComponentHelpers::releaseAllCachedImageResources (*child); ComponentHelpers::releaseAllCachedImageResources (*child);
// (NB: there are obscure situations where child->isShowing() = false, but it still has the focus) // (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))
{ {
if (sendParentEvents)
{
const WeakReference<Component> thisPointer (this);
const WeakReference<Component> safeThis (this);
giveAwayFocus (sendChildEvents || currentlyFocusedComponent != child);
child->giveAwayKeyboardFocusInternal (sendChildEvents || currentlyFocusedComponent != child);
if (thisPointer == nullptr)
if (sendParentEvents)
{
if (safeThis == nullptr)
return child; return child;
grabKeyboardFocus(); grabKeyboardFocus();
} }
else
{
giveAwayFocus (sendChildEvents || currentlyFocusedComponent != child);
}
} }
if (sendChildEvents) if (sendChildEvents)
@@ -1542,9 +1542,11 @@ Component* Component::removeChildComponent (int index, bool sendParentEvents, bo
if (sendParentEvents) if (sendParentEvents)
internalChildrenChanged(); internalChildrenChanged();
return child;
} }
return child;
return nullptr;
} }
//============================================================================== //==============================================================================
@@ -1656,6 +1658,10 @@ void Component::internalHierarchyChanged()
i = jmin (i, childComponentList.size()); 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) if (! flags.dontFocusOnMouseClickFlag)
{ {
grabFocusInternal (focusChangedByMouseClick, true);
grabKeyboardFocusInternal (focusChangedByMouseClick, true);
if (checker.shouldBailOut()) if (checker.shouldBailOut())
return; return;
@@ -2644,36 +2650,48 @@ void Component::focusGained (FocusChangeType) {}
void Component::focusLost (FocusChangeType) {} void Component::focusLost (FocusChangeType) {}
void Component::focusOfChildComponentChanged (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); focusGained (cause);
if (safePointer != nullptr) 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); const WeakReference<Component> safePointer (this);
focusLost (cause); focusLost (cause);
if (safePointer != nullptr) 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); focusOfChildComponentChanged (cause);
@@ -2682,12 +2700,12 @@ void Component::internalChildFocusChange (FocusChangeType cause, const WeakRefer
} }
if (parentComponent != nullptr) if (parentComponent != nullptr)
parentComponent->internalChildFocusChange (cause, WeakReference<Component> (parentComponent));
parentComponent->internalChildKeyboardFocusChange (cause, parentComponent);
} }
void Component::setWantsKeyboardFocus (bool wantsFocus) noexcept void Component::setWantsKeyboardFocus (bool wantsFocus) noexcept
{ {
flags.wantsFocusFlag = wantsFocus;
flags.wantsKeyboardFocusFlag = wantsFocus;
} }
void Component::setMouseClickGrabsKeyboardFocus (bool shouldGrabFocus) void Component::setMouseClickGrabsKeyboardFocus (bool shouldGrabFocus)
@@ -2702,12 +2720,15 @@ bool Component::getMouseClickGrabsKeyboardFocus() const noexcept
bool Component::getWantsKeyboardFocus() 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 bool Component::isFocusContainer() const noexcept
@@ -2715,6 +2736,35 @@ bool Component::isFocusContainer() const noexcept
return flags.isFocusContainerFlag; 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"); static const Identifier juce_explicitFocusOrderId ("_jexfo");
int Component::getExplicitFocusOrder() const int Component::getExplicitFocusOrder() const
@@ -2727,85 +2777,78 @@ void Component::setExplicitFocusOrder (int newFocusOrderIndex)
properties.set (juce_explicitFocusOrderId, newFocusOrderIndex); properties.set (juce_explicitFocusOrderId, newFocusOrderIndex);
} }
KeyboardFocusTraverser* Component::createFocusTraverser()
std::unique_ptr<ComponentTraverser> Component::createFocusTraverser()
{ {
if (flags.isFocusContainerFlag || parentComponent == nullptr) if (flags.isFocusContainerFlag || parentComponent == nullptr)
return new KeyboardFocusTraverser();
return std::make_unique<FocusTraverser>();
return parentComponent->createFocusTraverser(); 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) void Component::takeKeyboardFocus (FocusChangeType cause)
{ {
// give the focus to this component
if (currentlyFocusedComponent != this)
if (currentlyFocusedComponent == this)
return;
if (auto* peer = getPeer())
{ {
// get the focus onto our desktop window
if (auto* peer = getPeer())
{
const WeakReference<Component> safePointer (this);
peer->grabFocus();
const WeakReference<Component> safePointer (this);
peer->grabFocus();
if (peer->isFocused() && currentlyFocusedComponent != this)
{
WeakReference<Component> componentLosingFocus (currentlyFocusedComponent);
currentlyFocusedComponent = this;
if (! peer->isFocused() || currentlyFocusedComponent == this)
return;
Desktop::getInstance().triggerFocusCallback();
WeakReference<Component> componentLosingFocus (currentlyFocusedComponent);
currentlyFocusedComponent = this;
// 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);
Desktop::getInstance().triggerFocusCallback();
if (currentlyFocusedComponent == this)
internalFocusGain (cause, safePointer);
}
}
// 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->internalKeyboardFocusLoss (cause);
if (currentlyFocusedComponent == this)
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))
{
takeKeyboardFocus (cause);
}
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 (! isShowing())
return;
if (traverser != nullptr)
{
auto* defaultComp = traverser->getDefaultComponent (this);
traverser.reset();
if (flags.wantsKeyboardFocusFlag
&& (isEnabled() || parentComponent == nullptr))
{
takeKeyboardFocus (cause);
return;
}
if (defaultComp != nullptr)
{
defaultComp->grabFocusInternal (cause, false);
return;
}
}
if (isParentOf (currentlyFocusedComponent) && currentlyFocusedComponent->isShowing())
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 (auto traverser = createKeyboardFocusTraverser())
{
if (auto* defaultComp = traverser->getDefaultComponent (this))
{
defaultComp->grabKeyboardFocusInternal (cause, false);
return;
} }
} }
// if no children want it and we're allowed to try our parent comp,
// then pass up to parent, which will try our siblings.
if (canTryParent && parentComponent != nullptr)
parentComponent->grabKeyboardFocusInternal (cause, true);
} }
void Component::grabKeyboardFocus() 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. // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe.
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED 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! // 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 // 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()); 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) void Component::moveKeyboardFocusToSibling (bool moveToNext)
{ {
// if component methods are being called from threads other than the message // if component methods are being called from threads other than the message
@@ -2831,15 +2899,27 @@ void Component::moveKeyboardFocusToSibling (bool moveToNext)
if (parentComponent != nullptr) 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 (auto* focusContainer = findKeyboardFocusContainer())
{
auto allFocusableComponents = traverser->getAllComponents (focusContainer);
if (! allFocusableComponents.empty())
return moveToNext ? allFocusableComponents.front()
: allFocusableComponents.back();
}
return nullptr;
};
if (nextComp != nullptr)
if (auto* nextComp = findComponentToFocus())
{ {
if (nextComp->isCurrentlyBlockedByAnotherModalComponent()) if (nextComp->isCurrentlyBlockedByAnotherModalComponent())
{ {
@@ -2850,7 +2930,7 @@ void Component::moveKeyboardFocusToSibling (bool moveToNext)
return; return;
} }
nextComp->grabFocusInternal (focusChangedByTabKey, true);
nextComp->grabKeyboardFocusInternal (focusChangedByTabKey, true);
return; return;
} }
} }
@@ -2872,19 +2952,8 @@ Component* JUCE_CALLTYPE Component::getCurrentlyFocusedComponent() noexcept
void JUCE_CALLTYPE Component::unfocusAllComponents() 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; 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 } // namespace juce

+ 298
- 117
modules/juce_gui_basics/components/juce_Component.h View File

@@ -73,7 +73,7 @@ public:
/** Returns the name of this component. /** Returns the name of this component.
@see setName @see setName
*/ */
const String& getName() const noexcept { return componentName; }
String getName() const noexcept { return componentName; }
/** Sets the name of this component. /** Sets the name of this component.
@@ -87,7 +87,7 @@ public:
/** Returns the ID string that was set by setComponentID(). /** Returns the ID string that was set by setComponentID().
@see setComponentID, findChildWithID @see setComponentID, findChildWithID
*/ */
const String& getComponentID() const noexcept { return componentID; }
String getComponentID() const noexcept { return componentID; }
/** Sets the component's ID string. /** Sets the component's ID string.
You can retrieve the ID using getComponentID(). 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 then they will still be kept in front of this one (unless of course this
one is also 'always-on-top'). 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 @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. /** Changes this component's z-order to be at the back of all its siblings.
@@ -1203,55 +1204,156 @@ public:
bool isBroughtToFrontOnMouseClick() const noexcept; 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. focus, but this method can be used to turn this on.
See the grabKeyboardFocus() method for details about the way a component See the grabKeyboardFocus() method for details about the way a component
is chosen to receive the focus. is chosen to receive the focus.
@see grabKeyboardFocus, getWantsKeyboardFocus
@see grabKeyboardFocus, giveAwayKeyboardFocus, getWantsKeyboardFocus
*/ */
void setWantsKeyboardFocus (bool wantsFocus) noexcept; void setWantsKeyboardFocus (bool wantsFocus) noexcept;
/** Returns true if the component is interested in getting keyboard focus. /** 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 @see setWantsKeyboardFocus
*/ */
bool getWantsKeyboardFocus() const noexcept; bool getWantsKeyboardFocus() const noexcept;
//==============================================================================
/** Chooses whether a click on this component automatically grabs the focus. /** 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 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); void setMouseClickGrabsKeyboardFocus (bool shouldGrabFocus);
/** Returns the last value set with setMouseClickGrabsKeyboardFocus(). /** Returns the last value set with setMouseClickGrabsKeyboardFocus().
See setMouseClickGrabsKeyboardFocus() for more info.
@see setMouseClickGrabsKeyboardFocus
*/ */
bool getMouseClickGrabsKeyboardFocus() const noexcept; bool getMouseClickGrabsKeyboardFocus() const noexcept;
//==============================================================================
/** Tries to give keyboard focus to this component. /** 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 - if the component that was clicked on actually wants focus (as indicated
by calling getWantsKeyboardFocus), it gets it. by calling getWantsKeyboardFocus), it gets it.
- if the component itself doesn't want focus, it will try to pass 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 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 - 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, parent instead, unless it's a top-level component without a parent,
in which case it just takes the focus itself. 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 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. 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 keyPressed, keyStateChanged
*/ */
void grabKeyboardFocus(); 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. /** Returns true if this component currently has the keyboard focus.
@param trueIfChildIsFocused if this is true, then the method returns true if @param trueIfChildIsFocused if this is true, then the method returns true if
@@ -1274,97 +1385,61 @@ public:
have the focus. If false, the method only returns true if have the focus. If false, the method only returns true if
this component has the focus. this component has the focus.
@see grabKeyboardFocus, setWantsKeyboardFocus, getCurrentlyFocusedComponent,
focusGained, focusLost
@see grabKeyboardFocus, giveAwayKeyboardFocus, setWantsKeyboardFocus,
getCurrentlyFocusedComponent, focusGained, focusLost
*/ */
bool hasKeyboardFocus (bool trueIfChildIsFocused) const; bool hasKeyboardFocus (bool trueIfChildIsFocused) const;
/** Returns the component that currently has the keyboard focus.
@returns the focused component, or null if nothing is focused.
*/
static Component* JUCE_CALLTYPE getCurrentlyFocusedComponent() noexcept;
/** If any component has keyboard focus, this will defocus it. */
static void JUCE_CALLTYPE unfocusAllComponents();
//==============================================================================
/** Tries to move the keyboard focus to one of this component's siblings. /** 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. (This
is the method that is used when shifting focus by pressing the tab key).
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().
Components for which getWantsKeyboardFocus() returns false are not looked at.
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 @param moveToNext if true, the focus will move forwards; if false, it will
move backwards move backwards
@see grabKeyboardFocus, setFocusContainer, setWantsKeyboardFocus
@see grabKeyboardFocus, giveAwayKeyboardFocus, setFocusContainer, setWantsKeyboardFocus
*/ */
void moveKeyboardFocusToSibling (bool moveToNext); void moveKeyboardFocusToSibling (bool moveToNext);
/** Creates a KeyboardFocusTraverser object to use to determine the logic by
which 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.
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.
*/
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.
/** Returns the component that currently has the keyboard focus.
@see getExplicitFocusOrder, moveKeyboardFocusToSibling
@returns the focused component, or nullptr if nothing is focused.
*/ */
void setExplicitFocusOrder (int newFocusOrderIndex);
static Component* JUCE_CALLTYPE getCurrentlyFocusedComponent() noexcept;
/** Indicates whether this component is a parent for components that can have
their focus traversed.
/** If any component has keyboard focus, this will defocus it. */
static void JUCE_CALLTYPE unfocusAllComponents();
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.
//==============================================================================
/** Creates a ComponentTraverser object to determine the logic by which focus should be
passed from this component.
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.
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.
@see isFocusContainer, createFocusTraverser, moveKeyboardFocusToSibling
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 setFocusContainer (bool shouldBeFocusContainer) noexcept;
virtual std::unique_ptr<ComponentTraverser> createFocusTraverser();
/** Returns true if this component has been marked as a focus container.
/** Creates a ComponentTraverser object to use to determine the logic by which keyboard
focus should be passed from this component.
See setFocusContainer() for more details.
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.
@see setFocusContainer, moveKeyboardFocusToSibling, createFocusTraverser
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.
*/ */
bool isFocusContainer() const noexcept;
virtual std::unique_ptr<ComponentTraverser> createKeyboardFocusTraverser();
//============================================================================== //==============================================================================
/** Returns true if the component (and all its parents) are enabled. /** Returns true if the component (and all its parents) are enabled.
@@ -2284,7 +2359,109 @@ public:
*/ */
bool getViewportIgnoreDragFlag() const noexcept { return flags.viewportIgnoreDragFlag; } 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: 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 ComponentPeer;
friend class MouseInputSource; friend class MouseInputSource;
@@ -2294,7 +2471,7 @@ private:
static Component* currentlyFocusedComponent; static Component* currentlyFocusedComponent;
//============================================================================== //==============================================================================
String componentName, componentID;
String componentName, componentID, componentTitle, componentDescription, componentHelpText;
Component* parentComponent = nullptr; Component* parentComponent = nullptr;
Rectangle<int> boundsRelativeToParent; Rectangle<int> boundsRelativeToParent;
std::unique_ptr<Positioner> positioner; std::unique_ptr<Positioner> positioner;
@@ -2314,29 +2491,33 @@ private:
friend class WeakReference<Component>; friend class WeakReference<Component>;
WeakReference<Component>::Master masterReference; WeakReference<Component>::Master masterReference;
std::unique_ptr<AccessibilityHandler> accessibilityHandler;
struct ComponentFlags struct ComponentFlags
{ {
bool hasHeavyweightPeerFlag : 1;
bool visibleFlag : 1;
bool opaqueFlag : 1;
bool ignoresMouseClicksFlag : 1;
bool allowChildMouseClicksFlag : 1;
bool wantsFocusFlag : 1;
bool isFocusContainerFlag : 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 hasHeavyweightPeerFlag : 1;
bool visibleFlag : 1;
bool opaqueFlag : 1;
bool ignoresMouseClicksFlag : 1;
bool allowChildMouseClicksFlag : 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 dontClipGraphicsFlag : 1;
bool mouseDownWasBlocked : 1;
bool isMoveCallbackPending : 1;
bool isResizeCallbackPending : 1;
bool viewportIgnoreDragFlag : 1;
bool accessibilityIgnoredFlag : 1;
#if JUCE_DEBUG #if JUCE_DEBUG
bool isInsidePaintCall : 1;
bool isInsidePaintCall : 1;
#endif #endif
}; };
@@ -2358,10 +2539,10 @@ private:
void internalMouseWheel (MouseInputSource, Point<float>, Time, const MouseWheelDetails&); void internalMouseWheel (MouseInputSource, Point<float>, Time, const MouseWheelDetails&);
void internalMagnifyGesture (MouseInputSource, Point<float>, Time, float); void internalMagnifyGesture (MouseInputSource, Point<float>, Time, float);
void internalBroughtToFront(); 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 internalModalInputAttempt();
void internalModifierKeysChanged(); void internalModifierKeysChanged();
void internalChildrenChanged(); void internalChildrenChanged();
@@ -2377,8 +2558,8 @@ private:
void repaintParent(); void repaintParent();
void sendFakeMouseMove() const; void sendFakeMouseMove() const;
void takeKeyboardFocus (FocusChangeType); 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 sendEnablementChangeMessage();
void sendVisibilityChangeMessage(); void sendVisibilityChangeMessage();


+ 72
- 0
modules/juce_gui_basics/components/juce_ComponentTraverser.h View File

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

+ 359
- 0
modules/juce_gui_basics/components/juce_FocusTraverser.cpp View 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
- 0
modules/juce_gui_basics/components/juce_FocusTraverser.h View File

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

+ 6
- 0
modules/juce_gui_basics/drawables/juce_Drawable.cpp View File

@@ -204,4 +204,10 @@ std::unique_ptr<Drawable> Drawable::createFromImageFile (const File& file)
return {}; return {};
} }
//==============================================================================
std::unique_ptr<AccessibilityHandler> Drawable::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::ignored);
}
} // namespace juce } // namespace juce

+ 2
- 0
modules/juce_gui_basics/drawables/juce_Drawable.h View File

@@ -199,6 +199,8 @@ protected:
void setBoundsToEnclose (Rectangle<float>); void setBoundsToEnclose (Rectangle<float>);
/** @internal */ /** @internal */
void applyDrawableClipPath (Graphics&); void applyDrawableClipPath (Graphics&);
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
Point<int> originRelativeToComponent; Point<int> originRelativeToComponent;
std::unique_ptr<Drawable> drawableClipPath; std::unique_ptr<Drawable> drawableClipPath;


+ 6
- 0
modules/juce_gui_basics/drawables/juce_DrawableImage.cpp View File

@@ -133,4 +133,10 @@ Path DrawableImage::getOutlineAsPath() const
return {}; // not applicable for images return {}; // not applicable for images
} }
//==============================================================================
std::unique_ptr<AccessibilityHandler> DrawableImage::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::image);
}
} // namespace juce } // namespace juce

+ 2
- 0
modules/juce_gui_basics/drawables/juce_DrawableImage.h View File

@@ -94,6 +94,8 @@ public:
Rectangle<float> getDrawableBounds() const override; Rectangle<float> getDrawableBounds() const override;
/** @internal */ /** @internal */
Path getOutlineAsPath() const override; Path getOutlineAsPath() const override;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
private: private:
//============================================================================== //==============================================================================


+ 21
- 0
modules/juce_gui_basics/drawables/juce_DrawableText.cpp View File

@@ -208,4 +208,25 @@ bool DrawableText::replaceColour (Colour originalColour, Colour replacementColou
return true; 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 } // namespace juce

+ 2
- 0
modules/juce_gui_basics/drawables/juce_DrawableText.h View File

@@ -98,6 +98,8 @@ public:
Path getOutlineAsPath() const override; Path getOutlineAsPath() const override;
/** @internal */ /** @internal */
bool replaceColour (Colour originalColour, Colour replacementColour) override; bool replaceColour (Colour originalColour, Colour replacementColour) override;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
private: private:
//============================================================================== //==============================================================================


+ 6
- 0
modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp View File

@@ -616,4 +616,10 @@ void FileBrowserComponent::timerCallback()
} }
} }
//==============================================================================
std::unique_ptr<AccessibilityHandler> FileBrowserComponent::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
}
} // namespace juce } // namespace juce

+ 2
- 0
modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h View File

@@ -252,6 +252,8 @@ public:
FilePreviewComponent* getPreviewComponent() const noexcept; FilePreviewComponent* getPreviewComponent() const noexcept;
/** @internal */ /** @internal */
DirectoryContentsDisplayComponent* getDisplayComponent() const noexcept; DirectoryContentsDisplayComponent* getDisplayComponent() const noexcept;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
protected: protected:
/** Returns a list of names and paths for the default places the user might want to look. /** Returns a list of names and paths for the default places the user might want to look.


+ 12
- 1
modules/juce_gui_basics/filebrowser/juce_FileListComponent.cpp View File

@@ -35,6 +35,7 @@ FileListComponent::FileListComponent (DirectoryContentsList& listToShow)
DirectoryContentsDisplayComponent (listToShow), DirectoryContentsDisplayComponent (listToShow),
lastDirectory (listToShow.getDirectory()) lastDirectory (listToShow.getDirectory())
{ {
setTitle ("Files");
setModel (this); setModel (this);
directoryContentsList.addChangeListener (this); directoryContentsList.addChangeListener (this);
} }
@@ -68,7 +69,7 @@ void FileListComponent::setSelectedFile (const File& f)
{ {
for (int i = directoryContentsList.getNumFiles(); --i >= 0;) for (int i = directoryContentsList.getNumFiles(); --i >= 0;)
{ {
if (directoryContentsList.getFile(i) == f)
if (directoryContentsList.getFile (i) == f)
{ {
fileWaitingToBeSelected = File(); fileWaitingToBeSelected = File();
@@ -189,6 +190,11 @@ public:
repaint(); repaint();
} }
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
{
return nullptr;
}
private: private:
//============================================================================== //==============================================================================
FileListComponent& owner; FileListComponent& owner;
@@ -231,6 +237,11 @@ int FileListComponent::getNumRows()
return directoryContentsList.getNumFiles(); return directoryContentsList.getNumFiles();
} }
String FileListComponent::getNameForRow (int rowNumber)
{
return directoryContentsList.getFile (rowNumber).getFileName();
}
void FileListComponent::paintListBoxItem (int, Graphics&, int, int, bool) void FileListComponent::paintListBoxItem (int, Graphics&, int, int, bool)
{ {
} }


+ 1
- 0
modules/juce_gui_basics/filebrowser/juce_FileListComponent.h View File

@@ -82,6 +82,7 @@ private:
void changeListenerCallback (ChangeBroadcaster*) override; void changeListenerCallback (ChangeBroadcaster*) override;
int getNumRows() override; int getNumRows() override;
String getNameForRow (int rowNumber) override;
void paintListBoxItem (int, Graphics&, int, int, bool) override; void paintListBoxItem (int, Graphics&, int, int, bool) override;
Component* refreshComponentForRow (int rowNumber, bool isRowSelected, Component*) override; Component* refreshComponentForRow (int rowNumber, bool isRowSelected, Component*) override;
void selectedRowsChanged (int row) override; void selectedRowsChanged (int row) override;


+ 5
- 0
modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp View File

@@ -190,6 +190,11 @@ public:
indexInContentsList, owner); indexInContentsList, owner);
} }
String getAccessibilityName() override
{
return file.getFileName();
}
void itemClicked (const MouseEvent& e) override void itemClicked (const MouseEvent& e) override
{ {
owner.sendMouseClickMessage (file, e); owner.sendMouseClickMessage (file, e);


+ 2
- 2
modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp View File

@@ -70,11 +70,11 @@ void FilenameComponent::resized()
getLookAndFeel().layoutFilenameComponent (*this, &filenameBox, browseButton.get()); 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 // This prevents the sub-components from grabbing focus if the
// FilenameComponent has been set to refuse focus. // FilenameComponent has been set to refuse focus.
return getWantsKeyboardFocus() ? Component::createFocusTraverser() : nullptr;
return getWantsKeyboardFocus() ? Component::createKeyboardFocusTraverser() : nullptr;
} }
void FilenameComponent::setBrowseButtonText (const String& newBrowseButtonText) void FilenameComponent::setBrowseButtonText (const String& newBrowseButtonText)


+ 1
- 1
modules/juce_gui_basics/filebrowser/juce_FilenameComponent.h View File

@@ -212,7 +212,7 @@ public:
/** @internal */ /** @internal */
void fileDragExit (const StringArray&) override; void fileDragExit (const StringArray&) override;
/** @internal */ /** @internal */
KeyboardFocusTraverser* createFocusTraverser() override;
std::unique_ptr<ComponentTraverser> createKeyboardFocusTraverser() override;
private: private:
//============================================================================== //==============================================================================


+ 6
- 0
modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.cpp View File

@@ -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 } // namespace juce

+ 2
- 0
modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.h View File

@@ -53,6 +53,8 @@ public:
void paint (Graphics&) override; void paint (Graphics&) override;
/** @internal */ /** @internal */
void timerCallback() override; void timerCallback() override;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
private: private:
File fileToLoad; File fileToLoad;


+ 11
- 0
modules/juce_gui_basics/juce_gui_basics.cpp View File

@@ -66,6 +66,8 @@
#include <windowsx.h> #include <windowsx.h>
#include <vfw.h> #include <vfw.h>
#include <commdlg.h> #include <commdlg.h>
#include <UIAutomation.h>
#include <sapi.h>
#if JUCE_WEB_BROWSER #if JUCE_WEB_BROWSER
#include <exdisp.h> #include <exdisp.h>
@@ -103,8 +105,10 @@ namespace juce
extern bool juce_areThereAnyAlwaysOnTopWindows(); extern bool juce_areThereAnyAlwaysOnTopWindows();
} }
#include "accessibility/juce_AccessibilityHandler.cpp"
#include "components/juce_Component.cpp" #include "components/juce_Component.cpp"
#include "components/juce_ComponentListener.cpp" #include "components/juce_ComponentListener.cpp"
#include "components/juce_FocusTraverser.cpp"
#include "mouse/juce_MouseInputSource.cpp" #include "mouse/juce_MouseInputSource.cpp"
#include "desktop/juce_Displays.cpp" #include "desktop/juce_Displays.cpp"
#include "desktop/juce_Desktop.cpp" #include "desktop/juce_Desktop.cpp"
@@ -240,6 +244,7 @@ namespace juce
#endif #endif
#else #else
#include "native/accessibility/juce_mac_Accessibility.mm"
#include "native/juce_mac_NSViewComponentPeer.mm" #include "native/juce_mac_NSViewComponentPeer.mm"
#include "native/juce_mac_Windowing.mm" #include "native/juce_mac_Windowing.mm"
#include "native/juce_mac_MainMenu.mm" #include "native/juce_mac_MainMenu.mm"
@@ -249,6 +254,12 @@ namespace juce
#include "native/juce_mac_MouseCursor.mm" #include "native/juce_mac_MouseCursor.mm"
#elif JUCE_WINDOWS #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_Windowing.cpp"
#include "native/juce_win32_DragAndDrop.cpp" #include "native/juce_win32_DragAndDrop.cpp"
#include "native/juce_win32_FileChooser.cpp" #include "native/juce_win32_FileChooser.cpp"


+ 21
- 1
modules/juce_gui_basics/juce_gui_basics.h View File

@@ -155,6 +155,8 @@ namespace juce
class ApplicationCommandManagerListener; class ApplicationCommandManagerListener;
class DrawableButton; class DrawableButton;
class Displays; class Displays;
class AccessibilityHandler;
class KeyboardFocusTraverser;
class FlexBox; class FlexBox;
class Grid; class Grid;
@@ -167,7 +169,8 @@ namespace juce
#include "mouse/juce_MouseEvent.h" #include "mouse/juce_MouseEvent.h"
#include "keyboard/juce_KeyPress.h" #include "keyboard/juce_KeyPress.h"
#include "keyboard/juce_KeyListener.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_ModalComponentManager.h"
#include "components/juce_ComponentListener.h" #include "components/juce_ComponentListener.h"
#include "components/juce_CachedComponentImage.h" #include "components/juce_CachedComponentImage.h"
@@ -185,6 +188,7 @@ namespace juce
#include "mouse/juce_TextDragAndDropTarget.h" #include "mouse/juce_TextDragAndDropTarget.h"
#include "mouse/juce_TooltipClient.h" #include "mouse/juce_TooltipClient.h"
#include "keyboard/juce_CaretComponent.h" #include "keyboard/juce_CaretComponent.h"
#include "keyboard/juce_KeyboardFocusTraverser.h"
#include "keyboard/juce_SystemClipboard.h" #include "keyboard/juce_SystemClipboard.h"
#include "keyboard/juce_TextEditorKeyMapper.h" #include "keyboard/juce_TextEditorKeyMapper.h"
#include "keyboard/juce_TextInputTarget.h" #include "keyboard/juce_TextInputTarget.h"
@@ -293,6 +297,22 @@ namespace juce
#include "lookandfeel/juce_LookAndFeel_V3.h" #include "lookandfeel/juce_LookAndFeel_V3.h"
#include "lookandfeel/juce_LookAndFeel_V4.h" #include "lookandfeel/juce_LookAndFeel_V4.h"
#include "mouse/juce_LassoComponent.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_LINUX || JUCE_BSD
#if JUCE_GUI_BASICS_INCLUDE_XHEADERS #if JUCE_GUI_BASICS_INCLUDE_XHEADERS


+ 211
- 68
modules/juce_gui_basics/keyboard/juce_KeyboardFocusTraverser.cpp View File

@@ -26,105 +26,248 @@
namespace juce namespace juce
{ {
namespace KeyboardFocusHelpers
//==============================================================================
namespace KeyboardFocusTraverserHelpers
{ {
static int getOrder (const Component* c)
static bool isKeyboardFocusable (const Component* comp, const Component* container)
{
return comp->getWantsKeyboardFocus() && container->isParentOf (comp);
}
static Component* traverse (Component* current, Component* container,
FocusHelpers::NavigationDirection direction)
{ {
auto order = c->getExplicitFocusOrder();
return order > 0 ? order : (std::numeric_limits<int>::max() / 2);
if (auto* comp = FocusHelpers::navigateFocus (current, container, direction,
&Component::isKeyboardFocusContainer))
{
if (isKeyboardFocusable (comp, container))
return comp;
return traverse (comp, container, direction);
}
return nullptr;
} }
}
Component* KeyboardFocusTraverser::getNextComponent (Component* current)
{
return KeyboardFocusTraverserHelpers::traverse (current, current->findKeyboardFocusContainer(),
FocusHelpers::NavigationDirection::forwards);
}
Component* KeyboardFocusTraverser::getPreviousComponent (Component* current)
{
return KeyboardFocusTraverserHelpers::traverse (current, current->findKeyboardFocusContainer(),
FocusHelpers::NavigationDirection::backwards);
}
Component* KeyboardFocusTraverser::getDefaultComponent (Component* parentComponent)
{
for (auto* comp : getAllComponents (parentComponent))
if (KeyboardFocusTraverserHelpers::isKeyboardFocusable (comp, parentComponent))
return comp;
return nullptr;
}
static void findAllFocusableComponents (Component* parent, Array<Component*>& comps)
std::vector<Component*> KeyboardFocusTraverser::getAllComponents (Component* parentComponent)
{
std::vector<Component*> components;
FocusHelpers::findAllComponents (parentComponent,
components,
&Component::isKeyboardFocusContainer);
auto removePredicate = [parentComponent] (const Component* comp)
{ {
if (parent->getNumChildComponents() != 0)
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");
{ {
Array<Component*> localComps;
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* c : parent->getChildren())
if (c->isVisible() && c->isEnabled())
localComps.add (c);
for (auto* focusChild : focusChildren)
expect (std::find (allComponents.cbegin(), allComponents.cend(), focusChild) != allComponents.cend());
// 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* componentToTest = traverser.getDefaultComponent (&parent);
for (;;)
{ {
auto explicitOrder1 = getOrder (a);
auto explicitOrder2 = getOrder (b);
expect (componentToTest->getWantsKeyboardFocus());
expect (std::find (std::begin (focusChildren), std::end (focusChildren), componentToTest) != std::end (focusChildren));
componentToTest = traverser.getNextComponent (componentToTest);
if (explicitOrder1 != explicitOrder2)
return explicitOrder1 < explicitOrder2;
if (componentToTest == nullptr)
break;
}
if (a->getY() != b->getY())
return a->getY() < b->getY();
int focusOrder = 1;
for (auto* focusChild : focusChildren)
focusChild->setExplicitFocusOrder (focusOrder++);
return a->getX() < b->getX();
});
componentToTest = traverser.getDefaultComponent (&parent);
for (auto* c : localComps)
for (auto* focusChild : focusChildren)
{ {
if (c->getWantsKeyboardFocus())
comps.add (c);
expect (componentToTest == focusChild);
expect (componentToTest->getWantsKeyboardFocus());
if (! c->isFocusContainer())
findAllFocusableComponents (c, comps);
componentToTest = traverser.getNextComponent (componentToTest);
} }
} }
}
static Component* findFocusContainer (Component* c)
{
c = c->getParentComponent();
beginTest ("Single nested child wants keyboard focus");
{
TestComponent parent;
Component grandparent;
if (c != nullptr)
while (c->getParentComponent() != nullptr && ! c->isFocusContainer())
c = c->getParentComponent();
grandparent.addAndMakeVisible (parent);
return c;
}
auto& focusChild = parent.children[5];
static Component* getIncrementedComponent (Component* current, int delta)
{
if (auto* focusContainer = findFocusContainer (current))
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");
{ {
Array<Component*> comps;
KeyboardFocusHelpers::findAllFocusableComponents (focusContainer, comps);
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; }));
if (! comps.isEmpty())
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)
{ {
auto index = comps.indexOf (current);
return comps [(index + comps.size() + delta) % comps.size()];
grandparent.addAndMakeVisible (p);
p.setWantsKeyboardFocus (true);
} }
}
return nullptr;
}
}
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));
//==============================================================================
KeyboardFocusTraverser::KeyboardFocusTraverser() {}
KeyboardFocusTraverser::~KeyboardFocusTraverser() {}
for (auto* focusChild : focusChildren)
focusChild->setWantsKeyboardFocus (false);
Component* KeyboardFocusTraverser::getNextComponent (Component* current)
{
jassert (current != nullptr);
return KeyboardFocusHelpers::getIncrementedComponent (current, 1);
}
expect (traverser.getDefaultComponent (&grandparent) == &otherParents.front());
expect (traverser.getDefaultComponent (&parent) == nullptr);
expect (traverser.getAllComponents (&grandparent).size() == otherParents.size());
expect (traverser.getAllComponents (&parent).empty());
}
}
Component* KeyboardFocusTraverser::getPreviousComponent (Component* current)
{
jassert (current != nullptr);
return KeyboardFocusHelpers::getIncrementedComponent (current, -1);
}
private:
struct TestComponent : public Component
{
TestComponent()
{
for (auto& child : children)
addAndMakeVisible (child);
}
Component* KeyboardFocusTraverser::getDefaultComponent (Component* parentComponent)
{
Array<Component*> comps;
std::array<Component, 10> children;
};
if (parentComponent != nullptr)
KeyboardFocusHelpers::findAllFocusableComponents (parentComponent, comps);
KeyboardFocusTraverser traverser;
};
return comps.getFirst();
}
static KeyboardFocusTraverserTests keyboardFocusTraverserTests;
#endif
} // namespace juce } // namespace juce

+ 35
- 38
modules/juce_gui_basics/keyboard/juce_KeyboardFocusTraverser.h View File

@@ -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} @tags{GUI}
*/ */
class JUCE_API KeyboardFocusTraverser
class JUCE_API KeyboardFocusTraverser : public ComponentTraverser
{ {
public: public:
KeyboardFocusTraverser();
/** Destructor. */ /** Destructor. */
virtual ~KeyboardFocusTraverser();
/** Returns the component that should be given focus after the specified one
when moving "forwards".
~KeyboardFocusTraverser() override = default;
The default implementation will return the next component which is to the
right of or below this one.
/** Returns the component that should receive keyboard focus by default within the
given parent component.
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);
/** Returns the component that should be given focus after the specified one
when moving "backwards".
Component* getDefaultComponent (Component* parentComponent) override;
The default implementation will return the next component which is to the
left of or above this one.
/** Returns the component that should be given keyboard focus after the specified
one when moving "forwards".
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 return the previous focusable component (as
determined by FocusTraverser) that also wants keyboard focus, or nullptr if
there is no suitable component.
*/
Component* getPreviousComponent (Component* current) override;
The default implementation will just return the foremost child component that
wants focus.
/** Returns all of the components that can receive keyboard focus within the given
parent component in traversal order.
This may return nullptr if there's no suitable candidate.
The default implementation will return all focusable child components (as
determined by FocusTraverser) that also wants keyboard focus.
*/ */
virtual Component* getDefaultComponent (Component* parentComponent);
std::vector<Component*> getAllComponents (Component* parentComponent) override;
}; };
} // namespace juce } // namespace juce

+ 1
- 0
modules/juce_gui_basics/layout/juce_ComponentAnimator.cpp View File

@@ -150,6 +150,7 @@ public:
{ {
ProxyComponent (Component& c) ProxyComponent (Component& c)
{ {
setAccessible (false);
setWantsKeyboardFocus (false); setWantsKeyboardFocus (false);
setBounds (c.getBounds()); setBounds (c.getBounds());
setTransform (c.getTransform()); setTransform (c.getTransform());


+ 6
- 0
modules/juce_gui_basics/layout/juce_ConcertinaPanel.cpp View File

@@ -459,4 +459,10 @@ void ConcertinaPanel::panelHeaderDoubleClicked (Component* component)
setPanelSize (component, 0, true); setPanelSize (component, 0, true);
} }
//==============================================================================
std::unique_ptr<AccessibilityHandler> ConcertinaPanel::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
}
} // namespace juce } // namespace juce

+ 4
- 0
modules/juce_gui_basics/layout/juce_ConcertinaPanel.h View File

@@ -119,6 +119,10 @@ public:
ConcertinaPanel&, Component&) = 0; ConcertinaPanel&, Component&) = 0;
}; };
//==============================================================================
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
private: private:
void resized() override; void resized() override;


+ 6
- 0
modules/juce_gui_basics/layout/juce_GroupComponent.cpp View File

@@ -69,4 +69,10 @@ void GroupComponent::paint (Graphics& g)
void GroupComponent::enablementChanged() { repaint(); } void GroupComponent::enablementChanged() { repaint(); }
void GroupComponent::colourChanged() { repaint(); } void GroupComponent::colourChanged() { repaint(); }
//==============================================================================
std::unique_ptr<AccessibilityHandler> GroupComponent::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
}
} // namespace juce } // namespace juce

+ 2
- 0
modules/juce_gui_basics/layout/juce_GroupComponent.h View File

@@ -98,6 +98,8 @@ public:
void enablementChanged() override; void enablementChanged() override;
/** @internal */ /** @internal */
void colourChanged() override; void colourChanged() override;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
private: private:
String text; String text;


+ 43
- 1
modules/juce_gui_basics/layout/juce_ScrollBar.cpp View File

@@ -61,7 +61,7 @@ private:
ScrollBar::ScrollBar (bool shouldBeVertical) : vertical (shouldBeVertical) ScrollBar::ScrollBar (bool shouldBeVertical) : vertical (shouldBeVertical)
{ {
setRepaintsOnMouseActivity (true); setRepaintsOnMouseActivity (true);
setFocusContainer (true);
setFocusContainerType (FocusContainerType::keyboardFocusContainer);
} }
ScrollBar::~ScrollBar() ScrollBar::~ScrollBar()
@@ -440,4 +440,46 @@ bool ScrollBar::getVisibility() const noexcept
&& visibleRange.getLength() > 0.0); && 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 } // namespace juce

+ 7
- 0
modules/juce_gui_basics/layout/juce_ScrollBar.h View File

@@ -211,6 +211,11 @@ public:
*/ */
void setSingleStepSize (double newSingleStepSize) noexcept; 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. /** Moves the scrollbar by a number of single-steps.
This will move the bar by a multiple of its single-step interval (as This will move the bar by a multiple of its single-step interval (as
@@ -409,6 +414,8 @@ public:
void parentHierarchyChanged() override; void parentHierarchyChanged() override;
/** @internal */ /** @internal */
void setVisible (bool) override; void setVisible (bool) override;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
private: private:
//============================================================================== //==============================================================================


+ 6
- 0
modules/juce_gui_basics/layout/juce_SidePanel.cpp View File

@@ -294,4 +294,10 @@ bool SidePanel::isMouseEventInThisOrChildren (Component* eventComponent)
return false; return false;
} }
//==============================================================================
std::unique_ptr<AccessibilityHandler> SidePanel::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
}
} // namespace juce } // namespace juce

+ 2
- 0
modules/juce_gui_basics/layout/juce_SidePanel.h View File

@@ -195,6 +195,8 @@ public:
void mouseDrag (const MouseEvent&) override; void mouseDrag (const MouseEvent&) override;
/** @internal */ /** @internal */
void mouseUp (const MouseEvent&) override; void mouseUp (const MouseEvent&) override;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
private: private:
//============================================================================== //==============================================================================


+ 7
- 1
modules/juce_gui_basics/layout/juce_TabbedButtonBar.cpp View File

@@ -202,7 +202,7 @@ TabbedButtonBar::TabbedButtonBar (Orientation orientationToUse)
setInterceptsMouseClicks (false, true); setInterceptsMouseClicks (false, true);
behindFrontTab.reset (new BehindFrontTabComp (*this)); behindFrontTab.reset (new BehindFrontTabComp (*this));
addAndMakeVisible (behindFrontTab.get()); addAndMakeVisible (behindFrontTab.get());
setFocusContainer (true);
setFocusContainerType (FocusContainerType::keyboardFocusContainer);
} }
TabbedButtonBar::~TabbedButtonBar() TabbedButtonBar::~TabbedButtonBar()
@@ -574,4 +574,10 @@ void TabbedButtonBar::showExtraItemsMenu()
void TabbedButtonBar::currentTabChanged (int, const String&) {} void TabbedButtonBar::currentTabChanged (int, const String&) {}
void TabbedButtonBar::popupMenuClickOnTab (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 } // namespace juce

+ 2
- 0
modules/juce_gui_basics/layout/juce_TabbedButtonBar.h View File

@@ -334,6 +334,8 @@ public:
void resized() override; void resized() override;
/** @internal */ /** @internal */
void lookAndFeelChanged() override; void lookAndFeelChanged() override;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
protected: protected:
//============================================================================== //==============================================================================


+ 6
- 0
modules/juce_gui_basics/layout/juce_TabbedComponent.cpp View File

@@ -310,4 +310,10 @@ void TabbedComponent::changeCallback (int newCurrentTabIndex, const String& newT
void TabbedComponent::currentTabChanged (int, const String&) {} void TabbedComponent::currentTabChanged (int, const String&) {}
void TabbedComponent::popupMenuClickOnTab (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 } // namespace juce

+ 2
- 0
modules/juce_gui_basics/layout/juce_TabbedComponent.h View File

@@ -197,6 +197,8 @@ public:
void resized() override; void resized() override;
/** @internal */ /** @internal */
void lookAndFeelChanged() override; void lookAndFeelChanged() override;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
protected: protected:
//============================================================================== //==============================================================================


+ 6
- 0
modules/juce_gui_basics/menus/juce_BurgerMenuComponent.cpp View File

@@ -293,4 +293,10 @@ void BurgerMenuComponent::lookAndFeelChanged()
listBox.setRowHeight (roundToInt (getLookAndFeel().getPopupMenuFont().getHeight() * 2.0f)); listBox.setRowHeight (roundToInt (getLookAndFeel().getPopupMenuFont().getHeight() * 2.0f));
} }
//==============================================================================
std::unique_ptr<AccessibilityHandler> BurgerMenuComponent::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::menuBar);
}
} // namespace juce } // namespace juce

+ 2
- 0
modules/juce_gui_basics/menus/juce_BurgerMenuComponent.h View File

@@ -71,6 +71,8 @@ public:
/** @internal */ /** @internal */
void lookAndFeelChanged() override; void lookAndFeelChanged() override;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
private: private:
//============================================================================== //==============================================================================


+ 180
- 80
modules/juce_gui_basics/menus/juce_MenuBarComponent.cpp View File

@@ -26,11 +26,63 @@
namespace juce 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) MenuBarComponent::MenuBarComponent (MenuBarModel* m)
: model (nullptr),
itemUnderMouse (-1),
currentPopupIndex (-1),
topLevelIndexClicked (0)
{ {
setRepaintsOnMouseActivity (true); setRepaintsOnMouseActivity (true);
setWantsKeyboardFocus (false); setWantsKeyboardFocus (false);
@@ -70,77 +122,83 @@ void MenuBarComponent::setModel (MenuBarModel* const newModel)
//============================================================================== //==============================================================================
void MenuBarComponent::paint (Graphics& g) 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)
if (model == nullptr)
return;
for (size_t i = 0; i < itemComponents.size(); ++i)
{ {
for (int i = 0; i < menuNames.size(); ++i)
{
Graphics::ScopedSaveState ss (g);
g.setOrigin (xPositions [i], 0);
g.reduceClipRegion (0, 0, xPositions[i + 1] - xPositions[i], getHeight());
getLookAndFeel().drawMenuBarItem (g,
xPositions[i + 1] - xPositions[i],
getHeight(),
i,
menuNames[i],
i == itemUnderMouse,
i == currentPopupIndex,
isMouseOverBar,
*this);
}
const auto& itemComponent = itemComponents[i];
const auto itemBounds = itemComponent->getBounds();
Graphics::ScopedSaveState ss (g);
g.setOrigin (itemBounds.getX(), 0);
g.reduceClipRegion (0, 0, itemBounds.getWidth(), itemBounds.getHeight());
getLookAndFeel().drawMenuBarItem (g,
itemBounds.getWidth(),
itemBounds.getHeight(),
(int) i,
itemComponent->getName(),
(int) i == itemUnderMouse,
(int) i == currentPopupIndex,
isMouseOverBar,
*this);
} }
} }
void MenuBarComponent::resized() void MenuBarComponent::resized()
{ {
xPositions.clear();
int x = 0; 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) 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; return -1;
} }
void MenuBarComponent::repaintMenuItem (int index) 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)
{
repaintMenuItem (itemUnderMouse);
itemUnderMouse = index;
repaintMenuItem (itemUnderMouse);
}
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) void MenuBarComponent::setOpenItem (int index)
@@ -156,7 +214,7 @@ void MenuBarComponent::setOpenItem (int index)
currentPopupIndex = index; currentPopupIndex = index;
repaintMenuItem (currentPopupIndex); repaintMenuItem (currentPopupIndex);
Desktop& desktop = Desktop::getInstance();
auto& desktop = Desktop::getInstance();
if (index >= 0) if (index >= 0)
desktop.addGlobalMouseListener (this); desktop.addGlobalMouseListener (this);
@@ -180,30 +238,24 @@ void MenuBarComponent::showMenu (int index)
setOpenItem (index); setOpenItem (index);
setItemUnderMouse (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) if (m.lookAndFeel == nullptr)
m.setLookAndFeel (&getLookAndFeel()); 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) 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) void MenuBarComponent::menuDismissed (int topLevelIndex, int itemId)
{ {
topLevelIndexClicked = topLevelIndex; topLevelIndexClicked = topLevelIndex;
@@ -212,8 +264,7 @@ void MenuBarComponent::menuDismissed (int topLevelIndex, int itemId)
void MenuBarComponent::handleCommandMessage (int commandId) void MenuBarComponent::handleCommandMessage (int commandId)
{ {
const Point<int> mousePos (getMouseXYRelative());
updateItemUnderMouse (mousePos);
updateItemUnderMouse (getMouseXYRelative());
if (currentPopupIndex == topLevelIndexClicked) if (currentPopupIndex == topLevelIndexClicked)
setOpenItem (-1); setOpenItem (-1);
@@ -239,8 +290,7 @@ void MenuBarComponent::mouseDown (const MouseEvent& e)
{ {
if (currentPopupIndex < 0) if (currentPopupIndex < 0)
{ {
const MouseEvent e2 (e.getEventRelativeTo (this));
updateItemUnderMouse (e2.getPosition());
updateItemUnderMouse (e.getEventRelativeTo (this).getPosition());
currentPopupIndex = -2; currentPopupIndex = -2;
showMenu (itemUnderMouse); showMenu (itemUnderMouse);
@@ -249,8 +299,7 @@ void MenuBarComponent::mouseDown (const MouseEvent& e)
void MenuBarComponent::mouseDrag (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) if (item >= 0)
showMenu (item); showMenu (item);
@@ -258,7 +307,7 @@ void MenuBarComponent::mouseDrag (const MouseEvent& e)
void MenuBarComponent::mouseUp (const MouseEvent& e) void MenuBarComponent::mouseUp (const MouseEvent& e)
{ {
const MouseEvent e2 (e.getEventRelativeTo (this));
const auto e2 = e.getEventRelativeTo (this);
updateItemUnderMouse (e2.getPosition()); updateItemUnderMouse (e2.getPosition());
@@ -271,13 +320,13 @@ void MenuBarComponent::mouseUp (const MouseEvent& e)
void MenuBarComponent::mouseMove (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 (lastMousePos != e2.getPosition())
{ {
if (currentPopupIndex >= 0) if (currentPopupIndex >= 0)
{ {
const int item = getItemAt (e2.getPosition());
const auto item = getItemAt (e2.getPosition());
if (item >= 0) if (item >= 0)
showMenu (item); showMenu (item);
@@ -293,11 +342,11 @@ void MenuBarComponent::mouseMove (const MouseEvent& e)
bool MenuBarComponent::keyPressed (const KeyPress& key) bool MenuBarComponent::keyPressed (const KeyPress& key)
{ {
const int numMenus = menuNames.size();
const auto numMenus = (int) itemComponents.size();
if (numMenus > 0) if (numMenus > 0)
{ {
const int currentIndex = jlimit (0, numMenus - 1, currentPopupIndex);
const auto currentIndex = jlimit (0, numMenus - 1, currentPopupIndex);
if (key.isKeyCode (KeyPress::leftKey)) if (key.isKeyCode (KeyPress::leftKey))
{ {
@@ -315,34 +364,69 @@ bool MenuBarComponent::keyPressed (const KeyPress& key)
return false; return false;
} }
void MenuBarComponent::menuBarItemsChanged (MenuBarModel* /*menuBarModel*/)
void MenuBarComponent::menuBarItemsChanged (MenuBarModel*)
{ {
StringArray newNames; StringArray newNames;
if (model != nullptr) if (model != nullptr)
newNames = model->getMenuBarNames(); 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(); repaint();
resized(); 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) if (model == nullptr || (info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) != 0)
return; 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)) if (menu.containsCommandItem (info.commandID))
{ {
setItemUnderMouse (i);
setItemUnderMouse ((int) i);
startTimer (200); startTimer (200);
break; break;
} }
@@ -355,4 +439,20 @@ void MenuBarComponent::timerCallback()
updateItemUnderMouse (getMouseXYRelative()); 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 } // namespace juce

+ 19
- 11
modules/juce_gui_basics/menus/juce_MenuBarComponent.h View File

@@ -95,24 +95,32 @@ public:
void menuBarItemsChanged (MenuBarModel*) override; void menuBarItemsChanged (MenuBarModel*) override;
/** @internal */ /** @internal */
void menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo&) override; void menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo&) override;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
private: private:
//============================================================================== //==============================================================================
MenuBarModel* model;
class AccessibleItemComponent;
StringArray menuNames;
Array<int> xPositions;
Point<int> lastMousePos;
int itemUnderMouse, currentPopupIndex, topLevelIndexClicked;
//==============================================================================
void timerCallback() override;
int getItemAt (Point<int>); int getItemAt (Point<int>);
void setItemUnderMouse (int index);
void setOpenItem (int index);
void setItemUnderMouse (int);
void setOpenItem (int);
void updateItemUnderMouse (Point<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) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MenuBarComponent)
}; };


+ 143
- 37
modules/juce_gui_basics/menus/juce_PopupMenu.cpp View File

@@ -84,7 +84,7 @@ struct ItemComponent : public Component
ItemComponent (const PopupMenu::Item& i, ItemComponent (const PopupMenu::Item& i,
const PopupMenu::Options& o, const PopupMenu::Options& o,
MenuWindow& parent) MenuWindow& parent)
: item (i), options (o), customComp (i.customComponent)
: item (i), parentWindow (parent), options (o), customComp (i.customComponent)
{ {
if (item.isSectionHeader) if (item.isSectionHeader)
customComp = *new HeaderItemComponent (item.text, options); customComp = *new HeaderItemComponent (item.text, options);
@@ -156,13 +156,99 @@ struct ItemComponent : public Component
if (customComp != nullptr) if (customComp != nullptr)
customComp->setHighlighted (shouldBeHighlighted); customComp->setHighlighted (shouldBeHighlighted);
if (isHighlighted)
if (auto* handler = getAccessibilityHandler())
handler->grabFocus();
repaint(); repaint();
} }
} }
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
{
return item.isSeparator ? nullptr : std::make_unique<ItemAccessibilityHandler> (*this);
}
PopupMenu::Item item; PopupMenu::Item item;
private: 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; 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 // NB: we use a copy of the one from the item info in case we're using our own section comp
ReferenceCountedObjectPtr<CustomComponent> customComp; ReferenceCountedObjectPtr<CustomComponent> customComp;
@@ -223,6 +309,7 @@ struct MenuWindow : public Component
setWantsKeyboardFocus (false); setWantsKeyboardFocus (false);
setMouseClickGrabsKeyboardFocus (false); setMouseClickGrabsKeyboardFocus (false);
setAlwaysOnTop (true); setAlwaysOnTop (true);
setFocusContainerType (FocusContainerType::focusContainer);
setLookAndFeel (parent != nullptr ? &(parent->getLookAndFeel()) setLookAndFeel (parent != nullptr ? &(parent->getLookAndFeel())
: menu.lookAndFeel.get()); : menu.lookAndFeel.get());
@@ -275,11 +362,19 @@ struct MenuWindow : public Component
if (auto visibleID = options.getItemThatMustBeVisible()) if (auto visibleID = options.getItemThatMustBeVisible())
{ {
auto targetPosition = parentComponent != nullptr ? parentComponent->getLocalPoint (nullptr, targetArea.getTopLeft())
: targetArea.getTopLeft();
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();
ensureItemComponentIsVisible (*item, isPositiveAndBelow (y, windowPos.getHeight()) ? y : -1);
auto y = targetPosition.getY() - windowPos.getY();
ensureItemIsVisible (visibleID, isPositiveAndBelow (y, windowPos.getHeight()) ? y : -1);
break;
}
}
} }
resizeToBestWindowPos(); resizeToBestWindowPos();
@@ -887,47 +982,36 @@ struct MenuWindow : public Component
return correctColumnWidths (maxMenuW); return correctColumnWidths (maxMenuW);
} }
void ensureItemIsVisible (const int itemID, int wantedY)
void ensureItemComponentIsVisible (const ItemComponent& itemComp, int wantedY)
{ {
jassert (itemID != 0);
for (int i = items.size(); --i >= 0;)
if (windowPos.getHeight() > PopupMenuSettings::scrollZone * 4)
{ {
if (auto* m = items.getUnchecked (i))
{
if (m->item.itemID == itemID
&& windowPos.getHeight() > PopupMenuSettings::scrollZone * 4)
{
auto currentY = m->getY();
auto currentY = itemComp.getY();
if (wantedY > 0 || currentY < 0 || m->getBottom() > windowPos.getHeight())
{
if (wantedY < 0)
wantedY = jlimit (PopupMenuSettings::scrollZone,
jmax (PopupMenuSettings::scrollZone,
windowPos.getHeight() - (PopupMenuSettings::scrollZone + m->getHeight())),
currentY);
auto parentArea = getParentArea (windowPos.getPosition(), parentComponent) / scaleFactor;
auto deltaY = wantedY - currentY;
if (wantedY > 0 || currentY < 0 || itemComp.getBottom() > windowPos.getHeight())
{
if (wantedY < 0)
wantedY = jlimit (PopupMenuSettings::scrollZone,
jmax (PopupMenuSettings::scrollZone,
windowPos.getHeight() - (PopupMenuSettings::scrollZone + itemComp.getHeight())),
currentY);
windowPos.setSize (jmin (windowPos.getWidth(), parentArea.getWidth()),
jmin (windowPos.getHeight(), parentArea.getHeight()));
auto parentArea = getParentArea (windowPos.getPosition(), parentComponent) / scaleFactor;
auto deltaY = wantedY - currentY;
auto newY = jlimit (parentArea.getY(),
parentArea.getBottom() - windowPos.getHeight(),
windowPos.getY() + deltaY);
windowPos.setSize (jmin (windowPos.getWidth(), parentArea.getWidth()),
jmin (windowPos.getHeight(), parentArea.getHeight()));
deltaY -= newY - windowPos.getY();
auto newY = jlimit (parentArea.getY(),
parentArea.getBottom() - windowPos.getHeight(),
windowPos.getY() + deltaY);
childYOffset -= deltaY;
windowPos.setPosition (windowPos.getX(), newY);
deltaY -= newY - windowPos.getY();
updateYPositions();
}
childYOffset -= deltaY;
windowPos.setPosition (windowPos.getX(), newY);
break;
}
updateYPositions();
} }
} }
} }
@@ -1016,6 +1100,9 @@ struct MenuWindow : public Component
void setCurrentlyHighlightedChild (ItemComponent* child) void setCurrentlyHighlightedChild (ItemComponent* child)
{ {
if (currentChild == child)
return;
if (currentChild != nullptr) if (currentChild != nullptr)
currentChild->setHighlighted (false); currentChild->setHighlighted (false);
@@ -1026,6 +1113,9 @@ struct MenuWindow : public Component
currentChild->setHighlighted (true); currentChild->setHighlighted (true);
timeEnteredCurrentChildComp = Time::getApproximateMillisecondCounter(); timeEnteredCurrentChildComp = Time::getApproximateMillisecondCounter();
} }
if (auto* handler = getAccessibilityHandler())
handler->notifyAccessibilityEvent (AccessibilityEvent::rowSelectionChanged);
} }
bool isSubMenuVisible() const noexcept { return activeSubMenu != nullptr && activeSubMenu->isVisible(); } 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 isTopScrollZoneActive() const noexcept { return canScroll() && childYOffset > 0; }
bool isBottomScrollZoneActive() const noexcept { return canScroll() && childYOffset < contentHeight - windowPos.getHeight(); } 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; MenuWindow* parent;
const Options options; 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 window->toFront (false); // need to do this after making it modal, or it could
// be stuck behind other comps that are already modal.. // be stuck behind other comps that are already modal..
if (auto* handler = window->getAccessibilityHandler())
handler->grabFocus();
#if JUCE_MODAL_LOOPS_PERMITTED #if JUCE_MODAL_LOOPS_PERMITTED
if (userCallback == nullptr && canBeModal) if (userCallback == nullptr && canBeModal)
return window->runModalLoop(); return window->runModalLoop();


+ 4
- 4
modules/juce_gui_basics/misc/juce_BubbleComponent.h View File

@@ -156,6 +156,10 @@ public:
const Rectangle<float>& body) = 0; const Rectangle<float>& body) = 0;
}; };
//==============================================================================
/** @internal */
void paint (Graphics&) override;
protected: protected:
//============================================================================== //==============================================================================
/** Subclasses should override this to return the size of the content they /** 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; virtual void paintContent (Graphics& g, int width, int height) = 0;
public:
/** @internal */
void paint (Graphics&) override;
private: private:
Rectangle<int> content; Rectangle<int> content;
Point<int> arrowTip; Point<int> arrowTip;


+ 1
- 0
modules/juce_gui_basics/misc/juce_DropShadower.cpp View File

@@ -33,6 +33,7 @@ public:
: target (comp), shadow (ds) : target (comp), shadow (ds)
{ {
setVisible (true); setVisible (true);
setAccessible (false);
setInterceptsMouseClicks (false, false); setInterceptsMouseClicks (false, false);
if (comp->isOnDesktop()) if (comp->isOnDesktop())


+ 6
- 0
modules/juce_gui_basics/misc/juce_JUCESplashScreen.cpp View File

@@ -188,6 +188,12 @@ void JUCESplashScreen::mouseUp (const MouseEvent&)
juceWebsite.launchInDefaultBrowser(); juceWebsite.launchInDefaultBrowser();
} }
//==============================================================================
std::unique_ptr<AccessibilityHandler> JUCESplashScreen::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::splashScreen);
}
// END SECTION A // END SECTION A
} // namespace juce } // namespace juce

+ 4
- 0
modules/juce_gui_basics/misc/juce_JUCESplashScreen.h View File

@@ -55,6 +55,10 @@ public:
static std::unique_ptr<Drawable> getSplashScreenLogo(); static std::unique_ptr<Drawable> getSplashScreenLogo();
//==============================================================================
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
private: private:
void paint (Graphics&) override; void paint (Graphics&) override;
void timerCallback() override; void timerCallback() override;


+ 1078
- 0
modules/juce_gui_basics/native/accessibility/juce_mac_Accessibility.mm
File diff suppressed because it is too large
View File


+ 270
- 0
modules/juce_gui_basics/native/accessibility/juce_win32_Accessibility.cpp View File

@@ -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

+ 558
- 0
modules/juce_gui_basics/native/accessibility/juce_win32_AccessibilityElement.cpp View File

@@ -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

+ 80
- 0
modules/juce_gui_basics/native/accessibility/juce_win32_AccessibilityElement.h View File

@@ -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)
};
}

+ 86
- 0
modules/juce_gui_basics/native/accessibility/juce_win32_UIAExpandCollapseProvider.h View File

@@ -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

+ 101
- 0
modules/juce_gui_basics/native/accessibility/juce_win32_UIAGridItemProvider.h View File

@@ -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

+ 90
- 0
modules/juce_gui_basics/native/accessibility/juce_win32_UIAGridProvider.h View File

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

+ 103
- 0
modules/juce_gui_basics/native/accessibility/juce_win32_UIAHelpers.h View File

@@ -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

+ 62
- 0
modules/juce_gui_basics/native/accessibility/juce_win32_UIAInvokeProvider.h View File

@@ -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

+ 58
- 0
modules/juce_gui_basics/native/accessibility/juce_win32_UIAProviderBase.h View File

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

+ 43
- 0
modules/juce_gui_basics/native/accessibility/juce_win32_UIAProviders.h View File

@@ -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"

+ 140
- 0
modules/juce_gui_basics/native/accessibility/juce_win32_UIARangeValueProvider.h View File

@@ -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

+ 252
- 0
modules/juce_gui_basics/native/accessibility/juce_win32_UIASelectionProvider.h View File

@@ -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

+ 664
- 0
modules/juce_gui_basics/native/accessibility/juce_win32_UIATextProvider.h View File

@@ -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

+ 80
- 0
modules/juce_gui_basics/native/accessibility/juce_win32_UIAToggleProvider.h View File

@@ -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

+ 125
- 0
modules/juce_gui_basics/native/accessibility/juce_win32_UIATransformProvider.h View File

@@ -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

+ 121
- 0
modules/juce_gui_basics/native/accessibility/juce_win32_UIAValueProvider.h View File

@@ -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

+ 197
- 0
modules/juce_gui_basics/native/accessibility/juce_win32_UIAWindowProvider.h View File

@@ -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

+ 158
- 0
modules/juce_gui_basics/native/accessibility/juce_win32_WindowsUIAWrapper.h View File

@@ -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

+ 157
- 97
modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm View File

@@ -56,26 +56,6 @@ namespace juce
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, class NSViewComponentPeer : public ComponentPeer,
private Timer private Timer
@@ -124,7 +104,7 @@ public:
{ {
r.origin.x = (CGFloat) component.getX(); r.origin.x = (CGFloat) component.getX();
r.origin.y = (CGFloat) component.getY(); r.origin.y = (CGFloat) component.getY();
flipScreenRect (r);
r = flippedScreenRect (r);
window = [createWindowInstance() initWithContentRect: r window = [createWindowInstance() initWithContentRect: r
styleMask: getNSWindowStyleMask (windowStyleFlags) styleMask: getNSWindowStyleMask (windowStyleFlags)
@@ -323,7 +303,7 @@ public:
r = [[view superview] convertRect: r toView: nil]; r = [[view superview] convertRect: r toView: nil];
r = [viewWindow convertRectToScreen: r]; r = [viewWindow convertRectToScreen: r];
flipScreenRect (r);
r = flippedScreenRect (r);
} }
return convertToRectInt (r); return convertToRectInt (r);
@@ -1669,68 +1649,100 @@ const SEL NSViewComponentPeer::becomeKeySelector = @selector (becomeKey:);
JUCE_END_IGNORE_WARNINGS_GCC_LIKE JUCE_END_IGNORE_WARNINGS_GCC_LIKE
//============================================================================== //==============================================================================
struct JuceNSViewClass : public ObjCClass<NSView>
template <typename Base>
struct NSViewComponentPeerWrapper : public Base
{ {
JuceNSViewClass() : ObjCClass<NSView> ("JUCEView_")
{
addIvar<NSViewComponentPeer*> ("owner");
addMethod (@selector (isOpaque), isOpaque, "c@:");
addMethod (@selector (drawRect:), drawRect, "v@:", @encode (NSRect));
addMethod (@selector (mouseDown:), mouseDown, "v@:@");
addMethod (@selector (mouseUp:), mouseUp, "v@:@");
addMethod (@selector (mouseDragged:), mouseDragged, "v@:@");
addMethod (@selector (mouseMoved:), mouseMoved, "v@:@");
addMethod (@selector (mouseEntered:), mouseEntered, "v@:@");
addMethod (@selector (mouseExited:), mouseExited, "v@:@");
addMethod (@selector (rightMouseDown:), mouseDown, "v@:@");
addMethod (@selector (rightMouseDragged:), mouseDragged, "v@:@");
addMethod (@selector (rightMouseUp:), mouseUp, "v@:@");
addMethod (@selector (otherMouseDown:), mouseDown, "v@:@");
addMethod (@selector (otherMouseDragged:), mouseDragged, "v@:@");
addMethod (@selector (otherMouseUp:), mouseUp, "v@:@");
addMethod (@selector (scrollWheel:), scrollWheel, "v@:@");
addMethod (@selector (magnifyWithEvent:), magnify, "v@:@");
addMethod (@selector (acceptsFirstMouse:), acceptsFirstMouse, "c@:@");
addMethod (@selector (windowWillMiniaturize:), windowWillMiniaturize, "v@:@");
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@:@");
addMethod (@selector (keyUp:), keyUp, "v@:@");
addMethod (@selector (insertText:), insertText, "v@:@");
addMethod (@selector (doCommandBySelector:), doCommandBySelector, "v@::");
addMethod (@selector (setMarkedText:selectedRange:), setMarkedText, "v@:@", @encode (NSRange));
addMethod (@selector (unmarkText), unmarkText, "v@:");
addMethod (@selector (hasMarkedText), hasMarkedText, "c@:");
addMethod (@selector (conversationIdentifier), conversationIdentifier, "l@:");
addMethod (@selector (attributedSubstringFromRange:), attributedSubstringFromRange, "@@:", @encode (NSRange));
addMethod (@selector (markedRange), markedRange, @encode (NSRange), "@:");
addMethod (@selector (selectedRange), selectedRange, @encode (NSRange), "@:");
addMethod (@selector (firstRectForCharacterRange:), firstRectForCharacterRange, @encode (NSRect), "@:", @encode (NSRange));
addMethod (@selector (characterIndexForPoint:), characterIndexForPoint, "L@:", @encode (NSPoint));
addMethod (@selector (validAttributesForMarkedText), validAttributesForMarkedText, "@@:");
addMethod (@selector (flagsChanged:), flagsChanged, "v@:@");
addMethod (@selector (becomeFirstResponder), becomeFirstResponder, "c@:");
addMethod (@selector (resignFirstResponder), resignFirstResponder, "c@:");
addMethod (@selector (acceptsFirstResponder), acceptsFirstResponder, "c@:");
addMethod (@selector (draggingEntered:), draggingEntered, @encode (NSDragOperation), "@:@");
addMethod (@selector (draggingUpdated:), draggingUpdated, @encode (NSDragOperation), "@:@");
addMethod (@selector (draggingEnded:), draggingEnded, "v@:@");
addMethod (@selector (draggingExited:), draggingExited, "v@:@");
addMethod (@selector (prepareForDragOperation:), prepareForDragOperation, "c@:@");
addMethod (@selector (performDragOperation:), performDragOperation, "c@:@");
addMethod (@selector (concludeDragOperation:), concludeDragOperation, "v@:@");
addMethod (@selector (paste:), paste, "v@:@");
addMethod (@selector (copy:), copy, "v@:@");
addMethod (@selector (cut:), cut, "v@:@");
addMethod (@selector (selectAll:), selectAll, "v@:@");
explicit NSViewComponentPeerWrapper (const char* baseName)
: Base (baseName)
{
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@:@");
addMethod (@selector (mouseUp:), mouseUp, "v@:@");
addMethod (@selector (mouseDragged:), mouseDragged, "v@:@");
addMethod (@selector (mouseMoved:), mouseMoved, "v@:@");
addMethod (@selector (mouseEntered:), mouseEntered, "v@:@");
addMethod (@selector (mouseExited:), mouseExited, "v@:@");
addMethod (@selector (rightMouseDown:), mouseDown, "v@:@");
addMethod (@selector (rightMouseDragged:), mouseDragged, "v@:@");
addMethod (@selector (rightMouseUp:), mouseUp, "v@:@");
addMethod (@selector (otherMouseDown:), mouseDown, "v@:@");
addMethod (@selector (otherMouseDragged:), mouseDragged, "v@:@");
addMethod (@selector (otherMouseUp:), mouseUp, "v@:@");
addMethod (@selector (scrollWheel:), scrollWheel, "v@:@");
addMethod (@selector (magnifyWithEvent:), magnify, "v@:@");
addMethod (@selector (acceptsFirstMouse:), acceptsFirstMouse, "c@:@");
addMethod (@selector (windowWillMiniaturize:), windowWillMiniaturize, "v@:@");
addMethod (@selector (windowDidDeminiaturize:), windowDidDeminiaturize, "v@:@");
addMethod (@selector (wantsDefaultClipping), wantsDefaultClipping, "c@:");
addMethod (@selector (worksWhenModal), worksWhenModal, "c@:");
addMethod (@selector (viewDidMoveToWindow), viewDidMoveToWindow, "v@:");
addMethod (@selector (viewWillDraw), viewWillDraw, "v@:");
addMethod (@selector (keyDown:), keyDown, "v@:@");
addMethod (@selector (keyUp:), keyUp, "v@:@");
addMethod (@selector (insertText:), insertText, "v@:@");
addMethod (@selector (doCommandBySelector:), doCommandBySelector, "v@::");
addMethod (@selector (setMarkedText:selectedRange:), setMarkedText, "v@:@", @encode (NSRange));
addMethod (@selector (unmarkText), unmarkText, "v@:");
addMethod (@selector (hasMarkedText), hasMarkedText, "c@:");
addMethod (@selector (conversationIdentifier), conversationIdentifier, "l@:");
addMethod (@selector (attributedSubstringFromRange:), attributedSubstringFromRange, "@@:", @encode (NSRange));
addMethod (@selector (markedRange), markedRange, @encode (NSRange), "@:");
addMethod (@selector (selectedRange), selectedRange, @encode (NSRange), "@:");
addMethod (@selector (firstRectForCharacterRange:), firstRectForCharacterRange, @encode (NSRect), "@:", @encode (NSRange));
addMethod (@selector (characterIndexForPoint:), characterIndexForPoint, "L@:", @encode (NSPoint));
addMethod (@selector (validAttributesForMarkedText), validAttributesForMarkedText, "@@:");
addMethod (@selector (flagsChanged:), flagsChanged, "v@:@");
addMethod (@selector (becomeFirstResponder), becomeFirstResponder, "c@:");
addMethod (@selector (resignFirstResponder), resignFirstResponder, "c@:");
addMethod (@selector (acceptsFirstResponder), acceptsFirstResponder, "c@:");
addMethod (@selector (draggingEntered:), draggingEntered, @encode (NSDragOperation), "@:@");
addMethod (@selector (draggingUpdated:), draggingUpdated, @encode (NSDragOperation), "@:@");
addMethod (@selector (draggingEnded:), draggingEnded, "v@:@");
addMethod (@selector (draggingExited:), draggingExited, "v@:@");
addMethod (@selector (prepareForDragOperation:), prepareForDragOperation, "c@:@");
addMethod (@selector (performDragOperation:), performDragOperation, "c@:@");
addMethod (@selector (concludeDragOperation:), concludeDragOperation, "v@:@");
addMethod (@selector (paste:), paste, "v@:@");
addMethod (@selector (copy:), copy, "v@:@");
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 (@selector (isFlipped), isFlipped, "c@:");
@@ -1746,11 +1758,6 @@ struct JuceNSViewClass : public ObjCClass<NSView>
} }
private: private:
static NSViewComponentPeer* getOwner (id self)
{
return getIvar<NSViewComponentPeer*> (self, "owner");
}
static void mouseDown (id self, SEL s, NSEvent* ev) static void mouseDown (id self, SEL s, NSEvent* ev)
{ {
if (JUCEApplicationBase::isStandaloneApp()) if (JUCEApplicationBase::isStandaloneApp())
@@ -2072,15 +2079,47 @@ private:
} }
static void concludeDragOperation (id, SEL, id<NSDraggingInfo>) {} 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 (canBecomeKeyWindow), canBecomeKeyWindow, "c@:");
addMethod (@selector (canBecomeMainWindow), canBecomeMainWindow, "c@:"); addMethod (@selector (canBecomeMainWindow), canBecomeMainWindow, "c@:");
addMethod (@selector (becomeKeyWindow), becomeKeyWindow, "v@:"); addMethod (@selector (becomeKeyWindow), becomeKeyWindow, "v@:");
@@ -2096,6 +2135,12 @@ struct JuceNSWindowClass : public ObjCClass<NSWindow>
addMethod (@selector (window:shouldPopUpDocumentPathMenu:), shouldPopUpPathMenu, "B@:@", @encode (NSMenu*)); addMethod (@selector (window:shouldPopUpDocumentPathMenu:), shouldPopUpPathMenu, "B@:@", @encode (NSMenu*));
addMethod (@selector (isFlipped), isFlipped, "c@:"); 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:), addMethod (@selector (window:shouldDragDocumentWithEvent:from:withPasteboard:),
shouldAllowIconDrag, "B@:@", @encode (NSEvent*), @encode (NSPoint), @encode (NSPasteboard*)); shouldAllowIconDrag, "B@:@", @encode (NSEvent*), @encode (NSPoint), @encode (NSPasteboard*));
@@ -2105,11 +2150,6 @@ struct JuceNSWindowClass : public ObjCClass<NSWindow>
} }
private: private:
static NSViewComponentPeer* getOwner (id self)
{
return getIvar<NSViewComponentPeer*> (self, "owner");
}
//============================================================================== //==============================================================================
static BOOL isFlipped (id, SEL) { return true; } static BOOL isFlipped (id, SEL) { return true; }
@@ -2249,6 +2289,26 @@ private:
return false; 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() NSView* NSViewComponentPeer::createViewInstance()


+ 37
- 5
modules/juce_gui_basics/native/juce_win32_Windowing.cpp View File

@@ -63,6 +63,14 @@ static bool shouldDeactivateTitleBar = true;
void* getUser32Function (const char*); void* getUser32Function (const char*);
namespace WindowsAccessibility
{
void initialiseUIAWrapper();
long getUiaRootObjectId();
bool handleWmGetObject (AccessibilityHandler*, WPARAM, LPARAM, LRESULT*);
void revokeUIAMapEntriesForWindow (HWND);
}
#if JUCE_DEBUG #if JUCE_DEBUG
int numActiveScopedDpiAwarenessDisablers = 0; int numActiveScopedDpiAwarenessDisablers = 0;
bool isInScopedDPIAwarenessDisabler() { return numActiveScopedDpiAwarenessDisablers > 0; } bool isInScopedDPIAwarenessDisabler() { return numActiveScopedDpiAwarenessDisablers > 0; }
@@ -1372,12 +1380,14 @@ public:
parentToAddTo (parent), parentToAddTo (parent),
currentRenderingEngine (softwareRenderingEngine) currentRenderingEngine (softwareRenderingEngine)
{ {
// make sure that the UIA wrapper singleton is loaded
WindowsAccessibility::initialiseUIAWrapper();
callFunctionIfNotLocked (&createWindowCallback, this); callFunctionIfNotLocked (&createWindowCallback, this);
setTitle (component.getName()); setTitle (component.getName());
updateShadower(); updateShadower();
// make sure that the on-screen keyboard code is loaded
OnScreenKeyboard::getInstance(); OnScreenKeyboard::getInstance();
getNativeRealtimeModifiers = [] getNativeRealtimeModifiers = []
@@ -1397,13 +1407,15 @@ public:
~HWNDComponentPeer() ~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; shadower = nullptr;
currentTouches.deleteAllTouchesForPeer (this); 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); callFunctionIfNotLocked (&destroyWindowCallback, (void*) hwnd);
if (currentWindowIcon != nullptr) if (currentWindowIcon != nullptr)
@@ -1989,6 +2001,8 @@ private:
double scaleFactor = 1.0; double scaleFactor = 1.0;
bool isInDPIChange = false; bool isInDPIChange = false;
bool isAccessibilityActive = false;
//============================================================================== //==============================================================================
static MultiTouchMapper<DWORD> currentTouches; static MultiTouchMapper<DWORD> currentTouches;
@@ -3907,6 +3921,24 @@ private:
case WM_GETDLGCODE: case WM_GETDLGCODE:
return DLGC_WANTALLKEYS; 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: default:
break; break;
} }


+ 1
- 1
modules/juce_gui_basics/properties/juce_PropertyPanel.cpp View File

@@ -204,7 +204,7 @@ void PropertyPanel::init()
addAndMakeVisible (viewport); addAndMakeVisible (viewport);
viewport.setViewedComponent (propertyHolderComponent = new PropertyHolderComponent()); viewport.setViewedComponent (propertyHolderComponent = new PropertyHolderComponent());
viewport.setFocusContainer (true);
viewport.setFocusContainerType (FocusContainerType::keyboardFocusContainer);
} }
PropertyPanel::~PropertyPanel() PropertyPanel::~PropertyPanel()


+ 7
- 0
modules/juce_gui_basics/widgets/juce_ComboBox.cpp View File

@@ -424,6 +424,7 @@ void ComboBox::lookAndFeelChanged()
label->onTextChange = [this] { triggerAsyncUpdate(); }; label->onTextChange = [this] { triggerAsyncUpdate(); };
label->addMouseListener (this, false); label->addMouseListener (this, false);
label->setAccessible (labelEditableState == labelIsEditable);
label->setColour (Label::backgroundColourId, Colours::transparentBlack); label->setColour (Label::backgroundColourId, Colours::transparentBlack);
label->setColour (Label::textColourId, findColour (ComboBox::textColourId)); 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::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); } 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 } // namespace juce

+ 2
- 0
modules/juce_gui_basics/widgets/juce_ComboBox.h View File

@@ -419,6 +419,8 @@ public:
void valueChanged (Value&) override; void valueChanged (Value&) override;
/** @internal */ /** @internal */
void parentHierarchyChanged() override; void parentHierarchyChanged() override;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
// These methods' bool parameters have changed: see their new method signatures. // These methods' bool parameters have changed: see their new method signatures.
JUCE_DEPRECATED (void clear (bool)); JUCE_DEPRECATED (void clear (bool));


+ 6
- 0
modules/juce_gui_basics/widgets/juce_ImageComponent.cpp View File

@@ -80,4 +80,10 @@ void ImageComponent::paint (Graphics& g)
g.drawImage (image, getLocalBounds().toFloat(), placement); g.drawImage (image, getLocalBounds().toFloat(), placement);
} }
//==============================================================================
std::unique_ptr<AccessibilityHandler> ImageComponent::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::image);
}
} // namespace juce } // namespace juce

+ 2
- 0
modules/juce_gui_basics/widgets/juce_ImageComponent.h View File

@@ -68,6 +68,8 @@ public:
//============================================================================== //==============================================================================
/** @internal */ /** @internal */
void paint (Graphics&) override; void paint (Graphics&) override;
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
private: private:
Image image; Image image;


+ 51
- 11
modules/juce_gui_basics/widgets/juce_Label.cpp View File

@@ -105,8 +105,13 @@ void Label::setEditable (bool editOnSingleClick,
editDoubleClick = editOnDoubleClick; editDoubleClick = editOnDoubleClick;
lossOfFocusDiscardsChanges = lossOfFocusDiscards; 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) void Label::setJustificationType (Justification newJustification)
@@ -221,6 +226,7 @@ void Label::showEditor()
if (editor == nullptr) if (editor == nullptr)
{ {
editor.reset (createEditorComponent()); editor.reset (createEditorComponent());
editor->setSize (10, 10);
addAndMakeVisible (editor.get()); addAndMakeVisible (editor.get());
editor->setText (getText(), false); editor->setText (getText(), false);
editor->setKeyboardType (keyboardType); editor->setKeyboardType (keyboardType);
@@ -351,7 +357,9 @@ void Label::mouseDoubleClick (const MouseEvent& e)
if (editDoubleClick if (editDoubleClick
&& isEnabled() && isEnabled()
&& ! e.mods.isPopupMenu()) && ! e.mods.isPopupMenu())
{
showEditor(); showEditor();
}
} }
void Label::resized() void Label::resized()
@@ -364,8 +372,11 @@ void Label::focusGained (FocusChangeType cause)
{ {
if (editSingleClick if (editSingleClick
&& isEnabled() && isEnabled()
&& cause == focusChangedByTabKey)
&& (cause == focusChangedByTabKey
|| (cause == focusChangedDirectly && ! isCurrentlyModal())))
{
showEditor(); showEditor();
}
} }
void Label::enablementChanged() void Label::enablementChanged()
@@ -393,21 +404,45 @@ void Label::setMinimumHorizontalScale (const float newScale)
class LabelKeyboardFocusTraverser : public KeyboardFocusTraverser class LabelKeyboardFocusTraverser : public KeyboardFocusTraverser
{ {
public: 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;
};
Component* getNextComponent (Component* c) override { return KeyboardFocusTraverser::getNextComponent (getComp (c)); }
Component* getPreviousComponent (Component* c) override { return KeyboardFocusTraverser::getPreviousComponent (getComp (c)); }
if (auto* container = getContainer())
KeyboardFocusTraverser::getDefaultComponent (container);
static Component* getComp (Component* current)
return nullptr;
}
Component* getNextComponent (Component* c) override { return KeyboardFocusTraverser::getNextComponent (getComp (c)); }
Component* getPreviousComponent (Component* c) override { return KeyboardFocusTraverser::getPreviousComponent (getComp (c)); }
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); textEditorTextChanged (ed);
} }
std::unique_ptr<AccessibilityHandler> Label::createAccessibilityHandler()
{
return std::make_unique<LabelAccessibilityHandler> (*this);
}
} // namespace juce } // namespace juce

+ 3
- 1
modules/juce_gui_basics/widgets/juce_Label.h View File

@@ -323,7 +323,7 @@ protected:
/** @internal */ /** @internal */
void enablementChanged() override; void enablementChanged() override;
/** @internal */ /** @internal */
KeyboardFocusTraverser* createFocusTraverser() override;
std::unique_ptr<ComponentTraverser> createKeyboardFocusTraverser() override;
/** @internal */ /** @internal */
void textEditorTextChanged (TextEditor&) override; void textEditorTextChanged (TextEditor&) override;
/** @internal */ /** @internal */
@@ -338,6 +338,8 @@ protected:
void valueChanged (Value&) override; void valueChanged (Value&) override;
/** @internal */ /** @internal */
void callChangeListeners(); void callChangeListeners();
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
private: private:
//============================================================================== //==============================================================================


+ 120
- 14
modules/juce_gui_basics/widgets/juce_ListBox.cpp View File

@@ -26,6 +26,34 @@
namespace juce 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, class ListBox::RowComponent : public Component,
public TooltipClient public TooltipClient
{ {
@@ -35,16 +63,28 @@ public:
void paint (Graphics& g) override void paint (Graphics& g) override
{ {
if (auto* m = owner.getModel()) 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) 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(); repaint();
row = newRow;
selected = nowSelected;
if (rowHasChanged)
row = newRow;
if (selectionHasChanged)
{
isSelected = nowSelected;
if (auto* handler = getAccessibilityHandler())
isSelected ? handler->grabFocus() : handler->giveAwayFocus();
}
} }
if (auto* m = owner.getModel()) if (auto* m = owner.getModel())
@@ -57,6 +97,9 @@ public:
{ {
addAndMakeVisible (customComponent.get()); addAndMakeVisible (customComponent.get());
customComponent->setBounds (getLocalBounds()); customComponent->setBounds (getLocalBounds());
if (customComponent->getAccessibilityHandler() != nullptr)
invalidateAccessibilityHandler();
} }
} }
} }
@@ -85,7 +128,7 @@ public:
if (isEnabled()) if (isEnabled())
{ {
if (owner.selectOnMouseDown && ! (selected || isInDragToScrollViewport()))
if (owner.selectOnMouseDown && ! (isSelected || isInDragToScrollViewport()))
performSelection (e, false); performSelection (e, false);
else else
selectRowOnMouseUp = true; selectRowOnMouseUp = true;
@@ -150,10 +193,62 @@ public:
return {}; 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; ListBox& owner;
std::unique_ptr<Component> customComponent; std::unique_ptr<Component> customComponent;
int row = -1; 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) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RowComponent)
}; };
@@ -166,10 +261,13 @@ public:
ListViewport (ListBox& lb) : owner (lb) ListViewport (ListBox& lb) : owner (lb)
{ {
setWantsKeyboardFocus (false); setWantsKeyboardFocus (false);
setAccessible (false);
auto content = new Component();
setViewedComponent (content);
auto content = std::make_unique<Component>();
content->setWantsKeyboardFocus (false); content->setWantsKeyboardFocus (false);
content->setAccessible (false);
setViewedComponent (content.release());
} }
RowComponent* getComponentForRow (const int row) const noexcept RowComponent* getComponentForRow (const int row) const noexcept
@@ -233,13 +331,12 @@ public:
auto y = getViewPositionY(); auto y = getViewPositionY();
auto w = content.getWidth(); auto w = content.getWidth();
const int numNeeded = 2 + getMaximumVisibleHeight() / rowH;
const int numNeeded = 4 + getMaximumVisibleHeight() / rowH;
rows.removeRange (numNeeded, rows.size()); rows.removeRange (numNeeded, rows.size());
while (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); content.addAndMakeVisible (newRow);
} }
@@ -247,9 +344,11 @@ public:
firstWholeIndex = (y + rowH - 1) / rowH; firstWholeIndex = (y + rowH - 1) / rowH;
lastWholeIndex = (y + getMaximumVisibleHeight() - 1) / rowH; lastWholeIndex = (y + getMaximumVisibleHeight() - 1) / rowH;
auto startIndex = jmax (0, firstIndex - 1);
for (int i = 0; i < numNeeded; ++i) for (int i = 0; i < numNeeded; ++i)
{ {
const int row = i + firstIndex;
const int row = i + startIndex;
if (auto* rowComp = getComponentForRow (row)) if (auto* rowComp = getComponentForRow (row))
{ {
@@ -379,8 +478,9 @@ ListBox::ListBox (const String& name, ListBoxModel* const m)
viewport.reset (new ListViewport (*this)); viewport.reset (new ListViewport (*this));
addAndMakeVisible (viewport.get()); addAndMakeVisible (viewport.get());
ListBox::setWantsKeyboardFocus (true);
ListBox::colourChanged();
setWantsKeyboardFocus (true);
setFocusContainerType (FocusContainerType::focusContainer);
colourChanged();
} }
ListBox::~ListBox() 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) Component* ListBoxModel::refreshComponentForRow (int, bool, Component* existingComponentToUpdate)
{ {
@@ -946,6 +1051,7 @@ Component* ListBoxModel::refreshComponentForRow (int, bool, Component* existingC
return nullptr; return nullptr;
} }
String ListBoxModel::getNameForRow (int rowNumber) { return "Row " + String (rowNumber + 1); }
void ListBoxModel::listBoxItemClicked (int, const MouseEvent&) {} void ListBoxModel::listBoxItemClicked (int, const MouseEvent&) {}
void ListBoxModel::listBoxItemDoubleClicked (int, const MouseEvent&) {} void ListBoxModel::listBoxItemDoubleClicked (int, const MouseEvent&) {}
void ListBoxModel::backgroundClicked (const MouseEvent&) {} void ListBoxModel::backgroundClicked (const MouseEvent&) {}


+ 8
- 0
modules/juce_gui_basics/widgets/juce_ListBox.h View File

@@ -86,6 +86,12 @@ public:
virtual Component* refreshComponentForRow (int rowNumber, bool isRowSelected, virtual Component* refreshComponentForRow (int rowNumber, bool isRowSelected,
Component* existingComponentToUpdate); 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. /** This can be overridden to react to the user clicking on a row.
@see listBoxItemDoubleClicked @see listBoxItemDoubleClicked
*/ */
@@ -565,6 +571,8 @@ public:
/** @internal */ /** @internal */
void startDragAndDrop (const MouseEvent&, const SparseSet<int>& rowsToDrag, void startDragAndDrop (const MouseEvent&, const SparseSet<int>& rowsToDrag,
const var& dragDescription, bool allowDraggingToOtherWindows); const var& dragDescription, bool allowDraggingToOtherWindows);
/** @internal */
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
private: private:
//============================================================================== //==============================================================================


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

Loading…
Cancel
Save