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:
parent
5e626e1c2b
commit
707767fa4c
8 changed files with 233 additions and 187 deletions
|
|
@ -49,6 +49,21 @@ public:
|
|||
nullptr if there is no cell at the specified position.
|
||||
*/
|
||||
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
|
||||
|
|
|
|||
|
|
@ -116,9 +116,7 @@ private:
|
|||
addMethod (@selector (accessibilitySelectedRows), getAccessibilitySelectedRows);
|
||||
addMethod (@selector (setAccessibilitySelectedRows:), setAccessibilitySelectedRows);
|
||||
addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount);
|
||||
addMethod (@selector (accessibilityColumns), getAccessibilityColumns);
|
||||
addMethod (@selector (accessibilitySelectedColumns), getAccessibilitySelectedColumns);
|
||||
addMethod (@selector (setAccessibilitySelectedColumns:), setAccessibilitySelectedColumns);
|
||||
addMethod (@selector (accessibilityHeader), getAccessibilityHeader);
|
||||
|
||||
addMethod (@selector (accessibilityRowIndexRange), getAccessibilityRowIndexRange);
|
||||
addMethod (@selector (accessibilityColumnIndexRange), getAccessibilityColumnIndexRange);
|
||||
|
|
@ -166,26 +164,30 @@ private:
|
|||
return selected;
|
||||
}
|
||||
|
||||
static void setSelected (id item, bool selected)
|
||||
{
|
||||
auto* handler = getHandler (item);
|
||||
|
||||
if (handler == nullptr)
|
||||
return;
|
||||
|
||||
const auto currentState = handler->getCurrentState();
|
||||
|
||||
if (isSelectable (currentState))
|
||||
{
|
||||
if (currentState.isSelected() != selected)
|
||||
handler->getActions().invoke (AccessibilityActionType::toggle);
|
||||
}
|
||||
else if (currentState.isFocusable())
|
||||
{
|
||||
[item setAccessibilityFocused: selected];
|
||||
}
|
||||
}
|
||||
|
||||
static void setSelectedChildren (NSArray* children, NSArray* selected)
|
||||
{
|
||||
for (id child in children)
|
||||
{
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
setSelected (child, [selected containsObject: child]);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -218,15 +220,15 @@ private:
|
|||
if (auto* modalHandler = modal->getAccessibilityHandler())
|
||||
{
|
||||
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())
|
||||
return (id) focusChild->getNativeImplementation();
|
||||
return static_cast<id> (focusChild->getNativeImplementation());
|
||||
}
|
||||
|
||||
return nil;
|
||||
|
|
@ -237,7 +239,7 @@ private:
|
|||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (auto* child = handler->getChildAt (roundToIntPoint (flippedScreenPoint (point))))
|
||||
return (id) child->getNativeImplementation();
|
||||
return static_cast<id> (child->getNativeImplementation());
|
||||
|
||||
return self;
|
||||
}
|
||||
|
|
@ -250,9 +252,9 @@ private:
|
|||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
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;
|
||||
|
|
@ -267,7 +269,7 @@ private:
|
|||
auto* accessibleChildren = [NSMutableArray arrayWithCapacity: (NSUInteger) children.size()];
|
||||
|
||||
for (auto* childHandler : children)
|
||||
[accessibleChildren addObject: (id) childHandler->getNativeImplementation()];
|
||||
[accessibleChildren addObject: static_cast<id> (childHandler->getNativeImplementation())];
|
||||
|
||||
return accessibleChildren;
|
||||
}
|
||||
|
|
@ -581,27 +583,29 @@ private:
|
|||
//==============================================================================
|
||||
static NSArray* getAccessibilityRows (id self, SEL)
|
||||
{
|
||||
NSMutableArray* rows = [[NSMutableArray new] autorelease];
|
||||
|
||||
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
|
||||
{
|
||||
[rows addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityRowRole
|
||||
frame: NSZeroRect
|
||||
label: @"Offscreen Row"
|
||||
parent: self]];
|
||||
frame: NSZeroRect
|
||||
label: @"Offscreen Row"
|
||||
parent: self]];
|
||||
}
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
return rows;
|
||||
return nil;
|
||||
}
|
||||
|
||||
static NSArray* getAccessibilitySelectedRows (id self, SEL)
|
||||
|
|
@ -614,39 +618,13 @@ private:
|
|||
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))
|
||||
{
|
||||
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]];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (auto* handler = tableInterface->getHeaderHandler())
|
||||
return static_cast<id> (handler->getNativeImplementation());
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
static NSArray* getAccessibilitySelectedColumns (id self, SEL)
|
||||
{
|
||||
return getSelectedChildren ([self accessibilityColumns]);
|
||||
}
|
||||
|
||||
static void setAccessibilitySelectedColumns (id self, SEL, NSArray* selected)
|
||||
{
|
||||
setSelectedChildren ([self accessibilityColumns], selected);
|
||||
return nil;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -743,8 +721,7 @@ private:
|
|||
@selector (accessibilityRows),
|
||||
@selector (accessibilitySelectedRows),
|
||||
@selector (accessibilityColumnCount),
|
||||
@selector (accessibilityColumns),
|
||||
@selector (accessibilitySelectedColumns) })
|
||||
@selector (accessibilityHeader) })
|
||||
{
|
||||
if (selector == tableSelector)
|
||||
return handler->getTableInterface() != nullptr;
|
||||
|
|
@ -881,7 +858,7 @@ static void sendHandlerNotification (const AccessibilityHandler& handler,
|
|||
|
||||
if (@available (macOS 10.9, *))
|
||||
{
|
||||
if (id accessibilityElement = (id) handler.getNativeImplementation())
|
||||
if (id accessibilityElement = static_cast<id> (handler.getNativeImplementation()))
|
||||
{
|
||||
sendAccessibilityEvent (accessibilityElement, notification,
|
||||
(notification == NSAccessibilityLayoutChangedNotification
|
||||
|
|
@ -966,7 +943,7 @@ void AccessibilityHandler::postAnnouncement (const String& announcementString, A
|
|||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}();
|
||||
|
||||
sendAccessibilityEvent ((id) [NSApp mainWindow],
|
||||
sendAccessibilityEvent (static_cast<id> ([NSApp mainWindow]),
|
||||
NSAccessibilityAnnouncementRequestedNotification,
|
||||
@{ NSAccessibilityAnnouncementKey: juceStringToNS (announcementString),
|
||||
NSAccessibilityPriorityKey: @(nsPriority) });
|
||||
|
|
|
|||
|
|
@ -46,7 +46,9 @@ public:
|
|||
using Holder = std::unique_ptr<Base, AccessibleObjCClassDeleter>;
|
||||
|
||||
protected:
|
||||
AccessibleObjCClass() : ObjCClass<Base> ("JUCEAccessibilityElement_")
|
||||
AccessibleObjCClass() : AccessibleObjCClass ("JUCEAccessibilityElement_") {}
|
||||
|
||||
explicit AccessibleObjCClass (const char* name) : ObjCClass<Base> (name)
|
||||
{
|
||||
ObjCClass<Base>::template addIvar<AccessibilityHandler*> ("handler");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -246,12 +246,7 @@ public:
|
|||
|
||||
int getRowIndex() const override
|
||||
{
|
||||
const auto index = handler.rowComponent.row;
|
||||
|
||||
if (handler.rowComponent.owner.hasAccessibleHeaderComponent())
|
||||
return index + 1;
|
||||
|
||||
return index;
|
||||
return handler.rowComponent.row;
|
||||
}
|
||||
|
||||
int getRowSpan() const override { return 1; }
|
||||
|
|
@ -300,23 +295,14 @@ public:
|
|||
setViewedComponent (content.release());
|
||||
}
|
||||
|
||||
RowComponent* getComponentForRow (int row) const noexcept
|
||||
{
|
||||
if (isPositiveAndBelow (row, rows.size()))
|
||||
return rows[row];
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RowComponent* getComponentForRowWrapped (int row) const noexcept
|
||||
{
|
||||
return rows[row % jmax (1, rows.size())];
|
||||
}
|
||||
int getIndexOfFirstVisibleRow() const { return jmax (0, firstIndex - 1); }
|
||||
|
||||
RowComponent* getComponentForRowIfOnscreen (int row) const noexcept
|
||||
{
|
||||
return (row >= firstIndex && row < firstIndex + rows.size())
|
||||
? getComponentForRowWrapped (row) : nullptr;
|
||||
const auto startIndex = getIndexOfFirstVisibleRow();
|
||||
|
||||
return (startIndex <= row && row < startIndex + rows.size())
|
||||
? rows[row % jmax (1, rows.size())] : nullptr;
|
||||
}
|
||||
|
||||
int getRowNumberOfComponent (Component* const rowComponent) const noexcept
|
||||
|
|
@ -384,17 +370,20 @@ public:
|
|||
firstWholeIndex = (y + rowH - 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 = getComponentForRowWrapped (row))
|
||||
if (auto* rowComp = getComponentForRowIfOnscreen (row))
|
||||
{
|
||||
rowComp->setBounds (0, row * rowH, w, rowH);
|
||||
rowComp->update (row, owner.isRowSelected (row));
|
||||
}
|
||||
else
|
||||
{
|
||||
jassertfalse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1155,15 +1144,8 @@ std::unique_ptr<AccessibilityHandler> ListBox::createAccessibilityHandler()
|
|||
{
|
||||
listBox.checkModelPtrIsValid();
|
||||
|
||||
if (listBox.model == nullptr)
|
||||
return 0;
|
||||
|
||||
const auto numRows = listBox.model->getNumRows();
|
||||
|
||||
if (listBox.hasAccessibleHeaderComponent())
|
||||
return numRows + 1;
|
||||
|
||||
return numRows;
|
||||
return listBox.model != nullptr ? listBox.model->getNumRows()
|
||||
: 0;
|
||||
}
|
||||
|
||||
int getNumColumns() const override
|
||||
|
|
@ -1171,24 +1153,7 @@ std::unique_ptr<AccessibilityHandler> ListBox::createAccessibilityHandler()
|
|||
return 1;
|
||||
}
|
||||
|
||||
const AccessibilityHandler* getCellHandler (int row, int) 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
|
||||
const AccessibilityHandler* getHeaderHandler() const override
|
||||
{
|
||||
if (listBox.hasAccessibleHeaderComponent())
|
||||
return listBox.headerComponent->getAccessibilityHandler();
|
||||
|
|
@ -1196,6 +1161,20 @@ std::unique_ptr<AccessibilityHandler> ListBox::createAccessibilityHandler()
|
|||
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;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableInterface)
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ public:
|
|||
//==============================================================================
|
||||
TableHeaderComponent::TableHeaderComponent()
|
||||
{
|
||||
setFocusContainerType (FocusContainerType::focusContainer);
|
||||
}
|
||||
|
||||
TableHeaderComponent::~TableHeaderComponent()
|
||||
|
|
@ -86,7 +87,7 @@ int TableHeaderComponent::getNumColumns (const bool onlyCountVisibleColumns) con
|
|||
String TableHeaderComponent::getColumnName (const int columnId) const
|
||||
{
|
||||
if (auto* ci = getInfoForId (columnId))
|
||||
return ci->name;
|
||||
return ci->getTitle();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
@ -95,9 +96,9 @@ void TableHeaderComponent::setColumnName (const int columnId, const String& newN
|
|||
{
|
||||
if (auto* ci = getInfoForId (columnId))
|
||||
{
|
||||
if (ci->name != newName)
|
||||
if (ci->getTitle() != newName)
|
||||
{
|
||||
ci->name = newName;
|
||||
ci->setTitle (newName);
|
||||
sendColumnsChanged();
|
||||
}
|
||||
}
|
||||
|
|
@ -116,7 +117,7 @@ void TableHeaderComponent::addColumn (const String& columnName,
|
|||
jassert (width > 0);
|
||||
|
||||
auto ci = new ColumnInfo();
|
||||
ci->name = columnName;
|
||||
ci->setTitle (columnName);
|
||||
ci->id = columnId;
|
||||
ci->width = width;
|
||||
ci->lastDeliberateWidth = width;
|
||||
|
|
@ -125,7 +126,11 @@ void TableHeaderComponent::addColumn (const String& columnName,
|
|||
jassert (ci->maximumWidth >= ci->minimumWidth);
|
||||
ci->propertyFlags = propertyFlags;
|
||||
|
||||
columns.insert (insertIndex, ci);
|
||||
auto* added = columns.insert (insertIndex, ci);
|
||||
addChildComponent (added);
|
||||
added->setVisible ((propertyFlags & visible) != 0);
|
||||
|
||||
resized();
|
||||
sendColumnsChanged();
|
||||
}
|
||||
|
||||
|
|
@ -197,6 +202,7 @@ void TableHeaderComponent::setColumnWidth (const int columnId, const int newWidt
|
|||
}
|
||||
}
|
||||
|
||||
resized();
|
||||
repaint();
|
||||
columnsResized = true;
|
||||
triggerAsyncUpdate();
|
||||
|
|
@ -340,6 +346,7 @@ void TableHeaderComponent::resizeColumnsToFit (int firstColumnIndex, int targetT
|
|||
if (newWidth != ci->width)
|
||||
{
|
||||
ci->width = newWidth;
|
||||
resized();
|
||||
repaint();
|
||||
columnsResized = true;
|
||||
triggerAsyncUpdate();
|
||||
|
|
@ -354,11 +361,7 @@ void TableHeaderComponent::setColumnVisible (const int columnId, const bool shou
|
|||
{
|
||||
if (shouldBeVisible != ci->isVisible())
|
||||
{
|
||||
if (shouldBeVisible)
|
||||
ci->propertyFlags |= visible;
|
||||
else
|
||||
ci->propertyFlags &= ~visible;
|
||||
|
||||
ci->setVisible (shouldBeVisible);
|
||||
sendColumnsChanged();
|
||||
resized();
|
||||
}
|
||||
|
|
@ -409,6 +412,7 @@ bool TableHeaderComponent::isSortedForwards() const
|
|||
void TableHeaderComponent::reSortTable()
|
||||
{
|
||||
sortChanged = true;
|
||||
resized();
|
||||
repaint();
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
|
@ -485,7 +489,7 @@ void TableHeaderComponent::addMenuItems (PopupMenu& menu, const int /*columnIdCl
|
|||
{
|
||||
for (auto* ci : columns)
|
||||
if ((ci->propertyFlags & appearsOnColumnMenu) != 0)
|
||||
menu.addItem (ci->id, ci->name,
|
||||
menu.addItem (ci->id, ci->getTitle(),
|
||||
(ci->propertyFlags & (sortedForwards | sortedBackwards)) == 0,
|
||||
isColumnVisible (ci->id));
|
||||
}
|
||||
|
|
@ -502,28 +506,42 @@ void TableHeaderComponent::paint (Graphics& g)
|
|||
|
||||
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;
|
||||
|
||||
for (auto* ci : columns)
|
||||
ci->setBounds (0, 0, 0, 0);
|
||||
|
||||
for (auto* ci : columns)
|
||||
{
|
||||
if (ci->isVisible())
|
||||
{
|
||||
if (x + ci->width > clip.getX()
|
||||
&& (ci->id != columnIdBeingDragged
|
||||
|| dragOverlayComp == nullptr
|
||||
|| ! dragOverlayComp->isVisible()))
|
||||
&& (ci->id != columnIdBeingDragged
|
||||
|| dragOverlayComp == nullptr
|
||||
|| ! dragOverlayComp->isVisible()))
|
||||
{
|
||||
Graphics::ScopedSaveState ss (g);
|
||||
|
||||
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);
|
||||
ci->setBounds (x, 0, ci->width, getHeight());
|
||||
}
|
||||
|
||||
x += ci->width;
|
||||
|
|
@ -540,6 +558,7 @@ void TableHeaderComponent::mouseExit (const MouseEvent&) { setColumnUnderMou
|
|||
|
||||
void TableHeaderComponent::mouseDown (const MouseEvent& e)
|
||||
{
|
||||
resized();
|
||||
repaint();
|
||||
columnIdBeingResized = 0;
|
||||
columnIdBeingDragged = 0;
|
||||
|
|
@ -716,6 +735,7 @@ void TableHeaderComponent::endDrag (const int finalIndex)
|
|||
moveColumn (columnIdBeingDragged, finalIndex);
|
||||
|
||||
columnIdBeingDragged = 0;
|
||||
resized();
|
||||
repaint();
|
||||
|
||||
for (int i = listeners.size(); --i >= 0;)
|
||||
|
|
@ -735,6 +755,7 @@ void TableHeaderComponent::mouseUp (const MouseEvent& e)
|
|||
c->lastDeliberateWidth = c->width;
|
||||
|
||||
columnIdBeingResized = 0;
|
||||
resized();
|
||||
repaint();
|
||||
|
||||
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
|
||||
{
|
||||
|
|
@ -793,6 +810,7 @@ void TableHeaderComponent::sendColumnsChanged()
|
|||
if (stretchToFit && lastDeliberateWidth > 0)
|
||||
resizeAllColumnsToFit (lastDeliberateWidth);
|
||||
|
||||
resized();
|
||||
repaint();
|
||||
columnsChanged = true;
|
||||
triggerAsyncUpdate();
|
||||
|
|
@ -903,4 +921,9 @@ std::unique_ptr<AccessibilityHandler> TableHeaderComponent::createAccessibilityH
|
|||
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
|
||||
|
|
|
|||
|
|
@ -403,6 +403,8 @@ public:
|
|||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void mouseMove (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseEnter (const MouseEvent&) override;
|
||||
|
|
@ -421,13 +423,13 @@ public:
|
|||
virtual void showColumnChooserMenu (int columnIdClicked);
|
||||
|
||||
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;
|
||||
double lastDeliberateWidth;
|
||||
|
||||
bool isVisible() const;
|
||||
};
|
||||
|
||||
OwnedArray<ColumnInfo> columns;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
static const Identifier tableColumnProperty { "_tableColumnId" };
|
||||
static const Identifier tableAccessiblePlaceholderProperty { "_accessiblePlaceholder" };
|
||||
|
||||
class TableListBox::RowComp : public Component,
|
||||
public TooltipClient
|
||||
{
|
||||
|
|
@ -48,7 +51,7 @@ public:
|
|||
|
||||
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());
|
||||
|
||||
|
|
@ -86,33 +89,48 @@ public:
|
|||
|
||||
if (tableModel != nullptr && row < owner.getNumRows())
|
||||
{
|
||||
const Identifier columnProperty ("_tableColumnId");
|
||||
auto numColumns = owner.getHeader().getNumColumns (true);
|
||||
const auto numColumns = owner.getHeader().getNumColumns (true);
|
||||
columnComponents.resize ((size_t) numColumns);
|
||||
|
||||
for (int i = 0; i < numColumns; ++i)
|
||||
{
|
||||
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);
|
||||
comp = nullptr;
|
||||
}
|
||||
// We got a result from refreshComponentForCell, so use that
|
||||
if (newCustomComp != nullptr)
|
||||
return std::move (newCustomComp);
|
||||
|
||||
comp = tableModel->refreshComponentForCell (row, columnId, isSelected, comp);
|
||||
columnComponents.set (i, comp, false);
|
||||
// There was already a placeholder component for this column
|
||||
if (originalComp != nullptr)
|
||||
return std::move (originalComp);
|
||||
|
||||
if (comp != nullptr)
|
||||
{
|
||||
comp->getProperties().set (columnProperty, columnId);
|
||||
// Create a new placeholder component to use
|
||||
auto comp = std::make_unique<Component>();
|
||||
comp->setInterceptsMouseClicks (false, false);
|
||||
comp->getProperties().set (tableAccessiblePlaceholderProperty, true);
|
||||
return comp;
|
||||
}();
|
||||
|
||||
addAndMakeVisible (comp);
|
||||
resizeCustomComp (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.
|
||||
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
|
||||
{
|
||||
|
|
@ -122,15 +140,19 @@ public:
|
|||
|
||||
void resized() override
|
||||
{
|
||||
for (int i = columnComponents.size(); --i >= 0;)
|
||||
for (auto i = (int) columnComponents.size(); --i >= 0;)
|
||||
resizeCustomComp (i);
|
||||
}
|
||||
|
||||
void resizeCustomComp (int index)
|
||||
{
|
||||
if (auto* c = columnComponents.getUnchecked (index))
|
||||
c->setBounds (owner.getHeader().getColumnPosition (index)
|
||||
.withY (0).withHeight (getHeight()));
|
||||
if (auto& c = columnComponents[(size_t) index])
|
||||
{
|
||||
c->setBounds (owner.getHeader()
|
||||
.getColumnPosition (index)
|
||||
.withY (0)
|
||||
.withHeight (getHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
void mouseDown (const MouseEvent& e) override
|
||||
|
|
@ -220,7 +242,12 @@ public:
|
|||
|
||||
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
|
||||
|
|
@ -298,7 +325,7 @@ public:
|
|||
|
||||
//==============================================================================
|
||||
TableListBox& owner;
|
||||
OwnedArray<Component> columnComponents;
|
||||
std::vector<std::unique_ptr<Component>> columnComponents;
|
||||
int row = -1;
|
||||
bool isSelected = false, isDragging = false, selectRowOnMouseUp = false;
|
||||
|
||||
|
|
@ -567,17 +594,28 @@ std::unique_ptr<AccessibilityHandler> TableListBox::createAccessibilityHandler()
|
|||
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 (column, getNumColumns()))
|
||||
if (auto* cellComponent = tableListBox.getCellComponent (tableListBox.getHeader().getColumnIdOfIndex (column, false), row))
|
||||
return cellComponent->getAccessibilityHandler();
|
||||
|
||||
if (auto* rowComp = tableListBox.getComponentForRowNumber (row))
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1448,7 +1448,12 @@ std::unique_ptr<AccessibilityHandler> TreeView::createAccessibilityHandler()
|
|||
int getNumRows() const override { return treeView.getNumRowsInTree(); }
|
||||
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)))
|
||||
return itemComp->getAccessibilityHandler();
|
||||
|
|
@ -1456,6 +1461,11 @@ std::unique_ptr<AccessibilityHandler> TreeView::createAccessibilityHandler()
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
const AccessibilityHandler* getCellHandler (int, int) const override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
TreeView& treeView;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue