build(deps): vendor JUCE 7.0.12

This commit is contained in:
2026-09-08 14:55:21 +02:00
parent 5b99f51bd1
commit d6705ab29f
3591 changed files with 1218267 additions and 0 deletions
@@ -0,0 +1,37 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#if ! JUCE_NATIVE_ACCESSIBILITY_INCLUDED
class AccessibilityHandler::AccessibilityNativeImpl
{
public:
AccessibilityNativeImpl (AccessibilityHandler&) {}
};
#endif
} // namespace juce
@@ -0,0 +1,658 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
int AccessibilityNativeHandle::idCounter = 0;
//==============================================================================
class UIAScrollProvider final : public UIAProviderBase,
public ComBaseClassHelper<ComTypes::IScrollProvider>
{
public:
using UIAProviderBase::UIAProviderBase;
JUCE_COMCALL Scroll (ComTypes::ScrollAmount, ComTypes::ScrollAmount) override { return E_FAIL; }
JUCE_COMCALL SetScrollPercent (double, double) override { return E_FAIL; }
JUCE_COMCALL get_HorizontalScrollPercent (double*) override { return E_FAIL; }
JUCE_COMCALL get_VerticalScrollPercent (double*) override { return E_FAIL; }
JUCE_COMCALL get_HorizontalViewSize (double*) override { return E_FAIL; }
JUCE_COMCALL get_VerticalViewSize (double*) override { return E_FAIL; }
JUCE_COMCALL get_HorizontallyScrollable (BOOL*) override { return E_FAIL; }
JUCE_COMCALL get_VerticallyScrollable (BOOL*) override { return E_FAIL; }
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAScrollProvider)
};
class UIAScrollItemProvider final : public UIAProviderBase,
public ComBaseClassHelper<ComTypes::IScrollItemProvider>
{
public:
using UIAProviderBase::UIAProviderBase;
JUCE_COMCALL ScrollIntoView() override
{
if (auto* handler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (&getHandler(), &AccessibilityHandler::getTableInterface))
{
if (auto* tableInterface = handler->getTableInterface())
{
tableInterface->showCell (getHandler());
return S_OK;
}
}
return (HRESULT) UIA_E_NOTSUPPORTED;
}
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAScrollItemProvider)
};
//==============================================================================
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 auto roleToControlTypeId (AccessibilityRole roleType)
{
using namespace ComTypes::Constants;
switch (roleType)
{
case AccessibilityRole::popupMenu:
case AccessibilityRole::dialogWindow:
case AccessibilityRole::splashScreen:
case AccessibilityRole::window: return UIA_WindowControlTypeId;
case AccessibilityRole::label:
case AccessibilityRole::staticText: return UIA_TextControlTypeId;
case AccessibilityRole::column:
case AccessibilityRole::row: return UIA_ListItemControlTypeId;
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::editableText: return UIA_EditControlTypeId;
case AccessibilityRole::menuItem: return UIA_MenuItemControlTypeId;
case AccessibilityRole::menuBar: return UIA_MenuBarControlTypeId;
case AccessibilityRole::table: return UIA_TableControlTypeId;
case AccessibilityRole::tableHeader: return UIA_HeaderControlTypeId;
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::scrollBar: return UIA_ScrollBarControlTypeId;
case AccessibilityRole::tooltip: return UIA_ToolTipControlTypeId;
case AccessibilityRole::ignored:
case AccessibilityRole::unspecified: break;
};
return UIA_CustomControlTypeId;
}
//==============================================================================
AccessibilityNativeHandle::AccessibilityNativeHandle (AccessibilityHandler& handler)
: ComBaseClassHelper (0),
accessibilityHandler (handler)
{
}
//==============================================================================
JUCE_COMRESULT AccessibilityNativeHandle::QueryInterface (REFIID refId, void** result)
{
*result = nullptr;
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
if ((refId == __uuidof (ComTypes::IRawElementProviderFragmentRoot) && ! isFragmentRoot()))
return E_NOINTERFACE;
return ComBaseClassHelper::QueryInterface (refId, result);
}
//==============================================================================
JUCE_COMRESULT AccessibilityNativeHandle::get_HostRawElementProvider (IRawElementProviderSimple** pRetVal)
{
return withCheckedComArgs (pRetVal, *this, [&]
{
if (auto* wrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
{
if (isFragmentRoot())
return wrapper->hostProviderFromHwnd ((HWND) accessibilityHandler.getComponent().getWindowHandle(), pRetVal);
if (auto* embeddedWindow = static_cast<HWND> (AccessibilityHandler::getNativeChildForComponent (accessibilityHandler.getComponent())))
return wrapper->hostProviderFromHwnd (embeddedWindow, pRetVal);
}
return S_OK;
});
}
JUCE_COMRESULT AccessibilityNativeHandle::get_ProviderOptions (ProviderOptions* options)
{
if (options == nullptr)
return E_INVALIDARG;
*options = (ProviderOptions) (ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading);
if (AccessibilityHandler::getNativeChildForComponent (accessibilityHandler.getComponent()) != nullptr)
*options = (ProviderOptions) (*options | ProviderOptions_OverrideProvider);
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();
const auto isListOrTableCell = [] (auto& handler)
{
if (auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (&handler, &AccessibilityHandler::getTableInterface))
{
if (auto* tableInterface = tableHandler->getTableInterface())
{
const auto row = tableInterface->getRowSpan (handler);
const auto column = tableInterface->getColumnSpan (handler);
return row.hasValue() && column.hasValue();
}
}
return false;
};
using namespace ComTypes::Constants;
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:
{
if (accessibilityHandler.getValueInterface() != nullptr)
return new UIAValueProvider (this);
break;
}
case UIA_RangeValuePatternId:
{
if (accessibilityHandler.getValueInterface() != nullptr
&& accessibilityHandler.getValueInterface()->getRange().isValid())
{
return new UIARangeValueProvider (this);
}
break;
}
case UIA_TogglePatternId:
{
if (accessibilityHandler.getCurrentState().isCheckable()
&& (accessibilityHandler.getActions().contains (AccessibilityActionType::toggle)
|| accessibilityHandler.getActions().contains (AccessibilityActionType::press)))
{
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_TablePatternId:
case UIA_GridPatternId:
{
if (accessibilityHandler.getTableInterface() != nullptr
&& (pId == UIA_GridPatternId || accessibilityHandler.getRole() == AccessibilityRole::table))
return static_cast<ComTypes::IGridProvider*> (new UIAGridProvider (this));
break;
}
case UIA_TableItemPatternId:
case UIA_GridItemPatternId:
{
if (isListOrTableCell (accessibilityHandler))
return static_cast<ComTypes::IGridItemProvider*> (new UIAGridItemProvider (this));
break;
}
case UIA_InvokePatternId:
{
if (accessibilityHandler.getActions().contains (AccessibilityActionType::press))
return new UIAInvokeProvider (this);
break;
}
case UIA_ExpandCollapsePatternId:
{
if (accessibilityHandler.getActions().contains (AccessibilityActionType::showMenu)
&& accessibilityHandler.getCurrentState().isExpandable())
return new UIAExpandCollapseProvider (this);
break;
}
case UIA_ScrollPatternId:
{
if (accessibilityHandler.getTableInterface() != nullptr)
return new UIAScrollProvider (this);
break;
}
case UIA_ScrollItemPatternId:
{
if (isListOrTableCell (accessibilityHandler))
return new UIAScrollItemProvider (this);
break;
}
}
return nullptr;
}();
return S_OK;
});
}
JUCE_COMRESULT AccessibilityNativeHandle::GetPropertyValue (PROPERTYID propertyId, VARIANT* pRetVal)
{
return withCheckedComArgs (pRetVal, *this, [&]
{
VariantHelpers::clear (pRetVal);
const auto role = accessibilityHandler.getRole();
const auto state = accessibilityHandler.getCurrentState();
const auto ignored = accessibilityHandler.isIgnored();
using namespace ComTypes::Constants;
switch (propertyId)
{
case UIA_AutomationIdPropertyId:
VariantHelpers::setString (getAutomationId (accessibilityHandler), pRetVal);
break;
case UIA_ControlTypePropertyId:
VariantHelpers::setInt (roleToControlTypeId (role), 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 (! ignored && accessibilityHandler.isVisibleWithinParent(),
pRetVal);
break;
case UIA_IsControlElementPropertyId:
VariantHelpers::setBool (true, pRetVal);
break;
case UIA_IsDialogPropertyId:
VariantHelpers::setBool (role == AccessibilityRole::dialogWindow, pRetVal);
break;
case UIA_IsEnabledPropertyId:
VariantHelpers::setBool (accessibilityHandler.getComponent().isEnabled(), pRetVal);
break;
case UIA_IsKeyboardFocusablePropertyId:
VariantHelpers::setBool (state.isFocusable(), pRetVal);
break;
case UIA_HasKeyboardFocusPropertyId:
VariantHelpers::setBool (accessibilityHandler.hasFocus (true), pRetVal);
break;
case UIA_IsOffscreenPropertyId:
VariantHelpers::setBool (! accessibilityHandler.isVisibleWithinParent(), pRetVal);
break;
case UIA_IsPasswordPropertyId:
if (auto* textInterface = accessibilityHandler.getTextInterface())
VariantHelpers::setBool (textInterface->isDisplayingProtectedText(), pRetVal);
break;
case UIA_IsPeripheralPropertyId:
VariantHelpers::setBool (role == AccessibilityRole::tooltip
|| role == AccessibilityRole::popupMenu
|| role == AccessibilityRole::splashScreen,
pRetVal);
break;
case UIA_NamePropertyId:
if (! ignored)
VariantHelpers::setString (getElementName(), pRetVal);
break;
case UIA_ProcessIdPropertyId:
VariantHelpers::setInt ((int) GetCurrentProcessId(), pRetVal);
break;
case UIA_NativeWindowHandlePropertyId:
if (isFragmentRoot())
VariantHelpers::setInt ((int) (pointer_sized_int) accessibilityHandler.getComponent().getWindowHandle(), pRetVal);
break;
}
return S_OK;
});
}
//==============================================================================
JUCE_COMRESULT AccessibilityNativeHandle::Navigate (ComTypes::NavigateDirection direction, ComTypes::IRawElementProviderFragment** pRetVal)
{
return withCheckedComArgs (pRetVal, *this, [&]
{
auto* handler = [&]() -> AccessibilityHandler*
{
if (direction == ComTypes::NavigateDirection_Parent)
return accessibilityHandler.getParent();
if (direction == ComTypes::NavigateDirection_FirstChild
|| direction == ComTypes::NavigateDirection_LastChild)
{
auto children = accessibilityHandler.getChildren();
return children.empty() ? nullptr
: (direction == ComTypes::NavigateDirection_FirstChild ? children.front()
: children.back());
}
if (direction == ComTypes::NavigateDirection_NextSibling
|| direction == ComTypes::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 == ComTypes::NavigateDirection_NextSibling && iter != std::prev (siblings.cend()))
return *std::next (iter);
if (direction == ComTypes::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[(size_t) i]);
if (FAILED (hr))
return E_FAIL;
}
}
return S_OK;
});
}
JUCE_COMRESULT AccessibilityNativeHandle::get_BoundingRectangle (ComTypes::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 (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
const WeakReference<Component> safeComponent (&accessibilityHandler.getComponent());
accessibilityHandler.getActions().invoke (AccessibilityActionType::focus);
if (safeComponent != nullptr)
accessibilityHandler.grabFocus();
return S_OK;
}
JUCE_COMRESULT AccessibilityNativeHandle::get_FragmentRoot (ComTypes::IRawElementProviderFragmentRoot** pRetVal)
{
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
{
auto* handler = [&]() -> AccessibilityHandler*
{
if (isFragmentRoot())
return &accessibilityHandler;
if (auto* peer = accessibilityHandler.getComponent().getPeer())
return peer->getComponent().getAccessibilityHandler();
return nullptr;
}();
if (handler != nullptr)
{
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
return S_OK;
}
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
});
}
//==============================================================================
JUCE_COMRESULT AccessibilityNativeHandle::ElementProviderFromPoint (double x, double y, ComTypes::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 (ComTypes::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;
});
}
JUCE_COMRESULT AccessibilityNativeHandle::GetOverrideProviderForHwnd (HWND hwnd, IRawElementProviderSimple** pRetVal)
{
return withCheckedComArgs (pRetVal, *this, [&]
{
if (auto* component = AccessibilityHandler::getComponentForNativeChild (hwnd))
if (auto* handler = component->getAccessibilityHandler())
handler->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 detail::AccessibilityHelpers::getApplicationOrPluginName();
return name;
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
} // namespace juce
@@ -0,0 +1,80 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
class AccessibilityNativeHandle : public ComBaseClassHelper<IRawElementProviderSimple,
ComTypes::IRawElementProviderFragment,
ComTypes::IRawElementProviderFragmentRoot,
ComTypes::IRawElementProviderHwndOverride>
{
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 (ComTypes::NavigateDirection direction, ComTypes::IRawElementProviderFragment** pRetVal) override;
JUCE_COMRESULT GetRuntimeId (SAFEARRAY** pRetVal) override;
JUCE_COMRESULT get_BoundingRectangle (ComTypes::UiaRect* pRetVal) override;
JUCE_COMRESULT GetEmbeddedFragmentRoots (SAFEARRAY** pRetVal) override;
JUCE_COMRESULT SetFocus() override;
JUCE_COMRESULT get_FragmentRoot (ComTypes::IRawElementProviderFragmentRoot** pRetVal) override;
JUCE_COMRESULT ElementProviderFromPoint (double x, double y, ComTypes::IRawElementProviderFragment** pRetVal) override;
JUCE_COMRESULT GetFocus (ComTypes::IRawElementProviderFragment** pRetVal) override;
JUCE_COMRESULT GetOverrideProviderForHwnd (HWND hwnd, IRawElementProviderSimple** 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,274 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
struct AccessibleObjCClassDeleter
{
template <typename ElementType>
void operator() (ElementType* element) const
{
juceFreeAccessibilityPlatformSpecificData (element);
object_setInstanceVariable (element, "handler", nullptr);
[element release];
}
};
template <typename Base>
class AccessibleObjCClass : public ObjCClass<Base>
{
public:
using Holder = std::unique_ptr<Base, AccessibleObjCClassDeleter>;
protected:
AccessibleObjCClass() : AccessibleObjCClass ("JUCEAccessibilityElement_") {}
explicit AccessibleObjCClass (const char* name) : ObjCClass<Base> (name)
{
ObjCClass<Base>::template addIvar<AccessibilityHandler*> ("handler");
}
//==============================================================================
static AccessibilityHandler* getHandler (id self)
{
return getIvar<AccessibilityHandler*> (self, "handler");
}
template <typename MemberFn>
static auto getInterface (id self, MemberFn fn) noexcept -> decltype ((std::declval<AccessibilityHandler>().*fn)())
{
if (auto* handler = getHandler (self))
return (handler->*fn)();
return nullptr;
}
static AccessibilityTextInterface* getTextInterface (id self) noexcept { return getInterface (self, &AccessibilityHandler::getTextInterface); }
static AccessibilityValueInterface* getValueInterface (id self) noexcept { return getInterface (self, &AccessibilityHandler::getValueInterface); }
static AccessibilityTableInterface* getTableInterface (id self) noexcept { return getInterface (self, &AccessibilityHandler::getTableInterface); }
static AccessibilityCellInterface* getCellInterface (id self) noexcept { return getInterface (self, &AccessibilityHandler::getCellInterface); }
static bool hasEditableText (AccessibilityHandler& handler) noexcept
{
return handler.getRole() == AccessibilityRole::editableText
&& handler.getTextInterface() != nullptr
&& ! handler.getTextInterface()->isReadOnly();
}
static id getAccessibilityValueFromInterfaces (const AccessibilityHandler& handler)
{
if (auto* textInterface = handler.getTextInterface())
return juceStringToNS (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() }));
if (auto* valueInterface = handler.getValueInterface())
return juceStringToNS (valueInterface->getCurrentValueAsString());
return nil;
}
//==============================================================================
static BOOL getIsAccessibilityElement (id self, SEL)
{
if (auto* handler = getHandler (self))
return ! handler->isIgnored() && handler->getRole() != AccessibilityRole::window;
return NO;
}
static void setAccessibilityValue (id self, SEL, NSString* value)
{
if (auto* handler = getHandler (self))
{
if (hasEditableText (*handler))
{
handler->getTextInterface()->setText (nsStringToJuce (value));
return;
}
if (auto* valueInterface = handler->getValueInterface())
if (! valueInterface->isReadOnly())
valueInterface->setValueAsString (nsStringToJuce (value));
}
}
static BOOL performActionIfSupported (id self, AccessibilityActionType actionType)
{
if (auto* handler = getHandler (self))
if (handler->getActions().invoke (actionType))
return YES;
return NO;
}
static BOOL accessibilityPerformPress (id self, SEL)
{
if (auto* handler = getHandler (self))
if (handler->getCurrentState().isCheckable() && handler->getActions().invoke (AccessibilityActionType::toggle))
return YES;
return performActionIfSupported (self, AccessibilityActionType::press);
}
static BOOL accessibilityPerformIncrement (id self, SEL)
{
if (auto* valueInterface = getValueInterface (self))
{
if (! valueInterface->isReadOnly())
{
auto range = valueInterface->getRange();
if (range.isValid())
{
valueInterface->setValue (jlimit (range.getMinimumValue(),
range.getMaximumValue(),
valueInterface->getCurrentValue() + range.getInterval()));
return YES;
}
}
}
return NO;
}
static BOOL accessibilityPerformDecrement (id self, SEL)
{
if (auto* valueInterface = getValueInterface (self))
{
if (! valueInterface->isReadOnly())
{
auto range = valueInterface->getRange();
if (range.isValid())
{
valueInterface->setValue (jlimit (range.getMinimumValue(),
range.getMaximumValue(),
valueInterface->getCurrentValue() - range.getInterval()));
return YES;
}
}
}
return NO;
}
static NSString* getAccessibilityTitle (id self, SEL)
{
if (auto* handler = getHandler (self))
{
auto title = handler->getTitle();
if (title.isEmpty() && handler->getComponent().isOnDesktop())
title = detail::AccessibilityHelpers::getApplicationOrPluginName();
NSString* nsString = juceStringToNS (title);
if (nsString != nil && [[self accessibilityValue] isEqual: nsString])
return @"";
return nsString;
}
return nil;
}
static NSString* getAccessibilityHelp (id self, SEL)
{
if (auto* handler = getHandler (self))
return juceStringToNS (handler->getHelp());
return nil;
}
static BOOL getIsAccessibilityModal (id self, SEL)
{
if (auto* handler = getHandler (self))
return handler->getComponent().isCurrentlyModal();
return NO;
}
static NSInteger getAccessibilityRowCount (id self, SEL)
{
if (auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (getHandler (self), &AccessibilityHandler::getTableInterface))
if (auto* tableInterface = tableHandler->getTableInterface())
return tableInterface->getNumRows();
return 0;
}
static NSInteger getAccessibilityColumnCount (id self, SEL)
{
if (auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (getHandler (self), &AccessibilityHandler::getTableInterface))
if (auto* tableInterface = tableHandler->getTableInterface())
return tableInterface->getNumColumns();
return 0;
}
template <typename Getter>
static NSRange getCellDimensions (id self, Getter getter)
{
const auto notFound = NSMakeRange (NSNotFound, 0);
auto* handler = getHandler (self);
if (handler == nullptr)
return notFound;
auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (getHandler (self), &AccessibilityHandler::getTableInterface);
if (tableHandler == nullptr)
return notFound;
auto* tableInterface = tableHandler->getTableInterface();
if (tableInterface == nullptr)
return notFound;
const auto result = (tableInterface->*getter) (*handler);
if (! result.hasValue())
return notFound;
return NSMakeRange ((NSUInteger) result->begin, (NSUInteger) result->num);
}
static NSRange getAccessibilityRowIndexRange (id self, SEL)
{
return getCellDimensions (self, &AccessibilityTableInterface::getRowSpan);
}
static NSRange getAccessibilityColumnIndexRange (id self, SEL)
{
return getCellDimensions (self, &AccessibilityTableInterface::getColumnSpan);
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibleObjCClass)
};
} // namespace juce
@@ -0,0 +1,302 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
struct AccessibilityTextHelpers
{
/* Wraps a CharPtr into a stdlib-compatible iterator.
MSVC's std::reverse_iterator requires the wrapped iterator to be default constructible
when building in C++20 mode, but I don't really want to add public default constructors to
the CharPtr types. Instead, we add a very basic default constructor here which sets the
wrapped CharPtr to nullptr.
*/
template <typename CharPtr>
class CharPtrIteratorAdapter
{
public:
using difference_type = int;
using value_type = decltype (*std::declval<CharPtr>());
using pointer = value_type*;
using reference = value_type;
using iterator_category = std::bidirectional_iterator_tag;
CharPtrIteratorAdapter() = default;
constexpr explicit CharPtrIteratorAdapter (CharPtr arg) : ptr (arg) {}
constexpr auto operator*() const { return *ptr; }
constexpr CharPtrIteratorAdapter& operator++()
{
++ptr;
return *this;
}
constexpr CharPtrIteratorAdapter& operator--()
{
--ptr;
return *this;
}
constexpr bool operator== (const CharPtrIteratorAdapter& other) const { return ptr == other.ptr; }
constexpr bool operator!= (const CharPtrIteratorAdapter& other) const { return ptr != other.ptr; }
constexpr auto operator+ (difference_type offset) const { return CharPtrIteratorAdapter { ptr + offset }; }
constexpr auto operator- (difference_type offset) const { return CharPtrIteratorAdapter { ptr - offset }; }
private:
CharPtr ptr { {} };
};
template <typename CharPtr>
static auto makeCharPtrIteratorAdapter (CharPtr ptr)
{
return CharPtrIteratorAdapter<CharPtr> { ptr };
}
enum class BoundaryType
{
character,
word,
line,
document
};
enum class Direction
{
forwards,
backwards
};
enum class ExtendSelection
{
no,
yes
};
/* Indicates whether a function may return the current text position, in the case that the
position already falls on a text unit boundary.
*/
enum class IncludeThisBoundary
{
no, //< Always search for the following boundary, even if the current position falls on a boundary
yes //< Return the current position if it falls on a boundary
};
/* Indicates whether a word boundary should include any whitespaces that follow the
non-whitespace characters.
*/
enum class IncludeWhitespaceAfterWords
{
no, //< The word ends on the first whitespace character
yes //< The word ends after the last whitespace character
};
/* Like std::distance, but always does an O(N) count rather than an O(1) count, and doesn't
require the iterators to have any member type aliases.
*/
template <typename Iter>
static int countDifference (Iter from, Iter to)
{
int distance = 0;
while (from != to)
{
++from;
++distance;
}
return distance;
}
/* Returns the number of characters between ptr and the next word end in a specific
direction.
If ptr is inside a word, the result will be the distance to the end of the same
word.
*/
template <typename CharPtr>
static int findNextWordEndOffset (CharPtr beginIn,
CharPtr endIn,
CharPtr ptrIn,
Direction direction,
IncludeThisBoundary includeBoundary,
IncludeWhitespaceAfterWords includeWhitespace)
{
const auto begin = makeCharPtrIteratorAdapter (beginIn);
const auto end = makeCharPtrIteratorAdapter (endIn);
const auto ptr = makeCharPtrIteratorAdapter (ptrIn);
const auto move = [&] (auto b, auto e, auto iter)
{
const auto isSpace = [] (juce_wchar c) { return CharacterFunctions::isWhitespace (c); };
const auto start = [&]
{
if (iter == b && includeBoundary == IncludeThisBoundary::yes)
return b;
const auto nudged = iter - (iter != b && includeBoundary == IncludeThisBoundary::yes ? 1 : 0);
return includeWhitespace == IncludeWhitespaceAfterWords::yes
? std::find_if (nudged, e, isSpace)
: std::find_if_not (nudged, e, isSpace);
}();
const auto found = includeWhitespace == IncludeWhitespaceAfterWords::yes
? std::find_if_not (start, e, isSpace)
: std::find_if (start, e, isSpace);
return countDifference (iter, found);
};
return direction == Direction::forwards ? move (begin, end, ptr)
: -move (std::make_reverse_iterator (end),
std::make_reverse_iterator (begin),
std::make_reverse_iterator (ptr));
}
/* Returns the number of characters between ptr and the beginning of the next line in a
specific direction.
*/
template <typename CharPtr>
static int findNextLineOffset (CharPtr beginIn,
CharPtr endIn,
CharPtr ptrIn,
Direction direction,
IncludeThisBoundary includeBoundary)
{
const auto begin = makeCharPtrIteratorAdapter (beginIn);
const auto end = makeCharPtrIteratorAdapter (endIn);
const auto ptr = makeCharPtrIteratorAdapter (ptrIn);
const auto findNewline = [] (auto from, auto to) { return std::find (from, to, juce_wchar { '\n' }); };
if (direction == Direction::forwards)
{
if (ptr != begin && includeBoundary == IncludeThisBoundary::yes && *(ptr - 1) == '\n')
return 0;
const auto newline = findNewline (ptr, end);
return countDifference (ptr, newline) + (newline == end ? 0 : 1);
}
const auto rbegin = std::make_reverse_iterator (ptr);
const auto rend = std::make_reverse_iterator (begin);
return -countDifference (rbegin, findNewline (rbegin + (rbegin == rend || includeBoundary == IncludeThisBoundary::yes ? 0 : 1), rend));
}
/* Unfortunately, the method of computing end-points of text units depends on context, and on
the current platform.
Some examples of different behaviour:
- On Android, updating the cursor/selection always searches for the next text unit boundary;
but on Windows, ExpandToEnclosingUnit() should not move the starting point of the
selection if it already at a unit boundary. This means that we need both inclusive and
exclusive methods for finding the next text boundary.
- On Android, moving the cursor by 'words' should move to the first space following a
non-space character in the requested direction. On Windows, a 'word' includes trailing
whitespace, but not preceding whitespace. This means that we need a way of specifying
whether whitespace should be included when navigating by words.
*/
static int findTextBoundary (const AccessibilityTextInterface& textInterface,
int currentPosition,
BoundaryType boundary,
Direction direction,
IncludeThisBoundary includeBoundary,
IncludeWhitespaceAfterWords includeWhitespace)
{
const auto numCharacters = textInterface.getTotalNumCharacters();
const auto isForwards = (direction == Direction::forwards);
const auto currentClamped = jlimit (0, numCharacters, currentPosition);
switch (boundary)
{
case BoundaryType::character:
{
const auto offset = includeBoundary == IncludeThisBoundary::yes ? 0
: (isForwards ? 1 : -1);
return jlimit (0, numCharacters, currentPosition + offset);
}
case BoundaryType::word:
{
const auto str = textInterface.getText ({ 0, numCharacters });
return currentClamped + findNextWordEndOffset (str.begin(),
str.end(),
str.begin() + currentClamped,
direction,
includeBoundary,
includeWhitespace);
}
case BoundaryType::line:
{
const auto str = textInterface.getText ({ 0, numCharacters });
return currentClamped + findNextLineOffset (str.begin(),
str.end(),
str.begin() + currentClamped,
direction,
includeBoundary);
}
case BoundaryType::document:
return isForwards ? numCharacters : 0;
}
jassertfalse;
return -1;
}
/* Adjusts the current text selection range, using an algorithm appropriate for cursor movement
on Android.
*/
static Range<int> findNewSelectionRangeAndroid (const AccessibilityTextInterface& textInterface,
BoundaryType boundaryType,
ExtendSelection extend,
Direction direction)
{
const auto oldPos = textInterface.getTextInsertionOffset();
const auto cursorPos = findTextBoundary (textInterface,
oldPos,
boundaryType,
direction,
IncludeThisBoundary::no,
IncludeWhitespaceAfterWords::no);
if (extend == ExtendSelection::no)
return { cursorPos, cursorPos };
const auto currentSelection = textInterface.getSelection();
const auto start = currentSelection.getStart();
const auto end = currentSelection.getEnd();
return Range<int>::between (cursorPos, oldPos == start ? end : start);
}
};
} // namespace juce
@@ -0,0 +1,155 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
struct AccessibilityTextHelpersTest final : public UnitTest
{
AccessibilityTextHelpersTest()
: UnitTest ("AccessibilityTextHelpers", UnitTestCategories::gui) {}
void runTest() override
{
using ATH = AccessibilityTextHelpers;
beginTest ("Android find word end");
{
const auto testMultiple = [this] (String str,
int start,
const std::vector<int>& collection)
{
auto it = collection.begin();
for (const auto direction : { ATH::Direction::forwards, ATH::Direction::backwards })
{
for (const auto includeBoundary : { ATH::IncludeThisBoundary::no, ATH::IncludeThisBoundary::yes })
{
for (const auto includeWhitespace : { ATH::IncludeWhitespaceAfterWords::no, ATH::IncludeWhitespaceAfterWords::yes })
{
const auto actual = ATH::findNextWordEndOffset (str.begin(), str.end(), str.begin() + start, direction, includeBoundary, includeWhitespace);
const auto expected = *it++;
expect (expected == actual);
}
}
}
};
// Character Indices 0 3 56 13 50 51
// | | || | | |
const auto string = String ("hello world \r\n with some spaces in this sentence ") + String (CharPointer_UTF8 ("\xe2\x88\xae E\xe2\x8b\x85""da = Q"));
// Direction forwards forwards forwards forwards backwards backwards backwards backwards
// IncludeBoundary no no yes yes no no yes yes
// IncludeWhitespace no yes no yes no yes no yes
testMultiple (string, 0, { 5, 6, 5, 0, 0, 0, 0, 0 });
testMultiple (string, 3, { 2, 3, 2, 3, -3, -3, -3, -3 });
testMultiple (string, 5, { 6, 1, 0, 1, -5, -5, -5, 0 });
testMultiple (string, 6, { 5, 9, 5, 0, -6, -1, 0, -1 });
testMultiple (string, 13, { 6, 2, 6, 2, -7, -2, -7, -2 });
testMultiple (string, 50, { 1, 2, 1, 0, -9, -1, 0, -1 });
testMultiple (string, 51, { 5, 1, 0, 1, -1, -2, -1, 0 });
testMultiple (" a b ", 0, { 3, 2, 0, 2, 0, 0, 0, 0 });
testMultiple (" a b ", 1, { 2, 1, 2, 1, -1, -1, -1, -1 });
}
beginTest ("Android text range adjustment");
{
const auto testMultiple = [this] (String str,
Range<int> initial,
auto boundary,
const std::vector<Range<int>>& collection)
{
auto it = collection.begin();
for (auto extend : { ATH::ExtendSelection::no, ATH::ExtendSelection::yes })
{
for (auto direction : { ATH::Direction::forwards, ATH::Direction::backwards })
{
for (auto insert : { CursorPosition::begin, CursorPosition::end })
{
const MockAccessibilityTextInterface mock { str, initial, insert };
const auto actual = ATH::findNewSelectionRangeAndroid (mock, boundary, extend, direction);
const auto expected = *it++;
expect (expected == actual);
}
}
}
};
// Extend no no no no yes yes yes yes
// Direction forwards forwards backwards backwards forwards forwards backwards backwards
// Insert begin end begin end begin end begin end
testMultiple ("hello world", { 5, 5 }, ATH::BoundaryType::character, { { 6, 6 }, { 6, 6 }, { 4, 4 }, { 4, 4 }, { 5, 6 }, { 5, 6 }, { 4, 5 }, { 4, 5 } });
testMultiple ("hello world", { 0, 0 }, ATH::BoundaryType::character, { { 1, 1 }, { 1, 1 }, { 0, 0 }, { 0, 0 }, { 0, 1 }, { 0, 1 }, { 0, 0 }, { 0, 0 } });
testMultiple ("hello world", { 11, 11 }, ATH::BoundaryType::character, { { 11, 11 }, { 11, 11 }, { 10, 10 }, { 10, 10 }, { 11, 11 }, { 11, 11 }, { 10, 11 }, { 10, 11 } });
testMultiple ("hello world", { 4, 5 }, ATH::BoundaryType::character, { { 5, 5 }, { 6, 6 }, { 3, 3 }, { 4, 4 }, { 5, 5 }, { 4, 6 }, { 3, 5 }, { 4, 4 } });
testMultiple ("hello world", { 0, 1 }, ATH::BoundaryType::character, { { 1, 1 }, { 2, 2 }, { 0, 0 }, { 0, 0 }, { 1, 1 }, { 0, 2 }, { 0, 1 }, { 0, 0 } });
testMultiple ("hello world", { 10, 11 }, ATH::BoundaryType::character, { { 11, 11 }, { 11, 11 }, { 9, 9 }, { 10, 10 }, { 11, 11 }, { 10, 11 }, { 9, 11 }, { 10, 10 } });
testMultiple ("foo bar baz", { 0, 0 }, ATH::BoundaryType::word, { { 3, 3 }, { 3, 3 }, { 0, 0 }, { 0, 0 }, { 0, 3 }, { 0, 3 }, { 0, 0 }, { 0, 0 } });
testMultiple ("foo bar baz", { 1, 6 }, ATH::BoundaryType::word, { { 3, 3 }, { 8, 8 }, { 0, 0 }, { 5, 5 }, { 3, 6 }, { 1, 8 }, { 0, 6 }, { 1, 5 } });
testMultiple ("foo bar baz", { 3, 3 }, ATH::BoundaryType::word, { { 8, 8 }, { 8, 8 }, { 0, 0 }, { 0, 0 }, { 3, 8 }, { 3, 8 }, { 0, 3 }, { 0, 3 } });
testMultiple ("foo bar baz", { 3, 5 }, ATH::BoundaryType::word, { { 8, 8 }, { 8, 8 }, { 0, 0 }, { 0, 0 }, { 5, 8 }, { 3, 8 }, { 0, 5 }, { 0, 3 } });
testMultiple ("foo bar\n\n\na b\nc d e", { 0, 0 }, ATH::BoundaryType::line, { { 8, 8 }, { 8, 8 }, { 0, 0 }, { 0, 0 }, { 0, 8 }, { 0, 8 }, { 0, 0 }, { 0, 0 } });
testMultiple ("foo bar\n\n\na b\nc d e", { 7, 7 }, ATH::BoundaryType::line, { { 8, 8 }, { 8, 8 }, { 0, 0 }, { 0, 0 }, { 7, 8 }, { 7, 8 }, { 0, 7 }, { 0, 7 } });
testMultiple ("foo bar\n\n\na b\nc d e", { 8, 8 }, ATH::BoundaryType::line, { { 9, 9 }, { 9, 9 }, { 0, 0 }, { 0, 0 }, { 8, 9 }, { 8, 9 }, { 0, 8 }, { 0, 8 } });
testMultiple ("foo bar\r\na b\r\nxyz", { 0, 0 }, ATH::BoundaryType::line, { { 9, 9 }, { 9, 9 }, { 0, 0 }, { 0, 0 }, { 0, 9 }, { 0, 9 }, { 0, 0 }, { 0, 0 } });
testMultiple ("foo bar\r\na b\r\nxyz", { 10, 10 }, ATH::BoundaryType::line, { { 14, 14 }, { 14, 14 }, { 9, 9 }, { 9, 9 }, { 10, 14 }, { 10, 14 }, { 9, 10 }, { 9, 10 } });
}
}
enum class CursorPosition { begin, end };
class MockAccessibilityTextInterface final : public AccessibilityTextInterface
{
public:
MockAccessibilityTextInterface (String stringIn, Range<int> selectionIn, CursorPosition insertIn)
: string (stringIn), selection (selectionIn), insert (insertIn) {}
bool isDisplayingProtectedText() const override { return false; }
bool isReadOnly() const override { return false; }
int getTotalNumCharacters() const override { return string.length(); }
Range<int> getSelection() const override { return selection; }
int getTextInsertionOffset() const override { return insert == CursorPosition::begin ? selection.getStart() : selection.getEnd(); }
String getText (Range<int> range) const override { return string.substring (range.getStart(), range.getEnd()); }
RectangleList<int> getTextBounds (Range<int>) const override { return {}; }
int getOffsetAtPoint (Point<int>) const override { return 0; }
void setSelection (Range<int> newRange) override { selection = newRange; }
void setText (const String& newText) override { string = newText; }
private:
String string;
Range<int> selection;
CursorPosition insert;
};
};
static AccessibilityTextHelpersTest accessibilityTextHelpersTest;
} // namespace juce
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,677 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
static void juceFreeAccessibilityPlatformSpecificData (UIAccessibilityElement* element)
{
if (auto* container = juce::getIvar<UIAccessibilityElement*> (element, "container"))
{
object_setInstanceVariable (element, "container", nullptr);
object_setInstanceVariable (container, "handler", nullptr);
[container release];
}
}
namespace juce
{
#define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1
template <typename> struct Signature {};
template <typename Result, typename... Args>
struct Signature<Result (Args...)> {};
// @selector isn't constexpr, so the 'sel' members are functions rather than static constexpr data members
struct SignatureHasText final : public Signature<BOOL()> { static auto sel() { return @selector (hasText); } };
struct SignatureSetSelectedTextRange final : public Signature<void (UITextRange*)> { static auto sel() { return @selector (setSelectedTextRange:); } };
struct SignatureSelectedTextRange final : public Signature<UITextRange*()> { static auto sel() { return @selector (selectedTextRange); } };
struct SignatureMarkedTextRange final : public Signature<UITextRange*()> { static auto sel() { return @selector (markedTextRange); } };
struct SignatureSetMarkedTextSelectedRange final : public Signature<void (NSString*, NSRange)> { static auto sel() { return @selector (setMarkedText:selectedRange:); } };
struct SignatureUnmarkText final : public Signature<void()> { static auto sel() { return @selector (unmarkText); } };
struct SignatureMarkedTextStyle final : public Signature<NSDictionary<NSAttributedStringKey, id>*()> { static auto sel() { return @selector (markedTextStyle); } };
struct SignatureSetMarkedTextStyle final : public Signature<void (NSDictionary<NSAttributedStringKey, id>*)> { static auto sel() { return @selector (setMarkedTextStyle:); } };
struct SignatureBeginningOfDocument final : public Signature<UITextPosition*()> { static auto sel() { return @selector (beginningOfDocument); } };
struct SignatureEndOfDocument final : public Signature<UITextPosition*()> { static auto sel() { return @selector (endOfDocument); } };
struct SignatureTokenizer final : public Signature<id<UITextInputTokenizer>()> { static auto sel() { return @selector (tokenizer); } };
struct SignatureBaseWritingDirection final : public Signature<NSWritingDirection (UITextPosition*, UITextStorageDirection)> { static auto sel() { return @selector (baseWritingDirectionForPosition:inDirection:); } };
struct SignatureCaretRectForPosition final : public Signature<CGRect (UITextPosition*)> { static auto sel() { return @selector (caretRectForPosition:); } };
struct SignatureCharacterRangeByExtending final : public Signature<UITextRange* (UITextPosition*, UITextLayoutDirection)> { static auto sel() { return @selector (characterRangeByExtendingPosition:inDirection:); } };
struct SignatureCharacterRangeAtPoint final : public Signature<UITextRange* (CGPoint)> { static auto sel() { return @selector (characterRangeAtPoint:); } };
struct SignatureClosestPositionToPoint final : public Signature<UITextPosition* (CGPoint)> { static auto sel() { return @selector (closestPositionToPoint:); } };
struct SignatureClosestPositionToPointInRange final : public Signature<UITextPosition* (CGPoint, UITextRange*)> { static auto sel() { return @selector (closestPositionToPoint:withinRange:); } };
struct SignatureComparePositionToPosition final : public Signature<NSComparisonResult (UITextPosition*, UITextPosition*)> { static auto sel() { return @selector (comparePosition:toPosition:); } };
struct SignatureOffsetFromPositionToPosition final : public Signature<NSInteger (UITextPosition*, UITextPosition*)> { static auto sel() { return @selector (offsetFromPosition:toPosition:); } };
struct SignaturePositionFromPositionInDirection final : public Signature<UITextPosition* (UITextPosition*, UITextLayoutDirection, NSInteger)> { static auto sel() { return @selector (positionFromPosition:inDirection:offset:); } };
struct SignaturePositionFromPositionOffset final : public Signature<UITextPosition* (UITextPosition*, NSInteger)> { static auto sel() { return @selector (positionFromPosition:offset:); } };
struct SignatureFirstRectForRange final : public Signature<CGRect (UITextRange*)> { static auto sel() { return @selector (firstRectForRange:); } };
struct SignatureSelectionRectsForRange final : public Signature<NSArray<UITextSelectionRect*>* (UITextRange*)> { static auto sel() { return @selector (selectionRectsForRange:); } };
struct SignaturePositionWithinRange final : public Signature<UITextPosition* (UITextRange*, UITextLayoutDirection)> { static auto sel() { return @selector (positionWithinRange:farthestInDirection:); } };
struct SignatureReplaceRangeWithText final : public Signature<void (UITextRange*, NSString*)> { static auto sel() { return @selector (replaceRange:withText:); } };
struct SignatureSetBaseWritingDirection final : public Signature<void (NSWritingDirection, UITextRange*)> { static auto sel() { return @selector (setBaseWritingDirection:forRange:); } };
struct SignatureTextInRange final : public Signature<NSString* (UITextRange*)> { static auto sel() { return @selector (textInRange:); } };
struct SignatureTextRangeFromPosition final : public Signature<UITextRange* (UITextPosition*, UITextPosition*)> { static auto sel() { return @selector (textRangeFromPosition:toPosition:); } };
struct SignatureSetInputDelegate final : public Signature<void (id)> { static auto sel() { return @selector (setInputDelegate:); } };
struct SignatureInputDelegate final : public Signature<id()> { static auto sel() { return @selector (inputDelegate); } };
struct SignatureKeyboardType final : public Signature<UIKeyboardType()> { static auto sel() { return @selector (keyboardType); } };
struct SignatureAutocapitalizationType final : public Signature<UITextAutocapitalizationType()> { static auto sel() { return @selector (autocapitalizationType); } };
struct SignatureAutocorrectionType final : public Signature<UITextAutocorrectionType()> { static auto sel() { return @selector (autocorrectionType); } };
//==============================================================================
class AccessibilityHandler::AccessibilityNativeImpl
{
public:
explicit AccessibilityNativeImpl (AccessibilityHandler& handler)
: accessibilityElement (AccessibilityElement::create (handler))
{
}
UIAccessibilityElement* getAccessibilityElement() const noexcept
{
return accessibilityElement.get();
}
private:
//==============================================================================
class AccessibilityContainer final : public AccessibleObjCClass<NSObject>
{
public:
AccessibilityContainer()
: AccessibleObjCClass ("JUCEUIAccessibilityContainer_")
{
addMethod (@selector (isAccessibilityElement), [] (id, SEL) { return false; });
addMethod (@selector (accessibilityFrame), [] (id self, SEL)
{
if (auto* handler = getHandler (self))
return convertToCGRect (handler->getComponent().getScreenBounds());
return CGRectZero;
});
addMethod (@selector (accessibilityElements), [] (id self, SEL) -> NSArray*
{
if (auto* handler = getHandler (self))
return getContainerAccessibilityElements (*handler);
return nil;
});
if (@available (iOS 11.0, *))
{
addMethod (@selector (accessibilityDataTableCellElementForRow:column:), [] (id self, SEL, NSUInteger row, NSUInteger column) -> id
{
if (auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (getHandler (self), &AccessibilityHandler::getTableInterface))
if (auto* tableInterface = tableHandler->getTableInterface())
if (auto* cellHandler = tableInterface->getCellHandler ((int) row, (int) column))
if (auto* parent = getAccessibleParent (cellHandler))
return static_cast<id> (parent->getNativeImplementation());
return nil;
});
addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount);
addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount);
addMethod (@selector (accessibilityHeaderElementsForColumn:), [] (id self, SEL, NSUInteger column) -> NSArray*
{
if (auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (getHandler (self), &AccessibilityHandler::getTableInterface))
{
if (auto* tableInterface = tableHandler->getTableInterface())
{
if (auto* header = tableInterface->getHeaderHandler())
{
if (isPositiveAndBelow (column, header->getChildren().size()))
{
auto* result = [NSMutableArray new];
[result addObject: static_cast<id> (header->getChildren()[(size_t) column]->getNativeImplementation())];
return result;
}
}
}
}
return nullptr;
});
addProtocol (@protocol (UIAccessibilityContainerDataTable));
addMethod (@selector (accessibilityContainerType), [] (id self, SEL) -> NSInteger
{
if (auto* handler = getHandler (self))
{
if (handler->getTableInterface() != nullptr)
{
if (@available (iOS 11.0, *))
return UIAccessibilityContainerTypeDataTable;
return 1; // UIAccessibilityContainerTypeDataTable
}
const auto handlerRole = handler->getRole();
if (handlerRole == AccessibilityRole::popupMenu
|| handlerRole == AccessibilityRole::list
|| handlerRole == AccessibilityRole::tree)
{
if (@available (iOS 11.0, *))
return UIAccessibilityContainerTypeList;
return 2; // UIAccessibilityContainerTypeList
}
}
if (@available (iOS 11.0, *))
return UIAccessibilityContainerTypeNone;
return 0; // UIAccessibilityContainerTypeNone
});
}
registerClass();
}
private:
static const AccessibilityHandler* getAccessibleParent (const AccessibilityHandler* h)
{
if (h == nullptr)
return nullptr;
if ([static_cast<id> (h->getNativeImplementation()) isAccessibilityElement])
return h;
return getAccessibleParent (h->getParent());
}
static AccessibilityHandler* getHandler (id self)
{
return getIvar<AccessibilityHandler*> (self, "handler");
}
};
//==============================================================================
class AccessibilityElement final : public AccessibleObjCClass<UIAccessibilityElement>
{
template <typename Func, typename... Items>
static constexpr void forEach (Func&& func, Items&&... items)
{
(func (std::forward<Items> (items)), ...);
}
public:
enum class Type { defaultElement, textElement };
static Holder create (AccessibilityHandler& handler)
{
static AccessibilityElement cls { Type::defaultElement };
static AccessibilityElement textCls { Type::textElement };
id instance = (hasEditableText (handler) ? textCls : cls).createInstance();
Holder element ([instance initWithAccessibilityContainer: static_cast<id> (handler.getComponent().getWindowHandle())]);
object_setInstanceVariable (element.get(), "handler", &handler);
return element;
}
AccessibilityElement (Type elementType)
{
addMethod (@selector (isAccessibilityElement), [] (id self, SEL)
{
auto* handler = getHandler (self);
const auto hasAccessiblePropertiesOrIsTableCell = [] (auto& handlerRef)
{
const auto isTableCell = [&]
{
if (auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (&handlerRef, &AccessibilityHandler::getTableInterface))
{
if (auto* tableInterface = tableHandler->getTableInterface())
{
return tableInterface->getRowSpan (handlerRef).hasValue()
&& tableInterface->getColumnSpan (handlerRef).hasValue();
}
}
return false;
};
return handlerRef.getTitle().isNotEmpty()
|| handlerRef.getHelp().isNotEmpty()
|| handlerRef.getTextInterface() != nullptr
|| handlerRef.getValueInterface() != nullptr
|| isTableCell();
};
return handler != nullptr
&& ! handler->isIgnored()
&& handler->getRole() != AccessibilityRole::window
&& hasAccessiblePropertiesOrIsTableCell (*handler);
});
addMethod (@selector (accessibilityContainer), [] (id self, SEL) -> id
{
if (auto* handler = getHandler (self))
{
if (handler->getComponent().isOnDesktop())
return static_cast<id> (handler->getComponent().getWindowHandle());
if ( ! handler->getChildren().empty()
|| AccessibilityHandler::getNativeChildForComponent (handler->getComponent()) != nullptr)
{
if (UIAccessibilityElement* container = getContainer (self))
return container;
static AccessibilityContainer cls;
id container = cls.createInstance();
object_setInstanceVariable (container, "handler", handler);
object_setInstanceVariable (self, "container", container);
return container;
}
if (auto* parent = handler->getParent())
return [static_cast<id> (parent->getNativeImplementation()) accessibilityContainer];
}
return nil;
});
addMethod (@selector (accessibilityFrame), [] (id self, SEL)
{
if (auto* handler = getHandler (self))
return convertToCGRect (handler->getComponent().getScreenBounds());
return CGRectZero;
});
addMethod (@selector (accessibilityTraits), [] (id self, SEL)
{
auto traits = UIAccessibilityTraits{};
if (auto* handler = getHandler (self))
{
traits |= [&handler]
{
switch (handler->getRole())
{
case AccessibilityRole::button:
case AccessibilityRole::toggleButton:
case AccessibilityRole::radioButton:
case AccessibilityRole::comboBox: return UIAccessibilityTraitButton;
case AccessibilityRole::label:
case AccessibilityRole::staticText: return UIAccessibilityTraitStaticText;
case AccessibilityRole::image: return UIAccessibilityTraitImage;
case AccessibilityRole::tableHeader: return UIAccessibilityTraitHeader;
case AccessibilityRole::hyperlink: return UIAccessibilityTraitLink;
case AccessibilityRole::ignored: return UIAccessibilityTraitNotEnabled;
case AccessibilityRole::editableText: return UIAccessibilityTraitKeyboardKey;
case AccessibilityRole::slider:
case AccessibilityRole::menuItem:
case AccessibilityRole::menuBar:
case AccessibilityRole::popupMenu:
case AccessibilityRole::table:
case AccessibilityRole::column:
case AccessibilityRole::row:
case AccessibilityRole::cell:
case AccessibilityRole::list:
case AccessibilityRole::listItem:
case AccessibilityRole::tree:
case AccessibilityRole::treeItem:
case AccessibilityRole::progressBar:
case AccessibilityRole::group:
case AccessibilityRole::dialogWindow:
case AccessibilityRole::window:
case AccessibilityRole::scrollBar:
case AccessibilityRole::tooltip:
case AccessibilityRole::splashScreen:
case AccessibilityRole::unspecified: break;
}
return UIAccessibilityTraitNone;
}();
const auto state = handler->getCurrentState();
if (state.isSelected() || state.isChecked())
traits |= UIAccessibilityTraitSelected;
if (auto* valueInterface = getValueInterface (self))
if (! valueInterface->isReadOnly() && valueInterface->getRange().isValid())
traits |= UIAccessibilityTraitAdjustable;
}
return traits | sendSuperclassMessage<UIAccessibilityTraits> (self, @selector (accessibilityTraits));
});
addMethod (@selector (accessibilityLabel), getAccessibilityTitle);
addMethod (@selector (accessibilityHint), getAccessibilityHelp);
addMethod (@selector (accessibilityValue), [] (id self, SEL) -> NSString*
{
if (auto* handler = getHandler (self))
{
if (handler->getCurrentState().isCheckable())
return handler->getCurrentState().isChecked() ? @"1" : @"0";
return (NSString*) getAccessibilityValueFromInterfaces (*handler);
}
return nil;
});
addMethod (@selector (setAccessibilityValue:), setAccessibilityValue);
addMethod (@selector (accessibilityElementDidBecomeFocused), [] (id self, SEL)
{
if (auto* handler = getHandler (self))
{
const WeakReference<Component> safeComponent (&handler->getComponent());
performActionIfSupported (self, AccessibilityActionType::focus);
if (safeComponent != nullptr)
handler->grabFocus();
}
});
addMethod (@selector (accessibilityElementDidLoseFocus), [] (id self, SEL)
{
if (auto* handler = getHandler (self))
handler->giveAwayFocus();
});
addMethod (@selector (accessibilityElementIsFocused), [] (id self, SEL) -> BOOL
{
if (auto* handler = getHandler (self))
return handler->hasFocus (false);
return NO;
});
addMethod (@selector (accessibilityViewIsModal), getIsAccessibilityModal);
addMethod (@selector (accessibilityActivate), [] (id self, SEL)
{
if (auto* handler = getHandler (self))
{
// Occasionally VoiceOver sends accessibilityActivate to the wrong element, so we first query
// which element it thinks has focus and forward the event on to that element if it differs
id focusedElement = UIAccessibilityFocusedElement (UIAccessibilityNotificationVoiceOverIdentifier);
if (focusedElement != nullptr && ! [static_cast<id> (handler->getNativeImplementation()) isEqual: focusedElement])
return [focusedElement accessibilityActivate];
if (handler->hasFocus (false))
return accessibilityPerformPress (self, {});
}
return NO;
});
addMethod (@selector (accessibilityIncrement), accessibilityPerformIncrement);
addMethod (@selector (accessibilityDecrement), accessibilityPerformDecrement);
addMethod (@selector (accessibilityPerformEscape), [] (id self, SEL)
{
if (auto* handler = getHandler (self))
{
if (auto* modal = Component::getCurrentlyModalComponent())
{
if (auto* modalHandler = modal->getAccessibilityHandler())
{
if (modalHandler == handler || modalHandler->isParentOf (handler))
{
modal->exitModalState (0);
return YES;
}
}
}
}
return NO;
});
if (elementType == Type::textElement)
{
addMethod (@selector (deleteBackward), [] (id, SEL) {});
addMethod (@selector (insertText:), [] (id, SEL, NSString*) {});
forEach ([this] (auto signature) { addPassthroughMethodWithSignature (signature); },
SignatureHasText{},
SignatureSetSelectedTextRange{},
SignatureSelectedTextRange{},
SignatureMarkedTextRange{},
SignatureSetMarkedTextSelectedRange{},
SignatureUnmarkText{},
SignatureMarkedTextStyle{},
SignatureSetMarkedTextStyle{},
SignatureBeginningOfDocument{},
SignatureEndOfDocument{},
SignatureTokenizer{},
SignatureBaseWritingDirection{},
SignatureCaretRectForPosition{},
SignatureCharacterRangeByExtending{},
SignatureCharacterRangeAtPoint{},
SignatureClosestPositionToPoint{},
SignatureClosestPositionToPointInRange{},
SignatureComparePositionToPosition{},
SignatureOffsetFromPositionToPosition{},
SignaturePositionFromPositionInDirection{},
SignaturePositionFromPositionOffset{},
SignatureFirstRectForRange{},
SignatureSelectionRectsForRange{},
SignaturePositionWithinRange{},
SignatureReplaceRangeWithText{},
SignatureSetBaseWritingDirection{},
SignatureTextInRange{},
SignatureTextRangeFromPosition{},
SignatureSetInputDelegate{},
SignatureInputDelegate{},
SignatureKeyboardType{},
SignatureAutocapitalizationType{},
SignatureAutocorrectionType{});
addProtocol (@protocol (UITextInput));
}
if (@available (iOS 11.0, *))
{
addMethod (@selector (accessibilityRowRange), getAccessibilityRowIndexRange);
addMethod (@selector (accessibilityColumnRange), getAccessibilityColumnIndexRange);
addProtocol (@protocol (UIAccessibilityContainerDataTableCell));
}
addIvar<UIAccessibilityElement*> ("container");
registerClass();
}
private:
template <typename Result>
static auto getResult (NSInvocation* invocation, detail::Tag<Result>)
{
Result result{};
[invocation getReturnValue: &result];
return result;
}
static void getResult (NSInvocation*, detail::Tag<void>) {}
template <typename HasSelector, typename Result, typename... Args>
auto makePassthroughCallback (HasSelector, Signature<Result (Args...)>)
{
return [] (id self, SEL, Args... args) -> Result
{
if (auto* input = getPeerTextInput (self))
{
const auto s = detail::makeCompileTimeStr (@encode (Result), @encode (id), @encode (SEL), @encode (Args)...);
const auto signature = [NSMethodSignature signatureWithObjCTypes: s.data()];
if (signature == nullptr)
{
jassertfalse;
return {};
}
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullable-to-nonnull-conversion")
const auto invocation = [NSInvocation invocationWithMethodSignature: signature];
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
invocation.selector = HasSelector::sel();
// Indices 0 and 1 are 'id self' and 'SEL _cmd' respectively
auto counter = 2;
forEach ([&] (auto& arg) { [invocation setArgument: &arg atIndex: counter++]; }, args...);
[invocation invokeWithTarget: input];
return getResult (invocation, detail::Tag<Result>{});
}
jassertfalse;
return {};
};
}
template <typename Signature>
void addPassthroughMethodWithSignature (Signature signature)
{
addMethod (Signature::sel(), makePassthroughCallback (signature, signature));
}
static UIAccessibilityElement* getContainer (id self)
{
return getIvar<UIAccessibilityElement*> (self, "container");
}
static UIViewComponentPeer* getPeer (id self)
{
if (auto* handler = getHandler (self))
return static_cast<UIViewComponentPeer*> (handler->getComponent().getPeer());
return nil;
}
static JuceTextView* getPeerTextInput (id self)
{
if (auto* peer = getPeer (self))
return peer->hiddenTextInput.get();
return nil;
}
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityElement)
};
//==============================================================================
AccessibilityElement::Holder accessibilityElement;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeImpl)
};
//==============================================================================
AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const
{
return (AccessibilityNativeHandle*) nativeImpl->getAccessibilityElement();
}
static bool areAnyAccessibilityClientsActive()
{
return UIAccessibilityIsVoiceOverRunning();
}
static void sendAccessibilityEvent (UIAccessibilityNotifications notification, id argument)
{
if (! areAnyAccessibilityClientsActive())
return;
jassert (notification != UIAccessibilityNotifications{});
UIAccessibilityPostNotification (notification, argument);
}
void detail::AccessibilityHelpers::notifyAccessibilityEvent (const AccessibilityHandler& handler, Event eventType)
{
auto notification = [eventType]
{
switch (eventType)
{
case Event::elementCreated:
case Event::elementDestroyed:
case Event::elementMovedOrResized:
case Event::focusChanged: return UIAccessibilityLayoutChangedNotification;
case Event::windowOpened:
case Event::windowClosed: return UIAccessibilityScreenChangedNotification;
}
return UIAccessibilityNotifications{};
}();
if (notification != UIAccessibilityNotifications{})
{
const bool moveToHandler = (eventType == Event::focusChanged && handler.hasFocus (false));
sendAccessibilityEvent (notification,
moveToHandler ? static_cast<id> (handler.getNativeImplementation()) : nil);
}
}
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const
{
auto notification = [eventType]
{
switch (eventType)
{
case AccessibilityEvent::textSelectionChanged:
case AccessibilityEvent::rowSelectionChanged:
case AccessibilityEvent::textChanged:
case AccessibilityEvent::valueChanged:
case AccessibilityEvent::titleChanged: break;
case AccessibilityEvent::structureChanged: return UIAccessibilityLayoutChangedNotification;
}
return UIAccessibilityNotifications{};
}();
if (notification != UIAccessibilityNotifications{})
sendAccessibilityEvent (notification, static_cast<id> (getNativeImplementation()));
}
void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority)
{
sendAccessibilityEvent (UIAccessibilityAnnouncementNotification, juceStringToNS (announcementString));
}
} // namespace juce
@@ -0,0 +1,960 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
API_AVAILABLE (macos (10.10))
static void juceFreeAccessibilityPlatformSpecificData (NSAccessibilityElement<NSAccessibility>*) {}
namespace juce
{
#define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1
//==============================================================================
class AccessibilityHandler::AccessibilityNativeImpl
{
public:
explicit AccessibilityNativeImpl (AccessibilityHandler& handler)
{
if (@available (macOS 10.10, *))
accessibilityElement = AccessibilityElement::create (handler);
}
API_AVAILABLE (macos (10.10))
NSAccessibilityElement<NSAccessibility>* getAccessibilityElement() const noexcept
{
return accessibilityElement.get();
}
private:
//==============================================================================
class API_AVAILABLE (macos (10.10)) AccessibilityElement final : public AccessibleObjCClass<NSAccessibilityElement<NSAccessibility>>
{
public:
static Holder create (AccessibilityHandler& handler)
{
if (@available (macOS 10.10, *))
{
static AccessibilityElement cls;
Holder element ([cls.createInstance() init]);
object_setInstanceVariable (element.get(), "handler", &handler);
return element;
}
return {};
}
private:
AccessibilityElement()
{
addMethod (@selector (accessibilityNotifiesWhenDestroyed), [] (id, SEL) { return YES; });
addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement);
addMethod (@selector (isAccessibilityEnabled), [] (id self, SEL) -> BOOL
{
if (auto* handler = getHandler (self))
return handler->getComponent().isEnabled();
return NO;
});
addMethod (@selector (accessibilityWindow), getAccessibilityWindow);
addMethod (@selector (accessibilityTopLevelUIElement), getAccessibilityWindow);
addMethod (@selector (accessibilityChildren), getAccessibilityChildren);
addMethod (@selector (isAccessibilityModal), getIsAccessibilityModal);
addMethod (@selector (accessibilityFocusedUIElement), [] (id self, SEL) -> id
{
if (auto* handler = getHandler (self))
{
if (auto* modal = Component::getCurrentlyModalComponent())
{
const auto& handlerComponent = handler->getComponent();
if (! handlerComponent.isParentOf (modal)
&& handlerComponent.isCurrentlyBlockedByAnotherModalComponent())
{
if (auto* modalHandler = modal->getAccessibilityHandler())
{
if (auto* focusChild = modalHandler->getChildFocus())
return static_cast<id> (focusChild->getNativeImplementation());
return static_cast<id> (modalHandler->getNativeImplementation());
}
}
}
if (auto* focusChild = handler->getChildFocus())
return static_cast<id> (focusChild->getNativeImplementation());
}
return nil;
});
addMethod (@selector (accessibilityHitTest:), [] (id self, SEL, NSPoint point) -> id
{
if (auto* handler = getHandler (self))
{
if (auto* child = handler->getChildAt (roundToIntPoint (flippedScreenPoint (point))))
return static_cast<id> (child->getNativeImplementation());
return self;
}
return nil;
});
addMethod (@selector (accessibilityParent), [] (id self, SEL) -> id
{
if (auto* handler = getHandler (self))
{
if (auto* parentHandler = handler->getParent())
return NSAccessibilityUnignoredAncestor (static_cast<id> (parentHandler->getNativeImplementation()));
return NSAccessibilityUnignoredAncestor (static_cast<id> (handler->getComponent().getWindowHandle()));
}
return nil;
});
addMethod (@selector (isAccessibilityFocused), [] (id self, SEL)
{
return [[self accessibilityWindow] accessibilityFocusedUIElement] == self;
});
addMethod (@selector (setAccessibilityFocused:), [] (id self, SEL, BOOL focused)
{
if (auto* handler = getHandler (self))
{
if (focused)
{
const WeakReference<Component> safeComponent (&handler->getComponent());
performActionIfSupported (self, AccessibilityActionType::focus);
if (safeComponent != nullptr)
handler->grabFocus();
}
else
{
handler->giveAwayFocus();
}
}
});
addMethod (@selector (accessibilityFrame), [] (id self, SEL)
{
if (auto* handler = getHandler (self))
return flippedScreenRect (makeNSRect (handler->getComponent().getScreenBounds()));
return NSZeroRect;
});
addMethod (@selector (accessibilityRole), [] (id self, SEL) -> NSAccessibilityRole
{
if (auto* handler = getHandler (self))
{
switch (handler->getRole())
{
case AccessibilityRole::popupMenu:
case AccessibilityRole::tooltip:
case AccessibilityRole::splashScreen:
case AccessibilityRole::dialogWindow:
case AccessibilityRole::window: return NSAccessibilityWindowRole;
case AccessibilityRole::tableHeader:
case AccessibilityRole::unspecified:
case AccessibilityRole::group: return NSAccessibilityGroupRole;
case AccessibilityRole::label:
case AccessibilityRole::staticText: return NSAccessibilityStaticTextRole;
case AccessibilityRole::tree:
case AccessibilityRole::list: return NSAccessibilityOutlineRole;
case AccessibilityRole::listItem:
case AccessibilityRole::treeItem: return NSAccessibilityRowRole;
case AccessibilityRole::button: return NSAccessibilityButtonRole;
case AccessibilityRole::toggleButton: return NSAccessibilityCheckBoxRole;
case AccessibilityRole::radioButton: return NSAccessibilityRadioButtonRole;
case AccessibilityRole::comboBox: return NSAccessibilityPopUpButtonRole;
case AccessibilityRole::image: return NSAccessibilityImageRole;
case AccessibilityRole::slider: return NSAccessibilitySliderRole;
case AccessibilityRole::editableText: return NSAccessibilityTextAreaRole;
case AccessibilityRole::menuItem: return NSAccessibilityMenuItemRole;
case AccessibilityRole::menuBar: return NSAccessibilityMenuRole;
case AccessibilityRole::table: return NSAccessibilityOutlineRole;
case AccessibilityRole::column: return NSAccessibilityColumnRole;
case AccessibilityRole::row: return NSAccessibilityRowRole;
case AccessibilityRole::cell: return NSAccessibilityCellRole;
case AccessibilityRole::hyperlink: return NSAccessibilityLinkRole;
case AccessibilityRole::progressBar: return NSAccessibilityProgressIndicatorRole;
case AccessibilityRole::scrollBar: return NSAccessibilityScrollBarRole;
case AccessibilityRole::ignored: break;
}
return NSAccessibilityUnknownRole;
}
return nil;
});
addMethod (@selector (accessibilitySubrole), [] (id self, SEL)
{
if (auto* handler = getHandler (self))
{
if (auto* textInterface = getTextInterface (self))
if (textInterface->isDisplayingProtectedText())
return NSAccessibilitySecureTextFieldSubrole;
const auto handlerRole = handler->getRole();
if (handlerRole == AccessibilityRole::window) return NSAccessibilityStandardWindowSubrole;
if (handlerRole == AccessibilityRole::dialogWindow) return NSAccessibilityDialogSubrole;
if (handlerRole == AccessibilityRole::tooltip
|| handlerRole == AccessibilityRole::splashScreen) return NSAccessibilityFloatingWindowSubrole;
if (handlerRole == AccessibilityRole::toggleButton) return NSAccessibilityToggleSubrole;
if (handlerRole == AccessibilityRole::treeItem
|| handlerRole == AccessibilityRole::listItem) return NSAccessibilityOutlineRowSubrole;
if (handlerRole == AccessibilityRole::row && getCellInterface (self) != nullptr) return NSAccessibilityTableRowSubrole;
const auto& handlerComponent = handler->getComponent();
if (auto* documentWindow = handlerComponent.findParentComponentOfClass<DocumentWindow>())
{
if (handlerRole == AccessibilityRole::button)
{
if (&handlerComponent == documentWindow->getCloseButton()) return NSAccessibilityCloseButtonSubrole;
if (&handlerComponent == documentWindow->getMinimiseButton()) return NSAccessibilityMinimizeButtonSubrole;
if (&handlerComponent == documentWindow->getMaximiseButton()) return NSAccessibilityFullScreenButtonSubrole;
}
}
}
return NSAccessibilityUnknownRole;
});
addMethod (@selector (accessibilityLabel), [] (id self, SEL) -> NSString*
{
if (auto* handler = getHandler (self))
return juceStringToNS (handler->getDescription());
return nil;
});
addMethod (@selector (accessibilityValue), [] (id self, SEL) -> id
{
if (auto* handler = getHandler (self))
{
if (! handler->getCurrentState().isCheckable())
return getAccessibilityValueFromInterfaces (*handler);
const auto checked = handler->getCurrentState().isChecked();
if ( handler->getRole() == AccessibilityRole::toggleButton
|| handler->getRole() == AccessibilityRole::radioButton)
{
return checked ? @YES : @NO;
}
return juceStringToNS (checked ? TRANS ("On") : TRANS ("Off"));
}
return nil;
});
addMethod (@selector (accessibilityTitle), getAccessibilityTitle);
addMethod (@selector (accessibilityHelp), getAccessibilityHelp);
addMethod (@selector (setAccessibilityValue:), setAccessibilityValue);
addMethod (@selector (accessibilitySelectedChildren), [] (id self, SEL)
{
return getSelectedChildren ([self accessibilityChildren]);
});
addMethod (@selector (setAccessibilitySelectedChildren:), [] (id self, SEL, NSArray* selected)
{
setSelectedChildren ([self accessibilityChildren], selected);
});
addMethod (@selector (accessibilityOrientation), [] (id self, SEL)
{
if (auto* handler = getHandler (self))
return handler->getComponent().getBounds().toFloat().getAspectRatio() > 1.0f
? NSAccessibilityOrientationHorizontal
: NSAccessibilityOrientationVertical;
return NSAccessibilityOrientationUnknown;
});
addMethod (@selector (accessibilityInsertionPointLineNumber), [] (id self, SEL) -> NSInteger
{
if (auto* textInterface = getTextInterface (self))
return [self accessibilityLineForIndex: textInterface->getTextInsertionOffset()];
return 0;
});
addMethod (@selector (accessibilityVisibleCharacterRange), [] (id self, SEL)
{
if (auto* textInterface = getTextInterface (self))
return juceRangeToNS ({ 0, textInterface->getTotalNumCharacters() });
return NSMakeRange (0, 0);
});
addMethod (@selector (accessibilityNumberOfCharacters), [] (id self, SEL)
{
if (auto* textInterface = getTextInterface (self))
return textInterface->getTotalNumCharacters();
return 0;
});
addMethod (@selector (accessibilitySelectedText), [] (id self, SEL) -> NSString*
{
if (auto* textInterface = getTextInterface (self))
return juceStringToNS (textInterface->getText (textInterface->getSelection()));
return nil;
});
addMethod (@selector (accessibilitySelectedTextRange), [] (id self, SEL)
{
if (auto* textInterface = getTextInterface (self))
{
const auto currentSelection = textInterface->getSelection();
if (currentSelection.isEmpty())
return NSMakeRange ((NSUInteger) textInterface->getTextInsertionOffset(), 0);
return juceRangeToNS (currentSelection);
}
return NSMakeRange (0, 0);
});
addMethod (@selector (accessibilityAttributedStringForRange:), [] (id self, SEL, NSRange range) -> NSAttributedString*
{
NSString* string = [self accessibilityStringForRange: range];
if (string != nil)
return [[[NSAttributedString alloc] initWithString: string] autorelease];
return nil;
});
addMethod (@selector (accessibilityRangeForLine:), [] (id self, SEL, NSInteger line)
{
if (auto* textInterface = getTextInterface (self))
{
auto text = textInterface->getText ({ 0, textInterface->getTotalNumCharacters() });
auto lines = StringArray::fromLines (text);
if (line < lines.size())
{
auto lineText = lines[(int) line];
auto start = text.indexOf (lineText);
if (start >= 0)
return NSMakeRange ((NSUInteger) start, (NSUInteger) lineText.length());
}
}
return NSMakeRange (0, 0);
});
addMethod (@selector (accessibilityStringForRange:), [] (id self, SEL, NSRange range) -> NSString*
{
if (auto* textInterface = getTextInterface (self))
return juceStringToNS (textInterface->getText (nsRangeToJuce (range)));
return nil;
});
addMethod (@selector (accessibilityRangeForPosition:), [] (id self, SEL, NSPoint position)
{
if (auto* handler = getHandler (self))
{
if (auto* textInterface = handler->getTextInterface())
{
auto screenPoint = roundToIntPoint (flippedScreenPoint (position));
if (handler->getComponent().getScreenBounds().contains (screenPoint))
{
auto offset = textInterface->getOffsetAtPoint (screenPoint);
if (offset >= 0)
return NSMakeRange ((NSUInteger) offset, 1);
}
}
}
return NSMakeRange (0, 0);
});
addMethod (@selector (accessibilityRangeForIndex:), [] (id self, SEL, NSInteger index)
{
if (auto* textInterface = getTextInterface (self))
if (isPositiveAndBelow (index, textInterface->getTotalNumCharacters()))
return NSMakeRange ((NSUInteger) index, 1);
return NSMakeRange (0, 0);
});
addMethod (@selector (accessibilityFrameForRange:), [] (id self, SEL, NSRange range)
{
if (auto* textInterface = getTextInterface (self))
return flippedScreenRect (makeNSRect (textInterface->getTextBounds (nsRangeToJuce (range)).getBounds()));
return NSZeroRect;
});
addMethod (@selector (accessibilityLineForIndex:), [] (id self, SEL, NSInteger index)
{
if (auto* textInterface = getTextInterface (self))
{
auto text = textInterface->getText ({ 0, (int) index });
if (! text.isEmpty())
return StringArray::fromLines (text).size() - 1;
}
return 0;
});
addMethod (@selector (setAccessibilitySelectedTextRange:), [] (id self, SEL, NSRange selectedRange)
{
if (auto* textInterface = getTextInterface (self))
textInterface->setSelection (nsRangeToJuce (selectedRange));
});
addMethod (@selector (accessibilityRows), [] (id self, SEL) -> NSArray*
{
if (auto* tableInterface = getTableInterface (self))
{
auto* rows = [[NSMutableArray new] autorelease];
for (int row = 0, numRows = tableInterface->getNumRows(); row < numRows; ++row)
{
if (auto* rowHandler = tableInterface->getRowHandler (row))
{
[rows addObject: static_cast<id> (rowHandler->getNativeImplementation())];
}
else
{
[rows addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityRowRole
frame: NSZeroRect
label: @"Offscreen Row"
parent: self]];
}
}
return rows;
}
return nil;
});
addMethod (@selector (accessibilitySelectedRows), [] (id self, SEL)
{
return getSelectedChildren ([self accessibilityRows]);
});
addMethod (@selector (setAccessibilitySelectedRows:), [] (id self, SEL, NSArray* selected)
{
setSelectedChildren ([self accessibilityRows], selected);
});
addMethod (@selector (accessibilityHeader), [] (id self, SEL) -> id
{
if (auto* tableInterface = getTableInterface (self))
if (auto* handler = tableInterface->getHeaderHandler())
return static_cast<id> (handler->getNativeImplementation());
return nil;
});
addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount);
addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount);
addMethod (@selector (accessibilityRowIndexRange), getAccessibilityRowIndexRange);
addMethod (@selector (accessibilityColumnIndexRange), getAccessibilityColumnIndexRange);
addMethod (@selector (accessibilityIndex), [] (id self, SEL) -> NSInteger
{
if (auto* handler = getHandler (self))
{
if (auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (handler, &AccessibilityHandler::getTableInterface))
{
if (auto* tableInterface = tableHandler->getTableInterface())
{
NSAccessibilityRole handlerRole = [self accessibilityRole];
if ([handlerRole isEqual: NSAccessibilityRowRole])
if (const auto span = tableInterface->getRowSpan (*handler))
return span->begin;
if ([handlerRole isEqual: NSAccessibilityColumnRole])
if (const auto span = tableInterface->getColumnSpan (*handler))
return span->begin;
}
}
}
return 0;
});
addMethod (@selector (accessibilityDisclosureLevel), [] (id self, SEL)
{
if (auto* handler = getHandler (self))
if (auto* cellInterface = handler->getCellInterface())
return cellInterface->getDisclosureLevel();
return 0;
});
addMethod (@selector (accessibilityDisclosedRows), [] (id self, SEL) -> id
{
if (auto* handler = getHandler (self))
{
if (auto* cellInterface = handler->getCellInterface())
{
const auto rows = cellInterface->getDisclosedRows();
auto* result = [NSMutableArray arrayWithCapacity: rows.size()];
for (const auto& row : rows)
{
if (row != nullptr)
[result addObject: static_cast<id> (row->getNativeImplementation())];
else
[result addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityRowRole
frame: NSZeroRect
label: @"Offscreen Row"
parent: self]];
}
return result;
}
}
return nil;
});
addMethod (@selector (isAccessibilityExpanded), [] (id self, SEL) -> BOOL
{
if (auto* handler = getHandler (self))
return handler->getCurrentState().isExpanded();
return NO;
});
addMethod (@selector (accessibilityPerformIncrement), accessibilityPerformIncrement);
addMethod (@selector (accessibilityPerformDecrement), accessibilityPerformDecrement);
addMethod (@selector (accessibilityPerformDelete), [] (id self, SEL)
{
if (auto* handler = getHandler (self))
{
if (hasEditableText (*handler))
{
handler->getTextInterface()->setText ({});
return YES;
}
if (auto* valueInterface = handler->getValueInterface())
{
if (! valueInterface->isReadOnly())
{
valueInterface->setValue ({});
return YES;
}
}
}
return NO;
});
addMethod (@selector (accessibilityPerformPress), accessibilityPerformPress);
addMethod (@selector (accessibilityPerformShowMenu), [] (id self, SEL)
{
return performActionIfSupported (self, AccessibilityActionType::showMenu);
});
addMethod (@selector (accessibilityPerformRaise), [] (id self, SEL)
{
[self setAccessibilityFocused: YES]; return YES;
});
addMethod (@selector (isAccessibilitySelectorAllowed:), [] (id self, SEL, SEL selector) -> BOOL
{
if (auto* handler = getHandler (self))
{
const auto handlerRole = handler->getRole();
const auto currentState = handler->getCurrentState();
for (auto textSelector : { @selector (accessibilityInsertionPointLineNumber),
@selector (accessibilityVisibleCharacterRange),
@selector (accessibilityNumberOfCharacters),
@selector (accessibilitySelectedText),
@selector (accessibilitySelectedTextRange),
@selector (accessibilityAttributedStringForRange:),
@selector (accessibilityRangeForLine:),
@selector (accessibilityStringForRange:),
@selector (accessibilityRangeForPosition:),
@selector (accessibilityRangeForIndex:),
@selector (accessibilityFrameForRange:),
@selector (accessibilityLineForIndex:),
@selector (setAccessibilitySelectedTextRange:) })
{
if (selector == textSelector)
return handler->getTextInterface() != nullptr;
}
for (auto tableSelector : { @selector (accessibilityRowCount),
@selector (accessibilityRows),
@selector (accessibilitySelectedRows),
@selector (accessibilityColumnCount),
@selector (accessibilityHeader) })
{
if (selector == tableSelector)
return handler->getTableInterface() != nullptr;
}
for (auto cellSelector : { @selector (accessibilityRowIndexRange),
@selector (accessibilityColumnIndexRange),
@selector (accessibilityIndex),
@selector (accessibilityDisclosureLevel) })
{
if (selector == cellSelector)
return handler->getCellInterface() != nullptr;
}
for (auto valueSelector : { @selector (accessibilityValue),
@selector (setAccessibilityValue:),
@selector (accessibilityPerformDelete),
@selector (accessibilityPerformIncrement),
@selector (accessibilityPerformDecrement) })
{
if (selector != valueSelector)
continue;
auto* valueInterface = handler->getValueInterface();
if (selector == @selector (accessibilityValue))
return valueInterface != nullptr
|| hasEditableText (*handler)
|| currentState.isCheckable();
auto hasEditableValue = [valueInterface] { return valueInterface != nullptr && ! valueInterface->isReadOnly(); };
if (selector == @selector (setAccessibilityValue:)
|| selector == @selector (accessibilityPerformDelete))
return hasEditableValue() || hasEditableText (*handler);
auto isRanged = [valueInterface] { return valueInterface != nullptr && valueInterface->getRange().isValid(); };
if (selector == @selector (accessibilityPerformIncrement)
|| selector == @selector (accessibilityPerformDecrement))
return hasEditableValue() && isRanged();
return NO;
}
for (auto actionSelector : { @selector (accessibilityPerformPress),
@selector (accessibilityPerformShowMenu),
@selector (accessibilityPerformRaise),
@selector (setAccessibilityFocused:) })
{
if (selector != actionSelector)
continue;
if (selector == @selector (accessibilityPerformPress))
return (handler->getCurrentState().isCheckable() && handler->getActions().contains (AccessibilityActionType::toggle))
|| handler->getActions().contains (AccessibilityActionType::press);
if (selector == @selector (accessibilityPerformShowMenu))
return handler->getActions().contains (AccessibilityActionType::showMenu);
if (selector == @selector (accessibilityPerformRaise))
return [[self accessibilityRole] isEqual: NSAccessibilityWindowRole];
if (selector == @selector (setAccessibilityFocused:))
return currentState.isFocusable();
}
if (selector == @selector (accessibilitySelectedChildren))
return handlerRole == AccessibilityRole::popupMenu;
if (selector == @selector (accessibilityOrientation))
return handlerRole == AccessibilityRole::scrollBar;
if (selector == @selector (isAccessibilityExpanded))
return currentState.isExpandable();
return sendSuperclassMessage<BOOL> (self, @selector (isAccessibilitySelectorAllowed:), selector);
}
return NO;
});
addMethod (@selector (accessibilityChildrenInNavigationOrder), getAccessibilityChildren);
registerClass();
}
//==============================================================================
static bool isSelectable (AccessibleState state) noexcept
{
return state.isSelectable() || state.isMultiSelectable();
}
static NSArray* getSelectedChildren (NSArray* children)
{
NSMutableArray* selected = [[NSMutableArray new] autorelease];
for (id child in children)
{
if (auto* handler = getHandler (child))
{
const auto currentState = handler->getCurrentState();
if (isSelectable (currentState) && currentState.isSelected())
[selected addObject: child];
}
}
return selected;
}
static void setSelected (id item, bool selected)
{
auto* handler = getHandler (item);
if (handler == nullptr)
return;
const auto currentState = handler->getCurrentState();
if (isSelectable (currentState))
{
if (currentState.isSelected() != selected)
handler->getActions().invoke (AccessibilityActionType::toggle);
}
else if (currentState.isFocusable())
{
[item setAccessibilityFocused: selected];
}
}
static void setSelectedChildren (NSArray* children, NSArray* selected)
{
for (id child in children)
setSelected (child, [selected containsObject: child]);
}
//==============================================================================
static id getAccessibilityWindow (id self, SEL)
{
return [[self accessibilityParent] accessibilityWindow];
}
static NSArray* getAccessibilityChildren (id self, SEL)
{
if (auto* handler = getHandler (self))
{
auto children = handler->getChildren();
auto* accessibleChildren = [NSMutableArray arrayWithCapacity: (NSUInteger) children.size()];
for (auto* childHandler : children)
[accessibleChildren addObject: static_cast<id> (childHandler->getNativeImplementation())];
if (id nativeChild = static_cast<id> (AccessibilityHandler::getNativeChildForComponent (handler->getComponent())))
{
// Having both native and non-native children would require implementing an
// ordering. However, this situation doesn't occur with any of our current
// use-cases.
jassert ([accessibleChildren count] == 0);
if ([nativeChild isAccessibilityElement])
[accessibleChildren addObject:nativeChild];
else if (auto* childrenOfChild = [nativeChild accessibilityChildren]; childrenOfChild != nil)
[accessibleChildren addObjectsFromArray:(NSArray* _Nonnull) childrenOfChild];
}
return accessibleChildren;
}
return nil;
}
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityElement)
};
//==============================================================================
API_AVAILABLE (macos (10.10))
AccessibilityElement::Holder accessibilityElement;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeImpl)
};
//==============================================================================
AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const
{
if (@available (macOS 10.10, *))
return (AccessibilityNativeHandle*) nativeImpl->getAccessibilityElement();
return nullptr;
}
static bool areAnyAccessibilityClientsActive()
{
const String voiceOverKeyString ("voiceOverOnOffKey");
const String applicationIDString ("com.apple.universalaccess");
CFUniquePtr<CFStringRef> cfKey (voiceOverKeyString.toCFString());
CFUniquePtr<CFStringRef> cfID (applicationIDString.toCFString());
CFUniquePtr<CFPropertyListRef> value (CFPreferencesCopyAppValue (cfKey.get(), cfID.get()));
if (value != nullptr)
return CFBooleanGetValue ((CFBooleanRef) value.get());
return false;
}
static void sendAccessibilityEvent (id accessibilityElement,
NSAccessibilityNotificationName notification,
NSDictionary* userInfo)
{
jassert (notification != NSAccessibilityNotificationName{});
NSAccessibilityPostNotificationWithUserInfo (accessibilityElement, notification, userInfo);
}
static void sendHandlerNotification (const AccessibilityHandler& handler,
NSAccessibilityNotificationName notification)
{
if (! areAnyAccessibilityClientsActive() || notification == NSAccessibilityNotificationName{})
return;
if (@available (macOS 10.9, *))
{
if (id accessibilityElement = static_cast<id> (handler.getNativeImplementation()))
{
sendAccessibilityEvent (accessibilityElement, notification,
(notification == NSAccessibilityLayoutChangedNotification
? @{ NSAccessibilityUIElementsKey: @[ accessibilityElement ] }
: nil));
}
}
}
static NSAccessibilityNotificationName layoutChangedNotification()
{
if (@available (macOS 10.9, *))
return NSAccessibilityLayoutChangedNotification;
static NSString* layoutChangedString = @"AXLayoutChanged";
return layoutChangedString;
}
void detail::AccessibilityHelpers::notifyAccessibilityEvent (const AccessibilityHandler& handler, Event eventType)
{
auto notification = [eventType]
{
switch (eventType)
{
case Event::elementCreated: return NSAccessibilityCreatedNotification;
case Event::elementDestroyed: return NSAccessibilityUIElementDestroyedNotification;
case Event::elementMovedOrResized: return layoutChangedNotification();
case Event::focusChanged: return NSAccessibilityFocusedUIElementChangedNotification;
case Event::windowOpened: return NSAccessibilityWindowCreatedNotification;
case Event::windowClosed: break;
}
return NSAccessibilityNotificationName{};
}();
sendHandlerNotification (handler, notification);
}
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const
{
auto notification = [eventType]
{
switch (eventType)
{
case AccessibilityEvent::textSelectionChanged: return NSAccessibilitySelectedTextChangedNotification;
case AccessibilityEvent::rowSelectionChanged: return NSAccessibilitySelectedRowsChangedNotification;
case AccessibilityEvent::textChanged:
case AccessibilityEvent::valueChanged: return NSAccessibilityValueChangedNotification;
case AccessibilityEvent::titleChanged: return NSAccessibilityTitleChangedNotification;
case AccessibilityEvent::structureChanged: return layoutChangedNotification();
}
return NSAccessibilityNotificationName{};
}();
sendHandlerNotification (*this, notification);
}
void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority priority)
{
if (! areAnyAccessibilityClientsActive())
return;
if (@available (macOS 10.9, *))
{
auto nsPriority = [priority]
{
// The below doesn't get noticed by the @available check above
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability")
switch (priority)
{
case AnnouncementPriority::low: return NSAccessibilityPriorityLow;
case AnnouncementPriority::medium: return NSAccessibilityPriorityMedium;
case AnnouncementPriority::high: return NSAccessibilityPriorityHigh;
}
jassertfalse;
return NSAccessibilityPriorityLow;
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}();
sendAccessibilityEvent (static_cast<id> ([NSApp mainWindow]),
NSAccessibilityAnnouncementRequestedNotification,
@{ NSAccessibilityAnnouncementKey: juceStringToNS (announcementString),
NSAccessibilityPriorityKey: @(nsPriority) });
}
}
} // namespace juce
@@ -0,0 +1,331 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
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()
{
ComSmartPtr<IRawElementProviderSimple> provider;
accessibilityElement->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
accessibilityElement->invalidateElement();
--providerCount;
if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
{
uiaWrapper->disconnectProvider (provider);
if (providerCount == 0 && JUCEApplicationBase::isStandaloneApp())
uiaWrapper->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;
}
static bool areAnyAccessibilityClientsActive()
{
const auto areClientsListening = []
{
if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
return uiaWrapper->clientsAreListening() != 0;
return false;
};
const auto isScreenReaderRunning = []
{
BOOL isRunning = FALSE;
SystemParametersInfo (SPI_GETSCREENREADER, 0, (PVOID) &isRunning, 0);
return isRunning != 0;
};
return areClientsListening() || isScreenReaderRunning();
}
template <typename Callback>
void getProviderWithCheckedWrapper (const AccessibilityHandler& handler, Callback&& callback)
{
if (! areAnyAccessibilityClientsActive() || isStartingUpOrShuttingDown() || ! isHandlerValid (handler))
return;
if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
{
ComSmartPtr<IRawElementProviderSimple> provider;
handler.getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
callback (uiaWrapper, provider);
}
}
void sendAccessibilityAutomationEvent (const AccessibilityHandler& handler, EVENTID event)
{
jassert (event != EVENTID{});
getProviderWithCheckedWrapper (handler, [event] (WindowsUIAWrapper* uiaWrapper, ComSmartPtr<IRawElementProviderSimple>& provider)
{
uiaWrapper->raiseAutomationEvent (provider, event);
});
}
void sendAccessibilityPropertyChangedEvent (const AccessibilityHandler& handler, PROPERTYID property, VARIANT newValue)
{
jassert (property != PROPERTYID{});
getProviderWithCheckedWrapper (handler, [property, newValue] (WindowsUIAWrapper* uiaWrapper, ComSmartPtr<IRawElementProviderSimple>& provider)
{
VARIANT oldValue;
VariantHelpers::clear (&oldValue);
uiaWrapper->raiseAutomationPropertyChangedEvent (provider, property, oldValue, newValue);
});
}
void detail::AccessibilityHelpers::notifyAccessibilityEvent (const AccessibilityHandler& handler, Event eventType)
{
using namespace ComTypes::Constants;
if (eventType == Event::elementCreated
|| eventType == Event::elementDestroyed)
{
if (auto* parent = handler.getParent())
sendAccessibilityAutomationEvent (*parent, UIA_LayoutInvalidatedEventId);
return;
}
if (eventType == Event::windowOpened
|| eventType == Event::windowClosed)
{
if (auto* peer = handler.getComponent().getPeer())
if ((peer->getStyleFlags() & ComponentPeer::windowHasTitleBar) == 0)
return;
}
auto event = [eventType]() -> EVENTID
{
switch (eventType)
{
case Event::focusChanged: return UIA_AutomationFocusChangedEventId;
case Event::windowOpened: return UIA_Window_WindowOpenedEventId;
case Event::windowClosed: return UIA_Window_WindowClosedEventId;
case Event::elementCreated:
case Event::elementDestroyed:
case Event::elementMovedOrResized: break;
}
return {};
}();
if (event != EVENTID{})
sendAccessibilityAutomationEvent (handler, event);
}
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const
{
if (eventType == AccessibilityEvent::titleChanged)
{
VARIANT newValue;
VariantHelpers::setString (getTitle(), &newValue);
sendAccessibilityPropertyChangedEvent (*this, UIA_NamePropertyId, newValue);
return;
}
if (eventType == AccessibilityEvent::valueChanged)
{
if (auto* valueInterface = getValueInterface())
{
const auto propertyType = getRole() == AccessibilityRole::slider ? UIA_RangeValueValuePropertyId
: UIA_ValueValuePropertyId;
const auto value = getRole() == AccessibilityRole::slider
? VariantHelpers::getWithValue (valueInterface->getCurrentValue())
: VariantHelpers::getWithValue (valueInterface->getCurrentValueAsString());
sendAccessibilityPropertyChangedEvent (*this, propertyType, value);
}
return;
}
auto event = [eventType]() -> EVENTID
{
using namespace ComTypes::Constants;
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::titleChanged:
case AccessibilityEvent::valueChanged: break;
}
return {};
}();
if (event != EVENTID{})
sendAccessibilityAutomationEvent (*this, event);
}
struct SpVoiceWrapper final : public DeletedAtShutdown
{
SpVoiceWrapper()
{
[[maybe_unused]] auto hr = voice.CoCreateInstance (ComTypes::CLSID_SpVoice);
jassert (SUCCEEDED (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 (! areAnyAccessibilityClientsActive())
return;
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);
}
}
//==============================================================================
namespace WindowsAccessibility
{
static long getUiaRootObjectId()
{
return static_cast<long> (UiaRootObjectId);
}
static bool handleWmGetObject (AccessibilityHandler* handler, WPARAM wParam, LPARAM lParam, LRESULT* res)
{
if (isStartingUpOrShuttingDown() || (handler == nullptr || ! isHandlerValid (*handler)))
return false;
if (auto* uiaWrapper = WindowsUIAWrapper::getInstance())
{
ComSmartPtr<IRawElementProviderSimple> provider;
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
if (! uiaWrapper->isProviderDisconnecting (provider))
*res = uiaWrapper->returnRawElementProvider ((HWND) handler->getComponent().getWindowHandle(), wParam, lParam, provider);
return true;
}
return false;
}
static void revokeUIAMapEntriesForWindow (HWND hwnd)
{
if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
uiaWrapper->returnRawElementProvider (hwnd, 0, 0, nullptr);
}
}
JUCE_IMPLEMENT_SINGLETON (WindowsUIAWrapper)
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
} // namespace juce
@@ -0,0 +1,500 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce::ComTypes
{
/*
These interfaces would normally be included in the system platform headers.
However, those headers are likely to be incomplete when building with
MinGW. In order to allow building accessible applications under MinGW,
we reproduce all necessary definitions here.
*/
struct UiaPoint
{
double x;
double y;
};
struct UiaRect
{
double left;
double top;
double width;
double height;
};
enum NavigateDirection
{
NavigateDirection_Parent = 0,
NavigateDirection_NextSibling = 1,
NavigateDirection_PreviousSibling = 2,
NavigateDirection_FirstChild = 3,
NavigateDirection_LastChild = 4
};
enum ExpandCollapseState
{
ExpandCollapseState_Collapsed = 0,
ExpandCollapseState_Expanded = 1,
ExpandCollapseState_PartiallyExpanded = 2,
ExpandCollapseState_LeafNode = 3
};
enum TextPatternRangeEndpoint
{
TextPatternRangeEndpoint_Start = 0,
TextPatternRangeEndpoint_End = 1
};
enum TextUnit
{
TextUnit_Character = 0,
TextUnit_Format = 1,
TextUnit_Word = 2,
TextUnit_Line = 3,
TextUnit_Paragraph = 4,
TextUnit_Page = 5,
TextUnit_Document = 6
};
enum SupportedTextSelection
{
SupportedTextSelection_None = 0,
SupportedTextSelection_Single = 1,
SupportedTextSelection_Multiple = 2
};
enum CaretPosition
{
CaretPosition_Unknown = 0,
CaretPosition_EndOfLine = 1,
CaretPosition_BeginningOfLine = 2
};
enum ToggleState
{
ToggleState_Off = 0,
ToggleState_On = 1,
ToggleState_Indeterminate = 2
};
enum WindowVisualState
{
WindowVisualState_Normal = 0,
WindowVisualState_Maximized = 1,
WindowVisualState_Minimized = 2
};
enum WindowInteractionState
{
WindowInteractionState_Running = 0,
WindowInteractionState_Closing = 1,
WindowInteractionState_ReadyForUserInteraction = 2,
WindowInteractionState_BlockedByModalWindow = 3,
WindowInteractionState_NotResponding = 4
};
enum RowOrColumnMajor
{
RowOrColumnMajor_RowMajor = 0,
RowOrColumnMajor_ColumnMajor = 1,
RowOrColumnMajor_Indeterminate = 2
};
enum ScrollAmount
{
ScrollAmount_LargeDecrement = 0,
ScrollAmount_SmallDecrement = 1,
ScrollAmount_NoAmount = 2,
ScrollAmount_LargeIncrement = 3,
ScrollAmount_SmallIncrement = 4
};
namespace Constants
{
#undef UIA_InvokePatternId
#undef UIA_SelectionPatternId
#undef UIA_ValuePatternId
#undef UIA_RangeValuePatternId
#undef UIA_ScrollPatternId
#undef UIA_ExpandCollapsePatternId
#undef UIA_GridPatternId
#undef UIA_GridItemPatternId
#undef UIA_WindowPatternId
#undef UIA_SelectionItemPatternId
#undef UIA_TablePatternId
#undef UIA_TableItemPatternId
#undef UIA_TextPatternId
#undef UIA_TogglePatternId
#undef UIA_TransformPatternId
#undef UIA_ScrollItemPatternId
#undef UIA_TextPattern2Id
#undef UIA_StructureChangedEventId
#undef UIA_MenuOpenedEventId
#undef UIA_AutomationFocusChangedEventId
#undef UIA_MenuClosedEventId
#undef UIA_LayoutInvalidatedEventId
#undef UIA_Invoke_InvokedEventId
#undef UIA_SelectionItem_ElementSelectedEventId
#undef UIA_Text_TextSelectionChangedEventId
#undef UIA_Text_TextChangedEventId
#undef UIA_Window_WindowOpenedEventId
#undef UIA_Window_WindowClosedEventId
#undef UIA_IsPeripheralPropertyId
#undef UIA_FullDescriptionPropertyId
#undef UIA_IsDialogPropertyId
#undef UIA_IsReadOnlyAttributeId
#undef UIA_CaretPositionAttributeId
#undef UIA_ButtonControlTypeId
#undef UIA_CheckBoxControlTypeId
#undef UIA_ComboBoxControlTypeId
#undef UIA_EditControlTypeId
#undef UIA_HyperlinkControlTypeId
#undef UIA_ImageControlTypeId
#undef UIA_ListItemControlTypeId
#undef UIA_ListControlTypeId
#undef UIA_MenuBarControlTypeId
#undef UIA_MenuItemControlTypeId
#undef UIA_ProgressBarControlTypeId
#undef UIA_RadioButtonControlTypeId
#undef UIA_ScrollBarControlTypeId
#undef UIA_SliderControlTypeId
#undef UIA_TextControlTypeId
#undef UIA_ToolTipControlTypeId
#undef UIA_TreeControlTypeId
#undef UIA_TreeItemControlTypeId
#undef UIA_CustomControlTypeId
#undef UIA_GroupControlTypeId
#undef UIA_DataItemControlTypeId
#undef UIA_WindowControlTypeId
#undef UIA_HeaderControlTypeId
#undef UIA_HeaderItemControlTypeId
#undef UIA_TableControlTypeId
const long UIA_InvokePatternId = 10000;
const long UIA_SelectionPatternId = 10001;
const long UIA_ValuePatternId = 10002;
const long UIA_RangeValuePatternId = 10003;
const long UIA_ScrollPatternId = 10004;
const long UIA_ExpandCollapsePatternId = 10005;
const long UIA_GridPatternId = 10006;
const long UIA_GridItemPatternId = 10007;
const long UIA_WindowPatternId = 10009;
const long UIA_SelectionItemPatternId = 10010;
const long UIA_TablePatternId = 10012;
const long UIA_TableItemPatternId = 10013;
const long UIA_TextPatternId = 10014;
const long UIA_TogglePatternId = 10015;
const long UIA_TransformPatternId = 10016;
const long UIA_ScrollItemPatternId = 10017;
const long UIA_TextPattern2Id = 10024;
const long UIA_StructureChangedEventId = 20002;
const long UIA_MenuOpenedEventId = 20003;
const long UIA_AutomationFocusChangedEventId = 20005;
const long UIA_MenuClosedEventId = 20007;
const long UIA_LayoutInvalidatedEventId = 20008;
const long UIA_Invoke_InvokedEventId = 20009;
const long UIA_SelectionItem_ElementSelectedEventId = 20012;
const long UIA_Text_TextSelectionChangedEventId = 20014;
const long UIA_Text_TextChangedEventId = 20015;
const long UIA_Window_WindowOpenedEventId = 20016;
const long UIA_Window_WindowClosedEventId = 20017;
const long UIA_IsPeripheralPropertyId = 30150;
const long UIA_FullDescriptionPropertyId = 30159;
const long UIA_IsDialogPropertyId = 30174;
const long UIA_IsReadOnlyAttributeId = 40015;
const long UIA_CaretPositionAttributeId = 40038;
const long UIA_ButtonControlTypeId = 50000;
const long UIA_CheckBoxControlTypeId = 50002;
const long UIA_ComboBoxControlTypeId = 50003;
const long UIA_EditControlTypeId = 50004;
const long UIA_HyperlinkControlTypeId = 50005;
const long UIA_ImageControlTypeId = 50006;
const long UIA_ListItemControlTypeId = 50007;
const long UIA_ListControlTypeId = 50008;
const long UIA_MenuBarControlTypeId = 50010;
const long UIA_MenuItemControlTypeId = 50011;
const long UIA_ProgressBarControlTypeId = 50012;
const long UIA_RadioButtonControlTypeId = 50013;
const long UIA_ScrollBarControlTypeId = 50014;
const long UIA_SliderControlTypeId = 50015;
const long UIA_TextControlTypeId = 50020;
const long UIA_ToolTipControlTypeId = 50022;
const long UIA_TreeControlTypeId = 50023;
const long UIA_TreeItemControlTypeId = 50024;
const long UIA_CustomControlTypeId = 50025;
const long UIA_GroupControlTypeId = 50026;
const long UIA_DataItemControlTypeId = 50029;
const long UIA_WindowControlTypeId = 50032;
const long UIA_HeaderControlTypeId = 50034;
const long UIA_HeaderItemControlTypeId = 50035;
const long UIA_TableControlTypeId = 50036;
} // namespace Constants
interface IRawElementProviderFragmentRoot;
interface IRawElementProviderFragment;
JUCE_COMCLASS (IRawElementProviderFragmentRoot, "620ce2a5-ab8f-40a9-86cb-de3c75599b58") : public IUnknown
{
public:
JUCE_COMCALL ElementProviderFromPoint (double x, double y, __RPC__deref_out_opt IRawElementProviderFragment** pRetVal) = 0;
JUCE_COMCALL GetFocus (__RPC__deref_out_opt IRawElementProviderFragment * *pRetVal) = 0;
};
JUCE_COMCLASS (IRawElementProviderFragment, "f7063da8-8359-439c-9297-bbc5299a7d87") : public IUnknown
{
public:
JUCE_COMCALL Navigate (NavigateDirection direction, __RPC__deref_out_opt IRawElementProviderFragment** pRetVal) = 0;
JUCE_COMCALL GetRuntimeId (__RPC__deref_out_opt SAFEARRAY * *pRetVal) = 0;
JUCE_COMCALL get_BoundingRectangle (__RPC__out UiaRect * pRetVal) = 0;
JUCE_COMCALL GetEmbeddedFragmentRoots (__RPC__deref_out_opt SAFEARRAY * *pRetVal) = 0;
JUCE_COMCALL SetFocus() = 0;
JUCE_COMCALL get_FragmentRoot (__RPC__deref_out_opt IRawElementProviderFragmentRoot * *pRetVal) = 0;
};
JUCE_COMCLASS (IRawElementProviderHwndOverride, "1d5df27c-8947-4425-b8d9-79787bb460b8") : public IUnknown
{
public:
JUCE_COMCALL GetOverrideProviderForHwnd (__RPC__in HWND hwnd,
__RPC__deref_out_opt IRawElementProviderSimple** pRetVal) = 0;
};
JUCE_COMCLASS (IExpandCollapseProvider, "d847d3a5-cab0-4a98-8c32-ecb45c59ad24") : public IUnknown
{
public:
JUCE_COMCALL Expand() = 0;
JUCE_COMCALL Collapse() = 0;
JUCE_COMCALL get_ExpandCollapseState (__RPC__out ExpandCollapseState * pRetVal) = 0;
};
JUCE_COMCLASS (IGridItemProvider, "d02541f1-fb81-4d64-ae32-f520f8a6dbd1") : public IUnknown
{
public:
JUCE_COMCALL get_Row (__RPC__out int* pRetVal) = 0;
JUCE_COMCALL get_Column (__RPC__out int* pRetVal) = 0;
JUCE_COMCALL get_RowSpan (__RPC__out int* pRetVal) = 0;
JUCE_COMCALL get_ColumnSpan (__RPC__out int* pRetVal) = 0;
JUCE_COMCALL get_ContainingGrid (__RPC__deref_out_opt IRawElementProviderSimple * *pRetVal) = 0;
};
JUCE_COMCLASS (IGridProvider, "b17d6187-0907-464b-a168-0ef17a1572b1") : public IUnknown
{
public:
JUCE_COMCALL GetItem (int row, int column, __RPC__deref_out_opt IRawElementProviderSimple** pRetVal) = 0;
JUCE_COMCALL get_RowCount (__RPC__out int* pRetVal) = 0;
JUCE_COMCALL get_ColumnCount (__RPC__out int* pRetVal) = 0;
};
JUCE_COMCLASS (ITableItemProvider, "b9734fa6-771f-4d78-9c90-2517999349cd") : public IUnknown
{
public:
JUCE_COMCALL GetRowHeaderItems (SAFEARRAY** pRetVal) = 0;
JUCE_COMCALL GetColumnHeaderItems (SAFEARRAY** pRetVal) = 0;
};
JUCE_COMCLASS (ITableProvider, "9c860395-97b3-490a-b52a-858cc22af166") : public IUnknown
{
public:
JUCE_COMCALL GetRowHeaders (SAFEARRAY** pRetVal) = 0;
JUCE_COMCALL GetColumnHeaders (SAFEARRAY** pRetVal) = 0;
JUCE_COMCALL get_RowOrColumnMajor (RowOrColumnMajor* pRetVal) = 0;
};
JUCE_COMCLASS (IInvokeProvider, "54fcb24b-e18e-47a2-b4d3-eccbe77599a2") : public IUnknown
{
public:
JUCE_COMCALL Invoke() = 0;
};
JUCE_COMCLASS (IRangeValueProvider, "36dc7aef-33e6-4691-afe1-2be7274b3d33") : public IUnknown
{
public:
JUCE_COMCALL SetValue (double val) = 0;
JUCE_COMCALL get_Value (__RPC__out double* pRetVal) = 0;
JUCE_COMCALL get_IsReadOnly (__RPC__out BOOL * pRetVal) = 0;
JUCE_COMCALL get_Maximum (__RPC__out double* pRetVal) = 0;
JUCE_COMCALL get_Minimum (__RPC__out double* pRetVal) = 0;
JUCE_COMCALL get_LargeChange (__RPC__out double* pRetVal) = 0;
JUCE_COMCALL get_SmallChange (__RPC__out double* pRetVal) = 0;
};
JUCE_COMCLASS (ISelectionProvider, "fb8b03af-3bdf-48d4-bd36-1a65793be168") : public IUnknown
{
public:
JUCE_COMCALL GetSelection (__RPC__deref_out_opt SAFEARRAY * *pRetVal) = 0;
JUCE_COMCALL get_CanSelectMultiple (__RPC__out BOOL * pRetVal) = 0;
JUCE_COMCALL get_IsSelectionRequired (__RPC__out BOOL * pRetVal) = 0;
};
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;
};
JUCE_COMCLASS (ISelectionItemProvider, "2acad808-b2d4-452d-a407-91ff1ad167b2") : public IUnknown
{
public:
JUCE_COMCALL Select() = 0;
JUCE_COMCALL AddToSelection() = 0;
JUCE_COMCALL RemoveFromSelection() = 0;
JUCE_COMCALL get_IsSelected (__RPC__out BOOL * pRetVal) = 0;
JUCE_COMCALL get_SelectionContainer (__RPC__deref_out_opt IRawElementProviderSimple * *pRetVal) = 0;
};
JUCE_COMCLASS (ITextRangeProvider, "5347ad7b-c355-46f8-aff5-909033582f63") : public IUnknown
{
public:
JUCE_COMCALL Clone (__RPC__deref_out_opt ITextRangeProvider * *pRetVal) = 0;
JUCE_COMCALL Compare (__RPC__in_opt ITextRangeProvider * range, __RPC__out BOOL * pRetVal) = 0;
JUCE_COMCALL CompareEndpoints (TextPatternRangeEndpoint endpoint, __RPC__in_opt ITextRangeProvider * targetRange, TextPatternRangeEndpoint targetEndpoint, __RPC__out int* pRetVal) = 0;
JUCE_COMCALL ExpandToEnclosingUnit (TextUnit unit) = 0;
JUCE_COMCALL FindAttribute (TEXTATTRIBUTEID attributeId, VARIANT val, BOOL backward, __RPC__deref_out_opt ITextRangeProvider * *pRetVal) = 0;
JUCE_COMCALL FindText (__RPC__in BSTR text, BOOL backward, BOOL ignoreCase, __RPC__deref_out_opt ITextRangeProvider * *pRetVal) = 0;
JUCE_COMCALL GetAttributeValue (TEXTATTRIBUTEID attributeId, __RPC__out VARIANT * pRetVal) = 0;
JUCE_COMCALL GetBoundingRectangles (__RPC__deref_out_opt SAFEARRAY * *pRetVal) = 0;
JUCE_COMCALL GetEnclosingElement (__RPC__deref_out_opt IRawElementProviderSimple * *pRetVal) = 0;
JUCE_COMCALL GetText (int maxLength, __RPC__deref_out_opt BSTR* pRetVal) = 0;
JUCE_COMCALL Move (TextUnit unit, int count, __RPC__out int* pRetVal) = 0;
JUCE_COMCALL MoveEndpointByUnit (TextPatternRangeEndpoint endpoint, TextUnit unit, int count, __RPC__out int* pRetVal) = 0;
JUCE_COMCALL MoveEndpointByRange (TextPatternRangeEndpoint endpoint, __RPC__in_opt ITextRangeProvider * targetRange, TextPatternRangeEndpoint targetEndpoint) = 0;
JUCE_COMCALL Select() = 0;
JUCE_COMCALL AddToSelection() = 0;
JUCE_COMCALL RemoveFromSelection() = 0;
JUCE_COMCALL ScrollIntoView (BOOL alignToTop) = 0;
JUCE_COMCALL GetChildren (__RPC__deref_out_opt SAFEARRAY * *pRetVal) = 0;
};
JUCE_COMCLASS (ITextProvider, "3589c92c-63f3-4367-99bb-ada653b77cf2") : public IUnknown
{
public:
JUCE_COMCALL GetSelection (__RPC__deref_out_opt SAFEARRAY * *pRetVal) = 0;
JUCE_COMCALL GetVisibleRanges (__RPC__deref_out_opt SAFEARRAY * *pRetVal) = 0;
JUCE_COMCALL RangeFromChild (__RPC__in_opt IRawElementProviderSimple * childElement, __RPC__deref_out_opt ITextRangeProvider * *pRetVal) = 0;
JUCE_COMCALL RangeFromPoint (UiaPoint point, __RPC__deref_out_opt ITextRangeProvider * *pRetVal) = 0;
JUCE_COMCALL get_DocumentRange (__RPC__deref_out_opt ITextRangeProvider * *pRetVal) = 0;
JUCE_COMCALL get_SupportedTextSelection (__RPC__out SupportedTextSelection * pRetVal) = 0;
};
JUCE_COMCLASS (ITextProvider2, "0dc5e6ed-3e16-4bf1-8f9a-a979878bc195") : public ITextProvider
{
public:
JUCE_COMCALL RangeFromAnnotation (__RPC__in_opt IRawElementProviderSimple * annotationElement, __RPC__deref_out_opt ITextRangeProvider * *pRetVal) = 0;
JUCE_COMCALL GetCaretRange (__RPC__out BOOL * isActive, __RPC__deref_out_opt ITextRangeProvider * *pRetVal) = 0;
};
JUCE_COMCLASS (IToggleProvider, "56d00bd0-c4f4-433c-a836-1a52a57e0892") : public IUnknown
{
public:
JUCE_COMCALL Toggle() = 0;
JUCE_COMCALL get_ToggleState (__RPC__out ToggleState * pRetVal) = 0;
};
JUCE_COMCLASS (ITransformProvider, "6829ddc4-4f91-4ffa-b86f-bd3e2987cb4c") : public IUnknown
{
public:
JUCE_COMCALL Move (double x, double y) = 0;
JUCE_COMCALL Resize (double width, double height) = 0;
JUCE_COMCALL Rotate (double degrees) = 0;
JUCE_COMCALL get_CanMove (__RPC__out BOOL * pRetVal) = 0;
JUCE_COMCALL get_CanResize (__RPC__out BOOL * pRetVal) = 0;
JUCE_COMCALL get_CanRotate (__RPC__out BOOL * pRetVal) = 0;
};
JUCE_COMCLASS (IValueProvider, "c7935180-6fb3-4201-b174-7df73adbf64a") : public IUnknown
{
public:
JUCE_COMCALL SetValue (__RPC__in LPCWSTR val) = 0;
JUCE_COMCALL get_Value (__RPC__deref_out_opt BSTR * pRetVal) = 0;
JUCE_COMCALL get_IsReadOnly (__RPC__out BOOL * pRetVal) = 0;
};
JUCE_COMCLASS (IWindowProvider, "987df77b-db06-4d77-8f8a-86a9c3bb90b9") : public IUnknown
{
public:
JUCE_COMCALL SetVisualState (WindowVisualState state) = 0;
JUCE_COMCALL Close() = 0;
JUCE_COMCALL WaitForInputIdle (int milliseconds, __RPC__out BOOL* pRetVal) = 0;
JUCE_COMCALL get_CanMaximize (__RPC__out BOOL * pRetVal) = 0;
JUCE_COMCALL get_CanMinimize (__RPC__out BOOL * pRetVal) = 0;
JUCE_COMCALL get_IsModal (__RPC__out BOOL * pRetVal) = 0;
JUCE_COMCALL get_WindowVisualState (__RPC__out WindowVisualState * pRetVal) = 0;
JUCE_COMCALL get_WindowInteractionState (__RPC__out WindowInteractionState * pRetVal) = 0;
JUCE_COMCALL get_IsTopmost (__RPC__out BOOL * pRetVal) = 0;
};
JUCE_COMCLASS (IScrollProvider, "b38b8077-1fc3-42a5-8cae-d40c2215055a") : public IUnknown
{
public:
JUCE_COMCALL Scroll (ScrollAmount horizontalAmount, ScrollAmount verticalAmount) = 0;
JUCE_COMCALL SetScrollPercent (double horizontalPercent,double verticalPercent) = 0;
JUCE_COMCALL get_HorizontalScrollPercent (double* pRetVal) = 0;
JUCE_COMCALL get_VerticalScrollPercent (double* pRetVal) = 0;
JUCE_COMCALL get_HorizontalViewSize (double* pRetVal) = 0;
JUCE_COMCALL get_VerticalViewSize (double* pRetVal) = 0;
JUCE_COMCALL get_HorizontallyScrollable (BOOL* pRetVal) = 0;
JUCE_COMCALL get_VerticallyScrollable (BOOL* pRetVal) = 0;
};
JUCE_COMCLASS (IScrollItemProvider, "2360c714-4bf1-4b26-ba65-9b21316127eb") : public IUnknown
{
public:
JUCE_COMCALL ScrollIntoView() = 0;
};
constexpr CLSID CLSID_SpVoice { 0x96749377, 0x3391, 0x11D2, { 0x9E, 0xE3, 0x00, 0xC0, 0x4F, 0x79, 0x73, 0x96 } };
} // namespace juce::ComTypes
#ifdef __CRT_UUID_DECL
__CRT_UUID_DECL (juce::ComTypes::IRawElementProviderFragmentRoot, 0x620ce2a5, 0xab8f, 0x40a9, 0x86, 0xcb, 0xde, 0x3c, 0x75, 0x59, 0x9b, 0x58)
__CRT_UUID_DECL (juce::ComTypes::IRawElementProviderFragment, 0xf7063da8, 0x8359, 0x439c, 0x92, 0x97, 0xbb, 0xc5, 0x29, 0x9a, 0x7d, 0x87)
__CRT_UUID_DECL (juce::ComTypes::IRawElementProviderHwndOverride, 0x1d5df27c, 0x8947, 0x4425, 0xb8, 0xd9, 0x79, 0x78, 0x7b, 0xb4, 0x60, 0xb8)
__CRT_UUID_DECL (juce::ComTypes::IExpandCollapseProvider, 0xd847d3a5, 0xcab0, 0x4a98, 0x8c, 0x32, 0xec, 0xb4, 0x5c, 0x59, 0xad, 0x24)
__CRT_UUID_DECL (juce::ComTypes::IGridItemProvider, 0xd02541f1, 0xfb81, 0x4d64, 0xae, 0x32, 0xf5, 0x20, 0xf8, 0xa6, 0xdb, 0xd1)
__CRT_UUID_DECL (juce::ComTypes::IGridProvider, 0xb17d6187, 0x0907, 0x464b, 0xa1, 0x68, 0x0e, 0xf1, 0x7a, 0x15, 0x72, 0xb1)
__CRT_UUID_DECL (juce::ComTypes::IInvokeProvider, 0x54fcb24b, 0xe18e, 0x47a2, 0xb4, 0xd3, 0xec, 0xcb, 0xe7, 0x75, 0x99, 0xa2)
__CRT_UUID_DECL (juce::ComTypes::IRangeValueProvider, 0x36dc7aef, 0x33e6, 0x4691, 0xaf, 0xe1, 0x2b, 0xe7, 0x27, 0x4b, 0x3d, 0x33)
__CRT_UUID_DECL (juce::ComTypes::ISelectionProvider, 0xfb8b03af, 0x3bdf, 0x48d4, 0xbd, 0x36, 0x1a, 0x65, 0x79, 0x3b, 0xe1, 0x68)
__CRT_UUID_DECL (juce::ComTypes::ISelectionProvider2, 0x14f68475, 0xee1c, 0x44f6, 0xa8, 0x69, 0xd2, 0x39, 0x38, 0x1f, 0x0f, 0xe7)
__CRT_UUID_DECL (juce::ComTypes::ISelectionItemProvider, 0x2acad808, 0xb2d4, 0x452d, 0xa4, 0x07, 0x91, 0xff, 0x1a, 0xd1, 0x67, 0xb2)
__CRT_UUID_DECL (juce::ComTypes::ITextRangeProvider, 0x5347ad7b, 0xc355, 0x46f8, 0xaf, 0xf5, 0x90, 0x90, 0x33, 0x58, 0x2f, 0x63)
__CRT_UUID_DECL (juce::ComTypes::ITextProvider, 0x3589c92c, 0x63f3, 0x4367, 0x99, 0xbb, 0xad, 0xa6, 0x53, 0xb7, 0x7c, 0xf2)
__CRT_UUID_DECL (juce::ComTypes::ITextProvider2, 0x0dc5e6ed, 0x3e16, 0x4bf1, 0x8f, 0x9a, 0xa9, 0x79, 0x87, 0x8b, 0xc1, 0x95)
__CRT_UUID_DECL (juce::ComTypes::IToggleProvider, 0x56d00bd0, 0xc4f4, 0x433c, 0xa8, 0x36, 0x1a, 0x52, 0xa5, 0x7e, 0x08, 0x92)
__CRT_UUID_DECL (juce::ComTypes::ITransformProvider, 0x6829ddc4, 0x4f91, 0x4ffa, 0xb8, 0x6f, 0xbd, 0x3e, 0x29, 0x87, 0xcb, 0x4c)
__CRT_UUID_DECL (juce::ComTypes::IValueProvider, 0xc7935180, 0x6fb3, 0x4201, 0xb1, 0x74, 0x7d, 0xf7, 0x3a, 0xdb, 0xf6, 0x4a)
__CRT_UUID_DECL (juce::ComTypes::IWindowProvider, 0x987df77b, 0xdb06, 0x4d77, 0x8f, 0x8a, 0x86, 0xa9, 0xc3, 0xbb, 0x90, 0xb9)
__CRT_UUID_DECL (juce::ComTypes::ITableItemProvider, 0xb9734fa6, 0x771f, 0x4d78, 0x9c, 0x90, 0x25, 0x17, 0x99, 0x93, 0x49, 0xcd)
__CRT_UUID_DECL (juce::ComTypes::ITableProvider, 0x9c860395, 0x97b3, 0x490a, 0xb5, 0x2a, 0x85, 0x8c, 0xc2, 0x2a, 0xf1, 0x66)
__CRT_UUID_DECL (juce::ComTypes::IScrollProvider, 0xb38b8077, 0x1fc3, 0x42a5, 0x8c, 0xae, 0xd4, 0x0c, 0x22, 0x15, 0x05, 0x5a)
__CRT_UUID_DECL (juce::ComTypes::IScrollItemProvider, 0x2360c714, 0x4bf1, 0x4b26, 0xba, 0x65, 0x9b, 0x21, 0x31, 0x61, 0x27, 0xeb)
#endif
@@ -0,0 +1,85 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class UIAExpandCollapseProvider : public UIAProviderBase,
public ComBaseClassHelper<ComTypes::IExpandCollapseProvider>
{
public:
using UIAProviderBase::UIAProviderBase;
//==============================================================================
JUCE_COMRESULT Expand() override
{
return invokeShowMenu();
}
JUCE_COMRESULT Collapse() override
{
return invokeShowMenu();
}
JUCE_COMRESULT get_ExpandCollapseState (ComTypes::ExpandCollapseState* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
*pRetVal = getHandler().getCurrentState().isExpanded()
? ComTypes::ExpandCollapseState_Expanded
: ComTypes::ExpandCollapseState_Collapsed;
return S_OK;
});
}
private:
JUCE_COMRESULT invokeShowMenu()
{
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
const auto& handler = getHandler();
if (handler.getActions().invoke (AccessibilityActionType::showMenu))
{
using namespace ComTypes::Constants;
sendAccessibilityAutomationEvent (handler, handler.getCurrentState().isExpanded()
? UIA_MenuOpenedEventId
: UIA_MenuClosedEventId);
return S_OK;
}
return (HRESULT) UIA_E_NOTSUPPORTED;
}
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAExpandCollapseProvider)
};
} // namespace juce
@@ -0,0 +1,153 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class UIAGridItemProvider : public UIAProviderBase,
public ComBaseClassHelper<ComTypes::IGridItemProvider, ComTypes::ITableItemProvider>
{
public:
using UIAProviderBase::UIAProviderBase;
//==============================================================================
JUCE_COMRESULT get_Row (int* pRetVal) override
{
return withTableSpan (pRetVal,
&AccessibilityTableInterface::getRowSpan,
&AccessibilityTableInterface::Span::begin);
}
JUCE_COMRESULT get_Column (int* pRetVal) override
{
return withTableSpan (pRetVal,
&AccessibilityTableInterface::getColumnSpan,
&AccessibilityTableInterface::Span::begin);
}
JUCE_COMRESULT get_RowSpan (int* pRetVal) override
{
return withTableSpan (pRetVal,
&AccessibilityTableInterface::getRowSpan,
&AccessibilityTableInterface::Span::num);
}
JUCE_COMRESULT get_ColumnSpan (int* pRetVal) override
{
return withTableSpan (pRetVal,
&AccessibilityTableInterface::getColumnSpan,
&AccessibilityTableInterface::Span::num);
}
JUCE_COMRESULT get_ContainingGrid (IRawElementProviderSimple** pRetVal) override
{
return withTableInterface (pRetVal, [&] (const AccessibilityHandler& tableHandler)
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
tableHandler.getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
return true;
});
}
JUCE_COMRESULT GetRowHeaderItems (SAFEARRAY**) override
{
return (HRESULT) UIA_E_NOTSUPPORTED;
}
JUCE_COMRESULT GetColumnHeaderItems (SAFEARRAY** pRetVal) override
{
return withTableInterface (pRetVal, [&] (const AccessibilityHandler& tableHandler)
{
if (auto* tableInterface = tableHandler.getTableInterface())
{
if (const auto column = tableInterface->getColumnSpan (getHandler()))
{
if (auto* header = tableInterface->getHeaderHandler())
{
const auto children = header->getChildren();
if (isPositiveAndBelow (column->begin, children.size()))
{
ComSmartPtr<IRawElementProviderSimple> provider;
if (auto* child = children[(size_t) column->begin])
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
if (child->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress())) == S_OK && provider != nullptr)
{
*pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 1);
LONG index = 0;
const auto hr = SafeArrayPutElement (*pRetVal, &index, provider);
return ! FAILED (hr);
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
}
}
}
}
return false;
});
}
private:
template <typename Value, typename Callback>
JUCE_COMRESULT withTableInterface (Value* pRetVal, Callback&& callback) const
{
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
{
if (auto* handler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (&getHandler(), &AccessibilityHandler::getTableInterface))
if (handler->getTableInterface() != nullptr && callback (*handler))
return S_OK;
return (HRESULT) UIA_E_NOTSUPPORTED;
});
}
JUCE_COMRESULT withTableSpan (int* pRetVal,
Optional<AccessibilityTableInterface::Span> (AccessibilityTableInterface::* getSpan) (const AccessibilityHandler&) const,
int AccessibilityTableInterface::Span::* spanMember) const
{
return withTableInterface (pRetVal, [&] (const AccessibilityHandler& handler)
{
if (const auto span = ((handler.getTableInterface())->*getSpan) (getHandler()))
{
*pRetVal = (*span).*spanMember;
return true;
}
return false;
});
}
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAGridItemProvider)
};
} // namespace juce
@@ -0,0 +1,151 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class UIAGridProvider : public UIAProviderBase,
public ComBaseClassHelper<ComTypes::IGridProvider, ComTypes::ITableProvider>
{
public:
using UIAProviderBase::UIAProviderBase;
//==============================================================================
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;
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
if (auto* cellHandler = tableInterface.getCellHandler (row, column))
{
cellHandler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
return S_OK;
}
if (auto* rowHandler = tableInterface.getRowHandler (row))
{
rowHandler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
return S_OK;
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
return E_FAIL;
});
}
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;
});
}
JUCE_COMRESULT GetRowHeaders (SAFEARRAY**) override
{
return (HRESULT) UIA_E_NOTSUPPORTED;
}
JUCE_COMRESULT GetColumnHeaders (SAFEARRAY** pRetVal) override
{
return withTableInterface (pRetVal, [&] (const AccessibilityTableInterface& tableInterface)
{
if (auto* header = tableInterface.getHeaderHandler())
{
const auto children = header->getChildren();
*pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, (ULONG) children.size());
LONG index = 0;
for (const auto& child : children)
{
ComSmartPtr<IRawElementProviderSimple> provider;
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
if (child != nullptr)
child->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
if (provider == nullptr)
return E_FAIL;
const auto hr = SafeArrayPutElement (*pRetVal, &index, provider);
if (FAILED (hr))
return E_FAIL;
++index;
}
return S_OK;
}
return (HRESULT) UIA_E_NOTSUPPORTED;
});
}
JUCE_COMRESULT get_RowOrColumnMajor (ComTypes::RowOrColumnMajor* pRetVal) override
{
*pRetVal = ComTypes::RowOrColumnMajor_RowMajor;
return S_OK;
}
private:
template <typename Value, typename Callback>
JUCE_COMRESULT withTableInterface (Value* pRetVal, Callback&& callback) const
{
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
{
if (auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (&getHandler(), &AccessibilityHandler::getTableInterface))
if (auto* tableInterface = tableHandler->getTableInterface())
return callback (*tableInterface);
return (HRESULT) UIA_E_NOTSUPPORTED;
});
}
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAGridProvider)
};
} // namespace juce
@@ -0,0 +1,119 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
namespace VariantHelpers
{
namespace Detail
{
template <typename Fn, typename ValueType>
inline VARIANT getWithValueGeneric (Fn&& setter, ValueType value)
{
VARIANT result{};
setter (value, &result);
return result;
}
}
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;
}
inline VARIANT getWithValue (double value) { return Detail::getWithValueGeneric (&setDouble, value); }
inline VARIANT getWithValue (const String& value) { return Detail::getWithValueGeneric (&setString, value); }
}
inline 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[(size_t) i];
if (handler == nullptr)
continue;
ComSmartPtr<IRawElementProviderSimple> provider;
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
auto hr = SafeArrayPutElement (*pRetVal, &i, provider);
if (FAILED (hr))
return E_FAIL;
}
}
return S_OK;
}
template <typename Value, typename Object, typename Callback>
inline JUCE_COMRESULT withCheckedComArgs (Value* pRetVal, Object& handle, Callback&& callback)
{
if (pRetVal == nullptr)
return E_INVALIDARG;
*pRetVal = Value{};
if (! handle.isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
return callback();
}
} // namespace juce
@@ -0,0 +1,61 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class UIAInvokeProvider : public UIAProviderBase,
public ComBaseClassHelper<ComTypes::IInvokeProvider>
{
public:
using UIAProviderBase::UIAProviderBase;
//==============================================================================
JUCE_COMRESULT Invoke() override
{
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
const auto& handler = getHandler();
if (handler.getActions().invoke (AccessibilityActionType::press))
{
using namespace ComTypes::Constants;
if (isElementValid())
sendAccessibilityAutomationEvent (handler, UIA_Invoke_InvokedEventId);
return S_OK;
}
return (HRESULT) 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) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
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) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
void sendAccessibilityAutomationEvent (const AccessibilityHandler&, EVENTID);
void sendAccessibilityPropertyChangedEvent (const AccessibilityHandler&, PROPERTYID, VARIANT);
} // namespace juce
#include "juce_UIAProviderBase_windows.h"
#include "juce_UIAExpandCollapseProvider_windows.h"
#include "juce_UIAGridItemProvider_windows.h"
#include "juce_UIAGridProvider_windows.h"
#include "juce_UIAInvokeProvider_windows.h"
#include "juce_UIARangeValueProvider_windows.h"
#include "juce_UIASelectionProvider_windows.h"
#include "juce_UIATextProvider_windows.h"
#include "juce_UIAToggleProvider_windows.h"
#include "juce_UIATransformProvider_windows.h"
#include "juce_UIAValueProvider_windows.h"
#include "juce_UIAWindowProvider_windows.h"
@@ -0,0 +1,137 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class UIARangeValueProvider : public UIAProviderBase,
public ComBaseClassHelper<ComTypes::IRangeValueProvider>
{
public:
using UIAProviderBase::UIAProviderBase;
//==============================================================================
JUCE_COMRESULT SetValue (double val) override
{
if (! isElementValid())
return (HRESULT) 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 (HRESULT) 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 (HRESULT) UIA_E_NOTSUPPORTED;
});
}
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIARangeValueProvider)
};
} // namespace juce
@@ -0,0 +1,247 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
//==============================================================================
class UIASelectionItemProvider : public UIAProviderBase,
public ComBaseClassHelper<ComTypes::ISelectionItemProvider>
{
public:
explicit UIASelectionItemProvider (AccessibilityNativeHandle* handle)
: UIAProviderBase (handle),
isRadioButton (getHandler().getRole() == AccessibilityRole::radioButton)
{
}
//==============================================================================
JUCE_COMRESULT AddToSelection() override
{
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
const auto& handler = getHandler();
if (isRadioButton)
{
using namespace ComTypes::Constants;
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 (HRESULT) 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 (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
AddToSelection();
if (isElementValid() && ! 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<ComTypes::ISelectionProvider2>
{
public:
using UIAProviderBase::UIAProviderBase;
//==============================================================================
JUCE_COMRESULT QueryInterface (REFIID iid, void** result) override
{
if (iid == __uuidof (IUnknown) || iid == __uuidof (ComTypes::ISelectionProvider))
return castToType<ComTypes::ISelectionProvider> (result);
if (iid == __uuidof (ComTypes::ISelectionProvider2))
return castToType<ComTypes::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)
};
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
} // namespace juce
@@ -0,0 +1,619 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class UIATextProvider : public UIAProviderBase,
public ComBaseClassHelper<ComTypes::ITextProvider2>
{
public:
using UIAProviderBase::UIAProviderBase;
//==============================================================================
JUCE_COMRESULT QueryInterface (REFIID iid, void** result) override
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
if (iid == __uuidof (IUnknown) || iid == __uuidof (ComTypes::ITextProvider))
return castToType<ComTypes::ITextProvider> (result);
if (iid == __uuidof (ComTypes::ITextProvider2))
return castToType<ComTypes::ITextProvider2> (result);
*result = nullptr;
return E_NOINTERFACE;
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
//==============================================================================
JUCE_COMRESULT get_DocumentRange (ComTypes::ITextRangeProvider** pRetVal) override
{
return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
{
*pRetVal = new UIATextRangeProvider (*this, { 0, textInterface.getTotalNumCharacters() });
return S_OK;
});
}
JUCE_COMRESULT get_SupportedTextSelection (ComTypes::SupportedTextSelection* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
*pRetVal = ComTypes::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*, ComTypes::ITextRangeProvider** pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, []
{
return S_OK;
});
}
JUCE_COMRESULT RangeFromPoint (ComTypes::UiaPoint point, ComTypes::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, ComTypes::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*, ComTypes::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 (HRESULT) UIA_E_NOTSUPPORTED;
});
}
//==============================================================================
class UIATextRangeProvider : public UIAProviderBase,
public ComBaseClassHelper<ComTypes::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 (ComTypes::ITextRangeProvider** pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
*pRetVal = new UIATextRangeProvider (*owner, selectionRange);
return S_OK;
});
}
JUCE_COMRESULT Compare (ComTypes::ITextRangeProvider* range, BOOL* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
*pRetVal = (selectionRange == static_cast<UIATextRangeProvider*> (range)->getSelectionRange());
return S_OK;
});
}
JUCE_COMRESULT CompareEndpoints (ComTypes::TextPatternRangeEndpoint endpoint,
ComTypes::ITextRangeProvider* targetRange,
ComTypes::TextPatternRangeEndpoint targetEndpoint,
int* pRetVal) override
{
if (targetRange == nullptr)
return E_INVALIDARG;
return withCheckedComArgs (pRetVal, *this, [&]
{
auto offset = (endpoint == ComTypes::TextPatternRangeEndpoint_Start ? selectionRange.getStart()
: selectionRange.getEnd());
auto otherRange = static_cast<UIATextRangeProvider*> (targetRange)->getSelectionRange();
auto otherOffset = (targetEndpoint == ComTypes::TextPatternRangeEndpoint_Start ? otherRange.getStart()
: otherRange.getEnd());
*pRetVal = offset - otherOffset;
return S_OK;
});
}
JUCE_COMRESULT ExpandToEnclosingUnit (ComTypes::TextUnit unit) override
{
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
if (auto* textInterface = owner->getHandler().getTextInterface())
{
using ATH = AccessibilityTextHelpers;
const auto boundaryType = getBoundaryType (unit);
const auto start = ATH::findTextBoundary (*textInterface,
selectionRange.getStart(),
boundaryType,
ATH::Direction::backwards,
ATH::IncludeThisBoundary::yes,
ATH::IncludeWhitespaceAfterWords::no);
const auto end = ATH::findTextBoundary (*textInterface,
start,
boundaryType,
ATH::Direction::forwards,
ATH::IncludeThisBoundary::no,
ATH::IncludeWhitespaceAfterWords::yes);
selectionRange = Range<int> (start, end);
return S_OK;
}
return (HRESULT) UIA_E_NOTSUPPORTED;
}
JUCE_COMRESULT FindAttribute (TEXTATTRIBUTEID, VARIANT, BOOL, ComTypes::ITextRangeProvider** pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, []
{
return S_OK;
});
}
JUCE_COMRESULT FindText (BSTR text, BOOL backward, BOOL ignoreCase,
ComTypes::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);
using namespace ComTypes::Constants;
switch (attributeId)
{
case UIA_IsReadOnlyAttributeId:
{
VariantHelpers::setBool (textInterface.isReadOnly(), pRetVal);
break;
}
case UIA_CaretPositionAttributeId:
{
auto cursorPos = textInterface.getTextInsertionOffset();
auto caretPos = [&]
{
if (cursorPos == 0)
return ComTypes::CaretPosition_BeginningOfLine;
if (cursorPos == textInterface.getTotalNumCharacters())
return ComTypes::CaretPosition_EndOfLine;
return ComTypes::CaretPosition_Unknown;
}();
VariantHelpers::setInt (caretPos, pRetVal);
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 * (ULONG) 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
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
return withCheckedComArgs (pRetVal, *this, [&]
{
getHandler().getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
return S_OK;
});
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
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 (ComTypes::TextUnit unit, int count, int* pRetVal) override
{
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
{
using ATH = AccessibilityTextHelpers;
const auto boundaryType = getBoundaryType (unit);
const auto previousUnitBoundary = ATH::findTextBoundary (textInterface,
selectionRange.getStart(),
boundaryType,
ATH::Direction::backwards,
ATH::IncludeThisBoundary::yes,
ATH::IncludeWhitespaceAfterWords::no);
auto numMoved = 0;
auto movedEndpoint = previousUnitBoundary;
for (; numMoved < std::abs (count); ++numMoved)
{
const auto nextEndpoint = ATH::findTextBoundary (textInterface,
movedEndpoint,
boundaryType,
count > 0 ? ATH::Direction::forwards : ATH::Direction::backwards,
ATH::IncludeThisBoundary::no,
count > 0 ? ATH::IncludeWhitespaceAfterWords::yes : ATH::IncludeWhitespaceAfterWords::no);
if (nextEndpoint == movedEndpoint)
break;
movedEndpoint = nextEndpoint;
}
*pRetVal = numMoved;
ExpandToEnclosingUnit (unit);
return S_OK;
});
}
JUCE_COMRESULT MoveEndpointByRange (ComTypes::TextPatternRangeEndpoint endpoint,
ComTypes::ITextRangeProvider* targetRange,
ComTypes::TextPatternRangeEndpoint targetEndpoint) override
{
if (targetRange == nullptr)
return E_INVALIDARG;
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
if (owner->getHandler().getTextInterface() != nullptr)
{
auto otherRange = static_cast<UIATextRangeProvider*> (targetRange)->getSelectionRange();
auto targetPoint = (targetEndpoint == ComTypes::TextPatternRangeEndpoint_Start ? otherRange.getStart()
: otherRange.getEnd());
setEndpointChecked (endpoint, targetPoint);
return S_OK;
}
return (HRESULT) UIA_E_NOTSUPPORTED;
}
JUCE_COMRESULT MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint endpoint,
ComTypes::TextUnit unit,
int count,
int* pRetVal) override
{
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
{
if (count == 0 || textInterface.getTotalNumCharacters() == 0)
return S_OK;
const auto endpointToMove = (endpoint == ComTypes::TextPatternRangeEndpoint_Start ? selectionRange.getStart()
: selectionRange.getEnd());
using ATH = AccessibilityTextHelpers;
const auto direction = (count > 0 ? ATH::Direction::forwards
: ATH::Direction::backwards);
const auto boundaryType = getBoundaryType (unit);
auto movedEndpoint = endpointToMove;
int numMoved = 0;
for (; numMoved < std::abs (count); ++numMoved)
{
auto nextEndpoint = ATH::findTextBoundary (textInterface,
movedEndpoint,
boundaryType,
direction,
ATH::IncludeThisBoundary::no,
direction == ATH::Direction::forwards ? ATH::IncludeWhitespaceAfterWords::yes
: ATH::IncludeWhitespaceAfterWords::no);
if (nextEndpoint == movedEndpoint)
break;
movedEndpoint = nextEndpoint;
}
*pRetVal = numMoved;
setEndpointChecked (endpoint, movedEndpoint);
return S_OK;
});
}
JUCE_COMRESULT RemoveFromSelection() override
{
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
if (auto* textInterface = owner->getHandler().getTextInterface())
{
textInterface->setSelection ({});
return S_OK;
}
return (HRESULT) UIA_E_NOTSUPPORTED;
}
JUCE_COMRESULT ScrollIntoView (BOOL) override
{
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
return (HRESULT) UIA_E_NOTSUPPORTED;
}
JUCE_COMRESULT Select() override
{
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
if (auto* textInterface = owner->getHandler().getTextInterface())
{
textInterface->setSelection ({});
textInterface->setSelection (selectionRange);
return S_OK;
}
return (HRESULT) UIA_E_NOTSUPPORTED;
}
private:
static AccessibilityTextHelpers::BoundaryType getBoundaryType (ComTypes::TextUnit unit)
{
switch (unit)
{
case ComTypes::TextUnit_Character:
return AccessibilityTextHelpers::BoundaryType::character;
case ComTypes::TextUnit_Format:
case ComTypes::TextUnit_Word:
return AccessibilityTextHelpers::BoundaryType::word;
case ComTypes::TextUnit_Line:
return AccessibilityTextHelpers::BoundaryType::line;
case ComTypes::TextUnit_Paragraph:
case ComTypes::TextUnit_Page:
case ComTypes::TextUnit_Document:
return AccessibilityTextHelpers::BoundaryType::document;
};
jassertfalse;
return AccessibilityTextHelpers::BoundaryType::character;
}
void setEndpointChecked (ComTypes::TextPatternRangeEndpoint endpoint, int newEndpoint)
{
if (endpoint == ComTypes::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,78 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class UIAToggleProvider : public UIAProviderBase,
public ComBaseClassHelper<ComTypes::IToggleProvider>
{
public:
using UIAProviderBase::UIAProviderBase;
//==============================================================================
JUCE_COMRESULT Toggle() override
{
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
const auto& handler = getHandler();
if (handler.getActions().invoke (AccessibilityActionType::toggle)
|| handler.getActions().invoke (AccessibilityActionType::press))
{
VARIANT newValue;
VariantHelpers::setInt (getCurrentToggleState(), &newValue);
sendAccessibilityPropertyChangedEvent (handler, UIA_ToggleToggleStatePropertyId, newValue);
return S_OK;
}
return (HRESULT) UIA_E_NOTSUPPORTED;
}
JUCE_COMRESULT get_ToggleState (ComTypes::ToggleState* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
*pRetVal = getCurrentToggleState();
return S_OK;
});
}
private:
ComTypes::ToggleState getCurrentToggleState() const
{
return getHandler().getCurrentState().isChecked() ? ComTypes::ToggleState_On
: ComTypes::ToggleState_Off;
}
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAToggleProvider)
};
} // namespace juce
@@ -0,0 +1,122 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class UIATransformProvider : public UIAProviderBase,
public ComBaseClassHelper<ComTypes::ITransformProvider>
{
public:
using UIAProviderBase::UIAProviderBase;
//==============================================================================
JUCE_COMRESULT Move (double x, double y) override
{
if (! isElementValid())
return (HRESULT) 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 (HRESULT) 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 (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
return (HRESULT) 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,83 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class UIAValueProvider : public UIAProviderBase,
public ComBaseClassHelper<ComTypes::IValueProvider>
{
public:
using UIAProviderBase::UIAProviderBase;
//==============================================================================
JUCE_COMRESULT SetValue (LPCWSTR val) override
{
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
const auto& handler = getHandler();
auto& valueInterface = *handler.getValueInterface();
if (valueInterface.isReadOnly())
return (HRESULT) UIA_E_NOTSUPPORTED;
valueInterface.setValueAsString (String (val));
VARIANT newValue;
VariantHelpers::setString (valueInterface.getCurrentValueAsString(), &newValue);
sendAccessibilityPropertyChangedEvent (handler, UIA_ValueValuePropertyId, newValue);
return S_OK;
}
JUCE_COMRESULT get_Value (BSTR* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
auto currentValueString = getHandler().getValueInterface()->getCurrentValueAsString();
*pRetVal = SysAllocString ((const OLECHAR*) currentValueString.toWideCharPointer());
return S_OK;
});
}
JUCE_COMRESULT get_IsReadOnly (BOOL* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
*pRetVal = getHandler().getValueInterface()->isReadOnly();
return S_OK;
});
}
private:
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAValueProvider)
};
} // namespace juce
@@ -0,0 +1,194 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class UIAWindowProvider : public UIAProviderBase,
public ComBaseClassHelper<ComTypes::IWindowProvider>
{
public:
using UIAProviderBase::UIAProviderBase;
//==============================================================================
JUCE_COMRESULT SetVisualState (ComTypes::WindowVisualState state) override
{
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
if (auto* peer = getPeer())
{
switch (state)
{
case ComTypes::WindowVisualState_Maximized:
peer->setFullScreen (true);
break;
case ComTypes::WindowVisualState_Minimized:
peer->setMinimised (true);
break;
case ComTypes::WindowVisualState_Normal:
peer->setFullScreen (false);
peer->setMinimised (false);
break;
default:
break;
}
return S_OK;
}
return (HRESULT) UIA_E_NOTSUPPORTED;
}
JUCE_COMRESULT Close() override
{
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
if (auto* peer = getPeer())
{
peer->handleUserClosingWindow();
return S_OK;
}
return (HRESULT) UIA_E_NOTSUPPORTED;
}
JUCE_COMRESULT WaitForInputIdle (int, BOOL* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, []
{
return (HRESULT) 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 (HRESULT) 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 (HRESULT) 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 (HRESULT) UIA_E_NOTSUPPORTED;
});
}
JUCE_COMRESULT get_WindowVisualState (ComTypes::WindowVisualState* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
{
if (auto* peer = getPeer())
{
if (peer->isFullScreen())
*pRetVal = ComTypes::WindowVisualState_Maximized;
else if (peer->isMinimised())
*pRetVal = ComTypes::WindowVisualState_Minimized;
else
*pRetVal = ComTypes::WindowVisualState_Normal;
return S_OK;
}
return (HRESULT) UIA_E_NOTSUPPORTED;
});
}
JUCE_COMRESULT get_WindowInteractionState (ComTypes::WindowInteractionState* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
{
if (auto* peer = getPeer())
{
*pRetVal = peer->getComponent().isCurrentlyBlockedByAnotherModalComponent()
? ComTypes::WindowInteractionState::WindowInteractionState_BlockedByModalWindow
: ComTypes::WindowInteractionState::WindowInteractionState_Running;
return S_OK;
}
return (HRESULT) 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 (HRESULT) UIA_E_NOTSUPPORTED;
});
}
private:
ComponentPeer* getPeer() const
{
return getHandler().getComponent().getPeer();
}
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAWindowProvider)
};
} // namespace juce
@@ -0,0 +1,160 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
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)
: (HRESULT) UIA_E_NOTSUPPORTED;
}
JUCE_COMRESULT raiseAutomationPropertyChangedEvent (IRawElementProviderSimple* provider, PROPERTYID propID, VARIANT oldValue, VARIANT newValue)
{
return uiaRaiseAutomationPropertyChangedEvent != nullptr ? uiaRaiseAutomationPropertyChangedEvent (provider, propID, oldValue, newValue)
: (HRESULT) UIA_E_NOTSUPPORTED;
}
JUCE_COMRESULT raiseAutomationEvent (IRawElementProviderSimple* provider, EVENTID eventID)
{
return uiaRaiseAutomationEvent != nullptr ? uiaRaiseAutomationEvent (provider, eventID)
: (HRESULT) 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 (HRESULT) UIA_E_NOTSUPPORTED;
}
JUCE_COMRESULT disconnectAllProviders()
{
if (uiaDisconnectAllProviders != nullptr)
{
const ScopedValueSetter<bool> disconnectingAllProvidersSetter (disconnectingAllProviders, true);
return uiaDisconnectAllProviders();
}
return (HRESULT) 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, LPCSTR funcName)
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wcast-function-type")
return (FuncType) GetProcAddress (module, funcName);
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
//==============================================================================
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