1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-02-03 03:30:06 +00:00

Accessibility: Improve table navigation, row/column index/header reporting

This commit is contained in:
reuk 2022-06-30 21:23:32 +01:00
parent dd92f66387
commit 921d86e586
No known key found for this signature in database
GPG key ID: FCB43929F012EE5C
16 changed files with 775 additions and 204 deletions

View file

@ -39,18 +39,6 @@ public:
/** Destructor. */
virtual ~AccessibilityCellInterface() = default;
/** Returns the column index of the cell in the table. */
virtual int getColumnIndex() const = 0;
/** Returns the number of columns occupied by the cell in the table. */
virtual int getColumnSpan() const = 0;
/** Returns the row index of the cell in the table. */
virtual int getRowIndex() const = 0;
/** Returns the number of rows occupied by the cell in the table. */
virtual int getRowSpan() const = 0;
/** Returns the indentation level for the cell. */
virtual int getDisclosureLevel() const = 0;

View file

@ -64,6 +64,33 @@ public:
as there are columns in the table.
*/
virtual const AccessibilityHandler* getHeaderHandler() const = 0;
struct Span { int begin, num; };
/** Given the handler of one of the cells in the table, returns the rows covered
by that cell, or null if the cell does not exist in the table.
This function replaces the getRowIndex and getRowSpan
functions from AccessibilityCellInterface. Most of the time, it's easier for the
table itself to keep track of cell locations, than to delegate to the individual
cells.
*/
virtual Optional<Span> getRowSpan (const AccessibilityHandler&) const = 0;
/** Given the handler of one of the cells in the table, returns the columns covered
by that cell, or null if the cell does not exist in the table.
This function replaces the getColumnIndex and getColumnSpan
functions from AccessibilityCellInterface. Most of the time, it's easier for the
table itself to keep track of cell locations, than to delegate to the individual
cells.
*/
virtual Optional<Span> getColumnSpan (const AccessibilityHandler&) const = 0;
/** Attempts to scroll the table (if necessary) so that the cell with the given handler
is visible.
*/
virtual void showCell (const AccessibilityHandler&) const = 0;
};
} // namespace juce

View file

@ -127,6 +127,18 @@ namespace juce
ScaledImage image;
Point<int> hotspot;
};
template <typename MemberFn>
static const AccessibilityHandler* getEnclosingHandlerWithInterface (const AccessibilityHandler* handler, MemberFn fn)
{
if (handler == nullptr)
return nullptr;
if ((handler->*fn)() != nullptr)
return handler;
return getEnclosingHandlerWithInterface (handler->getParent(), fn);
}
} // namespace juce
#include "mouse/juce_PointerState.h"

View file

@ -56,6 +56,25 @@ namespace juce
DECLARE_JNI_CLASS (AndroidAccessibilityNodeInfo, "android/view/accessibility/AccessibilityNodeInfo")
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (setCollectionInfo, "setCollectionInfo", "(Landroid/view/accessibility/AccessibilityNodeInfo$CollectionInfo;)V") \
METHOD (setCollectionItemInfo, "setCollectionItemInfo", "(Landroid/view/accessibility/AccessibilityNodeInfo$CollectionItemInfo;)V")
DECLARE_JNI_CLASS (AndroidAccessibilityNodeInfo19, "android/view/accessibility/AccessibilityNodeInfo")
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
STATICMETHOD (obtain, "obtain", "(IIZ)Landroid/view/accessibility/AccessibilityNodeInfo$CollectionInfo;")
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidAccessibilityNodeInfoCollectionInfo, "android/view/accessibility/AccessibilityNodeInfo$CollectionInfo", 19)
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
STATICMETHOD (obtain, "obtain", "(IIIIZ)Landroid/view/accessibility/AccessibilityNodeInfo$CollectionItemInfo;")
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidAccessibilityNodeInfoCollectionItemInfo, "android/view/accessibility/AccessibilityNodeInfo$CollectionItemInfo", 19)
#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") \
@ -124,6 +143,18 @@ static jmethodID nodeInfoSetTextSelection = nullptr;
static jmethodID nodeInfoSetLiveRegion = nullptr;
static jmethodID accessibilityEventSetContentChangeTypes = nullptr;
template <typename MemberFn>
static AccessibilityHandler* getEnclosingHandlerWithInterface (AccessibilityHandler* handler, MemberFn fn)
{
if (handler == nullptr)
return nullptr;
if ((handler->*fn)() != nullptr)
return handler;
return getEnclosingHandlerWithInterface (handler->getParent(), fn);
}
static void loadSDKDependentMethods()
{
static bool hasChecked = false;
@ -160,8 +191,6 @@ static constexpr auto getClassName (AccessibilityRole role)
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:
@ -177,6 +206,11 @@ static constexpr auto getClassName (AccessibilityRole role)
case AccessibilityRole::splashScreen:
case AccessibilityRole::dialogWindow: return "android.widget.PopupWindow";
// If we don't supply a custom class type, then TalkBack will use the node's CollectionInfo
// to make a sensible decision about how to describe the container
case AccessibilityRole::list:
case AccessibilityRole::table:
case AccessibilityRole::column:
case AccessibilityRole::row:
case AccessibilityRole::cell:
@ -437,6 +471,63 @@ public:
}
}
}
if (getAndroidSDKVersion() >= 19)
{
if (auto* tableInterface = accessibilityHandler.getTableInterface())
{
const auto rows = tableInterface->getNumRows();
const auto columns = tableInterface->getNumColumns();
const LocalRef<jobject> collectionInfo { env->CallStaticObjectMethod (AndroidAccessibilityNodeInfoCollectionInfo,
AndroidAccessibilityNodeInfoCollectionInfo.obtain,
(jint) rows,
(jint) columns,
(jboolean) false) };
env->CallVoidMethod (info, AndroidAccessibilityNodeInfo19.setCollectionInfo, collectionInfo.get());
}
if (auto* enclosingTableHandler = getEnclosingHandlerWithInterface (&accessibilityHandler, &AccessibilityHandler::getTableInterface))
{
auto* interface = enclosingTableHandler->getTableInterface();
jassert (interface != nullptr);
const auto rowSpan = interface->getRowSpan (accessibilityHandler);
const auto columnSpan = interface->getColumnSpan (accessibilityHandler);
enum class IsHeader { no, yes };
const auto addCellInfo = [env, &info] (AccessibilityTableInterface::Span rows, AccessibilityTableInterface::Span columns, IsHeader header)
{
const LocalRef<jobject> collectionItemInfo { env->CallStaticObjectMethod (AndroidAccessibilityNodeInfoCollectionItemInfo,
AndroidAccessibilityNodeInfoCollectionItemInfo.obtain,
(jint) rows.begin,
(jint) rows.num,
(jint) columns.begin,
(jint) columns.num,
(jboolean) (header == IsHeader::yes)) };
env->CallVoidMethod (info, AndroidAccessibilityNodeInfo19.setCollectionItemInfo, collectionItemInfo.get());
};
if (rowSpan.hasValue() && columnSpan.hasValue())
{
addCellInfo (*rowSpan, *columnSpan, IsHeader::no);
}
else
{
if (auto* tableHeader = interface->getHeaderHandler())
{
if (accessibilityHandler.getParent() == tableHeader)
{
const auto children = tableHeader->getChildren();
const auto column = std::distance (children.cbegin(), std::find (children.cbegin(), children.cend(), &accessibilityHandler));
// Talkback will only treat a row as a column header if its row index is zero
// https://github.com/google/talkback/blob/acd0bc7631a3dfbcf183789c7557596a45319e1f/utils/src/main/java/CollectionState.java#L853
addCellInfo ({ 0, 1 }, { (int) column, 1 }, IsHeader::yes);
}
}
}
}
}
}
bool performAction (int action, jobject arguments)

View file

@ -54,7 +54,7 @@ static NSArray* getContainerAccessibilityElements (AccessibilityHandler& handler
{
id accessibleElement = [&childHandler]
{
id native = (id) childHandler->getNativeImplementation();
id native = static_cast<id> (childHandler->getNativeImplementation());
if (! childHandler->getChildren().empty())
return [native accessibilityContainer];
@ -66,7 +66,7 @@ static NSArray* getContainerAccessibilityElements (AccessibilityHandler& handler
[accessibleChildren addObject: accessibleElement];
}
[accessibleChildren addObject: (id) handler.getNativeImplementation()];
[accessibleChildren addObject: static_cast<id> (handler.getNativeImplementation())];
return accessibleChildren;
}
@ -87,11 +87,11 @@ public:
private:
//==============================================================================
class AccessibilityContainer : public ObjCClass<NSObject>
class AccessibilityContainer : public AccessibleObjCClass<NSObject>
{
public:
AccessibilityContainer()
: ObjCClass ("JUCEUIAccessibilityElementContainer_")
: AccessibleObjCClass ("JUCEUIAccessibilityContainer_")
{
addMethod (@selector (isAccessibilityElement), [] (id, SEL) { return false; });
@ -114,6 +114,43 @@ private:
#if JUCE_IOS_CONTAINER_API_AVAILABLE
if (@available (iOS 11.0, *))
{
addMethod (@selector (accessibilityDataTableCellElementForRow:column:), [] (id self, SEL, NSUInteger row, NSUInteger column) -> id
{
if (auto* tableHandler = getEnclosingHandlerWithInterface (getHandler (self), &AccessibilityHandler::getTableInterface))
if (auto* tableInterface = tableHandler->getTableInterface())
if (auto* cellHandler = tableInterface->getCellHandler ((int) row, (int) column))
if (auto* parent = getAccessibleParent (cellHandler))
return static_cast<id> (parent->getNativeImplementation());
return nil;
});
addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount);
addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount);
addMethod (@selector (accessibilityHeaderElementsForColumn:), [] (id self, SEL, NSUInteger column) -> NSArray*
{
if (auto* tableHandler = getEnclosingHandlerWithInterface (getHandler (self), &AccessibilityHandler::getTableInterface))
{
if (auto* tableInterface = tableHandler->getTableInterface())
{
if (auto* header = tableInterface->getHeaderHandler())
{
if (isPositiveAndBelow (column, header->getChildren().size()))
{
auto* result = [NSMutableArray new];
[result addObject: static_cast<id> (header->getChildren()[(size_t) column]->getNativeImplementation())];
return result;
}
}
}
}
return nullptr;
});
addProtocol (@protocol (UIAccessibilityContainerDataTable));
addMethod (@selector (accessibilityContainerType), [] (id self, SEL) -> NSInteger
{
if (auto* handler = getHandler (self))
@ -147,12 +184,21 @@ private:
}
#endif
addIvar<AccessibilityHandler*> ("handler");
registerClass();
}
private:
static const AccessibilityHandler* getAccessibleParent (const AccessibilityHandler* h)
{
if (h == nullptr)
return nullptr;
if ([static_cast<id> (h->getNativeImplementation()) isAccessibilityElement])
return h;
return getAccessibleParent (h->getParent());
}
static AccessibilityHandler* getHandler (id self)
{
return getIvar<AccessibilityHandler*> (self, "handler");
@ -172,7 +218,7 @@ private:
id instance = (hasEditableText (handler) ? textCls : cls).createInstance();
Holder element ([instance initWithAccessibilityContainer: (id) handler.getComponent().getWindowHandle()]);
Holder element ([instance initWithAccessibilityContainer: static_cast<id> (handler.getComponent().getWindowHandle())]);
object_setInstanceVariable (element.get(), "handler", &handler);
return element;
}
@ -183,15 +229,33 @@ private:
{
auto* handler = getHandler (self);
if (handler == nullptr)
return false;
const auto hasAccessiblePropertiesOrIsTableCell = [] (auto& handlerRef)
{
const auto isTableCell = [&]
{
if (auto* tableHandler = getEnclosingHandlerWithInterface (&handlerRef, &AccessibilityHandler::getTableInterface))
{
if (auto* tableInterface = tableHandler->getTableInterface())
{
return tableInterface->getRowSpan (handlerRef).hasValue()
&& tableInterface->getColumnSpan (handlerRef).hasValue();
}
}
return ! handler->isIgnored()
&& handler->getRole() != AccessibilityRole::window
&& (handler->getTitle().isNotEmpty()
|| handler->getDescription().isNotEmpty()
|| handler->getHelp().isNotEmpty()
|| handler->getValueInterface() != nullptr);
return false;
};
return handlerRef.getTitle().isNotEmpty()
|| handlerRef.getHelp().isNotEmpty()
|| handlerRef.getTextInterface() != nullptr
|| handlerRef.getValueInterface() != nullptr
|| isTableCell();
};
return handler != nullptr
&& ! handler->isIgnored()
&& handler->getRole() != AccessibilityRole::window
&& hasAccessiblePropertiesOrIsTableCell (*handler);
});
addMethod (@selector (accessibilityContainer), [] (id self, SEL) -> id
@ -199,7 +263,7 @@ private:
if (auto* handler = getHandler (self))
{
if (handler->getComponent().isOnDesktop())
return (id) handler->getComponent().getWindowHandle();
return static_cast<id> (handler->getComponent().getWindowHandle());
if (! handler->getChildren().empty())
{
@ -217,7 +281,7 @@ private:
}
if (auto* parent = handler->getParent())
return [(id) parent->getNativeImplementation() accessibilityContainer];
return [static_cast<id> (parent->getNativeImplementation()) accessibilityContainer];
}
return nil;
@ -252,9 +316,9 @@ private:
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::editableText:
case AccessibilityRole::slider:
case AccessibilityRole::menuItem:
case AccessibilityRole::menuBar:
@ -348,7 +412,7 @@ private:
// which element it thinks has focus and forward the event on to that element if it differs
id focusedElement = UIAccessibilityFocusedElement (UIAccessibilityNotificationVoiceOverIdentifier);
if (focusedElement != nullptr && ! [(id) handler->getNativeImplementation() isEqual: focusedElement])
if (focusedElement != nullptr && ! [static_cast<id> (handler->getNativeImplementation()) isEqual: focusedElement])
return [focusedElement accessibilityActivate];
if (handler->hasFocus (false))
@ -381,28 +445,6 @@ private:
return NO;
});
#if JUCE_IOS_CONTAINER_API_AVAILABLE
if (@available (iOS 11.0, *))
{
addMethod (@selector (accessibilityDataTableCellElementForRow:column:), [] (id self, SEL, NSUInteger row, NSUInteger column) -> id
{
if (auto* tableInterface = getEnclosingInterface (getHandler (self), &AccessibilityHandler::getTableInterface))
if (auto* cellHandler = tableInterface->getCellHandler ((int) row, (int) column))
return (id) cellHandler->getNativeImplementation();
return nil;
});
addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount);
addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount);
addProtocol (@protocol (UIAccessibilityContainerDataTable));
addMethod (@selector (accessibilityRowRange), getAccessibilityRowIndexRange);
addMethod (@selector (accessibilityColumnRange), getAccessibilityColumnIndexRange);
addProtocol (@protocol (UIAccessibilityContainerDataTableCell));
}
#endif
if (elementType == Type::textElement)
{
addMethod (@selector (accessibilityLineNumberForPoint:), [] (id self, SEL, CGPoint point)
@ -464,6 +506,15 @@ private:
addProtocol (@protocol (UIAccessibilityReadingContent));
}
#if JUCE_IOS_CONTAINER_API_AVAILABLE
if (@available (iOS 11.0, *))
{
addMethod (@selector (accessibilityRowRange), getAccessibilityRowIndexRange);
addMethod (@selector (accessibilityColumnRange), getAccessibilityColumnIndexRange);
addProtocol (@protocol (UIAccessibilityContainerDataTableCell));
}
#endif
addIvar<UIAccessibilityElement*> ("container");
registerClass();
@ -534,9 +585,8 @@ void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, Inte
const bool moveToHandler = (eventType == InternalAccessibilityEvent::focusChanged && handler.hasFocus (false));
sendAccessibilityEvent (notification,
moveToHandler ? (id) handler.getNativeImplementation() : nil);
moveToHandler ? static_cast<id> (handler.getNativeImplementation()) : nil);
}
}
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const
@ -558,7 +608,7 @@ void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventTyp
}();
if (notification != UIAccessibilityNotifications{})
sendAccessibilityEvent (notification, (id) getNativeImplementation());
sendAccessibilityEvent (notification, static_cast<id> (getNativeImplementation()));
}
void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority)

View file

@ -210,7 +210,7 @@ private:
case AccessibilityRole::editableText: return NSAccessibilityTextAreaRole;
case AccessibilityRole::menuItem: return NSAccessibilityMenuItemRole;
case AccessibilityRole::menuBar: return NSAccessibilityMenuRole;
case AccessibilityRole::table: return NSAccessibilityListRole;
case AccessibilityRole::table: return NSAccessibilityOutlineRole;
case AccessibilityRole::column: return NSAccessibilityColumnRole;
case AccessibilityRole::row: return NSAccessibilityRowRole;
case AccessibilityRole::cell: return NSAccessibilityCellRole;
@ -504,15 +504,20 @@ private:
{
if (auto* handler = getHandler (self))
{
if (auto* cellInterface = handler->getCellInterface())
if (auto* tableHandler = getEnclosingHandlerWithInterface (handler, &AccessibilityHandler::getTableInterface))
{
NSAccessibilityRole handlerRole = [self accessibilityRole];
if (auto* tableInterface = tableHandler->getTableInterface())
{
NSAccessibilityRole handlerRole = [self accessibilityRole];
if ([handlerRole isEqual: NSAccessibilityRowRole])
return cellInterface->getRowIndex();
if ([handlerRole isEqual: NSAccessibilityRowRole])
if (const auto span = tableInterface->getRowSpan (*handler))
return span->begin;
if ([handlerRole isEqual: NSAccessibilityColumnRole])
return cellInterface->getColumnIndex();
if ([handlerRole isEqual: NSAccessibilityColumnRole])
if (const auto span = tableInterface->getColumnSpan (*handler))
return span->begin;
}
}
}

View file

@ -73,18 +73,6 @@ protected:
static AccessibilityTableInterface* getTableInterface (id self) noexcept { return getInterface (self, &AccessibilityHandler::getTableInterface); }
static AccessibilityCellInterface* getCellInterface (id self) noexcept { return getInterface (self, &AccessibilityHandler::getCellInterface); }
template <typename MemberFn>
static auto getEnclosingInterface (AccessibilityHandler* handler, MemberFn fn) noexcept -> decltype ((std::declval<AccessibilityHandler>().*fn)())
{
if (handler == nullptr)
return nullptr;
if (auto* interface = (handler->*fn)())
return interface;
return getEnclosingInterface (handler->getParent(), fn);
}
static bool hasEditableText (AccessibilityHandler& handler) noexcept
{
return handler.getRole() == AccessibilityRole::editableText
@ -199,10 +187,8 @@ protected:
NSString* nsString = juceStringToNS (title);
#if ! JUCE_IOS
if (nsString != nil && [[self accessibilityValue] isEqual: nsString])
return @"";
#endif
return nsString;
}
@ -228,36 +214,58 @@ protected:
static NSInteger getAccessibilityRowCount (id self, SEL)
{
if (auto* tableInterface = getTableInterface (self))
return tableInterface->getNumRows();
if (auto* tableHandler = getEnclosingHandlerWithInterface (getHandler (self), &AccessibilityHandler::getTableInterface))
if (auto* tableInterface = tableHandler->getTableInterface())
return tableInterface->getNumRows();
return 0;
}
static NSInteger getAccessibilityColumnCount (id self, SEL)
{
if (auto* tableInterface = getTableInterface (self))
return tableInterface->getNumColumns();
if (auto* tableHandler = getEnclosingHandlerWithInterface (getHandler (self), &AccessibilityHandler::getTableInterface))
if (auto* tableInterface = tableHandler->getTableInterface())
return tableInterface->getNumColumns();
return 0;
}
template <typename Getter>
static NSRange getCellDimensions (id self, Getter getter)
{
const auto notFound = NSMakeRange (NSNotFound, 0);
auto* handler = getHandler (self);
if (handler == nullptr)
return notFound;
auto* tableHandler = getEnclosingHandlerWithInterface (getHandler (self), &AccessibilityHandler::getTableInterface);
if (tableHandler == nullptr)
return notFound;
auto* tableInterface = tableHandler->getTableInterface();
if (tableInterface == nullptr)
return notFound;
const auto result = (tableInterface->*getter) (*handler);
if (! result.hasValue())
return notFound;
return NSMakeRange ((NSUInteger) result->begin, (NSUInteger) result->num);
}
static NSRange getAccessibilityRowIndexRange (id self, SEL)
{
if (auto* cellInterface = getCellInterface (self))
return NSMakeRange ((NSUInteger) cellInterface->getRowIndex(),
(NSUInteger) cellInterface->getRowSpan());
return NSMakeRange (0, 0);
return getCellDimensions (self, &AccessibilityTableInterface::getRowSpan);
}
static NSRange getAccessibilityColumnIndexRange (id self, SEL)
{
if (auto* cellInterface = getCellInterface (self))
return NSMakeRange ((NSUInteger) cellInterface->getColumnIndex(),
(NSUInteger) cellInterface->getColumnSpan());
return NSMakeRange (0, 0);
return getCellDimensions (self, &AccessibilityTableInterface::getColumnSpan);
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibleObjCClass)

View file

@ -30,6 +30,50 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
int AccessibilityNativeHandle::idCounter = 0;
//==============================================================================
class UIAScrollProvider : public UIAProviderBase,
public ComBaseClassHelper<ComTypes::IScrollProvider>
{
public:
using UIAProviderBase::UIAProviderBase;
JUCE_COMCALL Scroll (ComTypes::ScrollAmount, ComTypes::ScrollAmount) override { return E_FAIL; }
JUCE_COMCALL SetScrollPercent (double, double) override { return E_FAIL; }
JUCE_COMCALL get_HorizontalScrollPercent (double*) override { return E_FAIL; }
JUCE_COMCALL get_VerticalScrollPercent (double*) override { return E_FAIL; }
JUCE_COMCALL get_HorizontalViewSize (double*) override { return E_FAIL; }
JUCE_COMCALL get_VerticalViewSize (double*) override { return E_FAIL; }
JUCE_COMCALL get_HorizontallyScrollable (BOOL*) override { return E_FAIL; }
JUCE_COMCALL get_VerticallyScrollable (BOOL*) override { return E_FAIL; }
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAScrollProvider)
};
class UIAScrollItemProvider : public UIAProviderBase,
public ComBaseClassHelper<ComTypes::IScrollItemProvider>
{
public:
using UIAProviderBase::UIAProviderBase;
JUCE_COMCALL ScrollIntoView() override
{
if (auto* handler = getEnclosingHandlerWithInterface (&getHandler(), &AccessibilityHandler::getTableInterface))
{
if (auto* tableInterface = handler->getTableInterface())
{
tableInterface->showCell (getHandler());
return S_OK;
}
}
return (HRESULT) UIA_E_NOTSUPPORTED;
}
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAScrollItemProvider)
};
//==============================================================================
static String getAutomationId (const AccessibilityHandler& handler)
{
@ -63,7 +107,7 @@ static auto roleToControlTypeId (AccessibilityRole roleType)
case AccessibilityRole::staticText: return ComTypes::UIA_TextControlTypeId;
case AccessibilityRole::column:
case AccessibilityRole::row: return ComTypes::UIA_HeaderItemControlTypeId;
case AccessibilityRole::row: return ComTypes::UIA_ListItemControlTypeId;
case AccessibilityRole::button: return ComTypes::UIA_ButtonControlTypeId;
case AccessibilityRole::toggleButton: return ComTypes::UIA_CheckBoxControlTypeId;
@ -146,6 +190,22 @@ JUCE_COMRESULT AccessibilityNativeHandle::GetPatternProvider (PATTERNID pId, IUn
const auto role = accessibilityHandler.getRole();
const auto fragmentRoot = isFragmentRoot();
const auto isListOrTableCell = [] (auto& handler)
{
if (auto* tableHandler = getEnclosingHandlerWithInterface (&handler, &AccessibilityHandler::getTableInterface))
{
if (auto* tableInterface = tableHandler->getTableInterface())
{
const auto row = tableInterface->getRowSpan (handler);
const auto column = tableInterface->getColumnSpan (handler);
return row.hasValue() && column.hasValue();
}
}
return false;
};
switch (pId)
{
case ComTypes::UIA_WindowPatternId:
@ -213,25 +273,27 @@ JUCE_COMRESULT AccessibilityNativeHandle::GetPatternProvider (PATTERNID pId, IUn
{
auto state = accessibilityHandler.getCurrentState();
if (state.isSelectable() || state.isMultiSelectable()
|| role == AccessibilityRole::radioButton)
if (state.isSelectable() || state.isMultiSelectable() || role == AccessibilityRole::radioButton)
{
return new UIASelectionItemProvider (this);
}
break;
}
case ComTypes::UIA_TablePatternId:
case ComTypes::UIA_GridPatternId:
{
if (accessibilityHandler.getTableInterface() != nullptr)
return new UIAGridProvider (this);
if (accessibilityHandler.getTableInterface() != nullptr
&& (pId == ComTypes::UIA_GridPatternId || accessibilityHandler.getRole() == AccessibilityRole::table))
return static_cast<ComTypes::IGridProvider*> (new UIAGridProvider (this));
break;
}
case ComTypes::UIA_TableItemPatternId:
case ComTypes::UIA_GridItemPatternId:
{
if (accessibilityHandler.getCellInterface() != nullptr)
return new UIAGridItemProvider (this);
if (isListOrTableCell (accessibilityHandler))
return static_cast<ComTypes::IGridItemProvider*> (new UIAGridItemProvider (this));
break;
}
@ -250,6 +312,20 @@ JUCE_COMRESULT AccessibilityNativeHandle::GetPatternProvider (PATTERNID pId, IUn
break;
}
case ComTypes::UIA_ScrollPatternId:
{
if (accessibilityHandler.getTableInterface() != nullptr)
return new UIAScrollProvider (this);
break;
}
case ComTypes::UIA_ScrollItemPatternId:
{
if (isListOrTableCell (accessibilityHandler))
return new UIAScrollItemProvider (this);
break;
}
}
return nullptr;

View file

@ -120,18 +120,38 @@ enum WindowInteractionState
WindowInteractionState_NotResponding = 4
};
enum RowOrColumnMajor
{
RowOrColumnMajor_RowMajor = 0,
RowOrColumnMajor_ColumnMajor = 1,
RowOrColumnMajor_Indeterminate = 2
};
enum ScrollAmount
{
ScrollAmount_LargeDecrement = 0,
ScrollAmount_SmallDecrement = 1,
ScrollAmount_NoAmount = 2,
ScrollAmount_LargeIncrement = 3,
ScrollAmount_SmallIncrement = 4
};
const long UIA_InvokePatternId = 10000;
const long UIA_SelectionPatternId = 10001;
const long UIA_ValuePatternId = 10002;
const long UIA_RangeValuePatternId = 10003;
const long UIA_ScrollPatternId = 10004;
const long UIA_ExpandCollapsePatternId = 10005;
const long UIA_GridPatternId = 10006;
const long UIA_GridItemPatternId = 10007;
const long UIA_WindowPatternId = 10009;
const long UIA_SelectionItemPatternId = 10010;
const long UIA_TablePatternId = 10012;
const long UIA_TableItemPatternId = 10013;
const long UIA_TextPatternId = 10014;
const long UIA_TogglePatternId = 10015;
const long UIA_TransformPatternId = 10016;
const long UIA_ScrollItemPatternId = 10017;
const long UIA_TextPattern2Id = 10024;
const long UIA_StructureChangedEventId = 20002;
const long UIA_MenuOpenedEventId = 20003;
@ -220,6 +240,21 @@ public:
JUCE_COMCALL get_ColumnCount (__RPC__out int* pRetVal) = 0;
};
JUCE_COMCLASS (ITableItemProvider, "b9734fa6-771f-4d78-9c90-2517999349cd") : public IUnknown
{
public:
JUCE_COMCALL GetRowHeaderItems (SAFEARRAY** pRetVal) = 0;
JUCE_COMCALL GetColumnHeaderItems (SAFEARRAY** pRetVal) = 0;
};
JUCE_COMCLASS (ITableProvider, "9c860395-97b3-490a-b52a-858cc22af166") : public IUnknown
{
public:
JUCE_COMCALL GetRowHeaders (SAFEARRAY** pRetVal) = 0;
JUCE_COMCALL GetColumnHeaders (SAFEARRAY** pRetVal) = 0;
JUCE_COMCALL get_RowOrColumnMajor (RowOrColumnMajor* pRetVal) = 0;
};
JUCE_COMCLASS (IInvokeProvider, "54fcb24b-e18e-47a2-b4d3-eccbe77599a2") : public IUnknown
{
public:
@ -345,6 +380,25 @@ public:
JUCE_COMCALL get_IsTopmost (__RPC__out BOOL * pRetVal) = 0;
};
JUCE_COMCLASS (IScrollProvider, "b38b8077-1fc3-42a5-8cae-d40c2215055a") : public IUnknown
{
public:
JUCE_COMCALL Scroll (ScrollAmount horizontalAmount, ScrollAmount verticalAmount) = 0;
JUCE_COMCALL SetScrollPercent (double horizontalPercent,double verticalPercent) = 0;
JUCE_COMCALL get_HorizontalScrollPercent (double* pRetVal) = 0;
JUCE_COMCALL get_VerticalScrollPercent (double* pRetVal) = 0;
JUCE_COMCALL get_HorizontalViewSize (double* pRetVal) = 0;
JUCE_COMCALL get_VerticalViewSize (double* pRetVal) = 0;
JUCE_COMCALL get_HorizontallyScrollable (BOOL* pRetVal) = 0;
JUCE_COMCALL get_VerticallyScrollable (BOOL* pRetVal) = 0;
};
JUCE_COMCLASS (IScrollItemProvider, "2360c714-4bf1-4b26-ba65-9b21316127eb") : public IUnknown
{
public:
JUCE_COMCALL ScrollIntoView() = 0;
};
constexpr CLSID CLSID_SpVoice { 0x96749377, 0x3391, 0x11D2, { 0x9E, 0xE3, 0x00, 0xC0, 0x4F, 0x79, 0x73, 0x96 } };
} // namespace ComTypes
@ -368,4 +422,8 @@ __CRT_UUID_DECL (juce::ComTypes::IToggleProvider, 0x56d00bd0, 0x
__CRT_UUID_DECL (juce::ComTypes::ITransformProvider, 0x6829ddc4, 0x4f91, 0x4ffa, 0xb8, 0x6f, 0xbd, 0x3e, 0x29, 0x87, 0xcb, 0x4c)
__CRT_UUID_DECL (juce::ComTypes::IValueProvider, 0xc7935180, 0x6fb3, 0x4201, 0xb1, 0x74, 0x7d, 0xf7, 0x3a, 0xdb, 0xf6, 0x4a)
__CRT_UUID_DECL (juce::ComTypes::IWindowProvider, 0x987df77b, 0xdb06, 0x4d77, 0x8f, 0x8a, 0x86, 0xa9, 0xc3, 0xbb, 0x90, 0xb9)
__CRT_UUID_DECL (juce::ComTypes::ITableItemProvider, 0xb9734fa6, 0x771f, 0x4d78, 0x9c, 0x90, 0x25, 0x17, 0x99, 0x93, 0x49, 0xcd)
__CRT_UUID_DECL (juce::ComTypes::ITableProvider, 0x9c860395, 0x97b3, 0x490a, 0xb5, 0x2a, 0x85, 0x8c, 0xc2, 0x2a, 0xf1, 0x66)
__CRT_UUID_DECL (juce::ComTypes::IScrollProvider, 0xb38b8077, 0x1fc3, 0x42a5, 0x8c, 0xae, 0xd4, 0x0c, 0x22, 0x15, 0x05, 0x5a)
__CRT_UUID_DECL (juce::ComTypes::IScrollItemProvider, 0x2360c714, 0x4bf1, 0x4b26, 0xba, 0x65, 0x9b, 0x21, 0x31, 0x61, 0x27, 0xeb)
#endif

View file

@ -28,7 +28,7 @@ namespace juce
//==============================================================================
class UIAGridItemProvider : public UIAProviderBase,
public ComBaseClassHelper<ComTypes::IGridItemProvider>
public ComBaseClassHelper<ComTypes::IGridItemProvider, ComTypes::ITableItemProvider>
{
public:
using UIAProviderBase::UIAProviderBase;
@ -36,65 +36,116 @@ public:
//==============================================================================
JUCE_COMRESULT get_Row (int* pRetVal) override
{
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
{
*pRetVal = cellInterface.getRowIndex();
});
return withTableSpan (pRetVal,
&AccessibilityTableInterface::getRowSpan,
&AccessibilityTableInterface::Span::begin);
}
JUCE_COMRESULT get_Column (int* pRetVal) override
{
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
{
*pRetVal = cellInterface.getColumnIndex();
});
return withTableSpan (pRetVal,
&AccessibilityTableInterface::getColumnSpan,
&AccessibilityTableInterface::Span::begin);
}
JUCE_COMRESULT get_RowSpan (int* pRetVal) override
{
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
{
*pRetVal = cellInterface.getRowSpan();
});
return withTableSpan (pRetVal,
&AccessibilityTableInterface::getRowSpan,
&AccessibilityTableInterface::Span::num);
}
JUCE_COMRESULT get_ColumnSpan (int* pRetVal) override
{
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
{
*pRetVal = cellInterface.getColumnSpan();
});
return withTableSpan (pRetVal,
&AccessibilityTableInterface::getColumnSpan,
&AccessibilityTableInterface::Span::num);
}
JUCE_COMRESULT get_ContainingGrid (IRawElementProviderSimple** pRetVal) override
{
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
return withTableInterface (pRetVal, [&] (const AccessibilityHandler& tableHandler)
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
if (auto* handler = cellInterface.getTableHandler())
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
tableHandler.getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
return true;
});
}
JUCE_COMRESULT GetRowHeaderItems (SAFEARRAY**) override
{
return (HRESULT) UIA_E_NOTSUPPORTED;
}
JUCE_COMRESULT GetColumnHeaderItems (SAFEARRAY** pRetVal) override
{
return withTableInterface (pRetVal, [&] (const AccessibilityHandler& tableHandler)
{
if (auto* tableInterface = tableHandler.getTableInterface())
{
if (const auto column = tableInterface->getColumnSpan (getHandler()))
{
if (auto* header = tableInterface->getHeaderHandler())
{
const auto children = header->getChildren();
if (isPositiveAndBelow (column->begin, children.size()))
{
IRawElementProviderSimple* provider = nullptr;
if (auto* child = children[(size_t) column->begin])
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
if (child->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (&provider)) == S_OK && provider != nullptr)
{
*pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 1);
LONG index = 0;
const auto hr = SafeArrayPutElement (*pRetVal, &index, provider);
return ! FAILED (hr);
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
}
}
}
}
return false;
});
}
private:
template <typename Value, typename Callback>
JUCE_COMRESULT withCellInterface (Value* pRetVal, Callback&& callback) const
JUCE_COMRESULT withTableInterface (Value* pRetVal, Callback&& callback) const
{
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
{
if (auto* cellInterface = getHandler().getCellInterface())
{
callback (*cellInterface);
return S_OK;
}
if (auto* handler = getEnclosingHandlerWithInterface (&getHandler(), &AccessibilityHandler::getTableInterface))
if (handler->getTableInterface() != nullptr && callback (*handler))
return S_OK;
return (HRESULT) UIA_E_NOTSUPPORTED;
});
}
JUCE_COMRESULT withTableSpan (int* pRetVal,
Optional<AccessibilityTableInterface::Span> (AccessibilityTableInterface::* getSpan) (const AccessibilityHandler&) const,
int AccessibilityTableInterface::Span::* spanMember) const
{
return withTableInterface (pRetVal, [&] (const AccessibilityHandler& handler)
{
if (const auto span = ((handler.getTableInterface())->*getSpan) (getHandler()))
{
*pRetVal = (*span).*spanMember;
return true;
}
return false;
});
}
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAGridItemProvider)
};

View file

@ -28,7 +28,7 @@ namespace juce
//==============================================================================
class UIAGridProvider : public UIAProviderBase,
public ComBaseClassHelper<ComTypes::IGridProvider>
public ComBaseClassHelper<ComTypes::IGridProvider, ComTypes::ITableProvider>
{
public:
using UIAProviderBase::UIAProviderBase;
@ -44,12 +44,21 @@ public:
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
if (auto* handler = tableInterface.getCellHandler (row, column))
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
if (auto* cellHandler = tableInterface.getCellHandler (row, column))
{
cellHandler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
return S_OK;
}
if (auto* rowHandler = tableInterface.getRowHandler (row))
{
rowHandler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
return S_OK;
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
return S_OK;
return E_FAIL;
});
}
@ -71,14 +80,65 @@ public:
});
}
JUCE_COMRESULT GetRowHeaders (SAFEARRAY**) override
{
return (HRESULT) UIA_E_NOTSUPPORTED;
}
JUCE_COMRESULT GetColumnHeaders (SAFEARRAY** pRetVal) override
{
return withTableInterface (pRetVal, [&] (const AccessibilityTableInterface& tableInterface)
{
if (auto* header = tableInterface.getHeaderHandler())
{
const auto children = header->getChildren();
*pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, (ULONG) children.size());
LONG index = 0;
for (const auto& child : children)
{
IRawElementProviderSimple* provider = nullptr;
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
if (child != nullptr)
child->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (&provider));
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
if (provider == nullptr)
return E_FAIL;
const auto hr = SafeArrayPutElement (*pRetVal, &index, provider);
if (FAILED (hr))
return E_FAIL;
++index;
}
return S_OK;
}
return (HRESULT) UIA_E_NOTSUPPORTED;
});
}
JUCE_COMRESULT get_RowOrColumnMajor (ComTypes::RowOrColumnMajor* pRetVal) override
{
*pRetVal = ComTypes::RowOrColumnMajor_RowMajor;
return S_OK;
}
private:
template <typename Value, typename Callback>
JUCE_COMRESULT withTableInterface (Value* pRetVal, Callback&& callback) const
{
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
{
if (auto* tableInterface = getHandler().getTableInterface())
return callback (*tableInterface);
if (auto* tableHandler = getEnclosingHandlerWithInterface (&getHandler(), &AccessibilityHandler::getTableInterface))
if (auto* tableInterface = tableHandler->getTableInterface())
return callback (*tableInterface);
return (HRESULT) UIA_E_NOTSUPPORTED;
});

View file

@ -241,16 +241,6 @@ public:
public:
explicit RowCellInterface (RowAccessibilityHandler& h) : handler (h) {}
int getColumnIndex() const override { return 0; }
int getColumnSpan() const override { return 1; }
int getRowIndex() const override
{
return handler.rowComponent.row;
}
int getRowSpan() const override { return 1; }
int getDisclosureLevel() const override { return 0; }
const AccessibilityHandler* getTableHandler() const override
@ -289,7 +279,15 @@ public:
{
setWantsKeyboardFocus (false);
auto content = std::make_unique<Component>();
struct IgnoredComponent : Component
{
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
{
return createIgnoredAccessibilityHandler (*this);
}
};
auto content = std::make_unique<IgnoredComponent>();
content->setWantsKeyboardFocus (false);
setViewedComponent (content.release());
@ -301,20 +299,23 @@ public:
{
const auto startIndex = getIndexOfFirstVisibleRow();
return (startIndex <= row && row < startIndex + rows.size())
? rows[row % jmax (1, rows.size())] : nullptr;
return (startIndex <= row && row < startIndex + (int) rows.size())
? rows[(size_t) (row % jmax (1, (int) rows.size()))].get()
: nullptr;
}
int getRowNumberOfComponent (Component* const rowComponent) const noexcept
int getRowNumberOfComponent (const Component* const rowComponent) const noexcept
{
const int index = getViewedComponent()->getIndexOfChildComponent (rowComponent);
const int num = rows.size();
const auto iter = std::find_if (rows.begin(), rows.end(), [=] (auto& ptr) { return ptr.get() == rowComponent; });
for (int i = num; --i >= 0;)
if (((firstIndex + i) % jmax (1, num)) == index)
return firstIndex + i;
if (iter == rows.end())
return -1;
return -1;
const auto index = (int) std::distance (rows.begin(), iter);
const auto mod = jmax (1, (int) rows.size());
const auto startIndex = getIndexOfFirstVisibleRow();
return index + mod * ((startIndex / mod) + (index < (startIndex % mod) ? 1 : 0));
}
void visibleAreaChanged (const Rectangle<int>&) override
@ -357,13 +358,13 @@ public:
auto y = getViewPositionY();
auto w = content.getWidth();
const int numNeeded = 4 + getMaximumVisibleHeight() / rowH;
rows.removeRange (numNeeded, rows.size());
const auto numNeeded = (size_t) (4 + getMaximumVisibleHeight() / rowH);
rows.resize (jmin (numNeeded, rows.size()));
while (numNeeded > rows.size())
{
auto* newRow = rows.add (new RowComponent (owner));
content.addAndMakeVisible (newRow);
rows.emplace_back (new RowComponent (owner));
content.addAndMakeVisible (*rows.back());
}
firstIndex = y / rowH;
@ -371,7 +372,7 @@ public:
lastWholeIndex = (y + getMaximumVisibleHeight() - 1) / rowH;
const auto startIndex = getIndexOfFirstVisibleRow();
const auto lastIndex = startIndex + rows.size();
const auto lastIndex = startIndex + (int) rows.size();
for (auto row = startIndex; row < lastIndex; ++row)
{
@ -477,7 +478,7 @@ private:
}
ListBox& owner;
OwnedArray<RowComponent> rows;
std::vector<std::unique_ptr<RowComponent>> rows;
int firstIndex = 0, firstWholeIndex = 0, lastWholeIndex = 0;
bool hasUpdated = false;
@ -839,7 +840,7 @@ Component* ListBox::getComponentForRowNumber (const int row) const noexcept
return nullptr;
}
int ListBox::getRowNumberOfComponent (Component* const rowComponent) const noexcept
int ListBox::getRowNumberOfComponent (const Component* const rowComponent) const noexcept
{
return viewport->getRowNumberOfComponent (rowComponent);
}
@ -1174,6 +1175,25 @@ std::unique_ptr<AccessibilityHandler> ListBox::createAccessibilityHandler()
return nullptr;
}
Optional<Span> getRowSpan (const AccessibilityHandler& handler) const override
{
const auto rowNumber = listBox.getRowNumberOfComponent (&handler.getComponent());
return rowNumber != -1 ? makeOptional (Span { rowNumber, 1 })
: nullopt;
}
Optional<Span> getColumnSpan (const AccessibilityHandler&) const override
{
return Span { 0, 1 };
}
void showCell (const AccessibilityHandler& h) const override
{
if (const auto row = getRowSpan (h))
listBox.scrollToEnsureRowIsOnscreen (row->begin);
}
private:
ListBox& listBox;

View file

@ -455,7 +455,7 @@ public:
/** Returns the row number that the given component represents.
If the component isn't one of the list's rows, this will return -1.
*/
int getRowNumberOfComponent (Component* rowComponent) const noexcept;
int getRowNumberOfComponent (const Component* rowComponent) const noexcept;
/** Returns the width of a row (which may be less than the width of this component
if there's a scrollbar).

View file

@ -46,8 +46,8 @@ public:
tableModel->paintRowBackground (g, row, getWidth(), getHeight(), isSelected);
auto& headerComp = owner.getHeader();
auto numColumns = headerComp.getNumColumns (true);
auto clipBounds = g.getClipBounds();
const auto numColumns = jmin ((int) columnComponents.size(), headerComp.getNumColumns (true));
const auto clipBounds = g.getClipBounds();
for (int i = 0; i < numColumns; ++i)
{
@ -89,8 +89,14 @@ public:
if (tableModel != nullptr && row < owner.getNumRows())
{
const ComponentDeleter deleter { columnForComponent };
const auto numColumns = owner.getHeader().getNumColumns (true);
columnComponents.resize ((size_t) numColumns);
while (numColumns < (int) columnComponents.size())
columnComponents.pop_back();
while ((int) columnComponents.size() < numColumns)
columnComponents.emplace_back (nullptr, deleter);
for (int i = 0; i < numColumns; ++i)
{
@ -98,11 +104,17 @@ public:
auto originalComp = std::move (columnComponents[(size_t) i]);
auto oldCustomComp = originalComp != nullptr && ! originalComp->getProperties().contains (tableAccessiblePlaceholderProperty)
? std::move (originalComp)
: nullptr;
: std::unique_ptr<Component, ComponentDeleter> { nullptr, deleter };
auto compToRefresh = oldCustomComp != nullptr && columnId == static_cast<int> (oldCustomComp->getProperties()[tableColumnProperty])
? std::move (oldCustomComp)
: nullptr;
auto newCustomComp = rawToUniquePtr (tableModel->refreshComponentForCell (row, columnId, isSelected, compToRefresh.release()));
: std::unique_ptr<Component, ComponentDeleter> { nullptr, deleter };
columnForComponent.erase (compToRefresh.get());
std::unique_ptr<Component, ComponentDeleter> newCustomComp { tableModel->refreshComponentForCell (row,
columnId,
isSelected,
compToRefresh.release()),
deleter };
auto columnComp = [&]
{
@ -115,12 +127,14 @@ public:
return std::move (originalComp);
// Create a new placeholder component to use
auto comp = std::make_unique<Component>();
std::unique_ptr<Component, ComponentDeleter> comp { new Component, deleter };
comp->setInterceptsMouseClicks (false, false);
comp->getProperties().set (tableAccessiblePlaceholderProperty, true);
return comp;
}();
columnForComponent.emplace (columnComp.get(), i);
// In order for navigation to work correctly on macOS, the number of child
// accessibility elements on each row must match the number of header accessibility
// elements.
@ -250,6 +264,12 @@ public:
return nullptr;
}
int getColumnNumberOfComponent (const Component* comp) const
{
const auto iter = columnForComponent.find (comp);
return iter != columnForComponent.cend() ? iter->second : -1;
}
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
{
return std::make_unique<RowAccessibilityHandler> (*this);
@ -297,6 +317,7 @@ public:
return state;
}
private:
class RowComponentCellInterface : public AccessibilityCellInterface
{
public:
@ -305,12 +326,6 @@ public:
{
}
int getColumnIndex() const override { return 0; }
int getColumnSpan() const override { return 1; }
int getRowIndex() const override { return owner.rowComponent.row; }
int getRowSpan() const override { return 1; }
int getDisclosureLevel() const override { return 0; }
const AccessibilityHandler* getTableHandler() const override { return owner.rowComponent.owner.getAccessibilityHandler(); }
@ -324,8 +339,27 @@ public:
};
//==============================================================================
class ComponentDeleter
{
public:
explicit ComponentDeleter (std::map<const Component*, int>& locations)
: columnForComponent (&locations) {}
void operator() (Component* comp) const
{
columnForComponent->erase (comp);
if (comp != nullptr)
delete comp;
}
private:
std::map<const Component*, int>* columnForComponent;
};
TableListBox& owner;
std::vector<std::unique_ptr<Component>> columnComponents;
std::map<const Component*, int> columnForComponent;
std::vector<std::unique_ptr<Component, ComponentDeleter>> columnComponents;
int row = -1;
bool isSelected = false, isDragging = false, selectRowOnMouseUp = false;
@ -571,6 +605,22 @@ void TableListBox::updateColumnComponents() const
rowComp->resized();
}
template <typename FindIndex>
Optional<AccessibilityTableInterface::Span> findRecursively (const AccessibilityHandler& handler,
Component* outermost,
FindIndex&& findIndexOfComponent)
{
for (auto* comp = &handler.getComponent(); comp != outermost; comp = comp->getParentComponent())
{
const auto result = findIndexOfComponent (comp);
if (result != -1)
return AccessibilityTableInterface::Span { result, 1 };
}
return nullopt;
}
std::unique_ptr<AccessibilityHandler> TableListBox::createAccessibilityHandler()
{
class TableInterface : public AccessibilityTableInterface
@ -591,7 +641,7 @@ std::unique_ptr<AccessibilityHandler> TableListBox::createAccessibilityHandler()
int getNumColumns() const override
{
return tableListBox.getHeader().getNumColumns (false);
return tableListBox.getHeader().getNumColumns (true);
}
const AccessibilityHandler* getRowHandler (int row) const override
@ -606,7 +656,7 @@ std::unique_ptr<AccessibilityHandler> TableListBox::createAccessibilityHandler()
const AccessibilityHandler* getCellHandler (int row, int column) const override
{
if (isPositiveAndBelow (row, getNumRows()) && isPositiveAndBelow (column, getNumColumns()))
if (auto* cellComponent = tableListBox.getCellComponent (tableListBox.getHeader().getColumnIdOfIndex (column, false), row))
if (auto* cellComponent = tableListBox.getCellComponent (tableListBox.getHeader().getColumnIdOfIndex (column, true), row))
return cellComponent->getAccessibilityHandler();
return nullptr;
@ -620,6 +670,35 @@ std::unique_ptr<AccessibilityHandler> TableListBox::createAccessibilityHandler()
return nullptr;
}
Optional<Span> getRowSpan (const AccessibilityHandler& handler) const override
{
if (tableListBox.isParentOf (&handler.getComponent()))
return findRecursively (handler, &tableListBox, [&] (auto* c) { return tableListBox.getRowNumberOfComponent (c); });
return nullopt;
}
Optional<Span> getColumnSpan (const AccessibilityHandler& handler) const override
{
if (const auto rowSpan = getRowSpan (handler))
if (auto* rowComponent = dynamic_cast<RowComp*> (tableListBox.getComponentForRowNumber (rowSpan->begin)))
return findRecursively (handler, &tableListBox, [&] (auto* c) { return rowComponent->getColumnNumberOfComponent (c); });
return nullopt;
}
void showCell (const AccessibilityHandler& handler) const override
{
const auto row = getRowSpan (handler);
const auto col = getColumnSpan (handler);
if (row.hasValue() && col.hasValue())
{
tableListBox.scrollToEnsureRowIsOnscreen (row->begin);
tableListBox.scrollToEnsureColumnIsOnscreen (col->begin);
}
}
private:
TableListBox& tableListBox;
@ -627,7 +706,7 @@ std::unique_ptr<AccessibilityHandler> TableListBox::createAccessibilityHandler()
};
return std::make_unique<AccessibilityHandler> (*this,
AccessibilityRole::list,
AccessibilityRole::table,
AccessibilityActions{},
AccessibilityHandler::Interfaces { std::make_unique<TableInterface> (*this) });
}

View file

@ -143,19 +143,6 @@ private:
public:
explicit ItemCellInterface (ItemComponent& c) : itemComponent (c) {}
int getColumnIndex() const override { return 0; }
int getColumnSpan() const override { return 1; }
int getRowIndex() const override
{
return itemComponent.getRepresentedItem().getRowNumberInTree();
}
int getRowSpan() const override
{
return 1;
}
int getDisclosureLevel() const override
{
return getItemDepth (&itemComponent.getRepresentedItem());
@ -312,7 +299,7 @@ public:
ItemComponent* getItemComponentAt (Point<int> p)
{
auto iter = std::find_if (itemComponents.cbegin(), itemComponents.cend(),
[p] (const std::unique_ptr<ItemComponent>& c)
[p] (const auto& c)
{
return c->getBounds().contains (p);
});
@ -326,7 +313,7 @@ public:
ItemComponent* getComponentForItem (const TreeViewItem* item) const
{
const auto iter = std::find_if (itemComponents.begin(), itemComponents.end(),
[item] (const std::unique_ptr<ItemComponent>& c)
[item] (const auto& c)
{
return &c->getRepresentedItem() == item;
});
@ -340,7 +327,7 @@ public:
void itemBeingDeleted (const TreeViewItem* item)
{
const auto iter = std::find_if (itemComponents.begin(), itemComponents.end(),
[item] (const std::unique_ptr<ItemComponent>& c)
[item] (const auto& c)
{
return &c->getRepresentedItem() == item;
});
@ -357,6 +344,12 @@ public:
}
}
const TreeViewItem* getItemForItemComponent (const Component* comp) const
{
const auto iter = itemForItemComponent.find (comp);
return iter != itemForItemComponent.cend() ? iter->second : nullptr;
}
void updateComponents()
{
std::set<ItemComponent*> componentsToKeep;
@ -369,7 +362,8 @@ public:
}
else
{
auto newComp = std::make_unique<ItemComponent> (*treeItem);
std::unique_ptr<ItemComponent, Deleter> newComp { new ItemComponent (*treeItem), Deleter { itemForItemComponent } };
itemForItemComponent.emplace (newComp.get(), treeItem);
addAndMakeVisible (*newComp);
newComp->addMouseListener (this, treeItem->customComponentUsesTreeViewMouseHandler());
@ -430,7 +424,7 @@ private:
updateItemUnderMouse (e);
isDragging = false;
scopedScrollDisabler = nullptr;
scopedScrollDisabler = nullopt;
needSelectionOnMouseUp = false;
if (! isEnabled())
@ -523,7 +517,7 @@ private:
auto imageOffset = pos.getPosition() - e.getPosition();
dragContainer->startDragging (dragDescription, &owner, { dragImage, additionalScale }, true, &imageOffset, &e.source);
scopedScrollDisabler = std::make_unique<ScopedDisableViewportScroll> (*itemComponent);
scopedScrollDisabler.emplace (*itemComponent);
}
else
{
@ -688,12 +682,32 @@ private:
return visibleItems;
}
//==============================================================================
class Deleter
{
public:
explicit Deleter (std::map<const Component*, const TreeViewItem*>& map)
: itemForItemComponent (&map) {}
void operator() (ItemComponent* ptr) const
{
itemForItemComponent->erase (ptr);
if (ptr != nullptr)
delete ptr;
}
private:
std::map<const Component*, const TreeViewItem*>* itemForItemComponent = nullptr;
};
//==============================================================================
TreeView& owner;
std::vector<std::unique_ptr<ItemComponent>> itemComponents;
std::map<const Component*, const TreeViewItem*> itemForItemComponent;
std::vector<std::unique_ptr<ItemComponent, Deleter>> itemComponents;
ItemComponent* itemUnderMouse = nullptr;
std::unique_ptr<ScopedDisableViewportScroll> scopedScrollDisabler;
Optional<ScopedDisableViewportScroll> scopedScrollDisabler;
bool isDragging = false, needSelectionOnMouseUp = false;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContentComponent)
@ -1081,7 +1095,7 @@ void TreeView::moveSelectedRow (int delta)
}
}
void TreeView::scrollToKeepItemVisible (TreeViewItem* item)
void TreeView::scrollToKeepItemVisible (const TreeViewItem* item)
{
if (item != nullptr && item->ownerView == this)
{
@ -1494,7 +1508,39 @@ std::unique_ptr<AccessibilityHandler> TreeView::createAccessibilityHandler()
return nullptr;
}
Optional<Span> getRowSpan (const AccessibilityHandler& handler) const override
{
auto* item = getItemForHandler (handler);
if (item == nullptr)
return nullopt;
const auto rowNumber = item->getRowNumberInTree();
return rowNumber != -1 ? makeOptional (Span { rowNumber, 1 })
: nullopt;
}
Optional<Span> getColumnSpan (const AccessibilityHandler&) const override
{
return Span { 0, 1 };
}
void showCell (const AccessibilityHandler& cellHandler) const override
{
treeView.scrollToKeepItemVisible (getItemForHandler (cellHandler));
}
private:
const TreeViewItem* getItemForHandler (const AccessibilityHandler& handler) const
{
for (auto* comp = &handler.getComponent(); comp != &treeView; comp = comp->getParentComponent())
if (auto* result = treeView.viewport->getContentComp()->getItemForItemComponent (comp))
return result;
return nullptr;
}
TreeView& treeView;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableInterface)
@ -1849,7 +1895,7 @@ void TreeViewItem::updatePositions (int newY)
}
}
TreeViewItem* TreeViewItem::getDeepestOpenParentItem() noexcept
const TreeViewItem* TreeViewItem::getDeepestOpenParentItem() const noexcept
{
auto* result = this;
auto* item = this;

View file

@ -613,7 +613,7 @@ private:
int getIndentX() const noexcept;
void setOwnerView (TreeView*) noexcept;
TreeViewItem* getTopLevelItem() noexcept;
TreeViewItem* getDeepestOpenParentItem() noexcept;
const TreeViewItem* getDeepestOpenParentItem() const noexcept;
int getNumRows() const noexcept;
TreeViewItem* getItemOnRow (int) noexcept;
void deselectAllRecursively (TreeViewItem*);
@ -798,7 +798,7 @@ public:
TreeViewItem* getItemAt (int yPosition) const noexcept;
/** Tries to scroll the tree so that this item is on-screen somewhere. */
void scrollToKeepItemVisible (TreeViewItem* item);
void scrollToKeepItemVisible (const TreeViewItem* item);
/** Returns the TreeView's Viewport object. */
Viewport* getViewport() const noexcept;