1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Accessibility: Multiple table fixes

- Fixes an off-by-one error when navigating by rows, caused by treating
  the table header as a row. The table header now has the header
  accessibility role.
- Fixes a bug where reordering table columns would cause the table to
  become inaccessible.
- Fixes a bug where the screen reader would try to navigate hidden table
  columns.
- Fixes an issue where moving the VoiceOver cursor to a partially hidden
  cell would cause the focus to move to the table itself, rather than to
  the cell.
This commit is contained in:
reuk 2022-06-28 12:51:03 +01:00
parent 5e626e1c2b
commit 707767fa4c
No known key found for this signature in database
GPG key ID: FCB43929F012EE5C
8 changed files with 233 additions and 187 deletions

View file

@ -49,6 +49,21 @@ public:
nullptr if there is no cell at the specified position. nullptr if there is no cell at the specified position.
*/ */
virtual const AccessibilityHandler* getCellHandler (int row, int column) const = 0; virtual const AccessibilityHandler* getCellHandler (int row, int column) const = 0;
/** Returns the AccessibilityHandler for a row in the table, or nullptr if there is
no row at this index.
The row component should have a child component for each column in the table.
*/
virtual const AccessibilityHandler* getRowHandler (int row) const = 0;
/** Returns the AccessibilityHandler for the header, or nullptr if there is
no header.
If you supply a header, it must have exactly the same number of children
as there are columns in the table.
*/
virtual const AccessibilityHandler* getHeaderHandler() const = 0;
}; };
} // namespace juce } // namespace juce

View file

@ -116,9 +116,7 @@ private:
addMethod (@selector (accessibilitySelectedRows), getAccessibilitySelectedRows); addMethod (@selector (accessibilitySelectedRows), getAccessibilitySelectedRows);
addMethod (@selector (setAccessibilitySelectedRows:), setAccessibilitySelectedRows); addMethod (@selector (setAccessibilitySelectedRows:), setAccessibilitySelectedRows);
addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount); addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount);
addMethod (@selector (accessibilityColumns), getAccessibilityColumns); addMethod (@selector (accessibilityHeader), getAccessibilityHeader);
addMethod (@selector (accessibilitySelectedColumns), getAccessibilitySelectedColumns);
addMethod (@selector (setAccessibilitySelectedColumns:), setAccessibilitySelectedColumns);
addMethod (@selector (accessibilityRowIndexRange), getAccessibilityRowIndexRange); addMethod (@selector (accessibilityRowIndexRange), getAccessibilityRowIndexRange);
addMethod (@selector (accessibilityColumnIndexRange), getAccessibilityColumnIndexRange); addMethod (@selector (accessibilityColumnIndexRange), getAccessibilityColumnIndexRange);
@ -166,26 +164,30 @@ private:
return selected; return selected;
} }
static void setSelected (id item, bool selected)
{
auto* handler = getHandler (item);
if (handler == nullptr)
return;
const auto currentState = handler->getCurrentState();
if (isSelectable (currentState))
{
if (currentState.isSelected() != selected)
handler->getActions().invoke (AccessibilityActionType::toggle);
}
else if (currentState.isFocusable())
{
[item setAccessibilityFocused: selected];
}
}
static void setSelectedChildren (NSArray* children, NSArray* selected) static void setSelectedChildren (NSArray* children, NSArray* selected)
{ {
for (id child in children) for (id child in children)
{ setSelected (child, [selected containsObject: child]);
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];
}
}
}
} }
//============================================================================== //==============================================================================
@ -218,15 +220,15 @@ private:
if (auto* modalHandler = modal->getAccessibilityHandler()) if (auto* modalHandler = modal->getAccessibilityHandler())
{ {
if (auto* focusChild = modalHandler->getChildFocus()) if (auto* focusChild = modalHandler->getChildFocus())
return (id) focusChild->getNativeImplementation(); return static_cast<id> (focusChild->getNativeImplementation());
return (id) modalHandler->getNativeImplementation(); return static_cast<id> (modalHandler->getNativeImplementation());
} }
} }
} }
if (auto* focusChild = handler->getChildFocus()) if (auto* focusChild = handler->getChildFocus())
return (id) focusChild->getNativeImplementation(); return static_cast<id> (focusChild->getNativeImplementation());
} }
return nil; return nil;
@ -237,7 +239,7 @@ private:
if (auto* handler = getHandler (self)) if (auto* handler = getHandler (self))
{ {
if (auto* child = handler->getChildAt (roundToIntPoint (flippedScreenPoint (point)))) if (auto* child = handler->getChildAt (roundToIntPoint (flippedScreenPoint (point))))
return (id) child->getNativeImplementation(); return static_cast<id> (child->getNativeImplementation());
return self; return self;
} }
@ -250,9 +252,9 @@ private:
if (auto* handler = getHandler (self)) if (auto* handler = getHandler (self))
{ {
if (auto* parentHandler = handler->getParent()) if (auto* parentHandler = handler->getParent())
return NSAccessibilityUnignoredAncestor ((id) parentHandler->getNativeImplementation()); return NSAccessibilityUnignoredAncestor (static_cast<id> (parentHandler->getNativeImplementation()));
return NSAccessibilityUnignoredAncestor ((id) handler->getComponent().getWindowHandle()); return NSAccessibilityUnignoredAncestor (static_cast<id> (handler->getComponent().getWindowHandle()));
} }
return nil; return nil;
@ -267,7 +269,7 @@ private:
auto* accessibleChildren = [NSMutableArray arrayWithCapacity: (NSUInteger) children.size()]; auto* accessibleChildren = [NSMutableArray arrayWithCapacity: (NSUInteger) children.size()];
for (auto* childHandler : children) for (auto* childHandler : children)
[accessibleChildren addObject: (id) childHandler->getNativeImplementation()]; [accessibleChildren addObject: static_cast<id> (childHandler->getNativeImplementation())];
return accessibleChildren; return accessibleChildren;
} }
@ -581,27 +583,29 @@ private:
//============================================================================== //==============================================================================
static NSArray* getAccessibilityRows (id self, SEL) static NSArray* getAccessibilityRows (id self, SEL)
{ {
NSMutableArray* rows = [[NSMutableArray new] autorelease];
if (auto* tableInterface = getTableInterface (self)) if (auto* tableInterface = getTableInterface (self))
{ {
for (int row = 0; row < tableInterface->getNumRows(); ++row) auto* rows = [[NSMutableArray new] autorelease];
for (int row = 0, numRows = tableInterface->getNumRows(); row < numRows; ++row)
{ {
if (auto* handler = tableInterface->getCellHandler (row, 0)) if (auto* rowHandler = tableInterface->getRowHandler (row))
{ {
[rows addObject: (id) handler->getNativeImplementation()]; [rows addObject: static_cast<id> (rowHandler->getNativeImplementation())];
} }
else else
{ {
[rows addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityRowRole [rows addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityRowRole
frame: NSZeroRect frame: NSZeroRect
label: @"Offscreen Row" label: @"Offscreen Row"
parent: self]]; parent: self]];
} }
} }
return rows;
} }
return rows; return nil;
} }
static NSArray* getAccessibilitySelectedRows (id self, SEL) static NSArray* getAccessibilitySelectedRows (id self, SEL)
@ -614,39 +618,13 @@ private:
setSelectedChildren ([self accessibilityRows], selected); setSelectedChildren ([self accessibilityRows], selected);
} }
static NSArray* getAccessibilityColumns (id self, SEL) static id getAccessibilityHeader (id self, SEL)
{ {
NSMutableArray* columns = [[NSMutableArray new] autorelease];
if (auto* tableInterface = getTableInterface (self)) if (auto* tableInterface = getTableInterface (self))
{ if (auto* handler = tableInterface->getHeaderHandler())
for (int column = 0; column < tableInterface->getNumColumns(); ++column) return static_cast<id> (handler->getNativeImplementation());
{
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; return nil;
}
static NSArray* getAccessibilitySelectedColumns (id self, SEL)
{
return getSelectedChildren ([self accessibilityColumns]);
}
static void setAccessibilitySelectedColumns (id self, SEL, NSArray* selected)
{
setSelectedChildren ([self accessibilityColumns], selected);
} }
//============================================================================== //==============================================================================
@ -743,8 +721,7 @@ private:
@selector (accessibilityRows), @selector (accessibilityRows),
@selector (accessibilitySelectedRows), @selector (accessibilitySelectedRows),
@selector (accessibilityColumnCount), @selector (accessibilityColumnCount),
@selector (accessibilityColumns), @selector (accessibilityHeader) })
@selector (accessibilitySelectedColumns) })
{ {
if (selector == tableSelector) if (selector == tableSelector)
return handler->getTableInterface() != nullptr; return handler->getTableInterface() != nullptr;
@ -881,7 +858,7 @@ static void sendHandlerNotification (const AccessibilityHandler& handler,
if (@available (macOS 10.9, *)) if (@available (macOS 10.9, *))
{ {
if (id accessibilityElement = (id) handler.getNativeImplementation()) if (id accessibilityElement = static_cast<id> (handler.getNativeImplementation()))
{ {
sendAccessibilityEvent (accessibilityElement, notification, sendAccessibilityEvent (accessibilityElement, notification,
(notification == NSAccessibilityLayoutChangedNotification (notification == NSAccessibilityLayoutChangedNotification
@ -966,7 +943,7 @@ void AccessibilityHandler::postAnnouncement (const String& announcementString, A
JUCE_END_IGNORE_WARNINGS_GCC_LIKE JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}(); }();
sendAccessibilityEvent ((id) [NSApp mainWindow], sendAccessibilityEvent (static_cast<id> ([NSApp mainWindow]),
NSAccessibilityAnnouncementRequestedNotification, NSAccessibilityAnnouncementRequestedNotification,
@{ NSAccessibilityAnnouncementKey: juceStringToNS (announcementString), @{ NSAccessibilityAnnouncementKey: juceStringToNS (announcementString),
NSAccessibilityPriorityKey: @(nsPriority) }); NSAccessibilityPriorityKey: @(nsPriority) });

View file

@ -46,7 +46,9 @@ public:
using Holder = std::unique_ptr<Base, AccessibleObjCClassDeleter>; using Holder = std::unique_ptr<Base, AccessibleObjCClassDeleter>;
protected: protected:
AccessibleObjCClass() : ObjCClass<Base> ("JUCEAccessibilityElement_") AccessibleObjCClass() : AccessibleObjCClass ("JUCEAccessibilityElement_") {}
explicit AccessibleObjCClass (const char* name) : ObjCClass<Base> (name)
{ {
ObjCClass<Base>::template addIvar<AccessibilityHandler*> ("handler"); ObjCClass<Base>::template addIvar<AccessibilityHandler*> ("handler");
} }

View file

@ -246,12 +246,7 @@ public:
int getRowIndex() const override int getRowIndex() const override
{ {
const auto index = handler.rowComponent.row; return handler.rowComponent.row;
if (handler.rowComponent.owner.hasAccessibleHeaderComponent())
return index + 1;
return index;
} }
int getRowSpan() const override { return 1; } int getRowSpan() const override { return 1; }
@ -300,23 +295,14 @@ public:
setViewedComponent (content.release()); setViewedComponent (content.release());
} }
RowComponent* getComponentForRow (int row) const noexcept int getIndexOfFirstVisibleRow() const { return jmax (0, firstIndex - 1); }
{
if (isPositiveAndBelow (row, rows.size()))
return rows[row];
return nullptr;
}
RowComponent* getComponentForRowWrapped (int row) const noexcept
{
return rows[row % jmax (1, rows.size())];
}
RowComponent* getComponentForRowIfOnscreen (int row) const noexcept RowComponent* getComponentForRowIfOnscreen (int row) const noexcept
{ {
return (row >= firstIndex && row < firstIndex + rows.size()) const auto startIndex = getIndexOfFirstVisibleRow();
? getComponentForRowWrapped (row) : nullptr;
return (startIndex <= row && row < startIndex + rows.size())
? rows[row % jmax (1, rows.size())] : nullptr;
} }
int getRowNumberOfComponent (Component* const rowComponent) const noexcept int getRowNumberOfComponent (Component* const rowComponent) const noexcept
@ -384,17 +370,20 @@ public:
firstWholeIndex = (y + rowH - 1) / rowH; firstWholeIndex = (y + rowH - 1) / rowH;
lastWholeIndex = (y + getMaximumVisibleHeight() - 1) / rowH; lastWholeIndex = (y + getMaximumVisibleHeight() - 1) / rowH;
auto startIndex = jmax (0, firstIndex - 1); const auto startIndex = getIndexOfFirstVisibleRow();
const auto lastIndex = startIndex + rows.size();
for (int i = 0; i < numNeeded; ++i) for (auto row = startIndex; row < lastIndex; ++row)
{ {
const int row = i + startIndex; if (auto* rowComp = getComponentForRowIfOnscreen (row))
if (auto* rowComp = getComponentForRowWrapped (row))
{ {
rowComp->setBounds (0, row * rowH, w, rowH); rowComp->setBounds (0, row * rowH, w, rowH);
rowComp->update (row, owner.isRowSelected (row)); rowComp->update (row, owner.isRowSelected (row));
} }
else
{
jassertfalse;
}
} }
} }
@ -1155,15 +1144,8 @@ std::unique_ptr<AccessibilityHandler> ListBox::createAccessibilityHandler()
{ {
listBox.checkModelPtrIsValid(); listBox.checkModelPtrIsValid();
if (listBox.model == nullptr) return listBox.model != nullptr ? listBox.model->getNumRows()
return 0; : 0;
const auto numRows = listBox.model->getNumRows();
if (listBox.hasAccessibleHeaderComponent())
return numRows + 1;
return numRows;
} }
int getNumColumns() const override int getNumColumns() const override
@ -1171,24 +1153,7 @@ std::unique_ptr<AccessibilityHandler> ListBox::createAccessibilityHandler()
return 1; return 1;
} }
const AccessibilityHandler* getCellHandler (int row, int) const override const AccessibilityHandler* getHeaderHandler() const override
{
if (auto* headerHandler = getHeaderHandler())
{
if (row == 0)
return headerHandler;
--row;
}
if (auto* rowComponent = listBox.viewport->getComponentForRow (row))
return rowComponent->getAccessibilityHandler();
return nullptr;
}
private:
const AccessibilityHandler* getHeaderHandler() const
{ {
if (listBox.hasAccessibleHeaderComponent()) if (listBox.hasAccessibleHeaderComponent())
return listBox.headerComponent->getAccessibilityHandler(); return listBox.headerComponent->getAccessibilityHandler();
@ -1196,6 +1161,20 @@ std::unique_ptr<AccessibilityHandler> ListBox::createAccessibilityHandler()
return nullptr; return nullptr;
} }
const AccessibilityHandler* getRowHandler (int row) const override
{
if (auto* rowComponent = listBox.viewport->getComponentForRowIfOnscreen (row))
return rowComponent->getAccessibilityHandler();
return nullptr;
}
const AccessibilityHandler* getCellHandler (int, int) const override
{
return nullptr;
}
private:
ListBox& listBox; ListBox& listBox;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableInterface) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableInterface)

View file

@ -50,6 +50,7 @@ public:
//============================================================================== //==============================================================================
TableHeaderComponent::TableHeaderComponent() TableHeaderComponent::TableHeaderComponent()
{ {
setFocusContainerType (FocusContainerType::focusContainer);
} }
TableHeaderComponent::~TableHeaderComponent() TableHeaderComponent::~TableHeaderComponent()
@ -86,7 +87,7 @@ int TableHeaderComponent::getNumColumns (const bool onlyCountVisibleColumns) con
String TableHeaderComponent::getColumnName (const int columnId) const String TableHeaderComponent::getColumnName (const int columnId) const
{ {
if (auto* ci = getInfoForId (columnId)) if (auto* ci = getInfoForId (columnId))
return ci->name; return ci->getTitle();
return {}; return {};
} }
@ -95,9 +96,9 @@ void TableHeaderComponent::setColumnName (const int columnId, const String& newN
{ {
if (auto* ci = getInfoForId (columnId)) if (auto* ci = getInfoForId (columnId))
{ {
if (ci->name != newName) if (ci->getTitle() != newName)
{ {
ci->name = newName; ci->setTitle (newName);
sendColumnsChanged(); sendColumnsChanged();
} }
} }
@ -116,7 +117,7 @@ void TableHeaderComponent::addColumn (const String& columnName,
jassert (width > 0); jassert (width > 0);
auto ci = new ColumnInfo(); auto ci = new ColumnInfo();
ci->name = columnName; ci->setTitle (columnName);
ci->id = columnId; ci->id = columnId;
ci->width = width; ci->width = width;
ci->lastDeliberateWidth = width; ci->lastDeliberateWidth = width;
@ -125,7 +126,11 @@ void TableHeaderComponent::addColumn (const String& columnName,
jassert (ci->maximumWidth >= ci->minimumWidth); jassert (ci->maximumWidth >= ci->minimumWidth);
ci->propertyFlags = propertyFlags; ci->propertyFlags = propertyFlags;
columns.insert (insertIndex, ci); auto* added = columns.insert (insertIndex, ci);
addChildComponent (added);
added->setVisible ((propertyFlags & visible) != 0);
resized();
sendColumnsChanged(); sendColumnsChanged();
} }
@ -197,6 +202,7 @@ void TableHeaderComponent::setColumnWidth (const int columnId, const int newWidt
} }
} }
resized();
repaint(); repaint();
columnsResized = true; columnsResized = true;
triggerAsyncUpdate(); triggerAsyncUpdate();
@ -340,6 +346,7 @@ void TableHeaderComponent::resizeColumnsToFit (int firstColumnIndex, int targetT
if (newWidth != ci->width) if (newWidth != ci->width)
{ {
ci->width = newWidth; ci->width = newWidth;
resized();
repaint(); repaint();
columnsResized = true; columnsResized = true;
triggerAsyncUpdate(); triggerAsyncUpdate();
@ -354,11 +361,7 @@ void TableHeaderComponent::setColumnVisible (const int columnId, const bool shou
{ {
if (shouldBeVisible != ci->isVisible()) if (shouldBeVisible != ci->isVisible())
{ {
if (shouldBeVisible) ci->setVisible (shouldBeVisible);
ci->propertyFlags |= visible;
else
ci->propertyFlags &= ~visible;
sendColumnsChanged(); sendColumnsChanged();
resized(); resized();
} }
@ -409,6 +412,7 @@ bool TableHeaderComponent::isSortedForwards() const
void TableHeaderComponent::reSortTable() void TableHeaderComponent::reSortTable()
{ {
sortChanged = true; sortChanged = true;
resized();
repaint(); repaint();
triggerAsyncUpdate(); triggerAsyncUpdate();
} }
@ -485,7 +489,7 @@ void TableHeaderComponent::addMenuItems (PopupMenu& menu, const int /*columnIdCl
{ {
for (auto* ci : columns) for (auto* ci : columns)
if ((ci->propertyFlags & appearsOnColumnMenu) != 0) if ((ci->propertyFlags & appearsOnColumnMenu) != 0)
menu.addItem (ci->id, ci->name, menu.addItem (ci->id, ci->getTitle(),
(ci->propertyFlags & (sortedForwards | sortedBackwards)) == 0, (ci->propertyFlags & (sortedForwards | sortedBackwards)) == 0,
isColumnVisible (ci->id)); isColumnVisible (ci->id));
} }
@ -502,28 +506,42 @@ void TableHeaderComponent::paint (Graphics& g)
lf.drawTableHeaderBackground (g, *this); lf.drawTableHeaderBackground (g, *this);
auto clip = g.getClipBounds(); for (auto* ci : columns)
{
if (ci->isVisible() && ci->getWidth() > 0)
{
Graphics::ScopedSaveState ss (g);
g.setOrigin (ci->getX(), ci->getY());
g.reduceClipRegion (0, 0, ci->getWidth(), ci->getHeight());
lf.drawTableHeaderColumn (g, *this, ci->getTitle(), ci->id, ci->width, getHeight(),
ci->id == columnIdUnderMouse,
ci->id == columnIdUnderMouse && isMouseButtonDown(),
ci->propertyFlags);
}
}
}
void TableHeaderComponent::resized()
{
auto clip = getBounds();
int x = 0; int x = 0;
for (auto* ci : columns)
ci->setBounds (0, 0, 0, 0);
for (auto* ci : columns) for (auto* ci : columns)
{ {
if (ci->isVisible()) if (ci->isVisible())
{ {
if (x + ci->width > clip.getX() if (x + ci->width > clip.getX()
&& (ci->id != columnIdBeingDragged && (ci->id != columnIdBeingDragged
|| dragOverlayComp == nullptr || dragOverlayComp == nullptr
|| ! dragOverlayComp->isVisible())) || ! dragOverlayComp->isVisible()))
{ {
Graphics::ScopedSaveState ss (g); ci->setBounds (x, 0, ci->width, getHeight());
g.setOrigin (x, 0);
g.reduceClipRegion (0, 0, ci->width, getHeight());
lf.drawTableHeaderColumn (g, *this, ci->name, ci->id, ci->width, getHeight(),
ci->id == columnIdUnderMouse,
ci->id == columnIdUnderMouse && isMouseButtonDown(),
ci->propertyFlags);
} }
x += ci->width; x += ci->width;
@ -540,6 +558,7 @@ void TableHeaderComponent::mouseExit (const MouseEvent&) { setColumnUnderMou
void TableHeaderComponent::mouseDown (const MouseEvent& e) void TableHeaderComponent::mouseDown (const MouseEvent& e)
{ {
resized();
repaint(); repaint();
columnIdBeingResized = 0; columnIdBeingResized = 0;
columnIdBeingDragged = 0; columnIdBeingDragged = 0;
@ -716,6 +735,7 @@ void TableHeaderComponent::endDrag (const int finalIndex)
moveColumn (columnIdBeingDragged, finalIndex); moveColumn (columnIdBeingDragged, finalIndex);
columnIdBeingDragged = 0; columnIdBeingDragged = 0;
resized();
repaint(); repaint();
for (int i = listeners.size(); --i >= 0;) for (int i = listeners.size(); --i >= 0;)
@ -735,6 +755,7 @@ void TableHeaderComponent::mouseUp (const MouseEvent& e)
c->lastDeliberateWidth = c->width; c->lastDeliberateWidth = c->width;
columnIdBeingResized = 0; columnIdBeingResized = 0;
resized();
repaint(); repaint();
endDrag (getIndexOfColumnId (columnIdBeingDragged, true)); endDrag (getIndexOfColumnId (columnIdBeingDragged, true));
@ -756,10 +777,6 @@ MouseCursor TableHeaderComponent::getMouseCursor()
} }
//============================================================================== //==============================================================================
bool TableHeaderComponent::ColumnInfo::isVisible() const
{
return (propertyFlags & TableHeaderComponent::visible) != 0;
}
TableHeaderComponent::ColumnInfo* TableHeaderComponent::getInfoForId (int id) const TableHeaderComponent::ColumnInfo* TableHeaderComponent::getInfoForId (int id) const
{ {
@ -793,6 +810,7 @@ void TableHeaderComponent::sendColumnsChanged()
if (stretchToFit && lastDeliberateWidth > 0) if (stretchToFit && lastDeliberateWidth > 0)
resizeAllColumnsToFit (lastDeliberateWidth); resizeAllColumnsToFit (lastDeliberateWidth);
resized();
repaint(); repaint();
columnsChanged = true; columnsChanged = true;
triggerAsyncUpdate(); triggerAsyncUpdate();
@ -903,4 +921,9 @@ std::unique_ptr<AccessibilityHandler> TableHeaderComponent::createAccessibilityH
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::tableHeader); return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::tableHeader);
} }
std::unique_ptr<AccessibilityHandler> TableHeaderComponent::ColumnInfo::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::tableHeader);
}
} // namespace juce } // namespace juce

View file

@ -403,6 +403,8 @@ public:
/** @internal */ /** @internal */
void paint (Graphics&) override; void paint (Graphics&) override;
/** @internal */ /** @internal */
void resized() override;
/** @internal */
void mouseMove (const MouseEvent&) override; void mouseMove (const MouseEvent&) override;
/** @internal */ /** @internal */
void mouseEnter (const MouseEvent&) override; void mouseEnter (const MouseEvent&) override;
@ -421,13 +423,13 @@ public:
virtual void showColumnChooserMenu (int columnIdClicked); virtual void showColumnChooserMenu (int columnIdClicked);
private: private:
struct ColumnInfo struct ColumnInfo : public Component
{ {
String name; ColumnInfo() { setInterceptsMouseClicks (false, false); }
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
int id, propertyFlags, width, minimumWidth, maximumWidth; int id, propertyFlags, width, minimumWidth, maximumWidth;
double lastDeliberateWidth; double lastDeliberateWidth;
bool isVisible() const;
}; };
OwnedArray<ColumnInfo> columns; OwnedArray<ColumnInfo> columns;

View file

@ -26,6 +26,9 @@
namespace juce namespace juce
{ {
static const Identifier tableColumnProperty { "_tableColumnId" };
static const Identifier tableAccessiblePlaceholderProperty { "_accessiblePlaceholder" };
class TableListBox::RowComp : public Component, class TableListBox::RowComp : public Component,
public TooltipClient public TooltipClient
{ {
@ -48,7 +51,7 @@ public:
for (int i = 0; i < numColumns; ++i) for (int i = 0; i < numColumns; ++i)
{ {
if (columnComponents[i] == nullptr) if (columnComponents[(size_t) i]->getProperties().contains (tableAccessiblePlaceholderProperty))
{ {
auto columnRect = headerComp.getColumnPosition (i).withHeight (getHeight()); auto columnRect = headerComp.getColumnPosition (i).withHeight (getHeight());
@ -86,33 +89,48 @@ public:
if (tableModel != nullptr && row < owner.getNumRows()) if (tableModel != nullptr && row < owner.getNumRows())
{ {
const Identifier columnProperty ("_tableColumnId"); const auto numColumns = owner.getHeader().getNumColumns (true);
auto numColumns = owner.getHeader().getNumColumns (true); columnComponents.resize ((size_t) numColumns);
for (int i = 0; i < numColumns; ++i) for (int i = 0; i < numColumns; ++i)
{ {
auto columnId = owner.getHeader().getColumnIdOfIndex (i, true); auto columnId = owner.getHeader().getColumnIdOfIndex (i, true);
auto* comp = columnComponents[i]; auto originalComp = std::move (columnComponents[(size_t) i]);
auto oldCustomComp = originalComp != nullptr && ! originalComp->getProperties().contains (tableAccessiblePlaceholderProperty)
? std::move (originalComp)
: nullptr;
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()));
if (comp != nullptr && columnId != static_cast<int> (comp->getProperties() [columnProperty])) auto columnComp = [&]
{ {
columnComponents.set (i, nullptr); // We got a result from refreshComponentForCell, so use that
comp = nullptr; if (newCustomComp != nullptr)
} return std::move (newCustomComp);
comp = tableModel->refreshComponentForCell (row, columnId, isSelected, comp); // There was already a placeholder component for this column
columnComponents.set (i, comp, false); if (originalComp != nullptr)
return std::move (originalComp);
if (comp != nullptr) // Create a new placeholder component to use
{ auto comp = std::make_unique<Component>();
comp->getProperties().set (columnProperty, columnId); comp->setInterceptsMouseClicks (false, false);
comp->getProperties().set (tableAccessiblePlaceholderProperty, true);
return comp;
}();
addAndMakeVisible (comp); // In order for navigation to work correctly on macOS, the number of child
resizeCustomComp (i); // accessibility elements on each row must match the number of header accessibility
} // elements.
columnComp->setFocusContainerType (FocusContainerType::focusContainer);
columnComp->getProperties().set (tableColumnProperty, columnId);
addAndMakeVisible (*columnComp);
columnComponents[(size_t) i] = std::move (columnComp);
resizeCustomComp (i);
} }
columnComponents.removeRange (numColumns, columnComponents.size());
} }
else else
{ {
@ -122,15 +140,19 @@ public:
void resized() override void resized() override
{ {
for (int i = columnComponents.size(); --i >= 0;) for (auto i = (int) columnComponents.size(); --i >= 0;)
resizeCustomComp (i); resizeCustomComp (i);
} }
void resizeCustomComp (int index) void resizeCustomComp (int index)
{ {
if (auto* c = columnComponents.getUnchecked (index)) if (auto& c = columnComponents[(size_t) index])
c->setBounds (owner.getHeader().getColumnPosition (index) {
.withY (0).withHeight (getHeight())); c->setBounds (owner.getHeader()
.getColumnPosition (index)
.withY (0)
.withHeight (getHeight()));
}
} }
void mouseDown (const MouseEvent& e) override void mouseDown (const MouseEvent& e) override
@ -220,7 +242,12 @@ public:
Component* findChildComponentForColumn (int columnId) const Component* findChildComponentForColumn (int columnId) const
{ {
return columnComponents [owner.getHeader().getIndexOfColumnId (columnId, true)]; const auto index = (size_t) owner.getHeader().getIndexOfColumnId (columnId, true);
if (isPositiveAndBelow (index, columnComponents.size()))
return columnComponents[index].get();
return nullptr;
} }
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
@ -298,7 +325,7 @@ public:
//============================================================================== //==============================================================================
TableListBox& owner; TableListBox& owner;
OwnedArray<Component> columnComponents; std::vector<std::unique_ptr<Component>> columnComponents;
int row = -1; int row = -1;
bool isSelected = false, isDragging = false, selectRowOnMouseUp = false; bool isSelected = false, isDragging = false, selectRowOnMouseUp = false;
@ -567,17 +594,28 @@ std::unique_ptr<AccessibilityHandler> TableListBox::createAccessibilityHandler()
return tableListBox.getHeader().getNumColumns (false); return tableListBox.getHeader().getNumColumns (false);
} }
const AccessibilityHandler* getCellHandler (int row, int column) const override const AccessibilityHandler* getRowHandler (int row) const override
{ {
if (isPositiveAndBelow (row, getNumRows())) if (isPositiveAndBelow (row, getNumRows()))
{
if (isPositiveAndBelow (column, getNumColumns()))
if (auto* cellComponent = tableListBox.getCellComponent (tableListBox.getHeader().getColumnIdOfIndex (column, false), row))
return cellComponent->getAccessibilityHandler();
if (auto* rowComp = tableListBox.getComponentForRowNumber (row)) if (auto* rowComp = tableListBox.getComponentForRowNumber (row))
return rowComp->getAccessibilityHandler(); return rowComp->getAccessibilityHandler();
}
return nullptr;
}
const AccessibilityHandler* getCellHandler (int row, int column) const override
{
if (isPositiveAndBelow (row, getNumRows()) && isPositiveAndBelow (column, getNumColumns()))
if (auto* cellComponent = tableListBox.getCellComponent (tableListBox.getHeader().getColumnIdOfIndex (column, false), row))
return cellComponent->getAccessibilityHandler();
return nullptr;
}
const AccessibilityHandler* getHeaderHandler() const override
{
if (tableListBox.hasAccessibleHeaderComponent())
return tableListBox.headerComponent->getAccessibilityHandler();
return nullptr; return nullptr;
} }

View file

@ -1448,7 +1448,12 @@ std::unique_ptr<AccessibilityHandler> TreeView::createAccessibilityHandler()
int getNumRows() const override { return treeView.getNumRowsInTree(); } int getNumRows() const override { return treeView.getNumRowsInTree(); }
int getNumColumns() const override { return 1; } int getNumColumns() const override { return 1; }
const AccessibilityHandler* getCellHandler (int row, int) const override const AccessibilityHandler* getHeaderHandler() const override
{
return nullptr;
}
const AccessibilityHandler* getRowHandler (int row) const override
{ {
if (auto* itemComp = treeView.getItemComponent (treeView.getItemOnRow (row))) if (auto* itemComp = treeView.getItemComponent (treeView.getItemOnRow (row)))
return itemComp->getAccessibilityHandler(); return itemComp->getAccessibilityHandler();
@ -1456,6 +1461,11 @@ std::unique_ptr<AccessibilityHandler> TreeView::createAccessibilityHandler()
return nullptr; return nullptr;
} }
const AccessibilityHandler* getCellHandler (int, int) const override
{
return nullptr;
}
private: private:
TreeView& treeView; TreeView& treeView;