| @@ -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' | ||||
| @@ -0,0 +1,51 @@ | |||||
| # JUCE Accessibility | |||||
| ## What is supported? | |||||
| Currently JUCE supports VoiceOver on macOS and Narrator on Windows. The JUCE | |||||
| accessibility API exposes the following to these clients: | |||||
| - Title, description, and help text for UI elements | |||||
| - Programmatic access to UI elements and text | |||||
| - Interaction with UI elements | |||||
| - Full UI keyboard navigation | |||||
| - Posting notifications to listening clients | |||||
| ## Customising Behaviour | |||||
| By default any visible and enabled `Component` is accessible to screen reader | |||||
| clients and exposes some basic information such as title, description, help | |||||
| text and its position in the hierarchy of UI elements. | |||||
| The `setTitle()`, `setDescription()` and `setHelpText()` methods can be used | |||||
| to customise the text that will be read out by accessibility clients when | |||||
| interacting with UI elements and the `setExplicitFocusOrder()`, | |||||
| `setFocusContainer()` and `createFocusTraverser()` methods can be used to | |||||
| control the parent/child relationships and the order of navigation between UI | |||||
| elements. | |||||
| ## Custom Components | |||||
| For further customisation of accessibility behaviours the `AccessibilityHandler` | |||||
| class provides a unified API to the underlying native accessibility libraries. | |||||
| This class wraps a component with a given role specified by the | |||||
| `AccessibilityRole` enum and takes a list of optional actions and interfaces to | |||||
| provide programmatic access and control over the UI element. Its state is used | |||||
| to convey further information to accessibility clients via the | |||||
| `getCurrentState()` method. | |||||
| To implement the desired behaviours for a custom component, subclass | |||||
| `AccessibilityHandler` and return an instance of this from the | |||||
| `Component::createAccessibilityHandler()` method. | |||||
| Examples of some common UI element handlers for existing JUCE widgets can be | |||||
| found in the [`widget_handlers`](/modules/juce_gui_basics/accessibility/widget_handlers) directory. | |||||
| ## Further Reading | |||||
| - [NSAccessibility protocol](https://developer.apple.com/documentation/appkit/nsaccessibility?language=objc) | |||||
| - [UI Automation for Win32 applications](https://docs.microsoft.com/en-us/windows/win32/winauto/entry-uiauto-win32) | |||||
| - A talk giving an overview of this feature from ADC 2020 can be found on | |||||
| YouTube at https://youtu.be/BqrEv4ApH3U | |||||
| @@ -29,6 +29,16 @@ namespace juce | |||||
| { | { | ||||
| //============================================================================== | //============================================================================== | ||||
| inline Range<int> nsRangeToJuce (NSRange range) | |||||
| { | |||||
| return { (int) range.location, (int) (range.location + range.length) }; | |||||
| } | |||||
| inline NSRange juceRangeToNS (Range<int> range) | |||||
| { | |||||
| return NSMakeRange ((NSUInteger) range.getStart(), (NSUInteger) range.getLength()); | |||||
| } | |||||
| inline String nsStringToJuce (NSString* s) | inline String nsStringToJuce (NSString* s) | ||||
| { | { | ||||
| return CharPointer_UTF8 ([s UTF8String]); | return CharPointer_UTF8 ([s UTF8String]); | ||||
| @@ -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> | ||||
| @@ -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); | ||||
| @@ -0,0 +1,119 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| /** An action that can be performed by an accessible UI element. | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| enum class AccessibilityActionType | |||||
| { | |||||
| /** Represents a "press" action. | |||||
| This will be called when the user "clicks" the UI element using an | |||||
| accessibility client. | |||||
| */ | |||||
| press, | |||||
| /** Represents a "toggle" action. | |||||
| This will be called when the user toggles the state of a UI element, | |||||
| for example a toggle button or the selection of a list item. | |||||
| */ | |||||
| toggle, | |||||
| /** Indicates that the UI element has received focus. | |||||
| This will be called when a UI element receives focus from an accessibility | |||||
| client, or keyboard focus from the application. | |||||
| */ | |||||
| focus, | |||||
| /** Represents the user showing a contextual menu for a UI element. | |||||
| This will be called for UI elements which expand and collapse to | |||||
| show contextual information or menus, or show a popup. | |||||
| */ | |||||
| showMenu | |||||
| }; | |||||
| /** A simple wrapper for building a collection of supported accessibility actions | |||||
| and corresponding callbacks for a UI element. | |||||
| Pass one of these when constructing an `AccessibilityHandler` to enable users | |||||
| to interact with a UI element via the supported actions. | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| class JUCE_API AccessibilityActions | |||||
| { | |||||
| public: | |||||
| /** Constructor. | |||||
| Creates a default AccessibilityActions object with no action callbacks. | |||||
| */ | |||||
| AccessibilityActions() = default; | |||||
| /** Adds an action. | |||||
| When the user performs this action with an accessibility client | |||||
| `actionCallback` will be called. | |||||
| Returns a reference to itself so that several calls can be chained. | |||||
| */ | |||||
| AccessibilityActions& addAction (AccessibilityActionType type, | |||||
| std::function<void()> actionCallback) | |||||
| { | |||||
| actionMap[type] = std::move (actionCallback); | |||||
| return *this; | |||||
| } | |||||
| /** Returns true if the specified action is supported. */ | |||||
| bool contains (AccessibilityActionType type) const | |||||
| { | |||||
| return actionMap.find (type) != actionMap.end(); | |||||
| } | |||||
| /** If an action has been registered for the provided action type, invokes the | |||||
| action and returns true. Otherwise, returns false. | |||||
| */ | |||||
| bool invoke (AccessibilityActionType type) const | |||||
| { | |||||
| auto iter = actionMap.find (type); | |||||
| if (iter == actionMap.end()) | |||||
| return false; | |||||
| iter->second(); | |||||
| return true; | |||||
| } | |||||
| private: | |||||
| std::map<AccessibilityActionType, std::function<void()>> actionMap; | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,75 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| /** A list of events that can be notified to any subscribed accessibility clients. | |||||
| To post a notification, call `AccessibilityHandler::notifyAccessibilityEvent` | |||||
| on the associated handler with the appropriate `AccessibilityEvent` type and | |||||
| listening clients will be notified. | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| enum class AccessibilityEvent | |||||
| { | |||||
| /** Indicates that the UI element's value has changed. | |||||
| This should be called on the handler that implements `AccessibilityValueInterface` | |||||
| for the UI element that has changed. | |||||
| */ | |||||
| valueChanged, | |||||
| /** Indicates that the structure of the UI elements has changed in a | |||||
| significant way. | |||||
| This should be posted on the top-level handler whose structure has changed. | |||||
| */ | |||||
| structureChanged, | |||||
| /** Indicates that the selection of a text element has changed. | |||||
| This should be called on the handler that implements `AccessibilityTextInterface` | |||||
| for the text element that has changed. | |||||
| */ | |||||
| textSelectionChanged, | |||||
| /** Indicates that the visible text of a text element has changed. | |||||
| This should be called on the handler that implements `AccessibilityTextInterface` | |||||
| for the text element that has changed. | |||||
| */ | |||||
| textChanged, | |||||
| /** Indicates that the selection of rows in a list or table has changed. | |||||
| This should be called on the handler that implements `AccessibilityTableInterface` | |||||
| for the UI element that has changed. | |||||
| */ | |||||
| rowSelectionChanged | |||||
| }; | |||||
| } | |||||
| @@ -0,0 +1,70 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| /** The list of available roles for an AccessibilityHandler object. | |||||
| When creating a custom AccessibilityHandler you should select the role that | |||||
| best describes the UI element being represented. | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| enum class AccessibilityRole | |||||
| { | |||||
| button, | |||||
| toggleButton, | |||||
| radioButton, | |||||
| comboBox, | |||||
| image, | |||||
| slider, | |||||
| staticText, | |||||
| editableText, | |||||
| menuItem, | |||||
| menuBar, | |||||
| popupMenu, | |||||
| table, | |||||
| tableHeader, | |||||
| column, | |||||
| row, | |||||
| cell, | |||||
| hyperlink, | |||||
| list, | |||||
| listItem, | |||||
| tree, | |||||
| treeItem, | |||||
| progressBar, | |||||
| group, | |||||
| dialogWindow, | |||||
| window, | |||||
| scrollBar, | |||||
| tooltip, | |||||
| splashScreen, | |||||
| ignored, | |||||
| unspecified | |||||
| }; | |||||
| } | |||||
| @@ -0,0 +1,61 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| /** An abstract interface which represents a UI element that supports a cell interface. | |||||
| This typically represents a single cell inside of a UI element which implements an | |||||
| AccessibilityTableInterface. | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| class JUCE_API AccessibilityCellInterface | |||||
| { | |||||
| public: | |||||
| /** Destructor. */ | |||||
| virtual ~AccessibilityCellInterface() = default; | |||||
| /** Returns the column index of the cell in the table. */ | |||||
| virtual int getColumnIndex() const = 0; | |||||
| /** Returns the number of columns occupied by the cell in the table. */ | |||||
| virtual int getColumnSpan() const = 0; | |||||
| /** Returns the row index of the cell in the table. */ | |||||
| virtual int getRowIndex() const = 0; | |||||
| /** Returns the number of rows occupied by the cell in the table. */ | |||||
| virtual int getRowSpan() const = 0; | |||||
| /** Returns the indentation level for the cell. */ | |||||
| virtual int getDisclosureLevel() const = 0; | |||||
| /** Returns the AccessibilityHandler of the table which contains the cell. */ | |||||
| virtual const AccessibilityHandler* getTableHandler() const = 0; | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,54 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| /** An abstract interface which represents a UI element that supports a table interface. | |||||
| Examples of UI elements which typically support a table interface are lists, tables, | |||||
| and trees. | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| class JUCE_API AccessibilityTableInterface | |||||
| { | |||||
| public: | |||||
| /** Destructor. */ | |||||
| virtual ~AccessibilityTableInterface() = default; | |||||
| /** Returns the total number of rows in the table. */ | |||||
| virtual int getNumRows() const = 0; | |||||
| /** Returns the total number of columns in the table. */ | |||||
| virtual int getNumColumns() const = 0; | |||||
| /** Returns the AccessibilityHandler for one of the cells in the table, or | |||||
| nullptr if there is no cell at the specified position. | |||||
| */ | |||||
| virtual const AccessibilityHandler* getCellHandler (int row, int column) const = 0; | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,78 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| /** An abstract interface which represents a UI element that supports a text interface. | |||||
| A UI element can use this interface to provide extended textual information which | |||||
| cannot be conveyed using just the title, description, and help text properties of | |||||
| AccessibilityHandler. This is typically for text that an accessibility client might | |||||
| want to read line-by-line, or provide text selection and input for. | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| class JUCE_API AccessibilityTextInterface | |||||
| { | |||||
| public: | |||||
| /** Destructor. */ | |||||
| virtual ~AccessibilityTextInterface() = default; | |||||
| /** Returns true if the text being displayed is protected and should not be | |||||
| exposed to the user, for example a password entry field. | |||||
| */ | |||||
| virtual bool isDisplayingProtectedText() const = 0; | |||||
| /** Returns the total number of characters in the text element. */ | |||||
| virtual int getTotalNumCharacters() const = 0; | |||||
| /** Returns the range of characters that are currently selected, or an empty | |||||
| range if nothing is selected. | |||||
| */ | |||||
| virtual Range<int> getSelection() const = 0; | |||||
| /** Selects a section of the text. */ | |||||
| virtual void setSelection (Range<int> newRange) = 0; | |||||
| /** Gets the current text insertion position, if supported. */ | |||||
| virtual int getTextInsertionOffset() const = 0; | |||||
| /** Returns a section of text. */ | |||||
| virtual String getText (Range<int> range) const = 0; | |||||
| /** Replaces the text with a new string. */ | |||||
| virtual void setText (const String& newText) = 0; | |||||
| /** Returns the bounding box in screen coordinates for a range of text. | |||||
| As the range may span multiple lines, this method returns a RectangleList. | |||||
| */ | |||||
| virtual RectangleList<int> getTextBounds (Range<int> textRange) const = 0; | |||||
| /** Returns the index of the character at a given position in screen coordinates. */ | |||||
| virtual int getOffsetAtPoint (Point<int> point) const = 0; | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,222 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| /** An abstract interface representing the value of an accessibility element. | |||||
| Values should be used when information needs to be conveyed which cannot | |||||
| be represented by the accessibility element's label alone. For example, a | |||||
| gain slider with the label "Gain" needs to also provide a value for its | |||||
| position whereas a "Save" button does not. | |||||
| This class allows for full control over the value text/numeric conversion, | |||||
| ranged, and read-only properties but in most cases you'll want to use one | |||||
| of the derived classes below which handle some of this for you. | |||||
| @see AccessibilityTextValueInterface, AccessibilityNumericValueInterface, | |||||
| AccessibilityRangedNumericValueInterface | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| class JUCE_API AccessibilityValueInterface | |||||
| { | |||||
| public: | |||||
| /** Destructor. */ | |||||
| virtual ~AccessibilityValueInterface() = default; | |||||
| /** Returns true if the value is read-only and cannot be modified by an | |||||
| accessibility client. | |||||
| @see setValue, setValueAsString | |||||
| */ | |||||
| virtual bool isReadOnly() const = 0; | |||||
| /** Returns the current value as a double. */ | |||||
| virtual double getCurrentValue() const = 0; | |||||
| /** Returns the current value as a String. */ | |||||
| virtual String getCurrentValueAsString() const = 0; | |||||
| /** Sets the current value to a new double value. */ | |||||
| virtual void setValue (double newValue) = 0; | |||||
| /** Sets the current value to a new String value. */ | |||||
| virtual void setValueAsString (const String& newValue) = 0; | |||||
| /** Represents the range of this value, if supported. | |||||
| Return one of these from the `getRange()` method, providing a minimum, | |||||
| maximum, and interval value for the range to indicate that this is a | |||||
| ranged value. | |||||
| The default state is an "invalid" range, indicating that the accessibility | |||||
| element does not support ranged values. | |||||
| @see AccessibilityRangedNumericValueInterface | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| class JUCE_API AccessibleValueRange | |||||
| { | |||||
| public: | |||||
| /** Constructor. | |||||
| Creates a default, "invalid" range that can be returned from | |||||
| `AccessibilityValueInterface::getRange()` to indicate that the value | |||||
| interface does not support ranged values. | |||||
| */ | |||||
| AccessibleValueRange() = default; | |||||
| /** The minimum and maximum values for this range, inclusive. */ | |||||
| struct JUCE_API MinAndMax { double min, max; }; | |||||
| /** Constructor. | |||||
| Creates a valid AccessibleValueRange with the provided minimum, maximum, | |||||
| and interval values. | |||||
| */ | |||||
| AccessibleValueRange (MinAndMax valueRange, double interval) | |||||
| : valid (true), | |||||
| range (valueRange), | |||||
| stepSize (interval) | |||||
| { | |||||
| jassert (range.min < range.max); | |||||
| } | |||||
| /** Returns true if this represents a valid range. */ | |||||
| bool isValid() const noexcept { return valid; } | |||||
| /** Returns the minimum value for this range. */ | |||||
| double getMinimumValue() const noexcept { return range.min; } | |||||
| /** Returns the maxiumum value for this range. */ | |||||
| double getMaximumValue() const noexcept { return range.max; } | |||||
| /** Returns the interval for this range. */ | |||||
| double getInterval() const noexcept { return stepSize; } | |||||
| private: | |||||
| bool valid = false; | |||||
| MinAndMax range {}; | |||||
| double stepSize = 0.0; | |||||
| }; | |||||
| /** If this is a ranged value, this should return a valid AccessibleValueRange | |||||
| object representing the supported numerical range. | |||||
| */ | |||||
| virtual AccessibleValueRange getRange() const = 0; | |||||
| }; | |||||
| //============================================================================== | |||||
| /** A value interface that represents a text value. | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| class JUCE_API AccessibilityTextValueInterface : public AccessibilityValueInterface | |||||
| { | |||||
| public: | |||||
| /** Returns true if the value is read-only and cannot be modified by an | |||||
| accessibility client. | |||||
| @see setValueAsString | |||||
| */ | |||||
| bool isReadOnly() const override = 0; | |||||
| /** Returns the current value. */ | |||||
| String getCurrentValueAsString() const override = 0; | |||||
| /** Sets the current value to a new value. */ | |||||
| void setValueAsString (const String& newValue) override = 0; | |||||
| /** @internal */ | |||||
| double getCurrentValue() const final { return getCurrentValueAsString().getDoubleValue(); } | |||||
| /** @internal */ | |||||
| void setValue (double newValue) final { setValueAsString (String (newValue)); } | |||||
| /** @internal */ | |||||
| AccessibleValueRange getRange() const final { return {}; } | |||||
| }; | |||||
| //============================================================================== | |||||
| /** A value interface that represents a non-ranged numeric value. | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| class JUCE_API AccessibilityNumericValueInterface : public AccessibilityValueInterface | |||||
| { | |||||
| public: | |||||
| /** Returns true if the value is read-only and cannot be modified by an | |||||
| accessibility client. | |||||
| @see setValue | |||||
| */ | |||||
| bool isReadOnly() const override = 0; | |||||
| /** Returns the current value. */ | |||||
| double getCurrentValue() const override = 0; | |||||
| /** Sets the current value to a new value. */ | |||||
| void setValue (double newValue) override = 0; | |||||
| /** @internal */ | |||||
| String getCurrentValueAsString() const final { return String (getCurrentValue()); } | |||||
| /** @internal */ | |||||
| void setValueAsString (const String& newValue) final { setValue (newValue.getDoubleValue()); } | |||||
| /** @internal */ | |||||
| AccessibleValueRange getRange() const final { return {}; } | |||||
| }; | |||||
| //============================================================================== | |||||
| /** A value interface that represents a ranged numeric value. | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| class JUCE_API AccessibilityRangedNumericValueInterface : public AccessibilityValueInterface | |||||
| { | |||||
| public: | |||||
| /** Returns true if the value is read-only and cannot be modified by an | |||||
| accessibility client. | |||||
| @see setValueAsString | |||||
| */ | |||||
| bool isReadOnly() const override = 0; | |||||
| /** Returns the current value. */ | |||||
| double getCurrentValue() const override = 0; | |||||
| /** Sets the current value to a new value. */ | |||||
| void setValue (double newValue) override = 0; | |||||
| /** Returns the range. */ | |||||
| AccessibleValueRange getRange() const override = 0; | |||||
| /** @internal */ | |||||
| String getCurrentValueAsString() const final { return String (getCurrentValue()); } | |||||
| /** @internal */ | |||||
| void setValueAsString (const String& newValue) final { setValue (newValue.getDoubleValue()); } | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,346 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| AccessibilityHandler* AccessibilityHandler::currentlyFocusedHandler = nullptr; | |||||
| enum class InternalAccessibilityEvent | |||||
| { | |||||
| elementCreated, | |||||
| elementDestroyed, | |||||
| focusChanged, | |||||
| windowOpened, | |||||
| windowClosed | |||||
| }; | |||||
| void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent event); | |||||
| inline String getAccessibleApplicationOrPluginName() | |||||
| { | |||||
| #if defined (JucePlugin_Name) | |||||
| return JucePlugin_Name; | |||||
| #else | |||||
| if (auto* app = JUCEApplicationBase::getInstance()) | |||||
| return app->getApplicationName(); | |||||
| return "JUCE Application"; | |||||
| #endif | |||||
| } | |||||
| AccessibilityHandler::AccessibilityHandler (Component& comp, | |||||
| AccessibilityRole accessibilityRole, | |||||
| AccessibilityActions accessibilityActions, | |||||
| Interfaces interfacesIn) | |||||
| : component (comp), | |||||
| typeIndex (typeid (component)), | |||||
| role (accessibilityRole), | |||||
| actions (std::move (accessibilityActions)), | |||||
| interfaces (std::move (interfacesIn)), | |||||
| nativeImpl (createNativeImpl (*this)) | |||||
| { | |||||
| notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::elementCreated); | |||||
| } | |||||
| AccessibilityHandler::~AccessibilityHandler() | |||||
| { | |||||
| giveAwayFocus(); | |||||
| notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::elementDestroyed); | |||||
| } | |||||
| //============================================================================== | |||||
| AccessibleState AccessibilityHandler::getCurrentState() const | |||||
| { | |||||
| auto state = AccessibleState().withFocusable(); | |||||
| return hasFocus (false) ? state.withFocused() : state; | |||||
| } | |||||
| static bool isComponentVisibleWithinWindow (const Component& comp) | |||||
| { | |||||
| if (auto* peer = comp.getPeer()) | |||||
| return ! peer->getAreaCoveredBy (comp).getIntersection (peer->getComponent().getLocalBounds()).isEmpty(); | |||||
| return false; | |||||
| } | |||||
| static bool isComponentVisibleWithinParent (Component* comp) | |||||
| { | |||||
| if (auto* parent = comp->getParentComponent()) | |||||
| { | |||||
| if (comp->getBoundsInParent().getIntersection (parent->getLocalBounds()).isEmpty()) | |||||
| return false; | |||||
| return isComponentVisibleWithinParent (parent); | |||||
| } | |||||
| return true; | |||||
| } | |||||
| bool AccessibilityHandler::isIgnored() const | |||||
| { | |||||
| const auto state = getCurrentState(); | |||||
| return role == AccessibilityRole::ignored | |||||
| || state.isIgnored() | |||||
| || ! component.isShowing() | |||||
| || (! state.isAccessibleOffscreen() | |||||
| && (! isComponentVisibleWithinParent (&component) | |||||
| || ! isComponentVisibleWithinWindow (component))); | |||||
| } | |||||
| //============================================================================== | |||||
| const AccessibilityActions& AccessibilityHandler::getActions() const noexcept | |||||
| { | |||||
| return actions; | |||||
| } | |||||
| AccessibilityValueInterface* AccessibilityHandler::getValueInterface() const | |||||
| { | |||||
| return interfaces.value.get(); | |||||
| } | |||||
| AccessibilityTableInterface* AccessibilityHandler::getTableInterface() const | |||||
| { | |||||
| return interfaces.table.get(); | |||||
| } | |||||
| AccessibilityCellInterface* AccessibilityHandler::getCellInterface() const | |||||
| { | |||||
| return interfaces.cell.get(); | |||||
| } | |||||
| AccessibilityTextInterface* AccessibilityHandler::getTextInterface() const | |||||
| { | |||||
| return interfaces.text.get(); | |||||
| } | |||||
| //============================================================================== | |||||
| static AccessibilityHandler* findEnclosingHandler (Component* comp) | |||||
| { | |||||
| if (comp != nullptr) | |||||
| { | |||||
| if (auto* handler = comp->getAccessibilityHandler()) | |||||
| return handler; | |||||
| return findEnclosingHandler (comp->getParentComponent()); | |||||
| } | |||||
| return nullptr; | |||||
| } | |||||
| static AccessibilityHandler* getUnignoredAncestor (AccessibilityHandler* handler) | |||||
| { | |||||
| while (handler != nullptr | |||||
| && handler->isIgnored() | |||||
| && handler->getParent() != nullptr) | |||||
| { | |||||
| handler = handler->getParent(); | |||||
| } | |||||
| return handler; | |||||
| } | |||||
| static AccessibilityHandler* findFirstUnignoredChild (const std::vector<AccessibilityHandler*>& handlers) | |||||
| { | |||||
| if (! handlers.empty()) | |||||
| { | |||||
| const auto iter = std::find_if (handlers.cbegin(), handlers.cend(), | |||||
| [] (const AccessibilityHandler* handler) { return ! handler->isIgnored(); }); | |||||
| if (iter != handlers.cend()) | |||||
| return *iter; | |||||
| for (auto* handler : handlers) | |||||
| if (auto* unignored = findFirstUnignoredChild (handler->getChildren())) | |||||
| return unignored; | |||||
| } | |||||
| return nullptr; | |||||
| } | |||||
| static AccessibilityHandler* getFirstUnignoredDescendant (AccessibilityHandler* handler) | |||||
| { | |||||
| if (handler != nullptr && handler->isIgnored()) | |||||
| return findFirstUnignoredChild (handler->getChildren()); | |||||
| return handler; | |||||
| } | |||||
| AccessibilityHandler* AccessibilityHandler::getParent() const | |||||
| { | |||||
| if (auto* focusContainer = component.findFocusContainer()) | |||||
| return getUnignoredAncestor (findEnclosingHandler (focusContainer)); | |||||
| return nullptr; | |||||
| } | |||||
| std::vector<AccessibilityHandler*> AccessibilityHandler::getChildren() const | |||||
| { | |||||
| if (! component.isFocusContainer() && component.getParentComponent() != nullptr) | |||||
| return {}; | |||||
| std::vector<AccessibilityHandler*> children; | |||||
| if (auto traverser = component.createFocusTraverser()) | |||||
| { | |||||
| for (auto* focusableChild : traverser->getAllComponents (&component)) | |||||
| { | |||||
| if (auto* handler = findEnclosingHandler (focusableChild)) | |||||
| { | |||||
| if (! isParentOf (handler)) | |||||
| continue; | |||||
| if (auto* unignored = getFirstUnignoredDescendant (handler)) | |||||
| if (std::find (children.cbegin(), children.cend(), unignored) == children.cend()) | |||||
| children.push_back (unignored); | |||||
| } | |||||
| } | |||||
| } | |||||
| return children; | |||||
| } | |||||
| bool AccessibilityHandler::isParentOf (const AccessibilityHandler* possibleChild) const noexcept | |||||
| { | |||||
| while (possibleChild != nullptr) | |||||
| { | |||||
| possibleChild = possibleChild->getParent(); | |||||
| if (possibleChild == this) | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| AccessibilityHandler* AccessibilityHandler::getChildAt (Point<int> screenPoint) | |||||
| { | |||||
| if (auto* comp = Desktop::getInstance().findComponentAt (screenPoint)) | |||||
| if (isParentOf (comp->getAccessibilityHandler())) | |||||
| return getUnignoredAncestor (findEnclosingHandler (comp)); | |||||
| return nullptr; | |||||
| } | |||||
| AccessibilityHandler* AccessibilityHandler::getChildFocus() | |||||
| { | |||||
| return hasFocus (true) ? getUnignoredAncestor (currentlyFocusedHandler) | |||||
| : nullptr; | |||||
| } | |||||
| bool AccessibilityHandler::hasFocus (bool trueIfChildFocused) const | |||||
| { | |||||
| return currentlyFocusedHandler != nullptr | |||||
| && (currentlyFocusedHandler == this | |||||
| || (trueIfChildFocused && isParentOf (currentlyFocusedHandler))); | |||||
| } | |||||
| void AccessibilityHandler::grabFocus() | |||||
| { | |||||
| if (! hasFocus (false)) | |||||
| grabFocusInternal (true); | |||||
| } | |||||
| void AccessibilityHandler::giveAwayFocus() const | |||||
| { | |||||
| if (hasFocus (true)) | |||||
| giveAwayFocusInternal(); | |||||
| } | |||||
| void AccessibilityHandler::grabFocusInternal (bool canTryParent) | |||||
| { | |||||
| if (getCurrentState().isFocusable() && ! isIgnored()) | |||||
| { | |||||
| takeFocus(); | |||||
| return; | |||||
| } | |||||
| if (isParentOf (currentlyFocusedHandler) && ! currentlyFocusedHandler->isIgnored()) | |||||
| return; | |||||
| if (component.isFocusContainer() || component.getParentComponent() == nullptr) | |||||
| { | |||||
| if (auto traverser = component.createFocusTraverser()) | |||||
| { | |||||
| if (auto* defaultComp = traverser->getDefaultComponent (&component)) | |||||
| { | |||||
| if (auto* handler = getUnignoredAncestor (findEnclosingHandler (defaultComp))) | |||||
| { | |||||
| if (isParentOf (handler)) | |||||
| { | |||||
| handler->grabFocusInternal (false); | |||||
| return; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| if (canTryParent) | |||||
| if (auto* parent = getParent()) | |||||
| parent->grabFocusInternal (true); | |||||
| } | |||||
| void AccessibilityHandler::giveAwayFocusInternal() const | |||||
| { | |||||
| currentlyFocusedHandler = nullptr; | |||||
| notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::focusChanged); | |||||
| if (auto* focusedComponent = Component::getCurrentlyFocusedComponent()) | |||||
| if (auto* handler = focusedComponent->getAccessibilityHandler()) | |||||
| handler->grabFocus(); | |||||
| } | |||||
| void AccessibilityHandler::takeFocus() | |||||
| { | |||||
| currentlyFocusedHandler = this; | |||||
| WeakReference<Component> weakComponent (&component); | |||||
| actions.invoke (AccessibilityActionType::focus); | |||||
| if (weakComponent != nullptr | |||||
| && component.getWantsKeyboardFocus() | |||||
| && ! component.hasKeyboardFocus (true)) | |||||
| { | |||||
| component.grabKeyboardFocus(); | |||||
| } | |||||
| notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::focusChanged); | |||||
| } | |||||
| //============================================================================== | |||||
| #if ! (JUCE_MAC || JUCE_WINDOWS) | |||||
| class AccessibilityHandler::AccessibilityNativeImpl { public: AccessibilityNativeImpl (AccessibilityHandler&) {} }; | |||||
| void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent) const {} | |||||
| void AccessibilityHandler::postAnnouncement (const String&, AnnouncementPriority) {} | |||||
| AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const { return nullptr; } | |||||
| AccessibilityHandler::AccessibilityNativeImpl* AccessibilityHandler::createNativeImpl (AccessibilityHandler&) { return nullptr; } | |||||
| void AccessibilityHandler::DestroyNativeImpl::operator() (AccessibilityHandler::AccessibilityNativeImpl*) const noexcept {} | |||||
| void notifyAccessibilityEventInternal (const AccessibilityHandler&, InternalAccessibilityEvent) {} | |||||
| #endif | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,325 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| class AccessibilityNativeHandle; | |||||
| /** Base class for accessible Components. | |||||
| This class wraps a Component and provides methods that allow an accessibility client, | |||||
| such as VoiceOver on macOS, or Narrator on Windows, to control it. | |||||
| It handles hierarchical navigation, properties, state, and various interfaces. | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| class JUCE_API AccessibilityHandler | |||||
| { | |||||
| public: | |||||
| /** Utility struct which holds one or more accessibility interfaces. | |||||
| The main purpose of this class is to provide convenience constructors from each | |||||
| of the four types of accessibility interface. | |||||
| */ | |||||
| struct JUCE_API Interfaces | |||||
| { | |||||
| Interfaces() = default; | |||||
| Interfaces (std::unique_ptr<AccessibilityValueInterface> ptr) : value (std::move (ptr)) {} | |||||
| Interfaces (std::unique_ptr<AccessibilityTextInterface> ptr) : text (std::move (ptr)) {} | |||||
| Interfaces (std::unique_ptr<AccessibilityTableInterface> ptr) : table (std::move (ptr)) {} | |||||
| Interfaces (std::unique_ptr<AccessibilityCellInterface> ptr) : cell (std::move (ptr)) {} | |||||
| Interfaces (std::unique_ptr<AccessibilityValueInterface> valueIn, | |||||
| std::unique_ptr<AccessibilityTextInterface> textIn, | |||||
| std::unique_ptr<AccessibilityTableInterface> tableIn, | |||||
| std::unique_ptr<AccessibilityCellInterface> cellIn) | |||||
| : value (std::move (valueIn)), | |||||
| text (std::move (textIn)), | |||||
| table (std::move (tableIn)), | |||||
| cell (std::move (cellIn)) | |||||
| { | |||||
| } | |||||
| std::unique_ptr<AccessibilityValueInterface> value; | |||||
| std::unique_ptr<AccessibilityTextInterface> text; | |||||
| std::unique_ptr<AccessibilityTableInterface> table; | |||||
| std::unique_ptr<AccessibilityCellInterface> cell; | |||||
| }; | |||||
| /** Constructor. | |||||
| This will create a AccessibilityHandler which wraps the provided Component and makes | |||||
| it visible to accessibility clients. You must also specify a role for the UI element | |||||
| from the `AccessibilityRole` list which best describes it. | |||||
| To enable users to interact with the UI element you should provide the set of supported | |||||
| actions and their associated callbacks via the `accessibilityActions` parameter. | |||||
| For UI elements that support more complex interaction the value, text, table, and cell | |||||
| interfaces should be implemented as required and passed as the final argument of this | |||||
| constructor. See the documentation of these classes for more information about the | |||||
| types of control they represent and which methods need to be implemented. | |||||
| */ | |||||
| AccessibilityHandler (Component& componentToWrap, | |||||
| AccessibilityRole accessibilityRole, | |||||
| AccessibilityActions actions = {}, | |||||
| Interfaces interfaces = {}); | |||||
| /** Destructor. */ | |||||
| virtual ~AccessibilityHandler(); | |||||
| //============================================================================== | |||||
| /** Returns the Component that this handler represents. */ | |||||
| const Component& getComponent() const noexcept { return component; } | |||||
| /** Returns the Component that this handler represents. */ | |||||
| Component& getComponent() noexcept { return component; } | |||||
| //============================================================================== | |||||
| /** The type of UI element that this accessibility handler represents. | |||||
| @see AccessibilityRole | |||||
| */ | |||||
| AccessibilityRole getRole() const noexcept { return role; } | |||||
| /** The title of the UI element. | |||||
| This will be read out by the system and should be concise, preferably matching | |||||
| the visible title of the UI element (if any). For example, this might be the | |||||
| text of a button or a simple label. | |||||
| The default implementation will call `Component::getTitle()`, but you can override | |||||
| this to return a different string if required. | |||||
| If neither a name nor a description is provided then the UI element may be | |||||
| ignored by accessibility clients. | |||||
| This must be a localised string. | |||||
| */ | |||||
| virtual String getTitle() const { return component.getTitle(); } | |||||
| /** A short description of the UI element. | |||||
| This may be read out by the system. It should not include the type of the UI | |||||
| element and should ideally be a single word, for example "Open" for a button | |||||
| that opens a window. | |||||
| The default implementation will call `Component::getDescription()`, but you | |||||
| can override this to return a different string if required. | |||||
| If neither a name nor a description is provided then the UI element may be | |||||
| ignored by accessibility clients. | |||||
| This must be a localised string. | |||||
| */ | |||||
| virtual String getDescription() const { return component.getDescription(); } | |||||
| /** Some help text for the UI element (if required). | |||||
| This may be read out by the system. This string functions in a similar way to | |||||
| a tooltip, for example "Click to open window." for a button which opens a window. | |||||
| The default implementation will call `Component::getHelpText()`, but you can | |||||
| override this to return a different string if required. | |||||
| This must be a localised string. | |||||
| */ | |||||
| virtual String getHelp() const { return component.getHelpText(); } | |||||
| /** Returns the current state of the UI element. | |||||
| The default implementation of this method will set the focusable flag and, if | |||||
| this UI element is currently focused, will also set the focused flag. | |||||
| */ | |||||
| virtual AccessibleState getCurrentState() const; | |||||
| /** Returns true if this UI element should be ignored by accessibility clients. */ | |||||
| bool isIgnored() const; | |||||
| //============================================================================== | |||||
| /** Returns the set of actions that the UI element supports and the associated | |||||
| callbacks. | |||||
| */ | |||||
| const AccessibilityActions& getActions() const noexcept; | |||||
| /** Returns the value interface for this UI element, or nullptr if it is not supported. | |||||
| @see AccessibilityValueInterface | |||||
| */ | |||||
| AccessibilityValueInterface* getValueInterface() const; | |||||
| /** Returns the table interface for this UI element, or nullptr if it is not supported. | |||||
| @see AccessibilityTableInterface | |||||
| */ | |||||
| AccessibilityTableInterface* getTableInterface() const; | |||||
| /** Returns the cell interface for this UI element, or nullptr if it is not supported. | |||||
| @see AccessibilityCellInterface | |||||
| */ | |||||
| AccessibilityCellInterface* getCellInterface() const; | |||||
| /** Returns the text interface for this UI element, or nullptr if it is not supported. | |||||
| @see AccessibilityTextInterface | |||||
| */ | |||||
| AccessibilityTextInterface* getTextInterface() const; | |||||
| //============================================================================== | |||||
| /** Returns the first unignored parent of this UI element in the accessibility hierarchy, | |||||
| or nullptr if this is a root element without a parent. | |||||
| */ | |||||
| AccessibilityHandler* getParent() const; | |||||
| /** Returns the unignored children of this UI element in the accessibility hierarchy. */ | |||||
| std::vector<AccessibilityHandler*> getChildren() const; | |||||
| /** Checks whether a given UI element is a child of this one in the accessibility | |||||
| hierarchy. | |||||
| */ | |||||
| bool isParentOf (const AccessibilityHandler* possibleChild) const noexcept; | |||||
| /** Returns the deepest child of this UI element in the accessibility hierarchy that | |||||
| contains the given screen point, or nullptr if there is no child at this point. | |||||
| */ | |||||
| AccessibilityHandler* getChildAt (Point<int> screenPoint); | |||||
| /** Returns the deepest UI element which currently has focus. | |||||
| This can be a child of this UI element or, if no child is focused, | |||||
| this element itself. | |||||
| Note that this can be different to the value of the Component with keyboard | |||||
| focus returned by Component::getCurrentlyFocusedComponent(). | |||||
| @see hasFocus | |||||
| */ | |||||
| AccessibilityHandler* getChildFocus(); | |||||
| /** Returns true if this UI element has the focus. | |||||
| @param trueIfChildFocused if this is true, this method will also return true | |||||
| if any child of this UI element in the accessibility | |||||
| hierarchy has focus | |||||
| */ | |||||
| bool hasFocus (bool trueIfChildFocused) const; | |||||
| /** Tries to give focus to this UI element. | |||||
| If the UI element is focusable, as indicated by AccessibleState::isFocusable(), | |||||
| this will perform its AccessibilityActionType::focus action, try to give keyboard | |||||
| focus to the Component it represents, and notify any listening accessibility | |||||
| clients that the current focus has changed. | |||||
| @see hasFocus, giveAwayFocus | |||||
| */ | |||||
| void grabFocus(); | |||||
| /** If this UI element or any of its children in the accessibility hierarchy currently | |||||
| have focus, this will defocus it. | |||||
| This will also give away the keyboard focus from the Component it represents, and | |||||
| notify any listening accessibility clients that the current focus has changed. | |||||
| @see hasFocus, grabFocus | |||||
| */ | |||||
| void giveAwayFocus() const; | |||||
| //============================================================================== | |||||
| /** Used to send a notification to any observing accessibility clients that something | |||||
| has changed in the UI element. | |||||
| @see AccessibilityEvent | |||||
| */ | |||||
| void notifyAccessibilityEvent (AccessibilityEvent event) const; | |||||
| /** A priority level that can help an accessibility client determine how to handle | |||||
| an announcement request. | |||||
| Exactly what this controls is platform-specific, but generally a low priority | |||||
| announcement will be read when the screen reader is free, whereas a high priority | |||||
| announcement will interrupt the current speech. | |||||
| */ | |||||
| enum class AnnouncementPriority | |||||
| { | |||||
| low, | |||||
| medium, | |||||
| high | |||||
| }; | |||||
| /** Posts an announcement to be made to the user. | |||||
| @param announcementString a localised string containing the announcement to be read out | |||||
| @param priority the appropriate priority level for the announcement | |||||
| */ | |||||
| static void postAnnouncement (const String& announcementString, AnnouncementPriority priority); | |||||
| //============================================================================== | |||||
| /** @internal */ | |||||
| AccessibilityNativeHandle* getNativeImplementation() const; | |||||
| /** @internal */ | |||||
| std::type_index getTypeIndex() const { return typeIndex; } | |||||
| private: | |||||
| //============================================================================== | |||||
| friend class AccessibilityNativeHandle; | |||||
| //============================================================================== | |||||
| void grabFocusInternal (bool); | |||||
| void giveAwayFocusInternal() const; | |||||
| void takeFocus(); | |||||
| static AccessibilityHandler* currentlyFocusedHandler; | |||||
| //============================================================================== | |||||
| Component& component; | |||||
| std::type_index typeIndex; | |||||
| const AccessibilityRole role; | |||||
| AccessibilityActions actions; | |||||
| Interfaces interfaces; | |||||
| //============================================================================== | |||||
| class AccessibilityNativeImpl; | |||||
| struct DestroyNativeImpl | |||||
| { | |||||
| void operator() (AccessibilityNativeImpl*) const noexcept; | |||||
| }; | |||||
| static AccessibilityNativeImpl* createNativeImpl (AccessibilityHandler&); | |||||
| std::unique_ptr<AccessibilityNativeImpl, DestroyNativeImpl> nativeImpl; | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityHandler) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,227 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| /** Represents the state of an accessible UI element. | |||||
| An instance of this class is returned by `AccessibilityHandler::getCurrentState()` | |||||
| to convey its current state to an accessibility client. | |||||
| @see AccessibilityHandler | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| class JUCE_API AccessibleState | |||||
| { | |||||
| public: | |||||
| /** Constructor. | |||||
| Represents a "default" state with no flags set. To set a flag, use one of the | |||||
| `withX()` methods - these can be chained together to set multiple flags. | |||||
| */ | |||||
| AccessibleState() = default; | |||||
| //============================================================================== | |||||
| /** Sets the checkable flag and returns the new state. | |||||
| @see isCheckable | |||||
| */ | |||||
| AccessibleState withCheckable() const noexcept { return withFlag (Flags::checkable); } | |||||
| /** Sets the checked flag and returns the new state. | |||||
| @see isChecked | |||||
| */ | |||||
| AccessibleState withChecked() const noexcept { return withFlag (Flags::checked); } | |||||
| /** Sets the collapsed flag and returns the new state. | |||||
| @see isCollapsed | |||||
| */ | |||||
| AccessibleState withCollapsed() const noexcept { return withFlag (Flags::collapsed); } | |||||
| /** Sets the expandable flag and returns the new state. | |||||
| @see isExpandable | |||||
| */ | |||||
| AccessibleState withExpandable() const noexcept { return withFlag (Flags::expandable); } | |||||
| /** Sets the expanded flag and returns the new state. | |||||
| @see isExpanded | |||||
| */ | |||||
| AccessibleState withExpanded() const noexcept { return withFlag (Flags::expanded); } | |||||
| /** Sets the focusable flag and returns the new state. | |||||
| @see isFocusable | |||||
| */ | |||||
| AccessibleState withFocusable() const noexcept { return withFlag (Flags::focusable); } | |||||
| /** Sets the focused flag and returns the new state. | |||||
| @see isFocused | |||||
| */ | |||||
| AccessibleState withFocused() const noexcept { return withFlag (Flags::focused); } | |||||
| /** Sets the ignored flag and returns the new state. | |||||
| @see isIgnored | |||||
| */ | |||||
| AccessibleState withIgnored() const noexcept { return withFlag (Flags::ignored); } | |||||
| /** Sets the selectable flag and returns the new state. | |||||
| @see isSelectable | |||||
| */ | |||||
| AccessibleState withSelectable() const noexcept { return withFlag (Flags::selectable); } | |||||
| /** Sets the multiSelectable flag and returns the new state. | |||||
| @see isMultiSelectable | |||||
| */ | |||||
| AccessibleState withMultiSelectable() const noexcept { return withFlag (Flags::multiSelectable); } | |||||
| /** Sets the selected flag and returns the new state. | |||||
| @see isSelected | |||||
| */ | |||||
| AccessibleState withSelected() const noexcept { return withFlag (Flags::selected); } | |||||
| /** Sets the accessible offscreen flag and returns the new state. | |||||
| @see isSelected | |||||
| */ | |||||
| AccessibleState withAccessibleOffscreen() const noexcept { return withFlag (Flags::accessibleOffscreen); } | |||||
| //============================================================================== | |||||
| /** Returns true if the UI element is checkable. | |||||
| @see withCheckable | |||||
| */ | |||||
| bool isCheckable() const noexcept { return isFlagSet (Flags::checkable); } | |||||
| /** Returns true if the UI element is checked. | |||||
| @see withChecked | |||||
| */ | |||||
| bool isChecked() const noexcept { return isFlagSet (Flags::checked); } | |||||
| /** Returns true if the UI element is collapsed. | |||||
| @see withCollapsed | |||||
| */ | |||||
| bool isCollapsed() const noexcept { return isFlagSet (Flags::collapsed); } | |||||
| /** Returns true if the UI element is expandable. | |||||
| @see withExpandable | |||||
| */ | |||||
| bool isExpandable() const noexcept { return isFlagSet (Flags::expandable); } | |||||
| /** Returns true if the UI element is expanded. | |||||
| @see withExpanded | |||||
| */ | |||||
| bool isExpanded() const noexcept { return isFlagSet (Flags::expanded); } | |||||
| /** Returns true if the UI element is focusable. | |||||
| @see withFocusable | |||||
| */ | |||||
| bool isFocusable() const noexcept { return isFlagSet (Flags::focusable); } | |||||
| /** Returns true if the UI element is focused. | |||||
| @see withFocused | |||||
| */ | |||||
| bool isFocused() const noexcept { return isFlagSet (Flags::focused); } | |||||
| /** Returns true if the UI element is ignored. | |||||
| @see withIgnored | |||||
| */ | |||||
| bool isIgnored() const noexcept { return isFlagSet (Flags::ignored); } | |||||
| /** Returns true if the UI element supports multiple item selection. | |||||
| @see withMultiSelectable | |||||
| */ | |||||
| bool isMultiSelectable() const noexcept { return isFlagSet (Flags::multiSelectable); } | |||||
| /** Returns true if the UI element is selectable. | |||||
| @see withSelectable | |||||
| */ | |||||
| bool isSelectable() const noexcept { return isFlagSet (Flags::selectable); } | |||||
| /** Returns true if the UI element is selected. | |||||
| @see withSelected | |||||
| */ | |||||
| bool isSelected() const noexcept { return isFlagSet (Flags::selected); } | |||||
| /** Returns true if the UI element is accessible offscreen. | |||||
| @see withSelected | |||||
| */ | |||||
| bool isAccessibleOffscreen() const noexcept { return isFlagSet (Flags::accessibleOffscreen); } | |||||
| private: | |||||
| enum Flags | |||||
| { | |||||
| checkable = (1 << 0), | |||||
| checked = (1 << 1), | |||||
| collapsed = (1 << 2), | |||||
| expandable = (1 << 3), | |||||
| expanded = (1 << 4), | |||||
| focusable = (1 << 5), | |||||
| focused = (1 << 6), | |||||
| ignored = (1 << 7), | |||||
| multiSelectable = (1 << 8), | |||||
| selectable = (1 << 9), | |||||
| selected = (1 << 10), | |||||
| accessibleOffscreen = (1 << 11) | |||||
| }; | |||||
| AccessibleState withFlag (int flag) const noexcept | |||||
| { | |||||
| auto copy = *this; | |||||
| copy.flags |= flag; | |||||
| return copy; | |||||
| } | |||||
| bool isFlagSet (int flag) const noexcept | |||||
| { | |||||
| return (flags & flag) != 0; | |||||
| } | |||||
| int flags = 0; | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,96 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| /** Basic accessible interface for a Button that can be clicked or toggled. | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| class JUCE_API ButtonAccessibilityHandler : public AccessibilityHandler | |||||
| { | |||||
| public: | |||||
| explicit ButtonAccessibilityHandler (Button& buttonToWrap) | |||||
| : AccessibilityHandler (buttonToWrap, | |||||
| getButtonRole (buttonToWrap), | |||||
| getAccessibilityActions (buttonToWrap)), | |||||
| button (buttonToWrap) | |||||
| { | |||||
| } | |||||
| AccessibleState getCurrentState() const override | |||||
| { | |||||
| auto state = AccessibilityHandler::getCurrentState(); | |||||
| if (button.getClickingTogglesState()) | |||||
| { | |||||
| state = state.withCheckable(); | |||||
| if (button.getToggleState()) | |||||
| state = state.withChecked(); | |||||
| } | |||||
| return state; | |||||
| } | |||||
| String getTitle() const override | |||||
| { | |||||
| auto title = AccessibilityHandler::getTitle(); | |||||
| if (title.isEmpty()) | |||||
| return button.getButtonText(); | |||||
| return title; | |||||
| } | |||||
| private: | |||||
| static AccessibilityRole getButtonRole (const Button& b) | |||||
| { | |||||
| if (b.getRadioGroupId() != 0) return AccessibilityRole::radioButton; | |||||
| if (b.getClickingTogglesState()) return AccessibilityRole::toggleButton; | |||||
| return AccessibilityRole::button; | |||||
| } | |||||
| static AccessibilityActions getAccessibilityActions (Button& button) | |||||
| { | |||||
| auto actions = AccessibilityActions().addAction (AccessibilityActionType::press, | |||||
| [&button] { button.triggerClick(); }); | |||||
| if (button.getClickingTogglesState()) | |||||
| actions = actions.addAction (AccessibilityActionType::toggle, | |||||
| [&button] { button.setToggleState (! button.getToggleState(), sendNotification); }); | |||||
| return actions; | |||||
| } | |||||
| Button& button; | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonAccessibilityHandler) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,66 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| /** Basic accessible interface for a ComboBox that can show a menu. | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| class JUCE_API ComboBoxAccessibilityHandler : public AccessibilityHandler | |||||
| { | |||||
| public: | |||||
| explicit ComboBoxAccessibilityHandler (ComboBox& comboBoxToWrap) | |||||
| : AccessibilityHandler (comboBoxToWrap, | |||||
| AccessibilityRole::comboBox, | |||||
| getAccessibilityActions (comboBoxToWrap)), | |||||
| comboBox (comboBoxToWrap) | |||||
| { | |||||
| } | |||||
| AccessibleState getCurrentState() const override | |||||
| { | |||||
| auto state = AccessibilityHandler::getCurrentState().withExpandable(); | |||||
| return comboBox.isPopupActive() ? state.withExpanded() : state.withCollapsed(); | |||||
| } | |||||
| String getTitle() const override { return comboBox.getText(); } | |||||
| private: | |||||
| static AccessibilityActions getAccessibilityActions (ComboBox& comboBox) | |||||
| { | |||||
| return AccessibilityActions().addAction (AccessibilityActionType::press, [&comboBox] { comboBox.showPopup(); }) | |||||
| .addAction (AccessibilityActionType::showMenu, [&comboBox] { comboBox.showPopup(); }); | |||||
| } | |||||
| ComboBox& comboBox; | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComboBoxAccessibilityHandler) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,62 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| /** Basic accessible interface for a Label that can also show a TextEditor | |||||
| when clicked. | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| class JUCE_API LabelAccessibilityHandler : public AccessibilityHandler | |||||
| { | |||||
| public: | |||||
| explicit LabelAccessibilityHandler (Label& labelToWrap) | |||||
| : AccessibilityHandler (labelToWrap, | |||||
| AccessibilityRole::staticText, | |||||
| getAccessibilityActions (labelToWrap)), | |||||
| label (labelToWrap) | |||||
| { | |||||
| } | |||||
| String getTitle() const override { return label.getText(); } | |||||
| private: | |||||
| static AccessibilityActions getAccessibilityActions (Label& label) | |||||
| { | |||||
| if (label.isEditable()) | |||||
| return AccessibilityActions().addAction (AccessibilityActionType::press, [&label] { label.showEditor(); }); | |||||
| return {}; | |||||
| } | |||||
| Label& label; | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LabelAccessibilityHandler) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,100 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| /** Basic accessible interface for a Slider. | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| class JUCE_API SliderAccessibilityHandler : public AccessibilityHandler | |||||
| { | |||||
| public: | |||||
| explicit SliderAccessibilityHandler (Slider& sliderToWrap) | |||||
| : AccessibilityHandler (sliderToWrap, | |||||
| AccessibilityRole::slider, | |||||
| {}, | |||||
| { std::make_unique<SliderValueInterface> (sliderToWrap) }) | |||||
| { | |||||
| } | |||||
| private: | |||||
| class SliderValueInterface : public AccessibilityValueInterface | |||||
| { | |||||
| public: | |||||
| explicit SliderValueInterface (Slider& sliderToWrap) | |||||
| : slider (sliderToWrap) | |||||
| { | |||||
| } | |||||
| bool isReadOnly() const override { return false; } | |||||
| double getCurrentValue() const override | |||||
| { | |||||
| return slider.isTwoValue() ? slider.getMaxValue() : slider.getValue(); | |||||
| } | |||||
| void setValue (double newValue) override | |||||
| { | |||||
| if (slider.isTwoValue()) | |||||
| slider.setMaxValue (newValue, sendNotification); | |||||
| else | |||||
| slider.setValue (newValue, sendNotification); | |||||
| } | |||||
| String getCurrentValueAsString() const override | |||||
| { | |||||
| return slider.getTextFromValue (getCurrentValue()); | |||||
| } | |||||
| void setValueAsString (const String& newValue) override | |||||
| { | |||||
| setValue (slider.getValueFromText (newValue)); | |||||
| } | |||||
| AccessibleValueRange getRange() const override | |||||
| { | |||||
| return { { slider.getMinimum(), slider.getMaximum() }, | |||||
| getStepSize() }; | |||||
| } | |||||
| private: | |||||
| double getStepSize() const | |||||
| { | |||||
| auto interval = slider.getInterval(); | |||||
| return interval != 0.0 ? interval | |||||
| : slider.proportionOfLengthToValue (0.01); | |||||
| } | |||||
| Slider& slider; | |||||
| }; | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SliderAccessibilityHandler) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,83 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| /** Basic accessible interface for a TableListBox. | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| class JUCE_API TableListBoxAccessibilityHandler : public AccessibilityHandler | |||||
| { | |||||
| public: | |||||
| explicit TableListBoxAccessibilityHandler (TableListBox& tableListBoxToWrap) | |||||
| : AccessibilityHandler (tableListBoxToWrap, | |||||
| AccessibilityRole::list, | |||||
| {}, | |||||
| { std::make_unique<TableListBoxTableInterface> (tableListBoxToWrap) }) | |||||
| { | |||||
| } | |||||
| private: | |||||
| class TableListBoxTableInterface : public AccessibilityTableInterface | |||||
| { | |||||
| public: | |||||
| explicit TableListBoxTableInterface (TableListBox& tableListBoxToWrap) | |||||
| : tableListBox (tableListBoxToWrap) | |||||
| { | |||||
| } | |||||
| int getNumRows() const override | |||||
| { | |||||
| if (auto* model = tableListBox.getModel()) | |||||
| return model->getNumRows(); | |||||
| return 0; | |||||
| } | |||||
| int getNumColumns() const override | |||||
| { | |||||
| return tableListBox.getHeader().getNumColumns (false); | |||||
| } | |||||
| const AccessibilityHandler* getCellHandler (int row, int column) const override | |||||
| { | |||||
| if (isPositiveAndBelow (row, getNumRows()) && isPositiveAndBelow (column, getNumColumns())) | |||||
| if (auto* cellComponent = tableListBox.getCellComponent (tableListBox.getHeader().getColumnIdOfIndex (column, false), row)) | |||||
| return cellComponent->getAccessibilityHandler(); | |||||
| return nullptr; | |||||
| } | |||||
| private: | |||||
| TableListBox& tableListBox; | |||||
| }; | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableListBoxAccessibilityHandler) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,108 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| /** Basic accessible interface for a TextEditor. | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| class JUCE_API TextEditorAccessibilityHandler : public AccessibilityHandler | |||||
| { | |||||
| public: | |||||
| explicit TextEditorAccessibilityHandler (TextEditor& textEditorToWrap) | |||||
| : AccessibilityHandler (textEditorToWrap, | |||||
| textEditorToWrap.isReadOnly() ? AccessibilityRole::staticText : AccessibilityRole::editableText, | |||||
| {}, | |||||
| { textEditorToWrap.isReadOnly() ? nullptr : std::make_unique<TextEditorTextInterface> (textEditorToWrap) }), | |||||
| textEditor (textEditorToWrap) | |||||
| { | |||||
| } | |||||
| String getTitle() const override | |||||
| { | |||||
| return textEditor.isReadOnly() ? textEditor.getText() : textEditor.getTitle(); | |||||
| } | |||||
| private: | |||||
| class TextEditorTextInterface : public AccessibilityTextInterface | |||||
| { | |||||
| public: | |||||
| explicit TextEditorTextInterface (TextEditor& editor) | |||||
| : textEditor (editor) | |||||
| { | |||||
| } | |||||
| bool isDisplayingProtectedText() const override { return textEditor.getPasswordCharacter() != 0; } | |||||
| int getTotalNumCharacters() const override { return textEditor.getText().length(); } | |||||
| Range<int> getSelection() const override { return textEditor.getHighlightedRegion(); } | |||||
| void setSelection (Range<int> r) override { textEditor.setHighlightedRegion (r); } | |||||
| String getText (Range<int> r) const override | |||||
| { | |||||
| if (isDisplayingProtectedText()) | |||||
| return String::repeatedString (String::charToString (textEditor.getPasswordCharacter()), | |||||
| getTotalNumCharacters()); | |||||
| return textEditor.getTextInRange (r); | |||||
| } | |||||
| void setText (const String& newText) override | |||||
| { | |||||
| textEditor.setText (newText); | |||||
| } | |||||
| int getTextInsertionOffset() const override { return textEditor.getCaretPosition(); } | |||||
| RectangleList<int> getTextBounds (Range<int> textRange) const override | |||||
| { | |||||
| auto localRects = textEditor.getTextBounds (textRange); | |||||
| RectangleList<int> globalRects; | |||||
| std::for_each (localRects.begin(), localRects.end(), | |||||
| [&] (const Rectangle<int>& r) { globalRects.add (textEditor.localAreaToGlobal (r)); }); | |||||
| return globalRects; | |||||
| } | |||||
| int getOffsetAtPoint (Point<int> point) const override | |||||
| { | |||||
| auto localPoint = textEditor.getLocalPoint (nullptr, point); | |||||
| return textEditor.getTextIndexAt (localPoint.x, localPoint.y); | |||||
| } | |||||
| private: | |||||
| TextEditor& textEditor; | |||||
| }; | |||||
| TextEditor& textEditor; | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TextEditorAccessibilityHandler) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,79 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| /** Basic accessible interface for a TreeView. | |||||
| @tags{Accessibility} | |||||
| */ | |||||
| class JUCE_API TreeViewAccessibilityHandler : public AccessibilityHandler | |||||
| { | |||||
| public: | |||||
| explicit TreeViewAccessibilityHandler (TreeView& treeViewToWrap) | |||||
| : AccessibilityHandler (treeViewToWrap, | |||||
| AccessibilityRole::tree, | |||||
| {}, | |||||
| { std::make_unique<TreeViewTableInterface> (treeViewToWrap) }) | |||||
| { | |||||
| } | |||||
| private: | |||||
| class TreeViewTableInterface : public AccessibilityTableInterface | |||||
| { | |||||
| public: | |||||
| explicit TreeViewTableInterface (TreeView& treeViewToWrap) | |||||
| : treeView (treeViewToWrap) | |||||
| { | |||||
| } | |||||
| int getNumRows() const override | |||||
| { | |||||
| return treeView.getNumRowsInTree(); | |||||
| } | |||||
| int getNumColumns() const override | |||||
| { | |||||
| return 1; | |||||
| } | |||||
| const AccessibilityHandler* getCellHandler (int row, int) const override | |||||
| { | |||||
| if (auto* itemComp = treeView.getItemComponent (treeView.getItemOnRow (row))) | |||||
| return itemComp->getAccessibilityHandler(); | |||||
| return nullptr; | |||||
| } | |||||
| private: | |||||
| TreeView& treeView; | |||||
| }; | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TreeViewAccessibilityHandler) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -188,6 +188,9 @@ void Button::setToggleState (bool shouldBeOn, NotificationType clickNotification | |||||
| sendStateMessage(); | 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 | ||||
| @@ -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: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -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 | ||||
| @@ -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(); | ||||
| @@ -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 | |||||
| @@ -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 | |||||
| @@ -0,0 +1,93 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| //============================================================================== | |||||
| /** | |||||
| Controls the order in which focus moves between components. | |||||
| The algorithm used by this class to work out the order of traversal is as | |||||
| follows: | |||||
| - Only visible and enabled components are considered focusable. | |||||
| - If two components both have an explicit focus order specified then the | |||||
| one with the lowest number comes first (see the | |||||
| Component::setExplicitFocusOrder() method). | |||||
| - Any component with an explicit focus order greater than 0 comes before ones | |||||
| that don't have an order specified. | |||||
| - Components with their 'always on top' flag set come before those without. | |||||
| - Any unspecified components are traversed in a left-to-right, then | |||||
| top-to-bottom order. | |||||
| If you need focus traversal in a more customised way you can create a | |||||
| ComponentTraverser subclass that uses your own algorithm and return it | |||||
| from Component::createFocusTraverser(). | |||||
| @see ComponentTraverser, Component::createFocusTraverser | |||||
| @tags{GUI} | |||||
| */ | |||||
| class JUCE_API FocusTraverser : public ComponentTraverser | |||||
| { | |||||
| public: | |||||
| /** Destructor. */ | |||||
| ~FocusTraverser() override = default; | |||||
| /** Returns the component that should receive focus by default within the given | |||||
| parent component. | |||||
| The default implementation will just return the foremost visible and enabled | |||||
| child component, and will return nullptr if there is no suitable component. | |||||
| */ | |||||
| Component* getDefaultComponent (Component* parentComponent) override; | |||||
| /** Returns the component that should be given focus after the specified one when | |||||
| moving "forwards". | |||||
| The default implementation will return the next visible and enabled component | |||||
| which is to the right of or below this one, and will return nullptr if there | |||||
| is no suitable component. | |||||
| */ | |||||
| Component* getNextComponent (Component* current) override; | |||||
| /** Returns the component that should be given focus after the specified one when | |||||
| moving "backwards". | |||||
| The default implementation will return the previous visible and enabled component | |||||
| which is to the left of or above this one, and will return nullptr if there | |||||
| is no suitable component. | |||||
| */ | |||||
| Component* getPreviousComponent (Component* current) override; | |||||
| /** Returns all of the components that can receive focus within the given parent | |||||
| component in traversal order. | |||||
| The default implementation will return all visible and enabled child components. | |||||
| */ | |||||
| std::vector<Component*> getAllComponents (Component* parentComponent) override; | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -204,4 +204,10 @@ std::unique_ptr<Drawable> Drawable::createFromImageFile (const File& file) | |||||
| return {}; | return {}; | ||||
| } | } | ||||
| //============================================================================== | |||||
| std::unique_ptr<AccessibilityHandler> Drawable::createAccessibilityHandler() | |||||
| { | |||||
| return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::ignored); | |||||
| } | |||||
| } // namespace juce | } // namespace juce | ||||
| @@ -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; | ||||
| @@ -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 | ||||
| @@ -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: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -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 | ||||
| @@ -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: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -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 | ||||
| @@ -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. | ||||
| @@ -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) | ||||
| { | { | ||||
| } | } | ||||
| @@ -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; | ||||
| @@ -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); | ||||
| @@ -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) | ||||
| @@ -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: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -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 | ||||
| @@ -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; | ||||
| @@ -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" | ||||
| @@ -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 | ||||
| @@ -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 | ||||
| @@ -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 | ||||
| @@ -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()); | ||||
| @@ -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 | ||||
| @@ -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; | ||||
| @@ -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 | ||||
| @@ -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; | ||||
| @@ -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 | ||||
| @@ -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: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -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 | ||||
| @@ -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: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -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 | ||||
| @@ -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: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -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 | ||||
| @@ -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: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -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 | ||||
| @@ -71,6 +71,8 @@ public: | |||||
| /** @internal */ | /** @internal */ | ||||
| void lookAndFeelChanged() override; | void lookAndFeelChanged() override; | ||||
| /** @internal */ | |||||
| std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override; | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -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 | ||||
| @@ -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) | ||||
| }; | }; | ||||
| @@ -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(); | ||||
| @@ -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; | ||||
| @@ -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()) | ||||
| @@ -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 | ||||
| @@ -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; | ||||
| @@ -0,0 +1,270 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| static bool isStartingUpOrShuttingDown() | |||||
| { | |||||
| if (auto* app = JUCEApplicationBase::getInstance()) | |||||
| if (app->isInitialising()) | |||||
| return true; | |||||
| if (auto* mm = MessageManager::getInstanceWithoutCreating()) | |||||
| if (mm->hasStopMessageBeenSent()) | |||||
| return true; | |||||
| return false; | |||||
| } | |||||
| static bool isHandlerValid (const AccessibilityHandler& handler) | |||||
| { | |||||
| if (auto* provider = handler.getNativeImplementation()) | |||||
| return provider->isElementValid(); | |||||
| return false; | |||||
| } | |||||
| //============================================================================== | |||||
| class AccessibilityHandler::AccessibilityNativeImpl | |||||
| { | |||||
| public: | |||||
| explicit AccessibilityNativeImpl (AccessibilityHandler& owner) | |||||
| : accessibilityElement (new AccessibilityNativeHandle (owner)) | |||||
| { | |||||
| ++providerCount; | |||||
| } | |||||
| ~AccessibilityNativeImpl() | |||||
| { | |||||
| accessibilityElement->invalidateElement(); | |||||
| if (auto* wrapper = WindowsUIAWrapper::getInstanceWithoutCreating()) | |||||
| { | |||||
| ComSmartPtr<IRawElementProviderSimple> provider; | |||||
| accessibilityElement->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress())); | |||||
| wrapper->disconnectProvider (provider); | |||||
| if (--providerCount == 0) | |||||
| wrapper->disconnectAllProviders(); | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| ComSmartPtr<AccessibilityNativeHandle> accessibilityElement; | |||||
| static int providerCount; | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeImpl) | |||||
| }; | |||||
| int AccessibilityHandler::AccessibilityNativeImpl::providerCount = 0; | |||||
| //============================================================================== | |||||
| AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const | |||||
| { | |||||
| return nativeImpl->accessibilityElement; | |||||
| } | |||||
| template <typename Callback> | |||||
| void getProviderWithCheckedWrapper (const AccessibilityHandler& handler, Callback&& callback) | |||||
| { | |||||
| if (isStartingUpOrShuttingDown() || ! isHandlerValid (handler)) | |||||
| return; | |||||
| if (auto* wrapper = WindowsUIAWrapper::getInstanceWithoutCreating()) | |||||
| { | |||||
| if (! wrapper->clientsAreListening()) | |||||
| return; | |||||
| ComSmartPtr<IRawElementProviderSimple> provider; | |||||
| handler.getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress())); | |||||
| callback (wrapper, provider); | |||||
| } | |||||
| } | |||||
| void sendAccessibilityAutomationEvent (const AccessibilityHandler& handler, EVENTID event) | |||||
| { | |||||
| jassert (event != EVENTID{}); | |||||
| getProviderWithCheckedWrapper (handler, [event] (WindowsUIAWrapper* wrapper, ComSmartPtr<IRawElementProviderSimple>& provider) | |||||
| { | |||||
| wrapper->raiseAutomationEvent (provider, event); | |||||
| }); | |||||
| } | |||||
| void sendAccessibilityPropertyChangedEvent (const AccessibilityHandler& handler, PROPERTYID property, VARIANT newValue) | |||||
| { | |||||
| jassert (property != PROPERTYID{}); | |||||
| getProviderWithCheckedWrapper (handler, [property, newValue] (WindowsUIAWrapper* wrapper, ComSmartPtr<IRawElementProviderSimple>& provider) | |||||
| { | |||||
| VARIANT oldValue; | |||||
| VariantHelpers::clear (&oldValue); | |||||
| wrapper->raiseAutomationPropertyChangedEvent (provider, property, oldValue, newValue); | |||||
| }); | |||||
| } | |||||
| void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent eventType) | |||||
| { | |||||
| auto event = [eventType]() -> EVENTID | |||||
| { | |||||
| switch (eventType) | |||||
| { | |||||
| case InternalAccessibilityEvent::elementCreated: | |||||
| case InternalAccessibilityEvent::elementDestroyed: return UIA_StructureChangedEventId; | |||||
| case InternalAccessibilityEvent::focusChanged: return UIA_AutomationFocusChangedEventId; | |||||
| case InternalAccessibilityEvent::windowOpened: return UIA_Window_WindowOpenedEventId; | |||||
| case InternalAccessibilityEvent::windowClosed: return UIA_Window_WindowClosedEventId; | |||||
| } | |||||
| return {}; | |||||
| }(); | |||||
| if (event != EVENTID{}) | |||||
| sendAccessibilityAutomationEvent (handler, event); | |||||
| } | |||||
| void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const | |||||
| { | |||||
| auto event = [eventType] () -> EVENTID | |||||
| { | |||||
| switch (eventType) | |||||
| { | |||||
| case AccessibilityEvent::textSelectionChanged: return UIA_Text_TextSelectionChangedEventId; | |||||
| case AccessibilityEvent::textChanged: return UIA_Text_TextChangedEventId; | |||||
| case AccessibilityEvent::structureChanged: return UIA_StructureChangedEventId; | |||||
| case AccessibilityEvent::rowSelectionChanged: return UIA_SelectionItem_ElementSelectedEventId; | |||||
| case AccessibilityEvent::valueChanged: break; | |||||
| } | |||||
| return {}; | |||||
| }(); | |||||
| if (event != EVENTID{}) | |||||
| sendAccessibilityAutomationEvent (*this, event); | |||||
| } | |||||
| struct SpVoiceWrapper : public DeletedAtShutdown | |||||
| { | |||||
| SpVoiceWrapper() | |||||
| { | |||||
| auto hr = voice.CoCreateInstance (CLSID_SpVoice); | |||||
| jassert (SUCCEEDED (hr)); | |||||
| ignoreUnused (hr); | |||||
| } | |||||
| ~SpVoiceWrapper() override | |||||
| { | |||||
| clearSingletonInstance(); | |||||
| } | |||||
| ComSmartPtr<ISpVoice> voice; | |||||
| JUCE_DECLARE_SINGLETON (SpVoiceWrapper, false) | |||||
| }; | |||||
| JUCE_IMPLEMENT_SINGLETON (SpVoiceWrapper) | |||||
| void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority priority) | |||||
| { | |||||
| if (auto* sharedVoice = SpVoiceWrapper::getInstance()) | |||||
| { | |||||
| auto voicePriority = [priority] | |||||
| { | |||||
| switch (priority) | |||||
| { | |||||
| case AnnouncementPriority::low: return SPVPRI_OVER; | |||||
| case AnnouncementPriority::medium: return SPVPRI_NORMAL; | |||||
| case AnnouncementPriority::high: return SPVPRI_ALERT; | |||||
| } | |||||
| jassertfalse; | |||||
| return SPVPRI_OVER; | |||||
| }(); | |||||
| sharedVoice->voice->SetPriority (voicePriority); | |||||
| sharedVoice->voice->Speak (announcementString.toWideCharPointer(), SPF_ASYNC, nullptr); | |||||
| } | |||||
| } | |||||
| AccessibilityHandler::AccessibilityNativeImpl* AccessibilityHandler::createNativeImpl (AccessibilityHandler& handler) | |||||
| { | |||||
| return new AccessibilityHandler::AccessibilityNativeImpl (handler); | |||||
| } | |||||
| void AccessibilityHandler::DestroyNativeImpl::operator() (AccessibilityHandler::AccessibilityNativeImpl* impl) const noexcept | |||||
| { | |||||
| delete impl; | |||||
| } | |||||
| //============================================================================== | |||||
| namespace WindowsAccessibility | |||||
| { | |||||
| void initialiseUIAWrapper() | |||||
| { | |||||
| WindowsUIAWrapper::getInstance(); | |||||
| } | |||||
| long getUiaRootObjectId() | |||||
| { | |||||
| return static_cast<long> (UiaRootObjectId); | |||||
| } | |||||
| bool handleWmGetObject (AccessibilityHandler* handler, WPARAM wParam, LPARAM lParam, LRESULT* res) | |||||
| { | |||||
| if (isStartingUpOrShuttingDown() || (handler == nullptr || ! isHandlerValid (*handler))) | |||||
| return false; | |||||
| if (auto* wrapper = WindowsUIAWrapper::getInstanceWithoutCreating()) | |||||
| { | |||||
| ComSmartPtr<IRawElementProviderSimple> provider; | |||||
| handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress())); | |||||
| if (! wrapper->isProviderDisconnecting (provider)) | |||||
| *res = wrapper->returnRawElementProvider ((HWND) handler->getComponent().getWindowHandle(), wParam, lParam, provider); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| void revokeUIAMapEntriesForWindow (HWND hwnd) | |||||
| { | |||||
| if (auto* wrapper = WindowsUIAWrapper::getInstanceWithoutCreating()) | |||||
| wrapper->returnRawElementProvider (hwnd, 0, 0, nullptr); | |||||
| } | |||||
| } | |||||
| JUCE_IMPLEMENT_SINGLETON (WindowsUIAWrapper) | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,558 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| int AccessibilityNativeHandle::idCounter = 0; | |||||
| //============================================================================== | |||||
| static String getAutomationId (const AccessibilityHandler& handler) | |||||
| { | |||||
| auto result = handler.getTitle(); | |||||
| auto* parentComponent = handler.getComponent().getParentComponent(); | |||||
| while (parentComponent != nullptr) | |||||
| { | |||||
| if (auto* parentHandler = parentComponent->getAccessibilityHandler()) | |||||
| { | |||||
| auto parentTitle = parentHandler->getTitle(); | |||||
| result << "." << (parentTitle.isNotEmpty() ? parentTitle : "<empty>"); | |||||
| } | |||||
| parentComponent = parentComponent->getParentComponent(); | |||||
| } | |||||
| return result; | |||||
| } | |||||
| static long roleToControlTypeId (AccessibilityRole roleType) | |||||
| { | |||||
| switch (roleType) | |||||
| { | |||||
| case AccessibilityRole::button: return UIA_ButtonControlTypeId; | |||||
| case AccessibilityRole::toggleButton: return UIA_CheckBoxControlTypeId; | |||||
| case AccessibilityRole::radioButton: return UIA_RadioButtonControlTypeId; | |||||
| case AccessibilityRole::comboBox: return UIA_ComboBoxControlTypeId; | |||||
| case AccessibilityRole::image: return UIA_ImageControlTypeId; | |||||
| case AccessibilityRole::slider: return UIA_SliderControlTypeId; | |||||
| case AccessibilityRole::staticText: return UIA_TextControlTypeId; | |||||
| case AccessibilityRole::editableText: return UIA_EditControlTypeId; | |||||
| case AccessibilityRole::menuItem: return UIA_MenuItemControlTypeId; | |||||
| case AccessibilityRole::menuBar: return UIA_MenuBarControlTypeId; | |||||
| case AccessibilityRole::popupMenu: return UIA_WindowControlTypeId; | |||||
| case AccessibilityRole::table: return UIA_TableControlTypeId; | |||||
| case AccessibilityRole::tableHeader: return UIA_HeaderControlTypeId; | |||||
| case AccessibilityRole::column: return UIA_HeaderItemControlTypeId; | |||||
| case AccessibilityRole::row: return UIA_HeaderItemControlTypeId; | |||||
| case AccessibilityRole::cell: return UIA_DataItemControlTypeId; | |||||
| case AccessibilityRole::hyperlink: return UIA_HyperlinkControlTypeId; | |||||
| case AccessibilityRole::list: return UIA_ListControlTypeId; | |||||
| case AccessibilityRole::listItem: return UIA_ListItemControlTypeId; | |||||
| case AccessibilityRole::tree: return UIA_TreeControlTypeId; | |||||
| case AccessibilityRole::treeItem: return UIA_TreeItemControlTypeId; | |||||
| case AccessibilityRole::progressBar: return UIA_ProgressBarControlTypeId; | |||||
| case AccessibilityRole::group: return UIA_GroupControlTypeId; | |||||
| case AccessibilityRole::dialogWindow: return UIA_WindowControlTypeId; | |||||
| case AccessibilityRole::window: return UIA_WindowControlTypeId; | |||||
| case AccessibilityRole::scrollBar: return UIA_ScrollBarControlTypeId; | |||||
| case AccessibilityRole::tooltip: return UIA_ToolTipControlTypeId; | |||||
| case AccessibilityRole::splashScreen: return UIA_WindowControlTypeId; | |||||
| case AccessibilityRole::ignored: return UIA_CustomControlTypeId; | |||||
| case AccessibilityRole::unspecified: return UIA_CustomControlTypeId; | |||||
| }; | |||||
| return UIA_CustomControlTypeId; | |||||
| } | |||||
| static bool isEditableText (const AccessibilityHandler& handler) | |||||
| { | |||||
| return handler.getRole() == AccessibilityRole::editableText | |||||
| && handler.getTextInterface() != nullptr; | |||||
| } | |||||
| //============================================================================== | |||||
| AccessibilityNativeHandle::AccessibilityNativeHandle (AccessibilityHandler& handler) | |||||
| : ComBaseClassHelper (0), | |||||
| accessibilityHandler (handler) | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT AccessibilityNativeHandle::QueryInterface (REFIID refId, void** result) | |||||
| { | |||||
| *result = nullptr; | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| if ((refId == __uuidof (IRawElementProviderFragmentRoot) && ! isFragmentRoot())) | |||||
| return E_NOINTERFACE; | |||||
| return ComBaseClassHelper::QueryInterface (refId, result); | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT AccessibilityNativeHandle::get_HostRawElementProvider (IRawElementProviderSimple** pRetVal) | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| if (isFragmentRoot()) | |||||
| if (auto* wrapper = WindowsUIAWrapper::getInstanceWithoutCreating()) | |||||
| return wrapper->hostProviderFromHwnd ((HWND) accessibilityHandler.getComponent().getWindowHandle(), pRetVal); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT AccessibilityNativeHandle::get_ProviderOptions (ProviderOptions* options) | |||||
| { | |||||
| if (options == nullptr) | |||||
| return E_INVALIDARG; | |||||
| *options = ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading; | |||||
| return S_OK; | |||||
| } | |||||
| JUCE_COMRESULT AccessibilityNativeHandle::GetPatternProvider (PATTERNID pId, IUnknown** pRetVal) | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| *pRetVal = [&]() -> IUnknown* | |||||
| { | |||||
| const auto role = accessibilityHandler.getRole(); | |||||
| const auto fragmentRoot = isFragmentRoot(); | |||||
| switch (pId) | |||||
| { | |||||
| case UIA_WindowPatternId: | |||||
| { | |||||
| if (fragmentRoot) | |||||
| return new UIAWindowProvider (this); | |||||
| break; | |||||
| } | |||||
| case UIA_TransformPatternId: | |||||
| { | |||||
| if (fragmentRoot) | |||||
| return new UIATransformProvider (this); | |||||
| break; | |||||
| } | |||||
| case UIA_TextPatternId: | |||||
| case UIA_TextPattern2Id: | |||||
| { | |||||
| if (accessibilityHandler.getTextInterface() != nullptr) | |||||
| return new UIATextProvider (this); | |||||
| break; | |||||
| } | |||||
| case UIA_ValuePatternId: | |||||
| { | |||||
| auto editableText = isEditableText (accessibilityHandler); | |||||
| if (accessibilityHandler.getValueInterface() != nullptr || editableText) | |||||
| return new UIAValueProvider (this, editableText); | |||||
| break; | |||||
| } | |||||
| case UIA_RangeValuePatternId: | |||||
| { | |||||
| if (accessibilityHandler.getValueInterface() != nullptr | |||||
| && accessibilityHandler.getValueInterface()->getRange().isValid()) | |||||
| { | |||||
| return new UIARangeValueProvider (this); | |||||
| } | |||||
| break; | |||||
| } | |||||
| case UIA_TogglePatternId: | |||||
| { | |||||
| if (accessibilityHandler.getActions().contains (AccessibilityActionType::toggle) | |||||
| && accessibilityHandler.getCurrentState().isCheckable()) | |||||
| { | |||||
| return new UIAToggleProvider (this); | |||||
| } | |||||
| break; | |||||
| } | |||||
| case UIA_SelectionPatternId: | |||||
| { | |||||
| if (role == AccessibilityRole::list | |||||
| || role == AccessibilityRole::popupMenu | |||||
| || role == AccessibilityRole::tree) | |||||
| { | |||||
| return new UIASelectionProvider (this); | |||||
| } | |||||
| break; | |||||
| } | |||||
| case UIA_SelectionItemPatternId: | |||||
| { | |||||
| auto state = accessibilityHandler.getCurrentState(); | |||||
| if (state.isSelectable() || state.isMultiSelectable() | |||||
| || role == AccessibilityRole::radioButton) | |||||
| { | |||||
| return new UIASelectionItemProvider (this); | |||||
| } | |||||
| break; | |||||
| } | |||||
| case UIA_GridPatternId: | |||||
| { | |||||
| if ((role == AccessibilityRole::table || role == AccessibilityRole::tree) | |||||
| && accessibilityHandler.getTableInterface() != nullptr) | |||||
| { | |||||
| return new UIAGridProvider (this); | |||||
| } | |||||
| break; | |||||
| } | |||||
| case UIA_GridItemPatternId: | |||||
| { | |||||
| if ((role == AccessibilityRole::cell || role == AccessibilityRole::treeItem) | |||||
| && accessibilityHandler.getCellInterface() != nullptr) | |||||
| { | |||||
| return new UIAGridItemProvider (this); | |||||
| } | |||||
| break; | |||||
| } | |||||
| case UIA_InvokePatternId: | |||||
| { | |||||
| if (accessibilityHandler.getActions().contains (AccessibilityActionType::press)) | |||||
| return new UIAInvokeProvider (this); | |||||
| break; | |||||
| } | |||||
| case UIA_ExpandCollapsePatternId: | |||||
| { | |||||
| if (role == AccessibilityRole::menuItem | |||||
| && accessibilityHandler.getActions().contains (AccessibilityActionType::showMenu)) | |||||
| { | |||||
| return new UIAExpandCollapseProvider (this); | |||||
| } | |||||
| break; | |||||
| } | |||||
| default: | |||||
| break; | |||||
| } | |||||
| return nullptr; | |||||
| }(); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT AccessibilityNativeHandle::GetPropertyValue (PROPERTYID propertyId, VARIANT* pRetVal) | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| VariantHelpers::clear (pRetVal); | |||||
| const auto fragmentRoot = isFragmentRoot(); | |||||
| switch (propertyId) | |||||
| { | |||||
| case UIA_AutomationIdPropertyId: | |||||
| VariantHelpers::setString (getAutomationId (accessibilityHandler), pRetVal); | |||||
| break; | |||||
| case UIA_ControlTypePropertyId: | |||||
| VariantHelpers::setInt (roleToControlTypeId (accessibilityHandler.getRole()), | |||||
| pRetVal); | |||||
| break; | |||||
| case UIA_FrameworkIdPropertyId: | |||||
| VariantHelpers::setString ("JUCE", pRetVal); | |||||
| break; | |||||
| case UIA_FullDescriptionPropertyId: | |||||
| VariantHelpers::setString (accessibilityHandler.getDescription(), pRetVal); | |||||
| break; | |||||
| case UIA_HelpTextPropertyId: | |||||
| VariantHelpers::setString (accessibilityHandler.getHelp(), pRetVal); | |||||
| break; | |||||
| case UIA_IsContentElementPropertyId: | |||||
| VariantHelpers::setBool (! accessibilityHandler.isIgnored(), pRetVal); | |||||
| break; | |||||
| case UIA_IsControlElementPropertyId: | |||||
| VariantHelpers::setBool (true, pRetVal); | |||||
| break; | |||||
| case UIA_IsDialogPropertyId: | |||||
| VariantHelpers::setBool (accessibilityHandler.getRole() == AccessibilityRole::dialogWindow, pRetVal); | |||||
| break; | |||||
| case UIA_IsEnabledPropertyId: | |||||
| VariantHelpers::setBool (accessibilityHandler.getComponent().isEnabled(), pRetVal); | |||||
| break; | |||||
| case UIA_IsKeyboardFocusablePropertyId: | |||||
| VariantHelpers::setBool (accessibilityHandler.getCurrentState().isFocusable(), pRetVal); | |||||
| break; | |||||
| case UIA_HasKeyboardFocusPropertyId: | |||||
| VariantHelpers::setBool (accessibilityHandler.hasFocus (true), pRetVal); | |||||
| break; | |||||
| case UIA_IsOffscreenPropertyId: | |||||
| VariantHelpers::setBool (false, pRetVal); | |||||
| break; | |||||
| case UIA_IsPasswordPropertyId: | |||||
| if (auto* textInterface = accessibilityHandler.getTextInterface()) | |||||
| VariantHelpers::setBool (textInterface->isDisplayingProtectedText(), pRetVal); | |||||
| break; | |||||
| case UIA_IsPeripheralPropertyId: | |||||
| VariantHelpers::setBool (accessibilityHandler.getRole() == AccessibilityRole::tooltip | |||||
| || accessibilityHandler.getRole() == AccessibilityRole::popupMenu | |||||
| || accessibilityHandler.getRole() == AccessibilityRole::splashScreen, | |||||
| pRetVal); | |||||
| break; | |||||
| case UIA_NamePropertyId: | |||||
| VariantHelpers::setString (getElementName(), pRetVal); | |||||
| break; | |||||
| case UIA_ProcessIdPropertyId: | |||||
| VariantHelpers::setInt ((int) GetCurrentProcessId(), pRetVal); | |||||
| break; | |||||
| case UIA_NativeWindowHandlePropertyId: | |||||
| if (fragmentRoot) | |||||
| VariantHelpers::setInt ((int) (pointer_sized_int) accessibilityHandler.getComponent().getWindowHandle(), pRetVal); | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT AccessibilityNativeHandle::Navigate (NavigateDirection direction, IRawElementProviderFragment** pRetVal) | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| auto* handler = [&]() -> AccessibilityHandler* | |||||
| { | |||||
| if (direction == NavigateDirection_Parent) | |||||
| return accessibilityHandler.getParent(); | |||||
| if (direction == NavigateDirection_FirstChild | |||||
| || direction == NavigateDirection_LastChild) | |||||
| { | |||||
| auto children = accessibilityHandler.getChildren(); | |||||
| return children.empty() ? nullptr | |||||
| : (direction == NavigateDirection_FirstChild ? children.front() | |||||
| : children.back()); | |||||
| } | |||||
| if (direction == NavigateDirection_NextSibling | |||||
| || direction == NavigateDirection_PreviousSibling) | |||||
| { | |||||
| if (auto* parent = accessibilityHandler.getParent()) | |||||
| { | |||||
| const auto siblings = parent->getChildren(); | |||||
| const auto iter = std::find (siblings.cbegin(), siblings.cend(), &accessibilityHandler); | |||||
| if (iter == siblings.end()) | |||||
| return nullptr; | |||||
| if (direction == NavigateDirection_NextSibling && iter != std::prev (siblings.cend())) | |||||
| return *std::next (iter); | |||||
| if (direction == NavigateDirection_PreviousSibling && iter != siblings.cbegin()) | |||||
| return *std::prev (iter); | |||||
| } | |||||
| } | |||||
| return nullptr; | |||||
| }(); | |||||
| if (handler != nullptr) | |||||
| if (auto* provider = handler->getNativeImplementation()) | |||||
| if (provider->isElementValid()) | |||||
| provider->QueryInterface (IID_PPV_ARGS (pRetVal)); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT AccessibilityNativeHandle::GetRuntimeId (SAFEARRAY** pRetVal) | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| if (! isFragmentRoot()) | |||||
| { | |||||
| *pRetVal = SafeArrayCreateVector (VT_I4, 0, 2); | |||||
| if (*pRetVal == nullptr) | |||||
| return E_OUTOFMEMORY; | |||||
| for (LONG i = 0; i < 2; ++i) | |||||
| { | |||||
| auto hr = SafeArrayPutElement (*pRetVal, &i, &rtid[i]); | |||||
| if (FAILED (hr)) | |||||
| return E_FAIL; | |||||
| } | |||||
| } | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT AccessibilityNativeHandle::get_BoundingRectangle (UiaRect* pRetVal) | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| auto bounds = Desktop::getInstance().getDisplays() | |||||
| .logicalToPhysical (accessibilityHandler.getComponent().getScreenBounds()); | |||||
| pRetVal->left = bounds.getX(); | |||||
| pRetVal->top = bounds.getY(); | |||||
| pRetVal->width = bounds.getWidth(); | |||||
| pRetVal->height = bounds.getHeight(); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT AccessibilityNativeHandle::GetEmbeddedFragmentRoots (SAFEARRAY** pRetVal) | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [] | |||||
| { | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT AccessibilityNativeHandle::SetFocus() | |||||
| { | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| accessibilityHandler.grabFocus(); | |||||
| return S_OK; | |||||
| } | |||||
| JUCE_COMRESULT AccessibilityNativeHandle::get_FragmentRoot (IRawElementProviderFragmentRoot** pRetVal) | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT | |||||
| { | |||||
| auto* handler = [&]() -> AccessibilityHandler* | |||||
| { | |||||
| if (isFragmentRoot()) | |||||
| return &accessibilityHandler; | |||||
| if (auto* peer = accessibilityHandler.getComponent().getPeer()) | |||||
| if (auto* handler = peer->getComponent().getAccessibilityHandler()) | |||||
| return handler; | |||||
| return nullptr; | |||||
| }(); | |||||
| if (handler != nullptr) | |||||
| { | |||||
| handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal)); | |||||
| return S_OK; | |||||
| } | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| }); | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT AccessibilityNativeHandle::ElementProviderFromPoint (double x, double y, IRawElementProviderFragment** pRetVal) | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| auto* handler = [&] | |||||
| { | |||||
| auto logicalScreenPoint = Desktop::getInstance().getDisplays() | |||||
| .physicalToLogical (Point<int> (roundToInt (x), | |||||
| roundToInt (y))); | |||||
| if (auto* child = accessibilityHandler.getChildAt (logicalScreenPoint)) | |||||
| return child; | |||||
| return &accessibilityHandler; | |||||
| }(); | |||||
| handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal)); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT AccessibilityNativeHandle::GetFocus (IRawElementProviderFragment** pRetVal) | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| const auto getFocusHandler = [this]() -> AccessibilityHandler* | |||||
| { | |||||
| if (auto* modal = Component::getCurrentlyModalComponent()) | |||||
| { | |||||
| const auto& component = accessibilityHandler.getComponent(); | |||||
| if (! component.isParentOf (modal) | |||||
| && component.isCurrentlyBlockedByAnotherModalComponent()) | |||||
| { | |||||
| if (auto* modalHandler = modal->getAccessibilityHandler()) | |||||
| { | |||||
| if (auto* focusChild = modalHandler->getChildFocus()) | |||||
| return focusChild; | |||||
| return modalHandler; | |||||
| } | |||||
| } | |||||
| } | |||||
| if (auto* focusChild = accessibilityHandler.getChildFocus()) | |||||
| return focusChild; | |||||
| return nullptr; | |||||
| }; | |||||
| if (auto* focusHandler = getFocusHandler()) | |||||
| focusHandler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal)); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| //============================================================================== | |||||
| String AccessibilityNativeHandle::getElementName() const | |||||
| { | |||||
| if (accessibilityHandler.getRole() == AccessibilityRole::tooltip) | |||||
| return accessibilityHandler.getDescription(); | |||||
| auto name = accessibilityHandler.getTitle(); | |||||
| if (name.isEmpty() && isFragmentRoot()) | |||||
| return getAccessibleApplicationOrPluginName(); | |||||
| return name; | |||||
| } | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,80 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| #define UIA_FullDescriptionPropertyId 30159 | |||||
| #define UIA_IsDialogPropertyId 30174 | |||||
| class AccessibilityNativeHandle : public ComBaseClassHelper<IRawElementProviderSimple, | |||||
| IRawElementProviderFragment, | |||||
| IRawElementProviderFragmentRoot> | |||||
| { | |||||
| public: | |||||
| explicit AccessibilityNativeHandle (AccessibilityHandler& handler); | |||||
| //============================================================================== | |||||
| void invalidateElement() noexcept { valid = false; } | |||||
| bool isElementValid() const noexcept { return valid; } | |||||
| const AccessibilityHandler& getHandler() { return accessibilityHandler; } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT QueryInterface (REFIID refId, void** result) override; | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT get_HostRawElementProvider (IRawElementProviderSimple** provider) override; | |||||
| JUCE_COMRESULT get_ProviderOptions (ProviderOptions* options) override; | |||||
| JUCE_COMRESULT GetPatternProvider (PATTERNID pId, IUnknown** provider) override; | |||||
| JUCE_COMRESULT GetPropertyValue (PROPERTYID propertyId, VARIANT* pRetVal) override; | |||||
| JUCE_COMRESULT Navigate (NavigateDirection direction, IRawElementProviderFragment** pRetVal) override; | |||||
| JUCE_COMRESULT GetRuntimeId (SAFEARRAY** pRetVal) override; | |||||
| JUCE_COMRESULT get_BoundingRectangle (UiaRect* pRetVal) override; | |||||
| JUCE_COMRESULT GetEmbeddedFragmentRoots (SAFEARRAY** pRetVal) override; | |||||
| JUCE_COMRESULT SetFocus() override; | |||||
| JUCE_COMRESULT get_FragmentRoot (IRawElementProviderFragmentRoot** pRetVal) override; | |||||
| JUCE_COMRESULT ElementProviderFromPoint (double x, double y, IRawElementProviderFragment** pRetVal) override; | |||||
| JUCE_COMRESULT GetFocus (IRawElementProviderFragment** pRetVal) override; | |||||
| private: | |||||
| //============================================================================== | |||||
| String getElementName() const; | |||||
| bool isFragmentRoot() const { return accessibilityHandler.getComponent().isOnDesktop(); } | |||||
| //============================================================================== | |||||
| AccessibilityHandler& accessibilityHandler; | |||||
| static int idCounter; | |||||
| std::array<int, 2> rtid { UiaAppendRuntimeId, ++idCounter }; | |||||
| bool valid = true; | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeHandle) | |||||
| }; | |||||
| } | |||||
| @@ -0,0 +1,86 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| //============================================================================== | |||||
| class UIAExpandCollapseProvider : public UIAProviderBase, | |||||
| public ComBaseClassHelper<IExpandCollapseProvider> | |||||
| { | |||||
| public: | |||||
| explicit UIAExpandCollapseProvider (AccessibilityNativeHandle* nativeHandle) | |||||
| : UIAProviderBase (nativeHandle) | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT Expand() override | |||||
| { | |||||
| return invokeShowMenu(); | |||||
| } | |||||
| JUCE_COMRESULT Collapse() override | |||||
| { | |||||
| return invokeShowMenu(); | |||||
| } | |||||
| JUCE_COMRESULT get_ExpandCollapseState (ExpandCollapseState* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| *pRetVal = getHandler().getCurrentState().isExpanded() | |||||
| ? ExpandCollapseState_Expanded | |||||
| : ExpandCollapseState_Collapsed; | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| private: | |||||
| JUCE_COMRESULT invokeShowMenu() | |||||
| { | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| const auto& handler = getHandler(); | |||||
| if (handler.getActions().invoke (AccessibilityActionType::showMenu)) | |||||
| { | |||||
| sendAccessibilityAutomationEvent (handler, handler.getCurrentState().isExpanded() | |||||
| ? UIA_MenuOpenedEventId | |||||
| : UIA_MenuClosedEventId); | |||||
| return S_OK; | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAExpandCollapseProvider) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,101 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| //============================================================================== | |||||
| class UIAGridItemProvider : public UIAProviderBase, | |||||
| public ComBaseClassHelper<IGridItemProvider> | |||||
| { | |||||
| public: | |||||
| explicit UIAGridItemProvider (AccessibilityNativeHandle* nativeHandle) | |||||
| : UIAProviderBase (nativeHandle) | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT get_Row (int* pRetVal) override | |||||
| { | |||||
| return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface) | |||||
| { | |||||
| *pRetVal = cellInterface.getRowIndex(); | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_Column (int* pRetVal) override | |||||
| { | |||||
| return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface) | |||||
| { | |||||
| *pRetVal = cellInterface.getColumnIndex(); | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_RowSpan (int* pRetVal) override | |||||
| { | |||||
| return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface) | |||||
| { | |||||
| *pRetVal = cellInterface.getRowSpan(); | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_ColumnSpan (int* pRetVal) override | |||||
| { | |||||
| return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface) | |||||
| { | |||||
| *pRetVal = cellInterface.getColumnSpan(); | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_ContainingGrid (IRawElementProviderSimple** pRetVal) override | |||||
| { | |||||
| return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface) | |||||
| { | |||||
| if (auto* handler = cellInterface.getTableHandler()) | |||||
| handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal)); | |||||
| }); | |||||
| } | |||||
| private: | |||||
| template <typename Value, typename Callback> | |||||
| JUCE_COMRESULT withCellInterface (Value* pRetVal, Callback&& callback) const | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT | |||||
| { | |||||
| if (auto* cellInterface = getHandler().getCellInterface()) | |||||
| { | |||||
| callback (*cellInterface); | |||||
| return S_OK; | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| }); | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAGridItemProvider) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,90 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| //============================================================================== | |||||
| class UIAGridProvider : public UIAProviderBase, | |||||
| public ComBaseClassHelper<IGridProvider> | |||||
| { | |||||
| public: | |||||
| explicit UIAGridProvider (AccessibilityNativeHandle* nativeHandle) | |||||
| : UIAProviderBase (nativeHandle) | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT GetItem (int row, int column, IRawElementProviderSimple** pRetVal) override | |||||
| { | |||||
| return withTableInterface (pRetVal, [&] (const AccessibilityTableInterface& tableInterface) | |||||
| { | |||||
| if (! isPositiveAndBelow (row, tableInterface.getNumRows()) | |||||
| || ! isPositiveAndBelow (column, tableInterface.getNumColumns())) | |||||
| return E_INVALIDARG; | |||||
| if (auto* handler = tableInterface.getCellHandler (row, column)) | |||||
| handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal)); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_RowCount (int* pRetVal) override | |||||
| { | |||||
| return withTableInterface (pRetVal, [&] (const AccessibilityTableInterface& tableInterface) | |||||
| { | |||||
| *pRetVal = tableInterface.getNumRows(); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_ColumnCount (int* pRetVal) override | |||||
| { | |||||
| return withTableInterface (pRetVal, [&] (const AccessibilityTableInterface& tableInterface) | |||||
| { | |||||
| *pRetVal = tableInterface.getNumColumns(); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| private: | |||||
| template <typename Value, typename Callback> | |||||
| JUCE_COMRESULT withTableInterface (Value* pRetVal, Callback&& callback) const | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT | |||||
| { | |||||
| if (auto* tableInterface = getHandler().getTableInterface()) | |||||
| return callback (*tableInterface); | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| }); | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAGridProvider) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,103 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| namespace VariantHelpers | |||||
| { | |||||
| inline void clear (VARIANT* variant) | |||||
| { | |||||
| variant->vt = VT_EMPTY; | |||||
| } | |||||
| inline void setInt (int value, VARIANT* variant) | |||||
| { | |||||
| variant->vt = VT_I4; | |||||
| variant->lVal = value; | |||||
| } | |||||
| inline void setBool (bool value, VARIANT* variant) | |||||
| { | |||||
| variant->vt = VT_BOOL; | |||||
| variant->boolVal = value ? -1 : 0; | |||||
| } | |||||
| inline void setString (const String& value, VARIANT* variant) | |||||
| { | |||||
| variant->vt = VT_BSTR; | |||||
| variant->bstrVal = SysAllocString ((const OLECHAR*) value.toWideCharPointer()); | |||||
| } | |||||
| inline void setDouble (double value, VARIANT* variant) | |||||
| { | |||||
| variant->vt = VT_R8; | |||||
| variant->dblVal = value; | |||||
| } | |||||
| } | |||||
| JUCE_COMRESULT addHandlersToArray (const std::vector<const AccessibilityHandler*>& handlers, SAFEARRAY** pRetVal) | |||||
| { | |||||
| auto numHandlers = handlers.size(); | |||||
| *pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, (ULONG) numHandlers); | |||||
| if (pRetVal != nullptr) | |||||
| { | |||||
| for (LONG i = 0; i < (LONG) numHandlers; ++i) | |||||
| { | |||||
| auto* handler = handlers[i]; | |||||
| if (handler == nullptr) | |||||
| continue; | |||||
| ComSmartPtr<IRawElementProviderSimple> provider; | |||||
| handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress())); | |||||
| auto hr = SafeArrayPutElement (*pRetVal, &i, provider); | |||||
| if (FAILED (hr)) | |||||
| return E_FAIL; | |||||
| } | |||||
| } | |||||
| return S_OK; | |||||
| } | |||||
| template <typename Value, typename Object, typename Callback> | |||||
| JUCE_COMRESULT withCheckedComArgs (Value* pRetVal, Object& handle, Callback&& callback) | |||||
| { | |||||
| if (pRetVal == nullptr) | |||||
| return E_INVALIDARG; | |||||
| *pRetVal = Value{}; | |||||
| if (! handle.isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| return callback(); | |||||
| } | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,62 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| //============================================================================== | |||||
| class UIAInvokeProvider : public UIAProviderBase, | |||||
| public ComBaseClassHelper<IInvokeProvider> | |||||
| { | |||||
| public: | |||||
| explicit UIAInvokeProvider (AccessibilityNativeHandle* nativeHandle) | |||||
| : UIAProviderBase (nativeHandle) | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT Invoke() override | |||||
| { | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| const auto& handler = getHandler(); | |||||
| if (handler.getActions().invoke (AccessibilityActionType::press)) | |||||
| { | |||||
| if (isElementValid()) | |||||
| sendAccessibilityAutomationEvent (handler, UIA_Invoke_InvokedEventId); | |||||
| return S_OK; | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAInvokeProvider) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,58 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| //============================================================================== | |||||
| class UIAProviderBase | |||||
| { | |||||
| public: | |||||
| explicit UIAProviderBase (AccessibilityNativeHandle* nativeHandleIn) | |||||
| : nativeHandle (nativeHandleIn) | |||||
| { | |||||
| } | |||||
| bool isElementValid() const | |||||
| { | |||||
| if (nativeHandle != nullptr) | |||||
| return nativeHandle->isElementValid(); | |||||
| return false; | |||||
| } | |||||
| const AccessibilityHandler& getHandler() const | |||||
| { | |||||
| return nativeHandle->getHandler(); | |||||
| } | |||||
| private: | |||||
| ComSmartPtr<AccessibilityNativeHandle> nativeHandle; | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAProviderBase) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,43 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| void sendAccessibilityAutomationEvent (const AccessibilityHandler&, EVENTID); | |||||
| void sendAccessibilityPropertyChangedEvent (const AccessibilityHandler&, PROPERTYID, VARIANT); | |||||
| } | |||||
| #include "juce_win32_UIAProviderBase.h" | |||||
| #include "juce_win32_UIAExpandCollapseProvider.h" | |||||
| #include "juce_win32_UIAGridItemProvider.h" | |||||
| #include "juce_win32_UIAGridProvider.h" | |||||
| #include "juce_win32_UIAInvokeProvider.h" | |||||
| #include "juce_win32_UIARangeValueProvider.h" | |||||
| #include "juce_win32_UIASelectionProvider.h" | |||||
| #include "juce_win32_UIATextProvider.h" | |||||
| #include "juce_win32_UIAToggleProvider.h" | |||||
| #include "juce_win32_UIATransformProvider.h" | |||||
| #include "juce_win32_UIAValueProvider.h" | |||||
| #include "juce_win32_UIAWindowProvider.h" | |||||
| @@ -0,0 +1,140 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| //============================================================================== | |||||
| class UIARangeValueProvider : public UIAProviderBase, | |||||
| public ComBaseClassHelper<IRangeValueProvider> | |||||
| { | |||||
| public: | |||||
| explicit UIARangeValueProvider (AccessibilityNativeHandle* nativeHandle) | |||||
| : UIAProviderBase (nativeHandle) | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT SetValue (double val) override | |||||
| { | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| const auto& handler = getHandler(); | |||||
| if (auto* valueInterface = handler.getValueInterface()) | |||||
| { | |||||
| auto range = valueInterface->getRange(); | |||||
| if (range.isValid()) | |||||
| { | |||||
| if (val < range.getMinimumValue() || val > range.getMaximumValue()) | |||||
| return E_INVALIDARG; | |||||
| if (! valueInterface->isReadOnly()) | |||||
| { | |||||
| valueInterface->setValue (val); | |||||
| VARIANT newValue; | |||||
| VariantHelpers::setDouble (valueInterface->getCurrentValue(), &newValue); | |||||
| sendAccessibilityPropertyChangedEvent (handler, UIA_RangeValueValuePropertyId, newValue); | |||||
| return S_OK; | |||||
| } | |||||
| } | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| } | |||||
| JUCE_COMRESULT get_Value (double* pRetVal) override | |||||
| { | |||||
| return withValueInterface (pRetVal, [] (const AccessibilityValueInterface& valueInterface) | |||||
| { | |||||
| return valueInterface.getCurrentValue(); | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_IsReadOnly (BOOL* pRetVal) override | |||||
| { | |||||
| return withValueInterface (pRetVal, [] (const AccessibilityValueInterface& valueInterface) | |||||
| { | |||||
| return valueInterface.isReadOnly(); | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_Maximum (double* pRetVal) override | |||||
| { | |||||
| return withValueInterface (pRetVal, [] (const AccessibilityValueInterface& valueInterface) | |||||
| { | |||||
| return valueInterface.getRange().getMaximumValue(); | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_Minimum (double* pRetVal) override | |||||
| { | |||||
| return withValueInterface (pRetVal, [] (const AccessibilityValueInterface& valueInterface) | |||||
| { | |||||
| return valueInterface.getRange().getMinimumValue(); | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_LargeChange (double* pRetVal) override | |||||
| { | |||||
| return get_SmallChange (pRetVal); | |||||
| } | |||||
| JUCE_COMRESULT get_SmallChange (double* pRetVal) override | |||||
| { | |||||
| return withValueInterface (pRetVal, [] (const AccessibilityValueInterface& valueInterface) | |||||
| { | |||||
| return valueInterface.getRange().getInterval(); | |||||
| }); | |||||
| } | |||||
| private: | |||||
| template <typename Value, typename Callback> | |||||
| JUCE_COMRESULT withValueInterface (Value* pRetVal, Callback&& callback) const | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT | |||||
| { | |||||
| if (auto* valueInterface = getHandler().getValueInterface()) | |||||
| { | |||||
| if (valueInterface->getRange().isValid()) | |||||
| { | |||||
| *pRetVal = callback (*valueInterface); | |||||
| return S_OK; | |||||
| } | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| }); | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIARangeValueProvider) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,252 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| JUCE_COMCLASS (ISelectionProvider2, "14f68475-ee1c-44f6-a869-d239381f0fe7") : public ISelectionProvider | |||||
| { | |||||
| JUCE_COMCALL get_FirstSelectedItem (IRawElementProviderSimple** retVal) = 0; | |||||
| JUCE_COMCALL get_LastSelectedItem (IRawElementProviderSimple** retVal) = 0; | |||||
| JUCE_COMCALL get_CurrentSelectedItem (IRawElementProviderSimple** retVal) = 0; | |||||
| JUCE_COMCALL get_ItemCount (int* retVal) = 0; | |||||
| }; | |||||
| //============================================================================== | |||||
| class UIASelectionItemProvider : public UIAProviderBase, | |||||
| public ComBaseClassHelper<ISelectionItemProvider> | |||||
| { | |||||
| public: | |||||
| explicit UIASelectionItemProvider (AccessibilityNativeHandle* nativeHandle) | |||||
| : UIAProviderBase (nativeHandle), | |||||
| isRadioButton (getHandler().getRole() == AccessibilityRole::radioButton) | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT AddToSelection() override | |||||
| { | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| const auto& handler = getHandler(); | |||||
| if (isRadioButton) | |||||
| { | |||||
| handler.getActions().invoke (AccessibilityActionType::press); | |||||
| sendAccessibilityAutomationEvent (handler, UIA_SelectionItem_ElementSelectedEventId); | |||||
| return S_OK; | |||||
| } | |||||
| handler.getActions().invoke (AccessibilityActionType::toggle); | |||||
| handler.getActions().invoke (AccessibilityActionType::press); | |||||
| return S_OK; | |||||
| } | |||||
| JUCE_COMRESULT get_IsSelected (BOOL* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| const auto state = getHandler().getCurrentState(); | |||||
| *pRetVal = isRadioButton ? state.isChecked() : state.isSelected(); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_SelectionContainer (IRawElementProviderSimple** pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| if (! isRadioButton) | |||||
| if (auto* parent = getHandler().getParent()) | |||||
| parent->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal)); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT RemoveFromSelection() override | |||||
| { | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| if (! isRadioButton) | |||||
| { | |||||
| const auto& handler = getHandler(); | |||||
| if (handler.getCurrentState().isSelected()) | |||||
| getHandler().getActions().invoke (AccessibilityActionType::toggle); | |||||
| } | |||||
| return S_OK; | |||||
| } | |||||
| JUCE_COMRESULT Select() override | |||||
| { | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| AddToSelection(); | |||||
| if (! isRadioButton) | |||||
| { | |||||
| const auto& handler = getHandler(); | |||||
| if (auto* parent = handler.getParent()) | |||||
| for (auto* child : parent->getChildren()) | |||||
| if (child != &handler && child->getCurrentState().isSelected()) | |||||
| child->getActions().invoke (AccessibilityActionType::toggle); | |||||
| } | |||||
| return S_OK; | |||||
| } | |||||
| private: | |||||
| const bool isRadioButton; | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIASelectionItemProvider) | |||||
| }; | |||||
| //============================================================================== | |||||
| class UIASelectionProvider : public UIAProviderBase, | |||||
| public ComBaseClassHelper<ISelectionProvider2> | |||||
| { | |||||
| public: | |||||
| explicit UIASelectionProvider (AccessibilityNativeHandle* nativeHandle) | |||||
| : UIAProviderBase (nativeHandle) | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT QueryInterface (REFIID iid, void** result) override | |||||
| { | |||||
| if (iid == _uuidof (IUnknown) || iid == _uuidof (ISelectionProvider)) | |||||
| return castToType<ISelectionProvider> (result); | |||||
| if (iid == _uuidof (ISelectionProvider2)) | |||||
| return castToType<ISelectionProvider2> (result); | |||||
| *result = nullptr; | |||||
| return E_NOINTERFACE; | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT get_CanSelectMultiple (BOOL* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| *pRetVal = isMultiSelectable(); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_IsSelectionRequired (BOOL* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| *pRetVal = getSelectedChildren().size() > 0 && ! isMultiSelectable(); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT GetSelection (SAFEARRAY** pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| return addHandlersToArray (getSelectedChildren(), pRetVal); | |||||
| }); | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT get_FirstSelectedItem (IRawElementProviderSimple** pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| auto selectedChildren = getSelectedChildren(); | |||||
| if (! selectedChildren.empty()) | |||||
| selectedChildren.front()->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal)); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_LastSelectedItem (IRawElementProviderSimple** pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| auto selectedChildren = getSelectedChildren(); | |||||
| if (! selectedChildren.empty()) | |||||
| selectedChildren.back()->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal)); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_CurrentSelectedItem (IRawElementProviderSimple** pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| get_FirstSelectedItem (pRetVal); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_ItemCount (int* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| *pRetVal = (int) getSelectedChildren().size(); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| private: | |||||
| bool isMultiSelectable() const noexcept | |||||
| { | |||||
| return getHandler().getCurrentState().isMultiSelectable(); | |||||
| } | |||||
| std::vector<const AccessibilityHandler*> getSelectedChildren() const | |||||
| { | |||||
| std::vector<const AccessibilityHandler*> selectedHandlers; | |||||
| for (auto* child : getHandler().getComponent().getChildren()) | |||||
| if (auto* handler = child->getAccessibilityHandler()) | |||||
| if (handler->getCurrentState().isSelected()) | |||||
| selectedHandlers.push_back (handler); | |||||
| return selectedHandlers; | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIASelectionProvider) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,664 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| //============================================================================== | |||||
| class UIATextProvider : public UIAProviderBase, | |||||
| public ComBaseClassHelper<ITextProvider2> | |||||
| { | |||||
| public: | |||||
| explicit UIATextProvider (AccessibilityNativeHandle* nativeHandle) | |||||
| : UIAProviderBase (nativeHandle) | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT QueryInterface (REFIID iid, void** result) override | |||||
| { | |||||
| if (iid == _uuidof (IUnknown) || iid == _uuidof (ITextProvider)) | |||||
| return castToType<ITextProvider> (result); | |||||
| if (iid == _uuidof (ITextProvider2)) | |||||
| return castToType<ITextProvider2> (result); | |||||
| *result = nullptr; | |||||
| return E_NOINTERFACE; | |||||
| } | |||||
| //============================================================================= | |||||
| JUCE_COMRESULT get_DocumentRange (ITextRangeProvider** pRetVal) override | |||||
| { | |||||
| return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface) | |||||
| { | |||||
| *pRetVal = new UIATextRangeProvider (*this, { 0, textInterface.getTotalNumCharacters() }); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_SupportedTextSelection (SupportedTextSelection* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| *pRetVal = SupportedTextSelection_Single; | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT GetSelection (SAFEARRAY** pRetVal) override | |||||
| { | |||||
| return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface) | |||||
| { | |||||
| *pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 1); | |||||
| if (pRetVal != nullptr) | |||||
| { | |||||
| auto selection = textInterface.getSelection(); | |||||
| auto hasSelection = ! selection.isEmpty(); | |||||
| auto cursorPos = textInterface.getTextInsertionOffset(); | |||||
| auto* rangeProvider = new UIATextRangeProvider (*this, | |||||
| { hasSelection ? selection.getStart() : cursorPos, | |||||
| hasSelection ? selection.getEnd() : cursorPos }); | |||||
| LONG pos = 0; | |||||
| auto hr = SafeArrayPutElement (*pRetVal, &pos, static_cast<IUnknown*> (rangeProvider)); | |||||
| if (FAILED (hr)) | |||||
| return E_FAIL; | |||||
| rangeProvider->Release(); | |||||
| } | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT GetVisibleRanges (SAFEARRAY** pRetVal) override | |||||
| { | |||||
| return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface) | |||||
| { | |||||
| *pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 1); | |||||
| if (pRetVal != nullptr) | |||||
| { | |||||
| auto* rangeProvider = new UIATextRangeProvider (*this, { 0, textInterface.getTotalNumCharacters() }); | |||||
| LONG pos = 0; | |||||
| auto hr = SafeArrayPutElement (*pRetVal, &pos, static_cast<IUnknown*> (rangeProvider)); | |||||
| if (FAILED (hr)) | |||||
| return E_FAIL; | |||||
| rangeProvider->Release(); | |||||
| } | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT RangeFromChild (IRawElementProviderSimple*, ITextRangeProvider** pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [] | |||||
| { | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT RangeFromPoint (UiaPoint point, ITextRangeProvider** pRetVal) override | |||||
| { | |||||
| return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface) | |||||
| { | |||||
| auto offset = textInterface.getOffsetAtPoint ({ roundToInt (point.x), roundToInt (point.y) }); | |||||
| if (offset > 0) | |||||
| *pRetVal = new UIATextRangeProvider (*this, { offset, offset }); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT GetCaretRange (BOOL* isActive, ITextRangeProvider** pRetVal) override | |||||
| { | |||||
| return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface) | |||||
| { | |||||
| *isActive = getHandler().hasFocus (false); | |||||
| auto cursorPos = textInterface.getTextInsertionOffset(); | |||||
| *pRetVal = new UIATextRangeProvider (*this, { cursorPos, cursorPos }); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT RangeFromAnnotation (IRawElementProviderSimple*, ITextRangeProvider** pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [] | |||||
| { | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| private: | |||||
| //============================================================================== | |||||
| template <typename Value, typename Callback> | |||||
| JUCE_COMRESULT withTextInterface (Value* pRetVal, Callback&& callback) const | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT | |||||
| { | |||||
| if (auto* textInterface = getHandler().getTextInterface()) | |||||
| return callback (*textInterface); | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| }); | |||||
| } | |||||
| //============================================================================== | |||||
| class UIATextRangeProvider : public UIAProviderBase, | |||||
| public ComBaseClassHelper<ITextRangeProvider> | |||||
| { | |||||
| public: | |||||
| UIATextRangeProvider (UIATextProvider& textProvider, Range<int> range) | |||||
| : UIAProviderBase (textProvider.getHandler().getNativeImplementation()), | |||||
| owner (&textProvider), | |||||
| selectionRange (range) | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| Range<int> getSelectionRange() const noexcept { return selectionRange; } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT AddToSelection() override | |||||
| { | |||||
| return Select(); | |||||
| } | |||||
| JUCE_COMRESULT Clone (ITextRangeProvider** pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| *pRetVal = new UIATextRangeProvider (*owner, selectionRange); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT Compare (ITextRangeProvider* range, BOOL* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| *pRetVal = (selectionRange == static_cast<UIATextRangeProvider*> (range)->getSelectionRange()); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT CompareEndpoints (TextPatternRangeEndpoint endpoint, | |||||
| ITextRangeProvider* targetRange, | |||||
| TextPatternRangeEndpoint targetEndpoint, | |||||
| int* pRetVal) override | |||||
| { | |||||
| if (targetRange == nullptr) | |||||
| return E_INVALIDARG; | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| auto offset = (endpoint == TextPatternRangeEndpoint_Start ? selectionRange.getStart() | |||||
| : selectionRange.getEnd()); | |||||
| auto otherRange = static_cast<UIATextRangeProvider*> (targetRange)->getSelectionRange(); | |||||
| auto otherOffset = (targetEndpoint == TextPatternRangeEndpoint_Start ? otherRange.getStart() | |||||
| : otherRange.getEnd()); | |||||
| *pRetVal = offset - otherOffset; | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT ExpandToEnclosingUnit (TextUnit unit) override | |||||
| { | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| if (auto* textInterface = getHandler().getTextInterface()) | |||||
| { | |||||
| auto numCharacters = textInterface->getTotalNumCharacters(); | |||||
| if (numCharacters == 0) | |||||
| { | |||||
| selectionRange = {}; | |||||
| return S_OK; | |||||
| } | |||||
| if (unit == TextUnit_Character) | |||||
| { | |||||
| selectionRange.setStart (jlimit (0, numCharacters - 1, selectionRange.getStart())); | |||||
| selectionRange.setEnd (selectionRange.getStart() + 1); | |||||
| return S_OK; | |||||
| } | |||||
| if (unit == TextUnit_Paragraph | |||||
| || unit == TextUnit_Page | |||||
| || unit == TextUnit_Document) | |||||
| { | |||||
| selectionRange = { 0, textInterface->getTotalNumCharacters() }; | |||||
| return S_OK; | |||||
| } | |||||
| auto start = getNextEndpointPosition (*textInterface, | |||||
| selectionRange.getStart(), | |||||
| unit, | |||||
| NextEndpointDirection::backwards); | |||||
| if (start >= 0) | |||||
| { | |||||
| auto end = getNextEndpointPosition (*textInterface, | |||||
| start, | |||||
| unit, | |||||
| NextEndpointDirection::forwards); | |||||
| if (end >= 0) | |||||
| selectionRange = Range<int> (start, end); | |||||
| } | |||||
| return S_OK; | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| } | |||||
| JUCE_COMRESULT FindAttribute (TEXTATTRIBUTEID, VARIANT, BOOL, ITextRangeProvider** pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [] | |||||
| { | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT FindText (BSTR text, BOOL backward, BOOL ignoreCase, | |||||
| ITextRangeProvider** pRetVal) override | |||||
| { | |||||
| return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface) | |||||
| { | |||||
| auto selectionText = textInterface.getText (selectionRange); | |||||
| String textToSearchFor (text); | |||||
| auto offset = (backward ? (ignoreCase ? selectionText.lastIndexOfIgnoreCase (textToSearchFor) : selectionText.lastIndexOf (textToSearchFor)) | |||||
| : (ignoreCase ? selectionText.indexOfIgnoreCase (textToSearchFor) : selectionText.indexOf (textToSearchFor))); | |||||
| if (offset != -1) | |||||
| *pRetVal = new UIATextRangeProvider (*owner, { offset, offset + textToSearchFor.length() }); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT GetAttributeValue (TEXTATTRIBUTEID attributeId, VARIANT* pRetVal) override | |||||
| { | |||||
| return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface) | |||||
| { | |||||
| VariantHelpers::clear (pRetVal); | |||||
| const auto& handler = getHandler(); | |||||
| switch (attributeId) | |||||
| { | |||||
| case UIA_IsReadOnlyAttributeId: | |||||
| { | |||||
| const auto readOnly = [&] | |||||
| { | |||||
| if (auto* valueInterface = handler.getValueInterface()) | |||||
| return valueInterface->isReadOnly(); | |||||
| return false; | |||||
| }(); | |||||
| VariantHelpers::setBool (readOnly, pRetVal); | |||||
| break; | |||||
| } | |||||
| case UIA_CaretPositionAttributeId: | |||||
| { | |||||
| auto cursorPos = textInterface.getTextInsertionOffset(); | |||||
| auto caretPos = [&] | |||||
| { | |||||
| if (cursorPos == 0) | |||||
| return CaretPosition_BeginningOfLine; | |||||
| if (cursorPos == textInterface.getTotalNumCharacters()) | |||||
| return CaretPosition_EndOfLine; | |||||
| return CaretPosition_Unknown; | |||||
| }(); | |||||
| VariantHelpers::setInt (caretPos, pRetVal); | |||||
| break; | |||||
| } | |||||
| default: | |||||
| break; | |||||
| } | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT GetBoundingRectangles (SAFEARRAY** pRetVal) override | |||||
| { | |||||
| return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface) | |||||
| { | |||||
| auto rectangleList = textInterface.getTextBounds (selectionRange); | |||||
| auto numRectangles = rectangleList.getNumRectangles(); | |||||
| *pRetVal = SafeArrayCreateVector (VT_R8, 0, 4 * numRectangles); | |||||
| if (*pRetVal == nullptr) | |||||
| return E_FAIL; | |||||
| if (numRectangles > 0) | |||||
| { | |||||
| double* doubleArr = nullptr; | |||||
| if (FAILED (SafeArrayAccessData (*pRetVal, reinterpret_cast<void**> (&doubleArr)))) | |||||
| { | |||||
| SafeArrayDestroy (*pRetVal); | |||||
| return E_FAIL; | |||||
| } | |||||
| for (int i = 0; i < numRectangles; ++i) | |||||
| { | |||||
| auto r = Desktop::getInstance().getDisplays().logicalToPhysical (rectangleList.getRectangle (i)); | |||||
| doubleArr[i * 4] = r.getX(); | |||||
| doubleArr[i * 4 + 1] = r.getY(); | |||||
| doubleArr[i * 4 + 2] = r.getWidth(); | |||||
| doubleArr[i * 4 + 3] = r.getHeight(); | |||||
| } | |||||
| if (FAILED (SafeArrayUnaccessData (*pRetVal))) | |||||
| { | |||||
| SafeArrayDestroy (*pRetVal); | |||||
| return E_FAIL; | |||||
| } | |||||
| } | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT GetChildren (SAFEARRAY** pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| *pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 0); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT GetEnclosingElement (IRawElementProviderSimple** pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| getHandler().getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal)); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT GetText (int maxLength, BSTR* pRetVal) override | |||||
| { | |||||
| return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface) | |||||
| { | |||||
| auto text = textInterface.getText (selectionRange); | |||||
| if (maxLength >= 0 && text.length() > maxLength) | |||||
| text = text.substring (0, maxLength); | |||||
| *pRetVal = SysAllocString ((const OLECHAR*) text.toWideCharPointer()); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT Move (TextUnit unit, int count, int* pRetVal) override | |||||
| { | |||||
| return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface&) | |||||
| { | |||||
| if (count > 0) | |||||
| { | |||||
| MoveEndpointByUnit (TextPatternRangeEndpoint_End, unit, count, pRetVal); | |||||
| MoveEndpointByUnit (TextPatternRangeEndpoint_Start, unit, count, pRetVal); | |||||
| } | |||||
| else if (count < 0) | |||||
| { | |||||
| MoveEndpointByUnit (TextPatternRangeEndpoint_Start, unit, count, pRetVal); | |||||
| MoveEndpointByUnit (TextPatternRangeEndpoint_End, unit, count, pRetVal); | |||||
| } | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT MoveEndpointByRange (TextPatternRangeEndpoint endpoint, | |||||
| ITextRangeProvider* targetRange, | |||||
| TextPatternRangeEndpoint targetEndpoint) override | |||||
| { | |||||
| if (targetRange == nullptr) | |||||
| return E_INVALIDARG; | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| if (auto* textInterface = getHandler().getTextInterface()) | |||||
| { | |||||
| auto otherRange = static_cast<UIATextRangeProvider*> (targetRange)->getSelectionRange(); | |||||
| auto targetPoint = (targetEndpoint == TextPatternRangeEndpoint_Start ? otherRange.getStart() | |||||
| : otherRange.getEnd()); | |||||
| setEndpointChecked (endpoint, targetPoint); | |||||
| return S_OK; | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| } | |||||
| JUCE_COMRESULT MoveEndpointByUnit (TextPatternRangeEndpoint endpoint, | |||||
| TextUnit unit, | |||||
| int count, | |||||
| int* pRetVal) override | |||||
| { | |||||
| return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface) | |||||
| { | |||||
| auto numCharacters = textInterface.getTotalNumCharacters(); | |||||
| if (count == 0 || numCharacters == 0) | |||||
| return S_OK; | |||||
| auto isStart = (endpoint == TextPatternRangeEndpoint_Start); | |||||
| auto endpointToMove = (isStart ? selectionRange.getStart() : selectionRange.getEnd()); | |||||
| if (unit == TextUnit_Character) | |||||
| { | |||||
| auto targetPoint = jlimit (0, numCharacters, endpointToMove + count); | |||||
| *pRetVal = targetPoint - endpointToMove; | |||||
| setEndpointChecked (endpoint, targetPoint); | |||||
| return S_OK; | |||||
| } | |||||
| auto direction = (count > 0 ? NextEndpointDirection::forwards | |||||
| : NextEndpointDirection::backwards); | |||||
| if (unit == TextUnit_Paragraph | |||||
| || unit == TextUnit_Page | |||||
| || unit == TextUnit_Document) | |||||
| { | |||||
| *pRetVal = (direction == NextEndpointDirection::forwards ? 1 : -1); | |||||
| setEndpointChecked (endpoint, numCharacters); | |||||
| return S_OK; | |||||
| } | |||||
| for (int i = 0; i < std::abs (count); ++i) | |||||
| { | |||||
| auto nextEndpoint = getNextEndpointPosition (textInterface, | |||||
| endpointToMove, | |||||
| unit, | |||||
| direction); | |||||
| if (nextEndpoint < 0) | |||||
| { | |||||
| *pRetVal = (direction == NextEndpointDirection::forwards ? i : -i); | |||||
| setEndpointChecked (endpoint, endpointToMove); | |||||
| return S_OK; | |||||
| } | |||||
| endpointToMove = nextEndpoint; | |||||
| } | |||||
| *pRetVal = count; | |||||
| setEndpointChecked (endpoint, endpointToMove); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT RemoveFromSelection() override | |||||
| { | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| if (auto* textInterface = getHandler().getTextInterface()) | |||||
| { | |||||
| textInterface->setSelection ({}); | |||||
| return S_OK; | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| } | |||||
| JUCE_COMRESULT ScrollIntoView (BOOL) override | |||||
| { | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| } | |||||
| JUCE_COMRESULT Select() override | |||||
| { | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| if (auto* textInterface = getHandler().getTextInterface()) | |||||
| { | |||||
| textInterface->setSelection ({}); | |||||
| textInterface->setSelection (selectionRange); | |||||
| return S_OK; | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| } | |||||
| private: | |||||
| enum class NextEndpointDirection { forwards, backwards }; | |||||
| static int getNextEndpointPosition (const AccessibilityTextInterface& textInterface, | |||||
| int currentPosition, | |||||
| TextUnit unit, | |||||
| NextEndpointDirection direction) | |||||
| { | |||||
| auto isTextUnitSeparator = [unit] (const juce_wchar c) | |||||
| { | |||||
| return ((unit == TextUnit_Word || unit == TextUnit_Format) && CharacterFunctions::isWhitespace (c)) | |||||
| || (unit == TextUnit_Line && (c == '\r' || c == '\n')); | |||||
| }; | |||||
| constexpr int textBufferSize = 1024; | |||||
| int numChars = 0; | |||||
| if (direction == NextEndpointDirection::forwards) | |||||
| { | |||||
| auto textBuffer = textInterface.getText ({ currentPosition, | |||||
| jmin (textInterface.getTotalNumCharacters(), currentPosition + textBufferSize) }); | |||||
| for (auto charPtr = textBuffer.getCharPointer(); ! charPtr.isEmpty();) | |||||
| { | |||||
| auto character = charPtr.getAndAdvance(); | |||||
| ++numChars; | |||||
| if (isTextUnitSeparator (character)) | |||||
| return currentPosition + numChars; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| auto textBuffer = textInterface.getText ({ jmax (0, currentPosition - textBufferSize), | |||||
| currentPosition }); | |||||
| for (auto charPtr = textBuffer.end() - 1; charPtr != textBuffer.begin(); --charPtr) | |||||
| { | |||||
| auto character = *charPtr; | |||||
| if (isTextUnitSeparator (character)) | |||||
| return currentPosition - numChars; | |||||
| ++numChars; | |||||
| } | |||||
| } | |||||
| return -1; | |||||
| } | |||||
| void setEndpointChecked (TextPatternRangeEndpoint endpoint, int newEndpoint) | |||||
| { | |||||
| if (endpoint == TextPatternRangeEndpoint_Start) | |||||
| { | |||||
| if (selectionRange.getEnd() < newEndpoint) | |||||
| selectionRange.setEnd (newEndpoint); | |||||
| selectionRange.setStart (newEndpoint); | |||||
| } | |||||
| else | |||||
| { | |||||
| if (selectionRange.getStart() > newEndpoint) | |||||
| selectionRange.setStart (newEndpoint); | |||||
| selectionRange.setEnd (newEndpoint); | |||||
| } | |||||
| } | |||||
| ComSmartPtr<UIATextProvider> owner; | |||||
| Range<int> selectionRange; | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATextRangeProvider) | |||||
| }; | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATextProvider) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,80 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| //============================================================================== | |||||
| class UIAToggleProvider : public UIAProviderBase, | |||||
| public ComBaseClassHelper<IToggleProvider> | |||||
| { | |||||
| public: | |||||
| explicit UIAToggleProvider (AccessibilityNativeHandle* nativeHandle) | |||||
| : UIAProviderBase (nativeHandle) | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT Toggle() override | |||||
| { | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| const auto& handler = getHandler(); | |||||
| if (handler.getActions().invoke (AccessibilityActionType::toggle)) | |||||
| { | |||||
| VARIANT newValue; | |||||
| VariantHelpers::setInt (getCurrentToggleState(), &newValue); | |||||
| sendAccessibilityPropertyChangedEvent (handler, UIA_ToggleToggleStatePropertyId, newValue); | |||||
| return S_OK; | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| } | |||||
| JUCE_COMRESULT get_ToggleState (ToggleState* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| *pRetVal = getCurrentToggleState(); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| private: | |||||
| ToggleState getCurrentToggleState() const | |||||
| { | |||||
| return getHandler().getCurrentState().isChecked() ? ToggleState_On | |||||
| : ToggleState_Off; | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAToggleProvider) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,125 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| //============================================================================== | |||||
| class UIATransformProvider : public UIAProviderBase, | |||||
| public ComBaseClassHelper<ITransformProvider> | |||||
| { | |||||
| public: | |||||
| explicit UIATransformProvider (AccessibilityNativeHandle* nativeHandle) | |||||
| : UIAProviderBase (nativeHandle) | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT Move (double x, double y) override | |||||
| { | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| if (auto* peer = getPeer()) | |||||
| { | |||||
| RECT rect; | |||||
| GetWindowRect ((HWND) peer->getNativeHandle(), &rect); | |||||
| rect.left = roundToInt (x); | |||||
| rect.top = roundToInt (y); | |||||
| auto bounds = Rectangle<int>::leftTopRightBottom (rect.left, rect.top, rect.right, rect.bottom); | |||||
| peer->setBounds (Desktop::getInstance().getDisplays().physicalToLogical (bounds), | |||||
| peer->isFullScreen()); | |||||
| } | |||||
| return S_OK; | |||||
| } | |||||
| JUCE_COMRESULT Resize (double width, double height) override | |||||
| { | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| if (auto* peer = getPeer()) | |||||
| { | |||||
| auto scale = peer->getPlatformScaleFactor(); | |||||
| peer->getComponent().setSize (roundToInt (width / scale), | |||||
| roundToInt (height / scale)); | |||||
| } | |||||
| return S_OK; | |||||
| } | |||||
| JUCE_COMRESULT Rotate (double) override | |||||
| { | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| } | |||||
| JUCE_COMRESULT get_CanMove (BOOL* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| *pRetVal = true; | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_CanResize (BOOL* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| if (auto* peer = getPeer()) | |||||
| *pRetVal = ((peer->getStyleFlags() & ComponentPeer::windowIsResizable) != 0); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_CanRotate (BOOL* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| *pRetVal = false; | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| private: | |||||
| ComponentPeer* getPeer() const | |||||
| { | |||||
| return getHandler().getComponent().getPeer(); | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATransformProvider) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,121 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| //============================================================================== | |||||
| class UIAValueProvider : public UIAProviderBase, | |||||
| public ComBaseClassHelper<IValueProvider> | |||||
| { | |||||
| public: | |||||
| UIAValueProvider (AccessibilityNativeHandle* nativeHandle, bool editableText) | |||||
| : UIAProviderBase (nativeHandle), | |||||
| isEditableText (editableText) | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT SetValue (LPCWSTR val) override | |||||
| { | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| const auto& handler = getHandler(); | |||||
| const auto sendValuePropertyChangeMessage = [&]() | |||||
| { | |||||
| VARIANT newValue; | |||||
| VariantHelpers::setString (getCurrentValueString(), &newValue); | |||||
| sendAccessibilityPropertyChangedEvent (handler, UIA_ValueValuePropertyId, newValue); | |||||
| }; | |||||
| if (isEditableText) | |||||
| { | |||||
| handler.getTextInterface()->setText (String (val)); | |||||
| sendValuePropertyChangeMessage(); | |||||
| return S_OK; | |||||
| } | |||||
| if (auto* valueInterface = handler.getValueInterface()) | |||||
| { | |||||
| if (! valueInterface->isReadOnly()) | |||||
| { | |||||
| valueInterface->setValueAsString (String (val)); | |||||
| sendValuePropertyChangeMessage(); | |||||
| return S_OK; | |||||
| } | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| } | |||||
| JUCE_COMRESULT get_Value (BSTR* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| auto currentValue = getCurrentValueString(); | |||||
| *pRetVal = SysAllocString ((const OLECHAR*) currentValue.toWideCharPointer()); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_IsReadOnly (BOOL* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&] | |||||
| { | |||||
| if (! isEditableText) | |||||
| if (auto* valueInterface = getHandler().getValueInterface()) | |||||
| *pRetVal = valueInterface->isReadOnly(); | |||||
| return S_OK; | |||||
| }); | |||||
| } | |||||
| private: | |||||
| String getCurrentValueString() const | |||||
| { | |||||
| if (isEditableText) | |||||
| if (auto* textInterface = getHandler().getTextInterface()) | |||||
| return textInterface->getText ({ 0, textInterface->getTotalNumCharacters() }); | |||||
| if (auto* valueInterface = getHandler().getValueInterface()) | |||||
| return valueInterface->getCurrentValueAsString(); | |||||
| jassertfalse; | |||||
| return {}; | |||||
| } | |||||
| const bool isEditableText; | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAValueProvider) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,197 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| //============================================================================== | |||||
| class UIAWindowProvider : public UIAProviderBase, | |||||
| public ComBaseClassHelper<IWindowProvider> | |||||
| { | |||||
| public: | |||||
| explicit UIAWindowProvider (AccessibilityNativeHandle* nativeHandle) | |||||
| : UIAProviderBase (nativeHandle) | |||||
| { | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_COMRESULT SetVisualState (WindowVisualState state) override | |||||
| { | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| if (auto* peer = getPeer()) | |||||
| { | |||||
| switch (state) | |||||
| { | |||||
| case WindowVisualState_Maximized: | |||||
| peer->setFullScreen (true); | |||||
| break; | |||||
| case WindowVisualState_Minimized: | |||||
| peer->setMinimised (true); | |||||
| break; | |||||
| case WindowVisualState_Normal: | |||||
| peer->setFullScreen (false); | |||||
| peer->setMinimised (false); | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| return S_OK; | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| } | |||||
| JUCE_COMRESULT Close() override | |||||
| { | |||||
| if (! isElementValid()) | |||||
| return UIA_E_ELEMENTNOTAVAILABLE; | |||||
| if (auto* peer = getPeer()) | |||||
| { | |||||
| peer->handleUserClosingWindow(); | |||||
| return S_OK; | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| } | |||||
| JUCE_COMRESULT WaitForInputIdle (int, BOOL* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [] | |||||
| { | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_CanMaximize (BOOL* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT | |||||
| { | |||||
| if (auto* peer = getPeer()) | |||||
| { | |||||
| *pRetVal = (peer->getStyleFlags() & ComponentPeer::windowHasMaximiseButton) != 0; | |||||
| return S_OK; | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_CanMinimize (BOOL* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT | |||||
| { | |||||
| if (auto* peer = getPeer()) | |||||
| { | |||||
| *pRetVal = (peer->getStyleFlags() & ComponentPeer::windowHasMinimiseButton) != 0; | |||||
| return S_OK; | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_IsModal (BOOL* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT | |||||
| { | |||||
| if (auto* peer = getPeer()) | |||||
| { | |||||
| *pRetVal = peer->getComponent().isCurrentlyModal(); | |||||
| return S_OK; | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_WindowVisualState (WindowVisualState* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT | |||||
| { | |||||
| if (auto* peer = getPeer()) | |||||
| { | |||||
| if (peer->isFullScreen()) | |||||
| *pRetVal = WindowVisualState_Maximized; | |||||
| else if (peer->isMinimised()) | |||||
| *pRetVal = WindowVisualState_Minimized; | |||||
| else | |||||
| *pRetVal = WindowVisualState_Normal; | |||||
| return S_OK; | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_WindowInteractionState (WindowInteractionState* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT | |||||
| { | |||||
| if (auto* peer = getPeer()) | |||||
| { | |||||
| *pRetVal = peer->getComponent().isCurrentlyBlockedByAnotherModalComponent() | |||||
| ? WindowInteractionState::WindowInteractionState_BlockedByModalWindow | |||||
| : WindowInteractionState::WindowInteractionState_Running; | |||||
| return S_OK; | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| }); | |||||
| } | |||||
| JUCE_COMRESULT get_IsTopmost (BOOL* pRetVal) override | |||||
| { | |||||
| return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT | |||||
| { | |||||
| if (auto* peer = getPeer()) | |||||
| { | |||||
| *pRetVal = peer->isFocused(); | |||||
| return S_OK; | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| }); | |||||
| } | |||||
| private: | |||||
| ComponentPeer* getPeer() const | |||||
| { | |||||
| return getHandler().getComponent().getPeer(); | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAWindowProvider) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,158 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2020 - Raw Material Software Limited | |||||
| JUCE is an open source library subject to commercial or open-source | |||||
| licensing. | |||||
| By using JUCE, you agree to the terms of both the JUCE 6 End-User License | |||||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| Privacy Policy: www.juce.com/juce-privacy-policy | |||||
| Or: You may also use this code under the terms of the GPL v3 (see | |||||
| www.gnu.org/licenses). | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| class WindowsUIAWrapper : public DeletedAtShutdown | |||||
| { | |||||
| public: | |||||
| bool isLoaded() const noexcept | |||||
| { | |||||
| return uiaReturnRawElementProvider != nullptr | |||||
| && uiaHostProviderFromHwnd != nullptr | |||||
| && uiaRaiseAutomationPropertyChangedEvent != nullptr | |||||
| && uiaRaiseAutomationEvent != nullptr | |||||
| && uiaClientsAreListening != nullptr | |||||
| && uiaDisconnectProvider != nullptr | |||||
| && uiaDisconnectAllProviders != nullptr; | |||||
| } | |||||
| //============================================================================== | |||||
| LRESULT returnRawElementProvider (HWND hwnd, WPARAM wParam, LPARAM lParam, IRawElementProviderSimple* provider) | |||||
| { | |||||
| return uiaReturnRawElementProvider != nullptr ? uiaReturnRawElementProvider (hwnd, wParam, lParam, provider) | |||||
| : (LRESULT) nullptr; | |||||
| } | |||||
| JUCE_COMRESULT hostProviderFromHwnd (HWND hwnd, IRawElementProviderSimple** provider) | |||||
| { | |||||
| return uiaHostProviderFromHwnd != nullptr ? uiaHostProviderFromHwnd (hwnd, provider) | |||||
| : UIA_E_NOTSUPPORTED; | |||||
| } | |||||
| JUCE_COMRESULT raiseAutomationPropertyChangedEvent (IRawElementProviderSimple* provider, PROPERTYID propID, VARIANT oldValue, VARIANT newValue) | |||||
| { | |||||
| return uiaRaiseAutomationPropertyChangedEvent != nullptr ? uiaRaiseAutomationPropertyChangedEvent (provider, propID, oldValue, newValue) | |||||
| : UIA_E_NOTSUPPORTED; | |||||
| } | |||||
| JUCE_COMRESULT raiseAutomationEvent (IRawElementProviderSimple* provider, EVENTID eventID) | |||||
| { | |||||
| return uiaRaiseAutomationEvent != nullptr ? uiaRaiseAutomationEvent (provider, eventID) | |||||
| : UIA_E_NOTSUPPORTED; | |||||
| } | |||||
| BOOL clientsAreListening() | |||||
| { | |||||
| return uiaClientsAreListening != nullptr ? uiaClientsAreListening() | |||||
| : false; | |||||
| } | |||||
| JUCE_COMRESULT disconnectProvider (IRawElementProviderSimple* provider) | |||||
| { | |||||
| if (uiaDisconnectProvider != nullptr) | |||||
| { | |||||
| const ScopedValueSetter<IRawElementProviderSimple*> disconnectingProviderSetter (disconnectingProvider, provider); | |||||
| return uiaDisconnectProvider (provider); | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| } | |||||
| JUCE_COMRESULT disconnectAllProviders() | |||||
| { | |||||
| if (uiaDisconnectAllProviders != nullptr) | |||||
| { | |||||
| const ScopedValueSetter<bool> disconnectingAllProvidersSetter (disconnectingAllProviders, true); | |||||
| return uiaDisconnectAllProviders(); | |||||
| } | |||||
| return UIA_E_NOTSUPPORTED; | |||||
| } | |||||
| //============================================================================== | |||||
| bool isProviderDisconnecting (IRawElementProviderSimple* provider) | |||||
| { | |||||
| return disconnectingProvider == provider || disconnectingAllProviders; | |||||
| } | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (WindowsUIAWrapper) | |||||
| private: | |||||
| //============================================================================== | |||||
| WindowsUIAWrapper() | |||||
| { | |||||
| // force UIA COM library initialisation here to prevent an exception when calling methods from SendMessage() | |||||
| if (isLoaded()) | |||||
| returnRawElementProvider (nullptr, 0, 0, nullptr); | |||||
| else | |||||
| jassertfalse; // UIAutomationCore could not be loaded! | |||||
| } | |||||
| ~WindowsUIAWrapper() | |||||
| { | |||||
| disconnectAllProviders(); | |||||
| if (uiaHandle != nullptr) | |||||
| ::FreeLibrary (uiaHandle); | |||||
| clearSingletonInstance(); | |||||
| } | |||||
| //============================================================================== | |||||
| template<typename FuncType> | |||||
| static FuncType getUiaFunction (HMODULE module, StringRef funcName) | |||||
| { | |||||
| return (FuncType) GetProcAddress (module, funcName); | |||||
| } | |||||
| //============================================================================== | |||||
| using UiaReturnRawElementProviderFunc = LRESULT (WINAPI*) (HWND, WPARAM, LPARAM, IRawElementProviderSimple*); | |||||
| using UiaHostProviderFromHwndFunc = HRESULT (WINAPI*) (HWND, IRawElementProviderSimple**); | |||||
| using UiaRaiseAutomationPropertyChangedEventFunc = HRESULT (WINAPI*) (IRawElementProviderSimple*, PROPERTYID, VARIANT, VARIANT); | |||||
| using UiaRaiseAutomationEventFunc = HRESULT (WINAPI*) (IRawElementProviderSimple*, EVENTID); | |||||
| using UiaClientsAreListeningFunc = BOOL (WINAPI*) (); | |||||
| using UiaDisconnectProviderFunc = HRESULT (WINAPI*) (IRawElementProviderSimple*); | |||||
| using UiaDisconnectAllProvidersFunc = HRESULT (WINAPI*) (); | |||||
| HMODULE uiaHandle = ::LoadLibraryA ("UIAutomationCore.dll"); | |||||
| UiaReturnRawElementProviderFunc uiaReturnRawElementProvider = getUiaFunction<UiaReturnRawElementProviderFunc> (uiaHandle, "UiaReturnRawElementProvider"); | |||||
| UiaHostProviderFromHwndFunc uiaHostProviderFromHwnd = getUiaFunction<UiaHostProviderFromHwndFunc> (uiaHandle, "UiaHostProviderFromHwnd"); | |||||
| UiaRaiseAutomationPropertyChangedEventFunc uiaRaiseAutomationPropertyChangedEvent = getUiaFunction<UiaRaiseAutomationPropertyChangedEventFunc> (uiaHandle, "UiaRaiseAutomationPropertyChangedEvent"); | |||||
| UiaRaiseAutomationEventFunc uiaRaiseAutomationEvent = getUiaFunction<UiaRaiseAutomationEventFunc> (uiaHandle, "UiaRaiseAutomationEvent"); | |||||
| UiaClientsAreListeningFunc uiaClientsAreListening = getUiaFunction<UiaClientsAreListeningFunc> (uiaHandle, "UiaClientsAreListening"); | |||||
| UiaDisconnectProviderFunc uiaDisconnectProvider = getUiaFunction<UiaDisconnectProviderFunc> (uiaHandle, "UiaDisconnectProvider"); | |||||
| UiaDisconnectAllProvidersFunc uiaDisconnectAllProviders = getUiaFunction<UiaDisconnectAllProvidersFunc> (uiaHandle, "UiaDisconnectAllProviders"); | |||||
| IRawElementProviderSimple* disconnectingProvider = nullptr; | |||||
| bool disconnectingAllProviders = false; | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsUIAWrapper) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -56,26 +56,6 @@ namespace juce | |||||
| namespace juce | 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() | ||||
| @@ -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; | ||||
| } | } | ||||
| @@ -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() | ||||
| @@ -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 | ||||
| @@ -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)); | ||||
| @@ -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 | ||||
| @@ -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; | ||||
| @@ -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 | ||||
| @@ -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: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -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&) {} | ||||
| @@ -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: | ||||
| //============================================================================== | //============================================================================== | ||||