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.
|
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
|
||||||
|
|
|
||||||
|
|
@ -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) });
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue