git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce

subrepo:
  subdir:   "deps/juce"
  merged:   "b13f9084e"
upstream:
  origin:   "https://github.com/essej/JUCE.git"
  branch:   "sono6good"
  commit:   "b13f9084e"
git-subrepo:
  version:  "0.4.3"
  origin:   "https://github.com/ingydotnet/git-subrepo.git"
  commit:   "2f68596"
This commit is contained in:
essej
2022-04-18 17:51:22 -04:00
parent 63e175fee6
commit 25bd5d8adb
3210 changed files with 1045392 additions and 0 deletions

View File

@ -0,0 +1,111 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
namespace AccessibilityTextHelpers
{
enum class BoundaryType
{
character,
word,
line,
document
};
enum class Direction
{
forwards,
backwards
};
static int findTextBoundary (const AccessibilityTextInterface& textInterface,
int currentPosition,
BoundaryType boundary,
Direction direction)
{
const auto numCharacters = textInterface.getTotalNumCharacters();
const auto isForwards = (direction == Direction::forwards);
auto offsetWithDirecton = [isForwards] (int input) { return isForwards ? input : -input; };
switch (boundary)
{
case BoundaryType::character:
return jlimit (0, numCharacters, currentPosition + offsetWithDirecton (1));
case BoundaryType::word:
case BoundaryType::line:
{
const auto text = [&]() -> String
{
if (isForwards)
return textInterface.getText ({ currentPosition, textInterface.getTotalNumCharacters() });
const auto str = textInterface.getText ({ 0, currentPosition });
auto start = str.getCharPointer();
auto end = start.findTerminatingNull();
const auto size = getAddressDifference (end.getAddress(), start.getAddress());
String reversed;
if (size > 0)
{
reversed.preallocateBytes ((size_t) size);
auto destPtr = reversed.getCharPointer();
for (;;)
{
destPtr.write (*--end);
if (end == start)
break;
}
destPtr.writeNull();
}
return reversed;
}();
auto tokens = (boundary == BoundaryType::line ? StringArray::fromLines (text)
: StringArray::fromTokens (text, false));
return currentPosition + offsetWithDirecton (tokens[0].length());
}
case BoundaryType::document:
return isForwards ? numCharacters : 0;
}
jassertfalse;
return -1;
}
}
} // namespace juce

View File

@ -0,0 +1,918 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (constructor, "<init>", "()V") \
METHOD (setSource, "setSource", "(Landroid/view/View;I)V") \
METHOD (addChild, "addChild", "(Landroid/view/View;I)V") \
METHOD (setParent, "setParent", "(Landroid/view/View;)V") \
METHOD (setVirtualParent, "setParent", "(Landroid/view/View;I)V") \
METHOD (setBoundsInScreen, "setBoundsInScreen", "(Landroid/graphics/Rect;)V") \
METHOD (setBoundsInParent, "setBoundsInParent", "(Landroid/graphics/Rect;)V") \
METHOD (setPackageName, "setPackageName", "(Ljava/lang/CharSequence;)V") \
METHOD (setClassName, "setClassName", "(Ljava/lang/CharSequence;)V") \
METHOD (setContentDescription, "setContentDescription", "(Ljava/lang/CharSequence;)V") \
METHOD (setCheckable, "setCheckable", "(Z)V") \
METHOD (setChecked, "setChecked", "(Z)V") \
METHOD (setClickable, "setClickable", "(Z)V") \
METHOD (setEnabled, "setEnabled", "(Z)V") \
METHOD (setFocusable, "setFocusable", "(Z)V") \
METHOD (setFocused, "setFocused", "(Z)V") \
METHOD (setPassword, "setPassword", "(Z)V") \
METHOD (setSelected, "setSelected", "(Z)V") \
METHOD (setVisibleToUser, "setVisibleToUser", "(Z)V") \
METHOD (setAccessibilityFocused, "setAccessibilityFocused", "(Z)V") \
METHOD (setText, "setText", "(Ljava/lang/CharSequence;)V") \
METHOD (setMovementGranularities, "setMovementGranularities", "(I)V") \
METHOD (addAction, "addAction", "(I)V") \
DECLARE_JNI_CLASS (AndroidAccessibilityNodeInfo, "android/view/accessibility/AccessibilityNodeInfo")
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
STATICMETHOD (obtain, "obtain", "(I)Landroid/view/accessibility/AccessibilityEvent;") \
METHOD (setPackageName, "setPackageName", "(Ljava/lang/CharSequence;)V") \
METHOD (setSource, "setSource", "(Landroid/view/View;I)V") \
DECLARE_JNI_CLASS (AndroidAccessibilityEvent, "android/view/accessibility/AccessibilityEvent")
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (isEnabled, "isEnabled", "()Z") \
DECLARE_JNI_CLASS (AndroidAccessibilityManager, "android/view/accessibility/AccessibilityManager")
#undef JNI_CLASS_MEMBERS
namespace
{
constexpr int HOST_VIEW_ID = -1;
constexpr int TYPE_VIEW_CLICKED = 0x00000001,
TYPE_VIEW_SELECTED = 0x00000004,
TYPE_VIEW_ACCESSIBILITY_FOCUSED = 0x00008000,
TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 0x00010000,
TYPE_WINDOW_CONTENT_CHANGED = 0x00000800,
TYPE_VIEW_TEXT_SELECTION_CHANGED = 0x00002000,
TYPE_VIEW_TEXT_CHANGED = 0x00000010;
constexpr int CONTENT_CHANGE_TYPE_SUBTREE = 0x00000001,
CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;
constexpr int ACTION_ACCESSIBILITY_FOCUS = 0x00000040,
ACTION_CLEAR_ACCESSIBILITY_FOCUS = 0x00000080,
ACTION_CLEAR_FOCUS = 0x00000002,
ACTION_CLEAR_SELECTION = 0x00000008,
ACTION_CLICK = 0x00000010,
ACTION_COLLAPSE = 0x00080000,
ACTION_EXPAND = 0x00040000,
ACTION_FOCUS = 0x00000001,
ACTION_NEXT_AT_MOVEMENT_GRANULARITY = 0x00000100,
ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 0x00000200,
ACTION_SCROLL_BACKWARD = 0x00002000,
ACTION_SCROLL_FORWARD = 0x00001000,
ACTION_SELECT = 0x00000004,
ACTION_SET_SELECTION = 0x00020000,
ACTION_SET_TEXT = 0x00200000;
constexpr int MOVEMENT_GRANULARITY_CHARACTER = 0x00000001,
MOVEMENT_GRANULARITY_LINE = 0x00000004,
MOVEMENT_GRANULARITY_PAGE = 0x00000010,
MOVEMENT_GRANULARITY_PARAGRAPH = 0x00000008,
MOVEMENT_GRANULARITY_WORD = 0x00000002,
ALL_GRANULARITIES = MOVEMENT_GRANULARITY_CHARACTER
| MOVEMENT_GRANULARITY_LINE
| MOVEMENT_GRANULARITY_PAGE
| MOVEMENT_GRANULARITY_PARAGRAPH
| MOVEMENT_GRANULARITY_WORD;
constexpr int ACCESSIBILITY_LIVE_REGION_POLITE = 0x00000001;
}
static jmethodID nodeInfoSetEditable = nullptr;
static jmethodID nodeInfoSetTextSelection = nullptr;
static jmethodID nodeInfoSetLiveRegion = nullptr;
static jmethodID accessibilityEventSetContentChangeTypes = nullptr;
static void loadSDKDependentMethods()
{
static bool hasChecked = false;
if (! hasChecked)
{
hasChecked = true;
auto* env = getEnv();
const auto sdkVersion = getAndroidSDKVersion();
if (sdkVersion >= 18)
{
nodeInfoSetEditable = env->GetMethodID (AndroidAccessibilityNodeInfo, "setEditable", "(Z)V");
nodeInfoSetTextSelection = env->GetMethodID (AndroidAccessibilityNodeInfo, "setTextSelection", "(II)V");
}
if (sdkVersion >= 19)
{
nodeInfoSetLiveRegion = env->GetMethodID (AndroidAccessibilityNodeInfo, "setLiveRegion", "(I)V");
accessibilityEventSetContentChangeTypes = env->GetMethodID (AndroidAccessibilityEvent, "setContentChangeTypes", "(I)V");
}
}
}
static constexpr auto getClassName (AccessibilityRole role)
{
switch (role)
{
case AccessibilityRole::editableText: return "android.widget.EditText";
case AccessibilityRole::toggleButton: return "android.widget.CheckBox";
case AccessibilityRole::radioButton: return "android.widget.RadioButton";
case AccessibilityRole::image: return "android.widget.ImageView";
case AccessibilityRole::popupMenu: return "android.widget.PopupMenu";
case AccessibilityRole::comboBox: return "android.widget.Spinner";
case AccessibilityRole::tree: return "android.widget.ExpandableListView";
case AccessibilityRole::list: return "android.widget.ListView";
case AccessibilityRole::table: return "android.widget.TableLayout";
case AccessibilityRole::progressBar: return "android.widget.ProgressBar";
case AccessibilityRole::scrollBar:
case AccessibilityRole::slider: return "android.widget.SeekBar";
case AccessibilityRole::hyperlink:
case AccessibilityRole::button: return "android.widget.Button";
case AccessibilityRole::label:
case AccessibilityRole::staticText: return "android.widget.TextView";
case AccessibilityRole::tooltip:
case AccessibilityRole::splashScreen:
case AccessibilityRole::dialogWindow: return "android.widget.PopupWindow";
case AccessibilityRole::column:
case AccessibilityRole::row:
case AccessibilityRole::cell:
case AccessibilityRole::menuItem:
case AccessibilityRole::menuBar:
case AccessibilityRole::listItem:
case AccessibilityRole::treeItem:
case AccessibilityRole::window:
case AccessibilityRole::tableHeader:
case AccessibilityRole::unspecified:
case AccessibilityRole::group:
case AccessibilityRole::ignored: break;
}
return "android.view.View";
}
static jobject getSourceView (const AccessibilityHandler& handler)
{
if (auto* peer = handler.getComponent().getPeer())
return (jobject) peer->getNativeHandle();
return nullptr;
}
void sendAccessibilityEventImpl (const AccessibilityHandler& handler, int eventType, int contentChangeTypes);
//==============================================================================
class AccessibilityNativeHandle
{
public:
static AccessibilityHandler* getAccessibilityHandlerForVirtualViewId (int virtualViewId)
{
auto iter = virtualViewIdMap.find (virtualViewId);
if (iter != virtualViewIdMap.end())
return iter->second;
return nullptr;
}
explicit AccessibilityNativeHandle (AccessibilityHandler& h)
: accessibilityHandler (h),
virtualViewId (getVirtualViewIdForHandler (accessibilityHandler))
{
loadSDKDependentMethods();
if (virtualViewId != HOST_VIEW_ID)
virtualViewIdMap[virtualViewId] = &accessibilityHandler;
}
~AccessibilityNativeHandle()
{
if (virtualViewId != HOST_VIEW_ID)
virtualViewIdMap.erase (virtualViewId);
}
int getVirtualViewId() const noexcept { return virtualViewId; }
void populateNodeInfo (jobject info)
{
const ScopedValueSetter<bool> svs (inPopulateNodeInfo, true);
const auto sourceView = getSourceView (accessibilityHandler);
if (sourceView == nullptr)
return;
auto* env = getEnv();
auto appContext = getAppContext();
if (appContext.get() == nullptr)
return;
{
for (auto* child : accessibilityHandler.getChildren())
env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.addChild,
sourceView, child->getNativeImplementation()->getVirtualViewId());
if (auto* parent = accessibilityHandler.getParent())
env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.setVirtualParent,
sourceView, parent->getNativeImplementation()->getVirtualViewId());
else
env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.setParent, sourceView);
}
{
const auto scale = Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale;
const auto screenBounds = accessibilityHandler.getComponent().getScreenBounds() * scale;
LocalRef<jobject> rect (env->NewObject (AndroidRect, AndroidRect.constructor,
screenBounds.getX(), screenBounds.getY(),
screenBounds.getRight(), screenBounds.getBottom()));
env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.setBoundsInScreen, rect.get());
const auto boundsInParent = accessibilityHandler.getComponent().getBoundsInParent() * scale;
rect = LocalRef<jobject> (env->NewObject (AndroidRect, AndroidRect.constructor,
boundsInParent.getX(), boundsInParent.getY(),
boundsInParent.getRight(), boundsInParent.getBottom()));
env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.setBoundsInParent, rect.get());
}
const auto state = accessibilityHandler.getCurrentState();
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.setEnabled,
! state.isIgnored());
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.setVisibleToUser,
true);
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.setPackageName,
env->CallObjectMethod (appContext.get(),
AndroidContext.getPackageName));
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.setSource,
sourceView,
virtualViewId);
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.setClassName,
javaString (getClassName (accessibilityHandler.getRole())).get());
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.setContentDescription,
getDescriptionString().get());
if (state.isFocusable())
{
env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.setFocusable, true);
const auto& component = accessibilityHandler.getComponent();
if (component.getWantsKeyboardFocus())
{
const auto hasKeyboardFocus = component.hasKeyboardFocus (false);
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.setFocused,
hasKeyboardFocus);
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.addAction,
hasKeyboardFocus ? ACTION_CLEAR_FOCUS : ACTION_FOCUS);
}
const auto isAccessibleFocused = accessibilityHandler.hasFocus (false);
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.setAccessibilityFocused,
isAccessibleFocused);
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.addAction,
isAccessibleFocused ? ACTION_CLEAR_ACCESSIBILITY_FOCUS
: ACTION_ACCESSIBILITY_FOCUS);
}
if (state.isCheckable())
{
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.setCheckable,
true);
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.setChecked,
state.isChecked());
}
if (state.isSelectable() || state.isMultiSelectable())
{
const auto isSelected = state.isSelected();
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.setSelected,
isSelected);
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.addAction,
isSelected ? ACTION_CLEAR_SELECTION : ACTION_SELECT);
}
if (accessibilityHandler.getActions().contains (AccessibilityActionType::press))
{
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.setClickable,
true);
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.addAction,
ACTION_CLICK);
}
if (accessibilityHandler.getActions().contains (AccessibilityActionType::showMenu)
&& state.isExpandable())
{
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.addAction,
state.isExpanded() ? ACTION_COLLAPSE : ACTION_EXPAND);
}
if (auto* textInterface = accessibilityHandler.getTextInterface())
{
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.setText,
javaString (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() })).get());
const auto isReadOnly = textInterface->isReadOnly();
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.setPassword,
textInterface->isDisplayingProtectedText());
if (nodeInfoSetEditable != nullptr)
env->CallVoidMethod (info, nodeInfoSetEditable, ! isReadOnly);
const auto selection = textInterface->getSelection();
if (nodeInfoSetTextSelection != nullptr && ! selection.isEmpty())
env->CallVoidMethod (info,
nodeInfoSetTextSelection,
selection.getStart(), selection.getEnd());
if (nodeInfoSetLiveRegion != nullptr && accessibilityHandler.hasFocus (false))
env->CallVoidMethod (info,
nodeInfoSetLiveRegion,
ACCESSIBILITY_LIVE_REGION_POLITE);
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.setMovementGranularities,
ALL_GRANULARITIES);
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.addAction,
ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.addAction,
ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.addAction,
ACTION_SET_SELECTION);
if (! isReadOnly)
env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.addAction, ACTION_SET_TEXT);
}
if (auto* valueInterface = accessibilityHandler.getValueInterface())
{
if (! valueInterface->isReadOnly())
{
const auto range = valueInterface->getRange();
if (range.isValid())
{
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.addAction,
ACTION_SCROLL_FORWARD);
env->CallVoidMethod (info,
AndroidAccessibilityNodeInfo.addAction,
ACTION_SCROLL_BACKWARD);
}
}
}
}
bool performAction (int action, jobject arguments)
{
switch (action)
{
case ACTION_ACCESSIBILITY_FOCUS:
{
const WeakReference<Component> safeComponent (&accessibilityHandler.getComponent());
accessibilityHandler.getActions().invoke (AccessibilityActionType::focus);
if (safeComponent != nullptr)
accessibilityHandler.grabFocus();
return true;
}
case ACTION_CLEAR_ACCESSIBILITY_FOCUS:
{
accessibilityHandler.giveAwayFocus();
return true;
}
case ACTION_FOCUS:
case ACTION_CLEAR_FOCUS:
{
auto& component = accessibilityHandler.getComponent();
if (component.getWantsKeyboardFocus())
{
const auto hasFocus = component.hasKeyboardFocus (false);
if (hasFocus && action == ACTION_CLEAR_FOCUS)
component.giveAwayKeyboardFocus();
else if (! hasFocus && action == ACTION_FOCUS)
component.grabKeyboardFocus();
return true;
}
break;
}
case ACTION_CLICK:
{
if (accessibilityHandler.getActions().invoke (AccessibilityActionType::press))
{
sendAccessibilityEventImpl (accessibilityHandler, TYPE_VIEW_CLICKED, 0);
return true;
}
break;
}
case ACTION_SELECT:
case ACTION_CLEAR_SELECTION:
{
const auto state = accessibilityHandler.getCurrentState();
if (state.isSelectable() || state.isMultiSelectable())
{
const auto isSelected = state.isSelected();
if ((isSelected && action == ACTION_CLEAR_SELECTION)
|| (! isSelected && action == ACTION_SELECT))
{
return accessibilityHandler.getActions().invoke (AccessibilityActionType::toggle);
}
}
break;
}
case ACTION_EXPAND:
case ACTION_COLLAPSE:
{
const auto state = accessibilityHandler.getCurrentState();
if (state.isExpandable())
{
const auto isExpanded = state.isExpanded();
if ((isExpanded && action == ACTION_COLLAPSE)
|| (! isExpanded && action == ACTION_EXPAND))
{
return accessibilityHandler.getActions().invoke (AccessibilityActionType::showMenu);
}
}
break;
}
case ACTION_NEXT_AT_MOVEMENT_GRANULARITY: return moveCursor (arguments, true);
case ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: return moveCursor (arguments, false);
case ACTION_SET_SELECTION:
{
if (auto* textInterface = accessibilityHandler.getTextInterface())
{
auto* env = getEnv();
const auto selection = [&]() -> Range<int>
{
const auto selectionStartKey = javaString ("ACTION_ARGUMENT_SELECTION_START_INT");
const auto selectionEndKey = javaString ("ACTION_ARGUMENT_SELECTION_END_INT");
const auto hasKey = [&env, &arguments] (const auto& key)
{
return env->CallBooleanMethod (arguments, AndroidBundle.containsKey, key.get());
};
if (hasKey (selectionStartKey) && hasKey (selectionEndKey))
{
const auto getKey = [&env, &arguments] (const auto& key)
{
return env->CallIntMethod (arguments, AndroidBundle.getInt, key.get());
};
return { getKey (selectionStartKey), getKey (selectionEndKey) };
}
return {};
}();
textInterface->setSelection (selection);
return true;
}
break;
}
case ACTION_SET_TEXT:
{
if (auto* textInterface = accessibilityHandler.getTextInterface())
{
if (! textInterface->isReadOnly())
{
const auto charSequenceKey = javaString ("ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE");
auto* env = getEnv();
const auto text = [&]() -> String
{
if (env->CallBooleanMethod (arguments, AndroidBundle.containsKey, charSequenceKey.get()))
{
LocalRef<jobject> charSequence (env->CallObjectMethod (arguments,
AndroidBundle.getCharSequence,
charSequenceKey.get()));
LocalRef<jstring> textStringRef ((jstring) env->CallObjectMethod (charSequence,
JavaCharSequence.toString));
return juceString (textStringRef.get());
}
return {};
}();
textInterface->setText (text);
}
}
break;
}
case ACTION_SCROLL_BACKWARD:
case ACTION_SCROLL_FORWARD:
{
if (auto* valueInterface = accessibilityHandler.getValueInterface())
{
if (! valueInterface->isReadOnly())
{
const auto range = valueInterface->getRange();
if (range.isValid())
{
const auto interval = action == ACTION_SCROLL_BACKWARD ? -range.getInterval()
: range.getInterval();
valueInterface->setValue (jlimit (range.getMinimumValue(),
range.getMaximumValue(),
valueInterface->getCurrentValue() + interval));
// required for Android to announce the new value
sendAccessibilityEventImpl (accessibilityHandler, TYPE_VIEW_SELECTED, 0);
return true;
}
}
}
break;
}
}
return false;
}
bool isInPopulateNodeInfo() const noexcept { return inPopulateNodeInfo; }
private:
static std::unordered_map<int, AccessibilityHandler*> virtualViewIdMap;
static int getVirtualViewIdForHandler (const AccessibilityHandler& handler)
{
static int counter = 0;
if (handler.getComponent().isOnDesktop())
return HOST_VIEW_ID;
return counter++;
}
LocalRef<jstring> getDescriptionString() const
{
const auto valueString = [this]() -> String
{
if (auto* textInterface = accessibilityHandler.getTextInterface())
return textInterface->getText ({ 0, textInterface->getTotalNumCharacters() });
if (auto* valueInterface = accessibilityHandler.getValueInterface())
return valueInterface->getCurrentValueAsString();
return {};
}();
StringArray strings (accessibilityHandler.getTitle(),
valueString,
accessibilityHandler.getDescription(),
accessibilityHandler.getHelp());
strings.removeEmptyStrings();
return javaString (strings.joinIntoString (","));
}
bool moveCursor (jobject arguments, bool forwards)
{
if (auto* textInterface = accessibilityHandler.getTextInterface())
{
const auto granularityKey = javaString ("ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT");
const auto extendSelectionKey = javaString ("ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN");
auto* env = getEnv();
const auto boundaryType = [&]
{
const auto granularity = env->CallIntMethod (arguments,
AndroidBundle.getInt,
granularityKey.get());
using BoundaryType = AccessibilityTextHelpers::BoundaryType;
switch (granularity)
{
case MOVEMENT_GRANULARITY_CHARACTER: return BoundaryType::character;
case MOVEMENT_GRANULARITY_WORD: return BoundaryType::word;
case MOVEMENT_GRANULARITY_LINE: return BoundaryType::line;
case MOVEMENT_GRANULARITY_PARAGRAPH:
case MOVEMENT_GRANULARITY_PAGE: return BoundaryType::document;
}
jassertfalse;
return BoundaryType::character;
}();
using Direction = AccessibilityTextHelpers::Direction;
const auto cursorPos = AccessibilityTextHelpers::findTextBoundary (*textInterface,
textInterface->getTextInsertionOffset(),
boundaryType,
forwards ? Direction::forwards
: Direction::backwards);
const auto newSelection = [&]() -> Range<int>
{
const auto currentSelection = textInterface->getSelection();
const auto extendSelection = env->CallBooleanMethod (arguments,
AndroidBundle.getBoolean,
extendSelectionKey.get());
if (! extendSelection)
return { cursorPos, cursorPos };
const auto start = currentSelection.getStart();
const auto end = currentSelection.getEnd();
if (forwards)
return { start, jmax (start, cursorPos) };
return { jmin (start, cursorPos), end };
}();
textInterface->setSelection (newSelection);
return true;
}
return false;
}
AccessibilityHandler& accessibilityHandler;
const int virtualViewId;
bool inPopulateNodeInfo = false;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeHandle)
};
std::unordered_map<int, AccessibilityHandler*> AccessibilityNativeHandle::virtualViewIdMap;
class AccessibilityHandler::AccessibilityNativeImpl : public AccessibilityNativeHandle
{
public:
using AccessibilityNativeHandle::AccessibilityNativeHandle;
};
//==============================================================================
AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const
{
return nativeImpl.get();
}
static bool areAnyAccessibilityClientsActive()
{
auto* env = getEnv();
auto appContext = getAppContext();
if (appContext.get() != nullptr)
{
LocalRef<jobject> accessibilityManager (env->CallObjectMethod (appContext.get(), AndroidContext.getSystemService,
javaString ("accessibility").get()));
if (accessibilityManager != nullptr)
return env->CallBooleanMethod (accessibilityManager.get(), AndroidAccessibilityManager.isEnabled);
}
return false;
}
void sendAccessibilityEventImpl (const AccessibilityHandler& handler, int eventType, int contentChangeTypes)
{
if (! areAnyAccessibilityClientsActive())
return;
if (const auto sourceView = getSourceView (handler))
{
const auto* nativeImpl = handler.getNativeImplementation();
if (nativeImpl == nullptr || nativeImpl->isInPopulateNodeInfo())
return;
auto* env = getEnv();
auto appContext = getAppContext();
if (appContext.get() == nullptr)
return;
LocalRef<jobject> event (env->CallStaticObjectMethod (AndroidAccessibilityEvent,
AndroidAccessibilityEvent.obtain,
eventType));
env->CallVoidMethod (event,
AndroidAccessibilityEvent.setPackageName,
env->CallObjectMethod (appContext.get(),
AndroidContext.getPackageName));
env->CallVoidMethod (event,
AndroidAccessibilityEvent.setSource,
sourceView,
nativeImpl->getVirtualViewId());
if (contentChangeTypes != 0 && accessibilityEventSetContentChangeTypes != nullptr)
env->CallVoidMethod (event,
accessibilityEventSetContentChangeTypes,
contentChangeTypes);
env->CallBooleanMethod (sourceView,
AndroidViewGroup.requestSendAccessibilityEvent,
sourceView,
event.get());
}
}
void notifyAccessibilityEventInternal (const AccessibilityHandler& handler,
InternalAccessibilityEvent eventType)
{
if (eventType == InternalAccessibilityEvent::elementCreated
|| eventType == InternalAccessibilityEvent::elementDestroyed
|| eventType == InternalAccessibilityEvent::elementMovedOrResized)
{
if (auto* parent = handler.getParent())
sendAccessibilityEventImpl (*parent, TYPE_WINDOW_CONTENT_CHANGED, CONTENT_CHANGE_TYPE_SUBTREE);
return;
}
auto notification = [&handler, eventType]
{
switch (eventType)
{
case InternalAccessibilityEvent::focusChanged:
return handler.hasFocus (false) ? TYPE_VIEW_ACCESSIBILITY_FOCUSED
: TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
case InternalAccessibilityEvent::elementCreated:
case InternalAccessibilityEvent::elementDestroyed:
case InternalAccessibilityEvent::elementMovedOrResized:
case InternalAccessibilityEvent::windowOpened:
case InternalAccessibilityEvent::windowClosed:
break;
}
return 0;
}();
if (notification != 0)
sendAccessibilityEventImpl (handler, notification, 0);
}
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const
{
auto notification = [eventType]
{
switch (eventType)
{
case AccessibilityEvent::textSelectionChanged: return TYPE_VIEW_TEXT_SELECTION_CHANGED;
case AccessibilityEvent::textChanged: return TYPE_VIEW_TEXT_CHANGED;
case AccessibilityEvent::titleChanged:
case AccessibilityEvent::structureChanged: return TYPE_WINDOW_CONTENT_CHANGED;
case AccessibilityEvent::rowSelectionChanged:
case AccessibilityEvent::valueChanged: break;
}
return 0;
}();
if (notification == 0)
return;
const auto contentChangeTypes = [eventType]
{
if (eventType == AccessibilityEvent::titleChanged) return CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION;
if (eventType == AccessibilityEvent::structureChanged) return CONTENT_CHANGE_TYPE_SUBTREE;
return 0;
}();
sendAccessibilityEventImpl (*this, notification, contentChangeTypes);
}
void AccessibilityHandler::postAnnouncement (const String& announcementString,
AnnouncementPriority)
{
if (! areAnyAccessibilityClientsActive())
return;
const auto rootView = []
{
LocalRef<jobject> activity (getMainActivity());
if (activity != nullptr)
{
auto* env = getEnv();
LocalRef<jobject> mainWindow (env->CallObjectMethod (activity.get(), AndroidActivity.getWindow));
LocalRef<jobject> decorView (env->CallObjectMethod (mainWindow.get(), AndroidWindow.getDecorView));
return LocalRef<jobject> (env->CallObjectMethod (decorView.get(), AndroidView.getRootView));
}
return LocalRef<jobject>();
}();
if (rootView != nullptr)
getEnv()->CallVoidMethod (rootView.get(),
AndroidView.announceForAccessibility,
javaString (announcementString).get());
}
} // namespace juce

View File

@ -0,0 +1,570 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
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
{
#if (defined (__IPHONE_11_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_11_0)
#define JUCE_IOS_CONTAINER_API_AVAILABLE 1
#endif
constexpr auto juceUIAccessibilityContainerTypeNone =
#if JUCE_IOS_CONTAINER_API_AVAILABLE
UIAccessibilityContainerTypeNone;
#else
0;
#endif
constexpr auto juceUIAccessibilityContainerTypeDataTable =
#if JUCE_IOS_CONTAINER_API_AVAILABLE
UIAccessibilityContainerTypeDataTable;
#else
1;
#endif
constexpr auto juceUIAccessibilityContainerTypeList =
#if JUCE_IOS_CONTAINER_API_AVAILABLE
UIAccessibilityContainerTypeList;
#else
2;
#endif
#define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1
//==============================================================================
static NSArray* getContainerAccessibilityElements (AccessibilityHandler& handler)
{
const auto children = handler.getChildren();
NSMutableArray* accessibleChildren = [NSMutableArray arrayWithCapacity: (NSUInteger) children.size()];
[accessibleChildren addObject: (id) handler.getNativeImplementation()];
for (auto* childHandler : children)
{
id accessibleElement = [&childHandler]
{
id native = (id) childHandler->getNativeImplementation();
if (childHandler->getChildren().size() > 0)
return [native accessibilityContainer];
return native;
}();
if (accessibleElement != nil)
[accessibleChildren addObject: accessibleElement];
}
return accessibleChildren;
}
//==============================================================================
class AccessibilityHandler::AccessibilityNativeImpl
{
public:
explicit AccessibilityNativeImpl (AccessibilityHandler& handler)
: accessibilityElement (AccessibilityElement::create (handler))
{
}
UIAccessibilityElement* getAccessibilityElement() const noexcept
{
return accessibilityElement.get();
}
private:
//==============================================================================
class AccessibilityContainer : public ObjCClass<UIAccessibilityElement>
{
public:
AccessibilityContainer()
: ObjCClass ("JUCEUIAccessibilityElementContainer_")
{
addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement, "c@:");
addMethod (@selector (accessibilityFrame), getAccessibilityFrame, @encode (CGRect), "@:");
addMethod (@selector (accessibilityElements), getAccessibilityElements, "@@:");
addMethod (@selector (accessibilityContainerType), getAccessibilityContainerType, "i@:");
addIvar<AccessibilityHandler*> ("handler");
registerClass();
}
private:
static AccessibilityHandler* getHandler (id self)
{
return getIvar<AccessibilityHandler*> (self, "handler");
}
static BOOL getIsAccessibilityElement (id, SEL)
{
return NO;
}
static CGRect getAccessibilityFrame (id self, SEL)
{
if (auto* handler = getHandler (self))
return convertToCGRect (handler->getComponent().getScreenBounds());
return CGRectZero;
}
static NSArray* getAccessibilityElements (id self, SEL)
{
if (auto* handler = getHandler (self))
return getContainerAccessibilityElements (*handler);
return nil;
}
static NSInteger getAccessibilityContainerType (id self, SEL)
{
if (auto* handler = getHandler (self))
{
if (handler->getTableInterface() != nullptr)
return juceUIAccessibilityContainerTypeDataTable;
const auto role = handler->getRole();
if (role == AccessibilityRole::popupMenu
|| role == AccessibilityRole::list
|| role == AccessibilityRole::tree)
{
return juceUIAccessibilityContainerTypeList;
}
}
return juceUIAccessibilityContainerTypeNone;
}
};
//==============================================================================
class AccessibilityElement : public AccessibleObjCClass<UIAccessibilityElement>
{
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: (id) handler.getComponent().getWindowHandle()]);
object_setInstanceVariable (element.get(), "handler", &handler);
return element;
}
AccessibilityElement (Type elementType)
{
addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement, "c@:");
addMethod (@selector (accessibilityContainer), getAccessibilityContainer, "@@:");
addMethod (@selector (accessibilityFrame), getAccessibilityFrame, @encode (CGRect), "@:");
addMethod (@selector (accessibilityTraits), getAccessibilityTraits, "i@:");
addMethod (@selector (accessibilityLabel), getAccessibilityTitle, "@@:");
addMethod (@selector (accessibilityHint), getAccessibilityHelp, "@@:");
addMethod (@selector (accessibilityValue), getAccessibilityValue, "@@:");
addMethod (@selector (setAccessibilityValue:), setAccessibilityValue, "v@:@");
addMethod (@selector (accessibilityElementDidBecomeFocused), onFocusGain, "v@:");
addMethod (@selector (accessibilityElementDidLoseFocus), onFocusLoss, "v@:");
addMethod (@selector (accessibilityElementIsFocused), isFocused, "c@:");
addMethod (@selector (accessibilityViewIsModal), getIsAccessibilityModal, "c@:");
addMethod (@selector (accessibilityActivate), accessibilityPerformActivate, "c@:");
addMethod (@selector (accessibilityIncrement), accessibilityPerformIncrement, "c@:");
addMethod (@selector (accessibilityDecrement), accessibilityPerformDecrement, "c@:");
addMethod (@selector (accessibilityPerformEscape), accessibilityEscape, "c@:");
addMethod (@selector (accessibilityDataTableCellElementForRow:column:), getAccessibilityDataTableCellElementForRowColumn, "@@:ii");
addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount, "i@:");
addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount, "i@:");
addProtocol (@protocol (UIAccessibilityContainerDataTable));
addMethod (@selector (accessibilityRowRange), getAccessibilityRowIndexRange, @encode (NSRange), "@:");
addMethod (@selector (accessibilityColumnRange), getAccessibilityColumnIndexRange, @encode (NSRange), "@:");
addProtocol (@protocol (UIAccessibilityContainerDataTableCell));
if (elementType == Type::textElement)
{
addMethod (@selector (accessibilityLineNumberForPoint:), getAccessibilityLineNumberForPoint, "i@:", @encode (CGPoint));
addMethod (@selector (accessibilityContentForLineNumber:), getAccessibilityContentForLineNumber, "@@:i");
addMethod (@selector (accessibilityFrameForLineNumber:), getAccessibilityFrameForLineNumber, @encode (CGRect), "@:i");
addMethod (@selector (accessibilityPageContent), getAccessibilityPageContent, "@@:");
addProtocol (@protocol (UIAccessibilityReadingContent));
}
addIvar<UIAccessibilityElement*> ("container");
registerClass();
}
private:
//==============================================================================
static UIAccessibilityElement* getContainer (id self)
{
return getIvar<UIAccessibilityElement*> (self, "container");
}
//==============================================================================
static id getAccessibilityContainer (id self, SEL)
{
if (auto* handler = getHandler (self))
{
if (handler->getComponent().isOnDesktop())
return (id) handler->getComponent().getWindowHandle();
if (handler->getChildren().size() > 0)
{
if (UIAccessibilityElement* container = getContainer (self))
return container;
static AccessibilityContainer cls;
id windowHandle = (id) handler->getComponent().getWindowHandle();
UIAccessibilityElement* container = [cls.createInstance() initWithAccessibilityContainer: windowHandle];
[container retain];
object_setInstanceVariable (container, "handler", handler);
object_setInstanceVariable (self, "container", container);
return container;
}
if (auto* parent = handler->getParent())
return [(id) parent->getNativeImplementation() accessibilityContainer];
}
return nil;
}
static CGRect getAccessibilityFrame (id self, SEL)
{
if (auto* handler = getHandler (self))
return convertToCGRect (handler->getComponent().getScreenBounds());
return CGRectZero;
}
static UIAccessibilityTraits getAccessibilityTraits (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::editableText: return UIAccessibilityTraitKeyboardKey;
case AccessibilityRole::ignored: return UIAccessibilityTraitNotEnabled;
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));
}
static NSString* getAccessibilityValue (id self, SEL)
{
if (auto* handler = getHandler (self))
{
if (handler->getCurrentState().isCheckable())
return handler->getCurrentState().isChecked() ? @"1" : @"0";
return (NSString*) getAccessibilityValueFromInterfaces (*handler);
}
return nil;
}
static void onFocusGain (id self, SEL)
{
if (auto* handler = getHandler (self))
{
const WeakReference<Component> safeComponent (&handler->getComponent());
performActionIfSupported (self, AccessibilityActionType::focus);
if (safeComponent != nullptr)
handler->grabFocus();
}
}
static void onFocusLoss (id self, SEL)
{
if (auto* handler = getHandler (self))
handler->giveAwayFocus();
}
static BOOL isFocused (id self, SEL)
{
if (auto* handler = getHandler (self))
return handler->hasFocus (false);
return NO;
}
static BOOL accessibilityPerformActivate (id self, SEL)
{
if (auto* handler = getHandler (self))
{
// occasionaly 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 (! [(id) handler->getNativeImplementation() isEqual: focusedElement])
return [focusedElement accessibilityActivate];
if (handler->hasFocus (false))
return accessibilityPerformPress (self, {});
}
return NO;
}
static BOOL accessibilityEscape (id self, SEL)
{
if (auto* handler = getHandler (self)) {
// HACK - look for parent that is a CalloutBox and dismiss it
if (CallOutBox* const cb = handler->getComponent().findParentComponentOfClass<CallOutBox>()) {
cb->dismiss();
return YES;
}
}
return NO;
}
static id getAccessibilityDataTableCellElementForRowColumn (id self, SEL, NSUInteger row, NSUInteger column)
{
if (auto* tableInterface = getTableInterface (self))
if (auto* cellHandler = tableInterface->getCellHandler ((int) row, (int) column))
return (id) cellHandler->getNativeImplementation();
return nil;
}
static NSInteger getAccessibilityLineNumberForPoint (id self, SEL, CGPoint point)
{
if (auto* handler = getHandler (self))
{
if (auto* textInterface = handler->getTextInterface())
{
auto pointInt = roundToIntPoint (point);
if (handler->getComponent().getScreenBounds().contains (pointInt))
{
auto textBounds = textInterface->getTextBounds ({ 0, textInterface->getTotalNumCharacters() });
for (int i = 0; i < textBounds.getNumRectangles(); ++i)
if (textBounds.getRectangle (i).contains (pointInt))
return (NSInteger) i;
}
}
}
return NSNotFound;
}
static NSString* getAccessibilityContentForLineNumber (id self, SEL, NSInteger lineNumber)
{
if (auto* textInterface = getTextInterface (self))
{
auto lines = StringArray::fromLines (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() }));
if ((int) lineNumber < lines.size())
return juceStringToNS (lines[(int) lineNumber]);
}
return nil;
}
static CGRect getAccessibilityFrameForLineNumber (id self, SEL, NSInteger lineNumber)
{
if (auto* textInterface = getTextInterface (self))
{
auto textBounds = textInterface->getTextBounds ({ 0, textInterface->getTotalNumCharacters() });
if (lineNumber < textBounds.getNumRectangles())
return convertToCGRect (textBounds.getRectangle ((int) lineNumber));
}
return CGRectZero;
}
static NSString* getAccessibilityPageContent (id self, SEL)
{
if (auto* textInterface = getTextInterface (self))
return juceStringToNS (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() }));
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 notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent eventType)
{
auto notification = [eventType]
{
switch (eventType)
{
case InternalAccessibilityEvent::elementCreated:
case InternalAccessibilityEvent::elementDestroyed:
case InternalAccessibilityEvent::elementMovedOrResized:
case InternalAccessibilityEvent::focusChanged: return UIAccessibilityLayoutChangedNotification;
case InternalAccessibilityEvent::windowOpened:
case InternalAccessibilityEvent::windowClosed: return UIAccessibilityScreenChangedNotification;
}
return UIAccessibilityNotifications{};
}();
if (notification != UIAccessibilityNotifications{})
{
const bool moveToHandler = (eventType == InternalAccessibilityEvent::focusChanged && handler.hasFocus (false));
sendAccessibilityEvent (notification,
moveToHandler ? (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, (id) getNativeImplementation());
}
void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority)
{
sendAccessibilityEvent (UIAccessibilityAnnouncementNotification, juceStringToNS (announcementString));
}
} // namespace juce

View File

@ -0,0 +1,963 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
static void juceFreeAccessibilityPlatformSpecificData (NSAccessibilityElement<NSAccessibility>*) {}
namespace juce
{
#if (! defined MAC_OS_X_VERSION_10_13) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13
using NSAccessibilityRole = NSString*;
using NSAccessibilityNotificationName = NSString*;
#endif
#define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wunguarded-availability-new")
//==============================================================================
class AccessibilityHandler::AccessibilityNativeImpl
{
public:
explicit AccessibilityNativeImpl (AccessibilityHandler& handler)
: accessibilityElement (AccessibilityElement::create (handler))
{}
NSAccessibilityElement<NSAccessibility>* getAccessibilityElement() const noexcept
{
return accessibilityElement.get();
}
private:
//==============================================================================
class AccessibilityElement : 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), getAccessibilityNotifiesWhenDestroyed, "c@:");
addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement, "c@:");
addMethod (@selector (isAccessibilityEnabled), getIsAccessibilityEnabled, "c@:");
addMethod (@selector (accessibilityWindow), getAccessibilityWindow, "@@:");
addMethod (@selector (accessibilityTopLevelUIElement), getAccessibilityWindow, "@@:");
addMethod (@selector (accessibilityFocusedUIElement), getAccessibilityFocusedUIElement, "@@:");
addMethod (@selector (accessibilityHitTest:), accessibilityHitTest, "@@:", @encode (NSPoint));
addMethod (@selector (accessibilityParent), getAccessibilityParent, "@@:");
addMethod (@selector (accessibilityChildren), getAccessibilityChildren, "@@:");
addMethod (@selector (isAccessibilityFocused), getIsAccessibilityFocused, "c@:");
addMethod (@selector (setAccessibilityFocused:), setAccessibilityFocused, "v@:c");
addMethod (@selector (isAccessibilityModal), getIsAccessibilityModal, "c@:");
addMethod (@selector (accessibilityFrame), getAccessibilityFrame, @encode (NSRect), "@:");
addMethod (@selector (accessibilityRole), getAccessibilityRole, "@@:");
addMethod (@selector (accessibilitySubrole), getAccessibilitySubrole, "@@:");
addMethod (@selector (accessibilityTitle), getAccessibilityTitle, "@@:");
addMethod (@selector (accessibilityLabel), getAccessibilityLabel, "@@:");
addMethod (@selector (accessibilityHelp), getAccessibilityHelp, "@@:");
addMethod (@selector (accessibilityValue), getAccessibilityValue, "@@:");
addMethod (@selector (setAccessibilityValue:), setAccessibilityValue, "v@:@");
addMethod (@selector (accessibilitySelectedChildren), getAccessibilitySelectedChildren, "@@:");
addMethod (@selector (setAccessibilitySelectedChildren:), setAccessibilitySelectedChildren, "v@:@");
addMethod (@selector (accessibilityOrientation), getAccessibilityOrientation, "i@:@");
addMethod (@selector (accessibilityInsertionPointLineNumber), getAccessibilityInsertionPointLineNumber, "i@:");
addMethod (@selector (accessibilityVisibleCharacterRange), getAccessibilityVisibleCharacterRange, @encode (NSRange), "@:");
addMethod (@selector (accessibilityNumberOfCharacters), getAccessibilityNumberOfCharacters, "i@:");
addMethod (@selector (accessibilitySelectedText), getAccessibilitySelectedText, "@@:");
addMethod (@selector (accessibilitySelectedTextRange), getAccessibilitySelectedTextRange, @encode (NSRange), "@:");
addMethod (@selector (accessibilityAttributedStringForRange:), getAccessibilityAttributedStringForRange, "@@:", @encode (NSRange));
addMethod (@selector (accessibilityRangeForLine:), getAccessibilityRangeForLine, @encode (NSRange), "@:i");
addMethod (@selector (accessibilityStringForRange:), getAccessibilityStringForRange, "@@:", @encode (NSRange));
addMethod (@selector (accessibilityRangeForPosition:), getAccessibilityRangeForPosition, @encode (NSRange), "@:", @encode (NSPoint));
addMethod (@selector (accessibilityRangeForIndex:), getAccessibilityRangeForIndex, @encode (NSRange), "@:i");
addMethod (@selector (accessibilityFrameForRange:), getAccessibilityFrameForRange, @encode (NSRect), "@:", @encode (NSRange));
addMethod (@selector (accessibilityLineForIndex:), getAccessibilityLineForIndex, "i@:i");
addMethod (@selector (setAccessibilitySelectedTextRange:), setAccessibilitySelectedTextRange, "v@:", @encode (NSRange));
addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount, "i@:");
addMethod (@selector (accessibilityRows), getAccessibilityRows, "@@:");
addMethod (@selector (accessibilitySelectedRows), getAccessibilitySelectedRows, "@@:");
addMethod (@selector (setAccessibilitySelectedRows:), setAccessibilitySelectedRows, "v@:@");
addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount, "i@:");
addMethod (@selector (accessibilityColumns), getAccessibilityColumns, "@@:");
addMethod (@selector (accessibilitySelectedColumns), getAccessibilitySelectedColumns, "@@:");
addMethod (@selector (setAccessibilitySelectedColumns:), setAccessibilitySelectedColumns, "v@:@");
addMethod (@selector (accessibilityRowIndexRange), getAccessibilityRowIndexRange, @encode (NSRange), "@:");
addMethod (@selector (accessibilityColumnIndexRange), getAccessibilityColumnIndexRange, @encode (NSRange), "@:");
addMethod (@selector (accessibilityIndex), getAccessibilityIndex, "i@:");
addMethod (@selector (accessibilityDisclosureLevel), getAccessibilityDisclosureLevel, "i@:");
addMethod (@selector (isAccessibilityExpanded), getIsAccessibilityExpanded, "c@:");
addMethod (@selector (accessibilityPerformIncrement), accessibilityPerformIncrement, "c@:");
addMethod (@selector (accessibilityPerformDecrement), accessibilityPerformDecrement, "c@:");
addMethod (@selector (accessibilityPerformDelete), accessibilityPerformDelete, "c@:");
addMethod (@selector (accessibilityPerformPress), accessibilityPerformPress, "c@:");
addMethod (@selector (accessibilityPerformShowMenu), accessibilityPerformShowMenu, "c@:");
addMethod (@selector (accessibilityPerformRaise), accessibilityPerformRaise, "c@:");
addMethod (@selector (isAccessibilitySelectorAllowed:), getIsAccessibilitySelectorAllowed, "c@:@");
#if defined (MAC_OS_X_VERSION_10_13) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13
addMethod (@selector (accessibilityChildrenInNavigationOrder), getAccessibilityChildren, "@@:");
#endif
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 setSelectedChildren (NSArray* children, NSArray* selected)
{
for (id child in children)
{
if (auto* handler = getHandler (child))
{
const auto currentState = handler->getCurrentState();
const BOOL isSelected = [selected containsObject: child];
if (isSelectable (currentState))
{
if (currentState.isSelected() != isSelected)
handler->getActions().invoke (AccessibilityActionType::toggle);
}
else if (currentState.isFocusable())
{
[child setAccessibilityFocused: isSelected];
}
}
}
}
//==============================================================================
static BOOL getAccessibilityNotifiesWhenDestroyed (id, SEL) { return YES; }
static BOOL getIsAccessibilityEnabled (id self, SEL)
{
if (auto* handler = getHandler (self))
return handler->getComponent().isEnabled();
return NO;
}
static id getAccessibilityWindow (id self, SEL)
{
return [[self accessibilityParent] accessibilityWindow];
}
static id getAccessibilityFocusedUIElement (id self, SEL)
{
if (auto* handler = getHandler (self))
{
if (auto* modal = Component::getCurrentlyModalComponent())
{
const auto& component = handler->getComponent();
if (! component.isParentOf (modal)
&& component.isCurrentlyBlockedByAnotherModalComponent())
{
if (auto* modalHandler = modal->getAccessibilityHandler())
{
if (auto* focusChild = modalHandler->getChildFocus())
return (id) focusChild->getNativeImplementation();
return (id) modalHandler->getNativeImplementation();
}
}
}
if (auto* focusChild = handler->getChildFocus())
return (id) focusChild->getNativeImplementation();
}
return nil;
}
static id accessibilityHitTest (id self, SEL, NSPoint point)
{
if (auto* handler = getHandler (self))
{
if (auto* child = handler->getChildAt (roundToIntPoint (flippedScreenPoint (point))))
return (id) child->getNativeImplementation();
return self;
}
return nil;
}
static id getAccessibilityParent (id self, SEL)
{
if (auto* handler = getHandler (self))
{
if (auto* parentHandler = handler->getParent())
return NSAccessibilityUnignoredAncestor ((id) parentHandler->getNativeImplementation());
return NSAccessibilityUnignoredAncestor ((id) handler->getComponent().getWindowHandle());
}
return nil;
}
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: (id) childHandler->getNativeImplementation()];
return accessibleChildren;
}
return nil;
}
static id getAccessibilityValue (id self, SEL)
{
if (auto* handler = getHandler (self))
{
if (handler->getCurrentState().isCheckable())
return handler->getCurrentState().isChecked() ? @(1) : @(0);
return getAccessibilityValueFromInterfaces (*handler);
}
return nil;
}
static NSArray* getAccessibilitySelectedChildren (id self, SEL)
{
return getSelectedChildren ([self accessibilityChildren]);
}
static void setAccessibilitySelectedChildren (id self, SEL, NSArray* selected)
{
setSelectedChildren ([self accessibilityChildren], selected);
}
static NSAccessibilityOrientation getAccessibilityOrientation (id self, SEL)
{
if (auto* handler = getHandler (self))
return handler->getComponent().getBounds().toFloat().getAspectRatio() > 1.0f
? NSAccessibilityOrientationHorizontal
: NSAccessibilityOrientationVertical;
return NSAccessibilityOrientationUnknown;
}
static BOOL getIsAccessibilityFocused (id self, SEL)
{
return [[self accessibilityWindow] accessibilityFocusedUIElement] == self;
}
static void 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();
}
}
}
static NSRect getAccessibilityFrame (id self, SEL)
{
if (auto* handler = getHandler (self))
return flippedScreenRect (makeNSRect (handler->getComponent().getScreenBounds()));
return NSZeroRect;
}
static NSAccessibilityRole getAccessibilityRole (id self, SEL)
{
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 NSAccessibilityListRole;
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;
}
static NSAccessibilityRole getAccessibilitySubrole (id self, SEL)
{
if (auto* handler = getHandler (self))
{
if (auto* textInterface = getTextInterface (self))
if (textInterface->isDisplayingProtectedText())
return NSAccessibilitySecureTextFieldSubrole;
const auto role = handler->getRole();
if (role == AccessibilityRole::window) return NSAccessibilityStandardWindowSubrole;
if (role == AccessibilityRole::dialogWindow) return NSAccessibilityDialogSubrole;
if (role == AccessibilityRole::tooltip
|| role == AccessibilityRole::splashScreen) return NSAccessibilityFloatingWindowSubrole;
if (role == AccessibilityRole::toggleButton) return NSAccessibilityToggleSubrole;
if (role == AccessibilityRole::treeItem
|| role == AccessibilityRole::listItem) return NSAccessibilityOutlineRowSubrole;
if (role == AccessibilityRole::row && getCellInterface (self) != nullptr) return NSAccessibilityTableRowSubrole;
const auto& component = handler->getComponent();
if (auto* documentWindow = component.findParentComponentOfClass<DocumentWindow>())
{
if (role == AccessibilityRole::button)
{
if (&component == documentWindow->getCloseButton()) return NSAccessibilityCloseButtonSubrole;
if (&component == documentWindow->getMinimiseButton()) return NSAccessibilityMinimizeButtonSubrole;
if (&component == documentWindow->getMaximiseButton()) return NSAccessibilityFullScreenButtonSubrole;
}
}
}
return NSAccessibilityUnknownRole;
}
static NSString* getAccessibilityLabel (id self, SEL)
{
if (auto* handler = getHandler (self))
return juceStringToNS (handler->getDescription());
return nil;
}
//==============================================================================
static NSInteger getAccessibilityInsertionPointLineNumber (id self, SEL)
{
if (auto* textInterface = getTextInterface (self))
return [self accessibilityLineForIndex: textInterface->getTextInsertionOffset()];
return 0;
}
static NSRange getAccessibilityVisibleCharacterRange (id self, SEL)
{
if (auto* textInterface = getTextInterface (self))
return juceRangeToNS ({ 0, textInterface->getTotalNumCharacters() });
return NSMakeRange (0, 0);
}
static NSInteger getAccessibilityNumberOfCharacters (id self, SEL)
{
if (auto* textInterface = getTextInterface (self))
return textInterface->getTotalNumCharacters();
return 0;
}
static NSString* getAccessibilitySelectedText (id self, SEL)
{
if (auto* textInterface = getTextInterface (self))
return juceStringToNS (textInterface->getText (textInterface->getSelection()));
return nil;
}
static NSRange getAccessibilitySelectedTextRange (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);
}
static NSAttributedString* getAccessibilityAttributedStringForRange (id self, SEL, NSRange range)
{
NSString* string = [self accessibilityStringForRange: range];
if (string != nil)
return [[[NSAttributedString alloc] initWithString: string] autorelease];
return nil;
}
static NSRange getAccessibilityRangeForLine (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);
}
static NSString* getAccessibilityStringForRange (id self, SEL, NSRange range)
{
if (auto* textInterface = getTextInterface (self))
return juceStringToNS (textInterface->getText (nsRangeToJuce (range)));
return nil;
}
static NSRange getAccessibilityRangeForPosition (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);
}
static NSRange getAccessibilityRangeForIndex (id self, SEL, NSInteger index)
{
if (auto* textInterface = getTextInterface (self))
if (isPositiveAndBelow (index, textInterface->getTotalNumCharacters()))
return NSMakeRange ((NSUInteger) index, 1);
return NSMakeRange (0, 0);
}
static NSRect getAccessibilityFrameForRange (id self, SEL, NSRange range)
{
if (auto* textInterface = getTextInterface (self))
return flippedScreenRect (makeNSRect (textInterface->getTextBounds (nsRangeToJuce (range)).getBounds()));
return NSZeroRect;
}
static NSInteger getAccessibilityLineForIndex (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;
}
static void setAccessibilitySelectedTextRange (id self, SEL, NSRange selectedRange)
{
if (auto* textInterface = getTextInterface (self))
textInterface->setSelection (nsRangeToJuce (selectedRange));
}
//==============================================================================
static NSArray* getAccessibilityRows (id self, SEL)
{
NSMutableArray* rows = [[NSMutableArray new] autorelease];
if (auto* tableInterface = getTableInterface (self))
{
for (int row = 0; row < tableInterface->getNumRows(); ++row)
{
if (auto* handler = tableInterface->getCellHandler (row, 0))
{
[rows addObject: (id) handler->getNativeImplementation()];
}
else
{
[rows addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityRowRole
frame: NSZeroRect
label: @"Offscreen Row"
parent: self]];
}
}
}
return rows;
}
static NSArray* getAccessibilitySelectedRows (id self, SEL)
{
return getSelectedChildren ([self accessibilityRows]);
}
static void setAccessibilitySelectedRows (id self, SEL, NSArray* selected)
{
setSelectedChildren ([self accessibilityRows], selected);
}
static NSArray* getAccessibilityColumns (id self, SEL)
{
NSMutableArray* columns = [[NSMutableArray new] autorelease];
if (auto* tableInterface = getTableInterface (self))
{
for (int column = 0; column < tableInterface->getNumColumns(); ++column)
{
if (auto* handler = tableInterface->getCellHandler (0, column))
{
[columns addObject: (id) handler->getNativeImplementation()];
}
else
{
[columns addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityColumnRole
frame: NSZeroRect
label: @"Offscreen Column"
parent: self]];
}
}
}
return columns;
}
static NSArray* getAccessibilitySelectedColumns (id self, SEL)
{
return getSelectedChildren ([self accessibilityColumns]);
}
static void setAccessibilitySelectedColumns (id self, SEL, NSArray* selected)
{
setSelectedChildren ([self accessibilityColumns], selected);
}
//==============================================================================
static NSInteger getAccessibilityIndex (id self, SEL)
{
if (auto* handler = getHandler (self))
{
if (auto* cellInterface = handler->getCellInterface())
{
NSAccessibilityRole role = [self accessibilityRole];
if ([role isEqual: NSAccessibilityRowRole])
return cellInterface->getRowIndex();
if ([role isEqual: NSAccessibilityColumnRole])
return cellInterface->getColumnIndex();
}
}
return 0;
}
static NSInteger getAccessibilityDisclosureLevel (id self, SEL)
{
if (auto* handler = getHandler (self))
if (auto* cellInterface = handler->getCellInterface())
return cellInterface->getDisclosureLevel();
return 0;
}
static BOOL getIsAccessibilityExpanded (id self, SEL)
{
if (auto* handler = getHandler (self))
return handler->getCurrentState().isExpanded();
return NO;
}
//==============================================================================
static BOOL accessibilityPerformShowMenu (id self, SEL) { return performActionIfSupported (self, AccessibilityActionType::showMenu); }
static BOOL accessibilityPerformRaise (id self, SEL) { [self setAccessibilityFocused: YES]; return YES; }
static BOOL 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;
}
//==============================================================================
static BOOL getIsAccessibilitySelectorAllowed (id self, SEL, SEL selector)
{
if (auto* handler = getHandler (self))
{
const auto role = 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 (accessibilityColumns),
@selector (accessibilitySelectedColumns) })
{
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->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 role == AccessibilityRole::popupMenu;
if (selector == @selector (accessibilityOrientation))
return role == AccessibilityRole::scrollBar;
if (selector == @selector (isAccessibilityExpanded))
return currentState.isExpandable();
return sendSuperclassMessage<BOOL> (self, @selector (isAccessibilitySelectorAllowed:), selector);
}
return NO;
}
//==============================================================================
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()
{
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 (id accessibilityElement = (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 notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent eventType)
{
auto notification = [eventType]
{
switch (eventType)
{
case InternalAccessibilityEvent::elementCreated: return NSAccessibilityCreatedNotification;
case InternalAccessibilityEvent::elementDestroyed: return NSAccessibilityUIElementDestroyedNotification;
case InternalAccessibilityEvent::elementMovedOrResized: return layoutChangedNotification();
case InternalAccessibilityEvent::focusChanged: return NSAccessibilityFocusedUIElementChangedNotification;
case InternalAccessibilityEvent::windowOpened: return NSAccessibilityWindowCreatedNotification;
case InternalAccessibilityEvent::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.10, *))
{
auto nsPriority = [priority]
{
switch (priority)
{
case AnnouncementPriority::low: return NSAccessibilityPriorityLow;
case AnnouncementPriority::medium: return NSAccessibilityPriorityMedium;
case AnnouncementPriority::high: return NSAccessibilityPriorityHigh;
}
jassertfalse;
return NSAccessibilityPriorityLow;
}();
sendAccessibilityEvent ((id) [NSApp mainWindow],
NSAccessibilityAnnouncementRequestedNotification,
@{ NSAccessibilityAnnouncementKey: juceStringToNS (announcementString),
NSAccessibilityPriorityKey: @(nsPriority) });
}
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
} // namespace juce

View File

@ -0,0 +1,249 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
struct 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() : ObjCClass<Base> ("JUCEAccessibilityElement_")
{
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)
{
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 = getAccessibleApplicationOrPluginName();
NSString* nsString = juceStringToNS (title);
#if ! JUCE_IOS
if (nsString != nil && [[self accessibilityValue] isEqual: nsString])
return @"";
#endif
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* tableInterface = getTableInterface (self))
return tableInterface->getNumRows();
return 0;
}
static NSInteger getAccessibilityColumnCount (id self, SEL)
{
if (auto* tableInterface = getTableInterface (self))
return tableInterface->getNumColumns();
return 0;
}
static NSRange getAccessibilityRowIndexRange (id self, SEL)
{
if (auto* cellInterface = getCellInterface (self))
return NSMakeRange ((NSUInteger) cellInterface->getRowIndex(),
(NSUInteger) cellInterface->getRowSpan());
return NSMakeRange (0, 0);
}
static NSRange getAccessibilityColumnIndexRange (id self, SEL)
{
if (auto* cellInterface = getCellInterface (self))
return NSMakeRange ((NSUInteger) cellInterface->getColumnIndex(),
(NSUInteger) cellInterface->getColumnSpan());
return NSMakeRange (0, 0);
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibleObjCClass)
};
}

View File

@ -0,0 +1,301 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#define 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()
{
accessibilityElement->invalidateElement();
--providerCount;
if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
{
ComSmartPtr<IRawElementProviderSimple> provider;
accessibilityElement->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
uiaWrapper->disconnectProvider (provider);
if (providerCount == 0)
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 notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent eventType)
{
if (eventType == InternalAccessibilityEvent::elementCreated
|| eventType == InternalAccessibilityEvent::elementDestroyed)
{
if (auto* parent = handler.getParent())
sendAccessibilityAutomationEvent (*parent, UIA_LayoutInvalidatedEventId);
return;
}
auto event = [eventType]() -> EVENTID
{
switch (eventType)
{
case InternalAccessibilityEvent::focusChanged: return UIA_AutomationFocusChangedEventId;
case InternalAccessibilityEvent::windowOpened: return UIA_Window_WindowOpenedEventId;
case InternalAccessibilityEvent::windowClosed: return UIA_Window_WindowClosedEventId;
case InternalAccessibilityEvent::elementCreated:
case InternalAccessibilityEvent::elementDestroyed:
case InternalAccessibilityEvent::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);
}
auto event = [eventType]() -> EVENTID
{
switch (eventType)
{
case AccessibilityEvent::textSelectionChanged: return UIA_Text_TextSelectionChangedEventId;
case AccessibilityEvent::textChanged: return UIA_Text_TextChangedEventId;
case AccessibilityEvent::structureChanged: return UIA_StructureChangedEventId;
case AccessibilityEvent::rowSelectionChanged: return UIA_SelectionItem_ElementSelectedEventId;
case AccessibilityEvent::titleChanged:
case AccessibilityEvent::valueChanged: break;
}
return {};
}();
if (event != EVENTID{})
sendAccessibilityAutomationEvent (*this, event);
}
struct SpVoiceWrapper : public DeletedAtShutdown
{
SpVoiceWrapper()
{
auto hr = voice.CoCreateInstance (CLSID_SpVoice);
jassertquiet (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
{
long getUiaRootObjectId()
{
return static_cast<long> (UiaRootObjectId);
}
bool handleWmGetObject (AccessibilityHandler* handler, WPARAM wParam, LPARAM lParam, LRESULT* res)
{
if (isStartingUpOrShuttingDown() || (handler == nullptr || ! isHandlerValid (*handler)))
return false;
if (auto* 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;
}
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

View File

@ -0,0 +1,553 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
int AccessibilityNativeHandle::idCounter = 0;
//==============================================================================
static String getAutomationId (const AccessibilityHandler& handler)
{
auto result = handler.getTitle();
auto* parentComponent = handler.getComponent().getParentComponent();
while (parentComponent != nullptr)
{
if (auto* parentHandler = parentComponent->getAccessibilityHandler())
{
auto parentTitle = parentHandler->getTitle();
result << "." << (parentTitle.isNotEmpty() ? parentTitle : "<empty>");
}
parentComponent = parentComponent->getParentComponent();
}
return result;
}
static auto roleToControlTypeId (AccessibilityRole roleType)
{
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_HeaderItemControlTypeId;
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 (IRawElementProviderFragmentRoot) && ! isFragmentRoot()))
return E_NOINTERFACE;
return ComBaseClassHelper::QueryInterface (refId, result);
}
//==============================================================================
JUCE_COMRESULT AccessibilityNativeHandle::get_HostRawElementProvider (IRawElementProviderSimple** pRetVal)
{
return withCheckedComArgs (pRetVal, *this, [&]
{
if (isFragmentRoot())
if (auto* wrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
return wrapper->hostProviderFromHwnd ((HWND) accessibilityHandler.getComponent().getWindowHandle(), pRetVal);
return S_OK;
});
}
JUCE_COMRESULT AccessibilityNativeHandle::get_ProviderOptions (ProviderOptions* options)
{
if (options == nullptr)
return E_INVALIDARG;
*options = ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading;
return S_OK;
}
JUCE_COMRESULT AccessibilityNativeHandle::GetPatternProvider (PATTERNID pId, IUnknown** pRetVal)
{
return withCheckedComArgs (pRetVal, *this, [&]
{
*pRetVal = [&]() -> IUnknown*
{
const auto role = accessibilityHandler.getRole();
const auto fragmentRoot = isFragmentRoot();
switch (pId)
{
case UIA_WindowPatternId:
{
if (fragmentRoot)
return new UIAWindowProvider (this);
break;
}
case UIA_TransformPatternId:
{
if (fragmentRoot)
return new UIATransformProvider (this);
break;
}
case UIA_TextPatternId:
case UIA_TextPattern2Id:
{
if (accessibilityHandler.getTextInterface() != nullptr)
return new UIATextProvider (this);
break;
}
case UIA_ValuePatternId:
{
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.getActions().contains (AccessibilityActionType::toggle)
&& accessibilityHandler.getCurrentState().isCheckable())
{
return new UIAToggleProvider (this);
}
break;
}
case UIA_SelectionPatternId:
{
if (role == AccessibilityRole::list
|| role == AccessibilityRole::popupMenu
|| role == AccessibilityRole::tree)
{
return new UIASelectionProvider (this);
}
break;
}
case UIA_SelectionItemPatternId:
{
auto state = accessibilityHandler.getCurrentState();
if (state.isSelectable() || state.isMultiSelectable()
|| role == AccessibilityRole::radioButton)
{
return new UIASelectionItemProvider (this);
}
break;
}
case UIA_GridPatternId:
{
if (accessibilityHandler.getTableInterface() != nullptr)
return new UIAGridProvider (this);
break;
}
case UIA_GridItemPatternId:
{
if (accessibilityHandler.getCellInterface() != nullptr)
return new UIAGridItemProvider (this);
break;
}
case UIA_InvokePatternId:
{
if (accessibilityHandler.getActions().contains (AccessibilityActionType::press))
return new UIAInvokeProvider (this);
break;
}
case UIA_ExpandCollapsePatternId:
{
if (accessibilityHandler.getActions().contains (AccessibilityActionType::showMenu)
&& accessibilityHandler.getCurrentState().isExpandable())
return new UIAExpandCollapseProvider (this);
break;
}
}
return nullptr;
}();
return S_OK;
});
}
JUCE_COMRESULT AccessibilityNativeHandle::GetPropertyValue (PROPERTYID propertyId, VARIANT* pRetVal)
{
return withCheckedComArgs (pRetVal, *this, [&]
{
VariantHelpers::clear (pRetVal);
const auto fragmentRoot = isFragmentRoot();
const auto role = accessibilityHandler.getRole();
const auto state = accessibilityHandler.getCurrentState();
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 (! accessibilityHandler.isIgnored() && 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:
VariantHelpers::setString (getElementName(), pRetVal);
break;
case UIA_ProcessIdPropertyId:
VariantHelpers::setInt ((int) GetCurrentProcessId(), pRetVal);
break;
case UIA_NativeWindowHandlePropertyId:
if (fragmentRoot)
VariantHelpers::setInt ((int) (pointer_sized_int) accessibilityHandler.getComponent().getWindowHandle(), pRetVal);
break;
}
return S_OK;
});
}
//==============================================================================
JUCE_COMRESULT AccessibilityNativeHandle::Navigate (NavigateDirection direction, IRawElementProviderFragment** pRetVal)
{
return withCheckedComArgs (pRetVal, *this, [&]
{
auto* handler = [&]() -> AccessibilityHandler*
{
if (direction == NavigateDirection_Parent)
return accessibilityHandler.getParent();
if (direction == NavigateDirection_FirstChild
|| direction == NavigateDirection_LastChild)
{
auto children = accessibilityHandler.getChildren();
return children.empty() ? nullptr
: (direction == NavigateDirection_FirstChild ? children.front()
: children.back());
}
if (direction == NavigateDirection_NextSibling
|| direction == NavigateDirection_PreviousSibling)
{
if (auto* parent = accessibilityHandler.getParent())
{
const auto siblings = parent->getChildren();
const auto iter = std::find (siblings.cbegin(), siblings.cend(), &accessibilityHandler);
if (iter == siblings.end())
return nullptr;
if (direction == NavigateDirection_NextSibling && iter != std::prev (siblings.cend()))
return *std::next (iter);
if (direction == NavigateDirection_PreviousSibling && iter != siblings.cbegin())
return *std::prev (iter);
}
}
return nullptr;
}();
if (handler != nullptr)
if (auto* provider = handler->getNativeImplementation())
if (provider->isElementValid())
provider->QueryInterface (IID_PPV_ARGS (pRetVal));
return S_OK;
});
}
JUCE_COMRESULT AccessibilityNativeHandle::GetRuntimeId (SAFEARRAY** pRetVal)
{
return withCheckedComArgs (pRetVal, *this, [&]
{
if (! isFragmentRoot())
{
*pRetVal = SafeArrayCreateVector (VT_I4, 0, 2);
if (*pRetVal == nullptr)
return E_OUTOFMEMORY;
for (LONG i = 0; i < 2; ++i)
{
auto hr = SafeArrayPutElement (*pRetVal, &i, &rtid[(size_t) i]);
if (FAILED (hr))
return E_FAIL;
}
}
return S_OK;
});
}
JUCE_COMRESULT AccessibilityNativeHandle::get_BoundingRectangle (UiaRect* pRetVal)
{
return withCheckedComArgs (pRetVal, *this, [&]
{
auto bounds = Desktop::getInstance().getDisplays()
.logicalToPhysical (accessibilityHandler.getComponent().getScreenBounds());
pRetVal->left = bounds.getX();
pRetVal->top = bounds.getY();
pRetVal->width = bounds.getWidth();
pRetVal->height = bounds.getHeight();
return S_OK;
});
}
JUCE_COMRESULT AccessibilityNativeHandle::GetEmbeddedFragmentRoots (SAFEARRAY** pRetVal)
{
return withCheckedComArgs (pRetVal, *this, []
{
return S_OK;
});
}
JUCE_COMRESULT AccessibilityNativeHandle::SetFocus()
{
if (! isElementValid())
return (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 (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, IRawElementProviderFragment** pRetVal)
{
return withCheckedComArgs (pRetVal, *this, [&]
{
auto* handler = [&]
{
auto logicalScreenPoint = Desktop::getInstance().getDisplays()
.physicalToLogical (Point<int> (roundToInt (x),
roundToInt (y)));
if (auto* child = accessibilityHandler.getChildAt (logicalScreenPoint))
return child;
return &accessibilityHandler;
}();
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
return S_OK;
});
}
JUCE_COMRESULT AccessibilityNativeHandle::GetFocus (IRawElementProviderFragment** pRetVal)
{
return withCheckedComArgs (pRetVal, *this, [&]
{
const auto getFocusHandler = [this]() -> AccessibilityHandler*
{
if (auto* modal = Component::getCurrentlyModalComponent())
{
const auto& component = accessibilityHandler.getComponent();
if (! component.isParentOf (modal)
&& component.isCurrentlyBlockedByAnotherModalComponent())
{
if (auto* modalHandler = modal->getAccessibilityHandler())
{
if (auto* focusChild = modalHandler->getChildFocus())
return focusChild;
return modalHandler;
}
}
}
if (auto* focusChild = accessibilityHandler.getChildFocus())
return focusChild;
return nullptr;
};
if (auto* focusHandler = getFocusHandler())
focusHandler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
return S_OK;
});
}
//==============================================================================
String AccessibilityNativeHandle::getElementName() const
{
if (accessibilityHandler.getRole() == AccessibilityRole::tooltip)
return accessibilityHandler.getDescription();
auto name = accessibilityHandler.getTitle();
if (name.isEmpty() && isFragmentRoot())
return getAccessibleApplicationOrPluginName();
return name;
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
} // namespace juce

View File

@ -0,0 +1,80 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#define UIA_FullDescriptionPropertyId 30159
#define UIA_IsDialogPropertyId 30174
class AccessibilityNativeHandle : public ComBaseClassHelper<IRawElementProviderSimple,
IRawElementProviderFragment,
IRawElementProviderFragmentRoot>
{
public:
explicit AccessibilityNativeHandle (AccessibilityHandler& handler);
//==============================================================================
void invalidateElement() noexcept { valid = false; }
bool isElementValid() const noexcept { return valid; }
const AccessibilityHandler& getHandler() { return accessibilityHandler; }
//==============================================================================
JUCE_COMRESULT QueryInterface (REFIID refId, void** result) override;
//==============================================================================
JUCE_COMRESULT get_HostRawElementProvider (IRawElementProviderSimple** provider) override;
JUCE_COMRESULT get_ProviderOptions (ProviderOptions* options) override;
JUCE_COMRESULT GetPatternProvider (PATTERNID pId, IUnknown** provider) override;
JUCE_COMRESULT GetPropertyValue (PROPERTYID propertyId, VARIANT* pRetVal) override;
JUCE_COMRESULT Navigate (NavigateDirection direction, IRawElementProviderFragment** pRetVal) override;
JUCE_COMRESULT GetRuntimeId (SAFEARRAY** pRetVal) override;
JUCE_COMRESULT get_BoundingRectangle (UiaRect* pRetVal) override;
JUCE_COMRESULT GetEmbeddedFragmentRoots (SAFEARRAY** pRetVal) override;
JUCE_COMRESULT SetFocus() override;
JUCE_COMRESULT get_FragmentRoot (IRawElementProviderFragmentRoot** pRetVal) override;
JUCE_COMRESULT ElementProviderFromPoint (double x, double y, IRawElementProviderFragment** pRetVal) override;
JUCE_COMRESULT GetFocus (IRawElementProviderFragment** pRetVal) override;
private:
//==============================================================================
String getElementName() const;
bool isFragmentRoot() const { return accessibilityHandler.getComponent().isOnDesktop(); }
//==============================================================================
AccessibilityHandler& accessibilityHandler;
static int idCounter;
std::array<int, 2> rtid { UiaAppendRuntimeId, ++idCounter };
bool valid = true;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeHandle)
};
}

View File

@ -0,0 +1,86 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class UIAExpandCollapseProvider : public UIAProviderBase,
public ComBaseClassHelper<IExpandCollapseProvider>
{
public:
explicit UIAExpandCollapseProvider (AccessibilityNativeHandle* nativeHandle)
: UIAProviderBase (nativeHandle)
{
}
//==============================================================================
JUCE_COMRESULT Expand() override
{
return invokeShowMenu();
}
JUCE_COMRESULT Collapse() override
{
return invokeShowMenu();
}
JUCE_COMRESULT get_ExpandCollapseState (ExpandCollapseState* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
*pRetVal = getHandler().getCurrentState().isExpanded()
? ExpandCollapseState_Expanded
: ExpandCollapseState_Collapsed;
return S_OK;
});
}
private:
JUCE_COMRESULT invokeShowMenu()
{
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
const auto& handler = getHandler();
if (handler.getActions().invoke (AccessibilityActionType::showMenu))
{
sendAccessibilityAutomationEvent (handler, handler.getCurrentState().isExpanded()
? UIA_MenuOpenedEventId
: UIA_MenuClosedEventId);
return S_OK;
}
return (HRESULT) UIA_E_NOTSUPPORTED;
}
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAExpandCollapseProvider)
};
} // namespace juce

View File

@ -0,0 +1,105 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class UIAGridItemProvider : public UIAProviderBase,
public ComBaseClassHelper<IGridItemProvider>
{
public:
explicit UIAGridItemProvider (AccessibilityNativeHandle* nativeHandle)
: UIAProviderBase (nativeHandle)
{
}
//==============================================================================
JUCE_COMRESULT get_Row (int* pRetVal) override
{
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
{
*pRetVal = cellInterface.getRowIndex();
});
}
JUCE_COMRESULT get_Column (int* pRetVal) override
{
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
{
*pRetVal = cellInterface.getColumnIndex();
});
}
JUCE_COMRESULT get_RowSpan (int* pRetVal) override
{
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
{
*pRetVal = cellInterface.getRowSpan();
});
}
JUCE_COMRESULT get_ColumnSpan (int* pRetVal) override
{
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
{
*pRetVal = cellInterface.getColumnSpan();
});
}
JUCE_COMRESULT get_ContainingGrid (IRawElementProviderSimple** pRetVal) override
{
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
if (auto* handler = cellInterface.getTableHandler())
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
});
}
private:
template <typename Value, typename Callback>
JUCE_COMRESULT withCellInterface (Value* pRetVal, Callback&& callback) const
{
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
{
if (auto* cellInterface = getHandler().getCellInterface())
{
callback (*cellInterface);
return S_OK;
}
return (HRESULT) UIA_E_NOTSUPPORTED;
});
}
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAGridItemProvider)
};
} // namespace juce

View File

@ -0,0 +1,94 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class UIAGridProvider : public UIAProviderBase,
public ComBaseClassHelper<IGridProvider>
{
public:
explicit UIAGridProvider (AccessibilityNativeHandle* nativeHandle)
: UIAProviderBase (nativeHandle)
{
}
//==============================================================================
JUCE_COMRESULT GetItem (int row, int column, IRawElementProviderSimple** pRetVal) override
{
return withTableInterface (pRetVal, [&] (const AccessibilityTableInterface& tableInterface)
{
if (! isPositiveAndBelow (row, tableInterface.getNumRows())
|| ! isPositiveAndBelow (column, tableInterface.getNumColumns()))
return E_INVALIDARG;
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
if (auto* handler = tableInterface.getCellHandler (row, column))
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
return S_OK;
});
}
JUCE_COMRESULT get_RowCount (int* pRetVal) override
{
return withTableInterface (pRetVal, [&] (const AccessibilityTableInterface& tableInterface)
{
*pRetVal = tableInterface.getNumRows();
return S_OK;
});
}
JUCE_COMRESULT get_ColumnCount (int* pRetVal) override
{
return withTableInterface (pRetVal, [&] (const AccessibilityTableInterface& tableInterface)
{
*pRetVal = tableInterface.getNumColumns();
return S_OK;
});
}
private:
template <typename Value, typename Callback>
JUCE_COMRESULT withTableInterface (Value* pRetVal, Callback&& callback) const
{
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
{
if (auto* tableInterface = getHandler().getTableInterface())
return callback (*tableInterface);
return (HRESULT) UIA_E_NOTSUPPORTED;
});
}
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAGridProvider)
};
} // namespace juce

View File

@ -0,0 +1,105 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
namespace VariantHelpers
{
inline void clear (VARIANT* variant)
{
variant->vt = VT_EMPTY;
}
inline void setInt (int value, VARIANT* variant)
{
variant->vt = VT_I4;
variant->lVal = value;
}
inline void setBool (bool value, VARIANT* variant)
{
variant->vt = VT_BOOL;
variant->boolVal = value ? -1 : 0;
}
inline void setString (const String& value, VARIANT* variant)
{
variant->vt = VT_BSTR;
variant->bstrVal = SysAllocString ((const OLECHAR*) value.toWideCharPointer());
}
inline void setDouble (double value, VARIANT* variant)
{
variant->vt = VT_R8;
variant->dblVal = value;
}
}
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

View File

@ -0,0 +1,62 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class UIAInvokeProvider : public UIAProviderBase,
public ComBaseClassHelper<IInvokeProvider>
{
public:
explicit UIAInvokeProvider (AccessibilityNativeHandle* nativeHandle)
: UIAProviderBase (nativeHandle)
{
}
//==============================================================================
JUCE_COMRESULT Invoke() override
{
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
const auto& handler = getHandler();
if (handler.getActions().invoke (AccessibilityActionType::press))
{
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

View File

@ -0,0 +1,58 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class UIAProviderBase
{
public:
explicit UIAProviderBase (AccessibilityNativeHandle* nativeHandleIn)
: nativeHandle (nativeHandleIn)
{
}
bool isElementValid() const
{
if (nativeHandle != nullptr)
return nativeHandle->isElementValid();
return false;
}
const AccessibilityHandler& getHandler() const
{
return nativeHandle->getHandler();
}
private:
ComSmartPtr<AccessibilityNativeHandle> nativeHandle;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAProviderBase)
};
} // namespace juce

View File

@ -0,0 +1,43 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
void sendAccessibilityAutomationEvent (const AccessibilityHandler&, EVENTID);
void sendAccessibilityPropertyChangedEvent (const AccessibilityHandler&, PROPERTYID, VARIANT);
}
#include "juce_win32_UIAProviderBase.h"
#include "juce_win32_UIAExpandCollapseProvider.h"
#include "juce_win32_UIAGridItemProvider.h"
#include "juce_win32_UIAGridProvider.h"
#include "juce_win32_UIAInvokeProvider.h"
#include "juce_win32_UIARangeValueProvider.h"
#include "juce_win32_UIASelectionProvider.h"
#include "juce_win32_UIATextProvider.h"
#include "juce_win32_UIAToggleProvider.h"
#include "juce_win32_UIATransformProvider.h"
#include "juce_win32_UIAValueProvider.h"
#include "juce_win32_UIAWindowProvider.h"

View File

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

View File

@ -0,0 +1,256 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
JUCE_COMCLASS (ISelectionProvider2, "14f68475-ee1c-44f6-a869-d239381f0fe7") : public ISelectionProvider
{
JUCE_COMCALL get_FirstSelectedItem (IRawElementProviderSimple** retVal) = 0;
JUCE_COMCALL get_LastSelectedItem (IRawElementProviderSimple** retVal) = 0;
JUCE_COMCALL get_CurrentSelectedItem (IRawElementProviderSimple** retVal) = 0;
JUCE_COMCALL get_ItemCount (int* retVal) = 0;
};
//==============================================================================
class UIASelectionItemProvider : public UIAProviderBase,
public ComBaseClassHelper<ISelectionItemProvider>
{
public:
explicit UIASelectionItemProvider (AccessibilityNativeHandle* nativeHandle)
: UIAProviderBase (nativeHandle),
isRadioButton (getHandler().getRole() == AccessibilityRole::radioButton)
{
}
//==============================================================================
JUCE_COMRESULT AddToSelection() override
{
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
const auto& handler = getHandler();
if (isRadioButton)
{
handler.getActions().invoke (AccessibilityActionType::press);
sendAccessibilityAutomationEvent (handler, UIA_SelectionItem_ElementSelectedEventId);
return S_OK;
}
handler.getActions().invoke (AccessibilityActionType::toggle);
handler.getActions().invoke (AccessibilityActionType::press);
return S_OK;
}
JUCE_COMRESULT get_IsSelected (BOOL* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
const auto state = getHandler().getCurrentState();
*pRetVal = isRadioButton ? state.isChecked() : state.isSelected();
return S_OK;
});
}
JUCE_COMRESULT get_SelectionContainer (IRawElementProviderSimple** pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
if (! isRadioButton)
if (auto* parent = getHandler().getParent())
parent->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
return S_OK;
});
}
JUCE_COMRESULT RemoveFromSelection() override
{
if (! isElementValid())
return (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<ISelectionProvider2>
{
public:
explicit UIASelectionProvider (AccessibilityNativeHandle* nativeHandle)
: UIAProviderBase (nativeHandle)
{
}
//==============================================================================
JUCE_COMRESULT QueryInterface (REFIID iid, void** result) override
{
if (iid == _uuidof (IUnknown) || iid == _uuidof (ISelectionProvider))
return castToType<ISelectionProvider> (result);
if (iid == _uuidof (ISelectionProvider2))
return castToType<ISelectionProvider2> (result);
*result = nullptr;
return E_NOINTERFACE;
}
//==============================================================================
JUCE_COMRESULT get_CanSelectMultiple (BOOL* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
*pRetVal = isMultiSelectable();
return S_OK;
});
}
JUCE_COMRESULT get_IsSelectionRequired (BOOL* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
*pRetVal = getSelectedChildren().size() > 0 && ! isMultiSelectable();
return S_OK;
});
}
JUCE_COMRESULT GetSelection (SAFEARRAY** pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
return addHandlersToArray (getSelectedChildren(), pRetVal);
});
}
//==============================================================================
JUCE_COMRESULT get_FirstSelectedItem (IRawElementProviderSimple** pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
auto selectedChildren = getSelectedChildren();
if (! selectedChildren.empty())
selectedChildren.front()->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
return S_OK;
});
}
JUCE_COMRESULT get_LastSelectedItem (IRawElementProviderSimple** pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
auto selectedChildren = getSelectedChildren();
if (! selectedChildren.empty())
selectedChildren.back()->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
return S_OK;
});
}
JUCE_COMRESULT get_CurrentSelectedItem (IRawElementProviderSimple** pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
get_FirstSelectedItem (pRetVal);
return S_OK;
});
}
JUCE_COMRESULT get_ItemCount (int* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
*pRetVal = (int) getSelectedChildren().size();
return S_OK;
});
}
private:
bool isMultiSelectable() const noexcept
{
return getHandler().getCurrentState().isMultiSelectable();
}
std::vector<const AccessibilityHandler*> getSelectedChildren() const
{
std::vector<const AccessibilityHandler*> selectedHandlers;
for (auto* child : getHandler().getComponent().getChildren())
if (auto* handler = child->getAccessibilityHandler())
if (handler->getCurrentState().isSelected())
selectedHandlers.push_back (handler);
return selectedHandlers;
}
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIASelectionProvider)
};
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
} // namespace juce

View File

@ -0,0 +1,592 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class UIATextProvider : public UIAProviderBase,
public ComBaseClassHelper<ITextProvider2>
{
public:
explicit UIATextProvider (AccessibilityNativeHandle* nativeHandle)
: UIAProviderBase (nativeHandle)
{
}
//==============================================================================
JUCE_COMRESULT QueryInterface (REFIID iid, void** result) override
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
if (iid == _uuidof (IUnknown) || iid == _uuidof (ITextProvider))
return castToType<ITextProvider> (result);
if (iid == _uuidof (ITextProvider2))
return castToType<ITextProvider2> (result);
*result = nullptr;
return E_NOINTERFACE;
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
//=============================================================================
JUCE_COMRESULT get_DocumentRange (ITextRangeProvider** pRetVal) override
{
return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
{
*pRetVal = new UIATextRangeProvider (*this, { 0, textInterface.getTotalNumCharacters() });
return S_OK;
});
}
JUCE_COMRESULT get_SupportedTextSelection (SupportedTextSelection* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
*pRetVal = SupportedTextSelection_Single;
return S_OK;
});
}
JUCE_COMRESULT GetSelection (SAFEARRAY** pRetVal) override
{
return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
{
*pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 1);
if (pRetVal != nullptr)
{
auto selection = textInterface.getSelection();
auto hasSelection = ! selection.isEmpty();
auto cursorPos = textInterface.getTextInsertionOffset();
auto* rangeProvider = new UIATextRangeProvider (*this,
{ hasSelection ? selection.getStart() : cursorPos,
hasSelection ? selection.getEnd() : cursorPos });
LONG pos = 0;
auto hr = SafeArrayPutElement (*pRetVal, &pos, static_cast<IUnknown*> (rangeProvider));
if (FAILED (hr))
return E_FAIL;
rangeProvider->Release();
}
return S_OK;
});
}
JUCE_COMRESULT GetVisibleRanges (SAFEARRAY** pRetVal) override
{
return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
{
*pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 1);
if (pRetVal != nullptr)
{
auto* rangeProvider = new UIATextRangeProvider (*this, { 0, textInterface.getTotalNumCharacters() });
LONG pos = 0;
auto hr = SafeArrayPutElement (*pRetVal, &pos, static_cast<IUnknown*> (rangeProvider));
if (FAILED (hr))
return E_FAIL;
rangeProvider->Release();
}
return S_OK;
});
}
JUCE_COMRESULT RangeFromChild (IRawElementProviderSimple*, ITextRangeProvider** pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, []
{
return S_OK;
});
}
JUCE_COMRESULT RangeFromPoint (UiaPoint point, ITextRangeProvider** pRetVal) override
{
return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
{
auto offset = textInterface.getOffsetAtPoint ({ roundToInt (point.x), roundToInt (point.y) });
if (offset > 0)
*pRetVal = new UIATextRangeProvider (*this, { offset, offset });
return S_OK;
});
}
//==============================================================================
JUCE_COMRESULT GetCaretRange (BOOL* isActive, ITextRangeProvider** pRetVal) override
{
return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
{
*isActive = getHandler().hasFocus (false);
auto cursorPos = textInterface.getTextInsertionOffset();
*pRetVal = new UIATextRangeProvider (*this, { cursorPos, cursorPos });
return S_OK;
});
}
JUCE_COMRESULT RangeFromAnnotation (IRawElementProviderSimple*, ITextRangeProvider** pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, []
{
return S_OK;
});
}
private:
//==============================================================================
template <typename Value, typename Callback>
JUCE_COMRESULT withTextInterface (Value* pRetVal, Callback&& callback) const
{
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
{
if (auto* textInterface = getHandler().getTextInterface())
return callback (*textInterface);
return (HRESULT) UIA_E_NOTSUPPORTED;
});
}
//==============================================================================
class UIATextRangeProvider : public UIAProviderBase,
public ComBaseClassHelper<ITextRangeProvider>
{
public:
UIATextRangeProvider (UIATextProvider& textProvider, Range<int> range)
: UIAProviderBase (textProvider.getHandler().getNativeImplementation()),
owner (&textProvider),
selectionRange (range)
{
}
//==============================================================================
Range<int> getSelectionRange() const noexcept { return selectionRange; }
//==============================================================================
JUCE_COMRESULT AddToSelection() override
{
return Select();
}
JUCE_COMRESULT Clone (ITextRangeProvider** pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
*pRetVal = new UIATextRangeProvider (*owner, selectionRange);
return S_OK;
});
}
JUCE_COMRESULT Compare (ITextRangeProvider* range, BOOL* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
*pRetVal = (selectionRange == static_cast<UIATextRangeProvider*> (range)->getSelectionRange());
return S_OK;
});
}
JUCE_COMRESULT CompareEndpoints (TextPatternRangeEndpoint endpoint,
ITextRangeProvider* targetRange,
TextPatternRangeEndpoint targetEndpoint,
int* pRetVal) override
{
if (targetRange == nullptr)
return E_INVALIDARG;
return withCheckedComArgs (pRetVal, *this, [&]
{
auto offset = (endpoint == TextPatternRangeEndpoint_Start ? selectionRange.getStart()
: selectionRange.getEnd());
auto otherRange = static_cast<UIATextRangeProvider*> (targetRange)->getSelectionRange();
auto otherOffset = (targetEndpoint == TextPatternRangeEndpoint_Start ? otherRange.getStart()
: otherRange.getEnd());
*pRetVal = offset - otherOffset;
return S_OK;
});
}
JUCE_COMRESULT ExpandToEnclosingUnit (TextUnit unit) override
{
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
if (auto* textInterface = owner->getHandler().getTextInterface())
{
const auto boundaryType = getBoundaryType (unit);
const auto start = AccessibilityTextHelpers::findTextBoundary (*textInterface,
selectionRange.getStart(),
boundaryType,
AccessibilityTextHelpers::Direction::backwards);
const auto end = AccessibilityTextHelpers::findTextBoundary (*textInterface,
start,
boundaryType,
AccessibilityTextHelpers::Direction::forwards);
selectionRange = Range<int> (start, end);
return S_OK;
}
return (HRESULT) UIA_E_NOTSUPPORTED;
}
JUCE_COMRESULT FindAttribute (TEXTATTRIBUTEID, VARIANT, BOOL, ITextRangeProvider** pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, []
{
return S_OK;
});
}
JUCE_COMRESULT FindText (BSTR text, BOOL backward, BOOL ignoreCase,
ITextRangeProvider** pRetVal) override
{
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
{
auto selectionText = textInterface.getText (selectionRange);
String textToSearchFor (text);
auto offset = (backward ? (ignoreCase ? selectionText.lastIndexOfIgnoreCase (textToSearchFor) : selectionText.lastIndexOf (textToSearchFor))
: (ignoreCase ? selectionText.indexOfIgnoreCase (textToSearchFor) : selectionText.indexOf (textToSearchFor)));
if (offset != -1)
*pRetVal = new UIATextRangeProvider (*owner, { offset, offset + textToSearchFor.length() });
return S_OK;
});
}
JUCE_COMRESULT GetAttributeValue (TEXTATTRIBUTEID attributeId, VARIANT* pRetVal) override
{
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
{
VariantHelpers::clear (pRetVal);
switch (attributeId)
{
case UIA_IsReadOnlyAttributeId:
{
VariantHelpers::setBool (textInterface.isReadOnly(), pRetVal);
break;
}
case UIA_CaretPositionAttributeId:
{
auto cursorPos = textInterface.getTextInsertionOffset();
auto caretPos = [&]
{
if (cursorPos == 0)
return CaretPosition_BeginningOfLine;
if (cursorPos == textInterface.getTotalNumCharacters())
return CaretPosition_EndOfLine;
return CaretPosition_Unknown;
}();
VariantHelpers::setInt (caretPos, pRetVal);
break;
}
}
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 (TextUnit unit, int count, int* pRetVal) override
{
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface&)
{
if (count > 0)
{
MoveEndpointByUnit (TextPatternRangeEndpoint_End, unit, count, pRetVal);
MoveEndpointByUnit (TextPatternRangeEndpoint_Start, unit, count, pRetVal);
}
else if (count < 0)
{
MoveEndpointByUnit (TextPatternRangeEndpoint_Start, unit, count, pRetVal);
MoveEndpointByUnit (TextPatternRangeEndpoint_End, unit, count, pRetVal);
}
return S_OK;
});
}
JUCE_COMRESULT MoveEndpointByRange (TextPatternRangeEndpoint endpoint,
ITextRangeProvider* targetRange,
TextPatternRangeEndpoint targetEndpoint) override
{
if (targetRange == nullptr)
return E_INVALIDARG;
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
if (auto* textInterface = owner->getHandler().getTextInterface())
{
auto otherRange = static_cast<UIATextRangeProvider*> (targetRange)->getSelectionRange();
auto targetPoint = (targetEndpoint == TextPatternRangeEndpoint_Start ? otherRange.getStart()
: otherRange.getEnd());
setEndpointChecked (endpoint, targetPoint);
return S_OK;
}
return (HRESULT) UIA_E_NOTSUPPORTED;
}
JUCE_COMRESULT MoveEndpointByUnit (TextPatternRangeEndpoint endpoint,
TextUnit unit,
int count,
int* pRetVal) override
{
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
{
if (count == 0 || textInterface.getTotalNumCharacters() == 0)
return S_OK;
auto endpointToMove = (endpoint == TextPatternRangeEndpoint_Start ? selectionRange.getStart()
: selectionRange.getEnd());
const auto direction = (count > 0 ? AccessibilityTextHelpers::Direction::forwards
: AccessibilityTextHelpers::Direction::backwards);
const auto boundaryType = getBoundaryType (unit);
// handle case where endpoint is on a boundary
if (AccessibilityTextHelpers::findTextBoundary (textInterface, endpointToMove, boundaryType, direction) == endpointToMove)
endpointToMove += (direction == AccessibilityTextHelpers::Direction::forwards ? 1 : -1);
int numMoved;
for (numMoved = 0; numMoved < std::abs (count); ++numMoved)
{
auto nextEndpoint = AccessibilityTextHelpers::findTextBoundary (textInterface,
endpointToMove,
boundaryType,
direction);
if (nextEndpoint == endpointToMove)
break;
endpointToMove = nextEndpoint;
}
*pRetVal = numMoved;
setEndpointChecked (endpoint, endpointToMove);
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 (TextUnit unit)
{
switch (unit)
{
case TextUnit_Character:
return AccessibilityTextHelpers::BoundaryType::character;
case TextUnit_Format:
case TextUnit_Word:
return AccessibilityTextHelpers::BoundaryType::word;
case TextUnit_Line:
return AccessibilityTextHelpers::BoundaryType::line;
case TextUnit_Paragraph:
case TextUnit_Page:
case TextUnit_Document:
return AccessibilityTextHelpers::BoundaryType::document;
};
jassertfalse;
return AccessibilityTextHelpers::BoundaryType::character;
}
void setEndpointChecked (TextPatternRangeEndpoint endpoint, int newEndpoint)
{
if (endpoint == TextPatternRangeEndpoint_Start)
{
if (selectionRange.getEnd() < newEndpoint)
selectionRange.setEnd (newEndpoint);
selectionRange.setStart (newEndpoint);
}
else
{
if (selectionRange.getStart() > newEndpoint)
selectionRange.setStart (newEndpoint);
selectionRange.setEnd (newEndpoint);
}
}
ComSmartPtr<UIATextProvider> owner;
Range<int> selectionRange;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATextRangeProvider)
};
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATextProvider)
};
} // namespace juce

View File

@ -0,0 +1,80 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class UIAToggleProvider : public UIAProviderBase,
public ComBaseClassHelper<IToggleProvider>
{
public:
explicit UIAToggleProvider (AccessibilityNativeHandle* nativeHandle)
: UIAProviderBase (nativeHandle)
{
}
//==============================================================================
JUCE_COMRESULT Toggle() override
{
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
const auto& handler = getHandler();
if (handler.getActions().invoke (AccessibilityActionType::toggle))
{
VARIANT newValue;
VariantHelpers::setInt (getCurrentToggleState(), &newValue);
sendAccessibilityPropertyChangedEvent (handler, UIA_ToggleToggleStatePropertyId, newValue);
return S_OK;
}
return (HRESULT) UIA_E_NOTSUPPORTED;
}
JUCE_COMRESULT get_ToggleState (ToggleState* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]
{
*pRetVal = getCurrentToggleState();
return S_OK;
});
}
private:
ToggleState getCurrentToggleState() const
{
return getHandler().getCurrentState().isChecked() ? ToggleState_On
: ToggleState_Off;
}
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAToggleProvider)
};
} // namespace juce

View File

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

View File

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

View File

@ -0,0 +1,197 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class UIAWindowProvider : public UIAProviderBase,
public ComBaseClassHelper<IWindowProvider>
{
public:
explicit UIAWindowProvider (AccessibilityNativeHandle* nativeHandle)
: UIAProviderBase (nativeHandle)
{
}
//==============================================================================
JUCE_COMRESULT SetVisualState (WindowVisualState state) override
{
if (! isElementValid())
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
if (auto* peer = getPeer())
{
switch (state)
{
case WindowVisualState_Maximized:
peer->setFullScreen (true);
break;
case WindowVisualState_Minimized:
peer->setMinimised (true);
break;
case WindowVisualState_Normal:
peer->setFullScreen (false);
peer->setMinimised (false);
break;
default:
break;
}
return S_OK;
}
return (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 (WindowVisualState* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
{
if (auto* peer = getPeer())
{
if (peer->isFullScreen())
*pRetVal = WindowVisualState_Maximized;
else if (peer->isMinimised())
*pRetVal = WindowVisualState_Minimized;
else
*pRetVal = WindowVisualState_Normal;
return S_OK;
}
return (HRESULT) UIA_E_NOTSUPPORTED;
});
}
JUCE_COMRESULT get_WindowInteractionState (WindowInteractionState* pRetVal) override
{
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
{
if (auto* peer = getPeer())
{
*pRetVal = peer->getComponent().isCurrentlyBlockedByAnotherModalComponent()
? WindowInteractionState::WindowInteractionState_BlockedByModalWindow
: WindowInteractionState::WindowInteractionState_Running;
return S_OK;
}
return (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

View File

@ -0,0 +1,158 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
class WindowsUIAWrapper : public DeletedAtShutdown
{
public:
bool isLoaded() const noexcept
{
return uiaReturnRawElementProvider != nullptr
&& uiaHostProviderFromHwnd != nullptr
&& uiaRaiseAutomationPropertyChangedEvent != nullptr
&& uiaRaiseAutomationEvent != nullptr
&& uiaClientsAreListening != nullptr
&& uiaDisconnectProvider != nullptr
&& uiaDisconnectAllProviders != nullptr;
}
//==============================================================================
LRESULT returnRawElementProvider (HWND hwnd, WPARAM wParam, LPARAM lParam, IRawElementProviderSimple* provider)
{
return uiaReturnRawElementProvider != nullptr ? uiaReturnRawElementProvider (hwnd, wParam, lParam, provider)
: (LRESULT) nullptr;
}
JUCE_COMRESULT hostProviderFromHwnd (HWND hwnd, IRawElementProviderSimple** provider)
{
return uiaHostProviderFromHwnd != nullptr ? uiaHostProviderFromHwnd (hwnd, provider)
: (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, StringRef funcName)
{
return (FuncType) GetProcAddress (module, funcName);
}
//==============================================================================
using UiaReturnRawElementProviderFunc = LRESULT (WINAPI*) (HWND, WPARAM, LPARAM, IRawElementProviderSimple*);
using UiaHostProviderFromHwndFunc = HRESULT (WINAPI*) (HWND, IRawElementProviderSimple**);
using UiaRaiseAutomationPropertyChangedEventFunc = HRESULT (WINAPI*) (IRawElementProviderSimple*, PROPERTYID, VARIANT, VARIANT);
using UiaRaiseAutomationEventFunc = HRESULT (WINAPI*) (IRawElementProviderSimple*, EVENTID);
using UiaClientsAreListeningFunc = BOOL (WINAPI*) ();
using UiaDisconnectProviderFunc = HRESULT (WINAPI*) (IRawElementProviderSimple*);
using UiaDisconnectAllProvidersFunc = HRESULT (WINAPI*) ();
HMODULE uiaHandle = ::LoadLibraryA ("UIAutomationCore.dll");
UiaReturnRawElementProviderFunc uiaReturnRawElementProvider = getUiaFunction<UiaReturnRawElementProviderFunc> (uiaHandle, "UiaReturnRawElementProvider");
UiaHostProviderFromHwndFunc uiaHostProviderFromHwnd = getUiaFunction<UiaHostProviderFromHwndFunc> (uiaHandle, "UiaHostProviderFromHwnd");
UiaRaiseAutomationPropertyChangedEventFunc uiaRaiseAutomationPropertyChangedEvent = getUiaFunction<UiaRaiseAutomationPropertyChangedEventFunc> (uiaHandle, "UiaRaiseAutomationPropertyChangedEvent");
UiaRaiseAutomationEventFunc uiaRaiseAutomationEvent = getUiaFunction<UiaRaiseAutomationEventFunc> (uiaHandle, "UiaRaiseAutomationEvent");
UiaClientsAreListeningFunc uiaClientsAreListening = getUiaFunction<UiaClientsAreListeningFunc> (uiaHandle, "UiaClientsAreListening");
UiaDisconnectProviderFunc uiaDisconnectProvider = getUiaFunction<UiaDisconnectProviderFunc> (uiaHandle, "UiaDisconnectProvider");
UiaDisconnectAllProvidersFunc uiaDisconnectAllProviders = getUiaFunction<UiaDisconnectAllProvidersFunc> (uiaHandle, "UiaDisconnectAllProviders");
IRawElementProviderSimple* disconnectingProvider = nullptr;
bool disconnectingAllProviders = false;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsUIAWrapper)
};
} // namespace juce