Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qcocoaaccessibilityelement.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <AppKit/AppKit.h>
5
8#include "qcocoahelpers.h"
9#include "qcocoawindow.h"
10#include "qcocoascreen.h"
11
12#include <QtGui/private/qaccessiblecache_p.h>
13#include <QtGui/private/qaccessiblebridgeutils_p.h>
14#include <QtGui/qaccessible.h>
15
17
18#if QT_CONFIG(accessibility)
19
33static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *offset, NSUInteger *start = 0, NSUInteger *end = 0)
34{
35 Q_ASSERT(*line == -1 || *offset == -1);
36 Q_ASSERT(*line != -1 || *offset != -1);
37 Q_ASSERT(*offset <= text->characterCount());
38
39 int curLine = -1;
40 int curStart = 0, curEnd = 0;
41
42 do {
43 curStart = curEnd;
44 text->textAtOffset(curStart, QAccessible::LineBoundary, &curStart, &curEnd);
45 // If the text is empty then we just return
46 if (curStart == -1 || curEnd == -1) {
47 if (start)
48 *start = 0;
49 if (end)
50 *end = 0;
51 return;
52 }
53 ++curLine;
54 {
55 // check for a case where a single word longer than the text edit's width and gets wrapped
56 // in the middle of the word; in this case curEnd will be an offset belonging to the next line
57 // and therefore nextEnd will not be equal to curEnd
58 int nextStart;
59 int nextEnd;
60 text->textAtOffset(curEnd, QAccessible::LineBoundary, &nextStart, &nextEnd);
61 if (nextEnd == curEnd)
62 ++curEnd;
63 }
64 } while ((*line == -1 || curLine < *line) && (*offset == -1 || (curEnd <= *offset)) && curEnd <= text->characterCount());
65
66 curEnd = qMin(curEnd, text->characterCount());
67
68 if (*line == -1)
69 *line = curLine;
70 if (*offset == -1)
71 *offset = curStart;
72
73 Q_ASSERT(curStart >= 0);
74 Q_ASSERT(curEnd >= 0);
75 if (start)
76 *start = curStart;
77 if (end)
78 *end = curEnd;
79}
80
81@implementation QMacAccessibilityElement {
82 QAccessible::Id axid;
83 int m_rowIndex;
84 int m_columnIndex;
85
86 // used by NSAccessibilityTable
87 NSMutableArray<QMacAccessibilityElement *> *rows; // corresponds to accessibilityRows
88 NSMutableArray<QMacAccessibilityElement *> *columns; // corresponds to accessibilityColumns
89
90 // If synthesizedRole is set, this means that this objects does not have a corresponding
91 // QAccessibleInterface, but it is synthesized by the cocoa plugin in order to meet the
92 // NSAccessibility requirements.
93 // The ownership is controlled by the parent object identified with the axid member variable.
94 // (Therefore, if this member is set, this objects axid member is the same as the parents axid
95 // member)
96 NSString *synthesizedRole;
97}
98
99- (instancetype)initWithId:(QAccessible::Id)anId
100{
101 return [self initWithId:anId role:nil];
102}
103
104- (instancetype)initWithId:(QAccessible::Id)anId role:(NSAccessibilityRole)role
105{
106 Q_ASSERT((int)anId < 0);
107 self = [super init];
108 if (self) {
109 axid = anId;
110 m_rowIndex = -1;
111 m_columnIndex = -1;
112 rows = nil;
113 columns = nil;
114 synthesizedRole = role;
115 // table: if this is not created as an element managed by the table, then
116 // it's either the table itself, or an element created for an already existing
117 // cell interface (or an element that's not at all related to a table).
118 if (!synthesizedRole) {
119 if (QAccessibleInterface *iface = QAccessible::accessibleInterface(axid)) {
120 if (iface->tableInterface()) {
121 [self updateTableModel];
122 } else if (const auto *cell = iface->tableCellInterface()) {
123 // If we create an element for a table cell, initialize it with row/column
124 // and insert it into the corresponding row's columns array.
125 m_rowIndex = cell->rowIndex();
126 m_columnIndex = cell->columnIndex();
127 QAccessibleInterface *table = cell->table();
129 QAccessibleTableInterface *tableInterface = table->tableInterface();
130 if (tableInterface) {
131 auto *tableElement = [QMacAccessibilityElement elementWithInterface:table];
132 Q_ASSERT(tableElement);
133 Q_ASSERT(tableElement->rows && int(tableElement->rows.count) > m_rowIndex);
134 auto *rowElement = tableElement->rows[m_rowIndex];
135 if (!rowElement->columns) {
136 rowElement->columns = [rowElement populateTableRow:rowElement->columns
137 count:tableInterface->columnCount()];
138 }
139 rowElement->columns[m_columnIndex] = self;
140 }
141 }
142 }
143 }
144 }
145
146 return self;
147}
148
149+ (instancetype)elementWithId:(QAccessible::Id)anId
150{
151 Q_ASSERT(anId);
152 if (!anId)
153 return nil;
154
155 QAccessibleCache *cache = QAccessibleCache::instance();
156
157 QMacAccessibilityElement *element = cache->elementForId(anId);
158 if (!element) {
159 Q_ASSERT(QAccessible::accessibleInterface(anId));
160 element = [[self alloc] initWithId:anId];
161 cache->insertElement(anId, element);
162 }
163 return element;
164}
165
166+ (instancetype)elementWithInterface:(QAccessibleInterface *)iface
167{
168 Q_ASSERT(iface);
169 if (!iface)
170 return nil;
171
172 const QAccessible::Id anId = QAccessible::uniqueId(iface);
173 return [self elementWithId:anId];
174}
175
176- (void)invalidate {
177 axid = 0;
178 rows = nil;
179 columns = nil;
180 synthesizedRole = nil;
181
182 NSAccessibilityPostNotification(self, NSAccessibilityUIElementDestroyedNotification);
183 [self release];
184}
185
186- (void)dealloc {
187 if (rows)
188 [rows release]; // will also release all entries first
189 if (columns)
190 [columns release]; // will also release all entries first
191 [super dealloc];
192}
193
194- (BOOL)isEqual:(id)object {
195 if ([object isKindOfClass:[QMacAccessibilityElement class]]) {
196 QMacAccessibilityElement *other = object;
197 return other->axid == axid && other->synthesizedRole == synthesizedRole;
198 } else {
199 return NO;
200 }
201}
202
203- (NSUInteger)hash {
204 return axid;
205}
206
207- (BOOL)isManagedByParent {
208 return synthesizedRole != nil;
209}
210
211- (NSMutableArray *)populateTableArray:(NSMutableArray *)array role:(NSAccessibilityRole)role count:(int)count
212{
213 if (QAccessibleInterface *iface = self.qtInterface) {
214 if (!array) {
215 array = [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:count];
216 [array retain];
217 } else {
218 [array removeAllObjects];
219 }
221 for (int n = 0; n < count; ++n) {
222 // columns will have same axid as table (but not inserted in cache)
223 QMacAccessibilityElement *element =
224 [[QMacAccessibilityElement alloc] initWithId:axid role:role];
225 if (element) {
226 if (role == NSAccessibilityRowRole)
227 element->m_rowIndex = n;
228 else if (role == NSAccessibilityColumnRole)
229 element->m_columnIndex = n;
230 [array addObject:element];
231 [element release];
232 } else {
233 qWarning("QCocoaAccessibility: invalid child");
234 }
235 }
236 return array;
237 }
238 return nil;
239}
240
241- (NSMutableArray *)populateTableRow:(NSMutableArray *)array count:(int)count
242{
243 Q_ASSERT(synthesizedRole == NSAccessibilityRowRole);
244 if (!array) {
245 array = [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:count];
246 [array retain];
247 // When macOS asks for the children of a row, then we populate the row's column
248 // array with synthetic elements as place holders. This way, we don't have to
249 // create QAccessibleInterfaces for every cell before they are really needed.
250 // We don't add those synthetic elements into the cache, and we give them the
251 // same axid as the table. This way, we can get easily to the table, and from
252 // there to the QAccessibleInterface for the cell, when we have to eventually
253 // associate such an interface with the element (at which point it is no longer
254 // a placeholder).
255 for (int n = 0; n < count; ++n) {
256 // columns will have same axid as table (but not inserted in cache)
257 QMacAccessibilityElement *cell =
258 [[QMacAccessibilityElement alloc] initWithId:axid role:NSAccessibilityCellRole];
259 if (cell) {
260 cell->m_rowIndex = m_rowIndex;
261 cell->m_columnIndex = n;
262 [array addObject:cell];
263 }
264 }
265 }
267 return array;
268}
269
270- (void)updateTableModel
271{
272 if (QAccessibleInterface *iface = self.qtInterface) {
273 if (QAccessibleTableInterface *table = iface->tableInterface()) {
274 Q_ASSERT(!self.isManagedByParent);
275 rows = [self populateTableArray:rows role:NSAccessibilityRowRole count:table->rowCount()];
276 columns = [self populateTableArray:columns role:NSAccessibilityColumnRole count:table->columnCount()];
277 }
278 }
279}
280
281- (QAccessibleInterface *)qtInterface
282{
283 QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
284 if (!iface || !iface->isValid())
285 return nullptr;
286
287 // If this is a placeholder element for a table cell, associate it with the
288 // cell interface (which will be created now, if needed). The current axid is
289 // for the table to which the cell belongs, so iface is pointing at the table.
290 if (synthesizedRole == NSAccessibilityCellRole) {
291 // get the cell interface - there must be a valid one
292 QAccessibleTableInterface *table = iface->tableInterface();
294 QAccessibleInterface *cell = table->cellAt(m_rowIndex, m_columnIndex);
295 if (!cell)
296 return nullptr;
297 Q_ASSERT(cell->isValid());
298 iface = cell;
299
300 // no longer a placeholder
301 axid = QAccessible::uniqueId(cell);
302 synthesizedRole = nil;
303
304 QAccessibleCache *cache = QAccessibleCache::instance();
305 if (QMacAccessibilityElement *cellElement = cache->elementForId(axid)) {
306 // there already is another, non-placeholder element in the cache
307 Q_ASSERT(cellElement->synthesizedRole == nil);
308 // we have to release it if it's not us
309 if (cellElement != self) {
310 // for the same cell position
311 Q_ASSERT(cellElement->m_rowIndex == m_rowIndex && cellElement->m_columnIndex == m_columnIndex);
312 [cellElement release];
313 }
314 }
315
316 cache->insertElement(axid, self);
317 }
318 return iface;
319}
320
321//
322// accessibility protocol
323//
324
325- (BOOL)isAccessibilityFocused
326{
327 // Just check if the app thinks we're focused.
328 id focusedElement = NSApp.accessibilityApplicationFocusedUIElement;
329 return [focusedElement isEqual:self];
330}
331
332// attributes
333
334+ (id) lineNumberForIndex: (int)index forText:(const QString &)text
335{
336 auto textBefore = QStringView(text).left(index);
337 qsizetype newlines = textBefore.count(u'\n');
338 return @(newlines);
339}
340
341- (BOOL) accessibilityNotifiesWhenDestroyed {
342 return YES;
343}
344
345- (NSString *) accessibilityRole {
346 // shortcut for cells, rows, and columns in a table
347 if (synthesizedRole)
348 return synthesizedRole;
349 if (QAccessibleInterface *iface = self.qtInterface)
350 return QCocoaAccessible::macRole(iface);
351 return NSAccessibilityUnknownRole;
352}
353
354- (NSString *) accessibilitySubRole {
355 if (QAccessibleInterface *iface = self.qtInterface)
356 return QCocoaAccessible::macSubrole(iface);
357 return NSAccessibilityUnknownRole;
358}
359
360- (NSString *) accessibilityRoleDescription {
361 if (QAccessibleInterface *iface = self.qtInterface)
362 return NSAccessibilityRoleDescription(self.accessibilityRole, self.accessibilitySubRole);
363 return NSAccessibilityUnknownRole;
364}
365
366- (NSArray *) accessibilityChildren {
367 // shortcut for cells
368 if (synthesizedRole == NSAccessibilityCellRole)
369 return nil;
370
371 QAccessibleInterface *iface = self.qtInterface;
372 if (!iface)
373 return nil;
374 if (QAccessibleTableInterface *table = iface->tableInterface()) {
375 // either a table or table rows/columns
376 if (!synthesizedRole) {
377 // This is the table element, parent of all rows and columns
378 /*
379 * Typical 2x2 table hierarchy as can be observed in a table found under
380 * Apple -> System Settings -> General -> Login Items (macOS 13)
381 *
382 * (AXTable)
383 * | Columns: NSArray* (2 items)
384 * | Rows: NSArray* (2 items)
385 * | Visible Columns: NSArray* (2 items)
386 * | Visible Rows: NSArray* (2 items)
387 * | Children: NSArray* (5 items)
388 +----<--| Header: (AXGroup)
389 | * +-- (AXRow)
390 | * | +-- (AXText)
391 | * | +-- (AXTextField)
392 | * +-- (AXRow)
393 | * | +-- (AXText)
394 | * | +-- (AXTextField)
395 | * +-- (AXColumn)
396 | * | Header: "Item" (sort button)
397 | * | Index: 0
398 | * | Rows: NSArray* (2 items)
399 | * | Visible Rows: NSArray* (2 items)
400 | * +-- (AXColumn)
401 | * | Header: "Kind" (sort button)
402 | * | Index: 1
403 | * | Rows: NSArray* (2 items)
404 | * | Visible Rows: NSArray* (2 items)
405 +----> +-- (AXGroup)
406 * +-- (AXButton/AXSortButton) Item [NSAccessibilityTableHeaderCellProxy]
407 * +-- (AXButton/AXSortButton) Kind [NSAccessibilityTableHeaderCellProxy]
408 */
409 NSArray *rs = [self accessibilityRows];
410 NSArray *cs = [self accessibilityColumns];
411 const int rCount = int([rs count]);
412 const int cCount = int([cs count]);
413 int childCount = rCount + cCount;
414 NSMutableArray<QMacAccessibilityElement *> *tableChildren =
415 [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:childCount];
416 for (int i = 0; i < rCount; ++i) {
417 [tableChildren addObject:[rs objectAtIndex:i]];
418 }
419 for (int i = 0; i < cCount; ++i) {
420 [tableChildren addObject:[cs objectAtIndex:i]];
421 }
422 return NSAccessibilityUnignoredChildren(tableChildren);
423 } else if (synthesizedRole == NSAccessibilityColumnRole) {
424 return nil;
425 } else if (synthesizedRole == NSAccessibilityRowRole) {
426 // axid matches the parent table axid so that we can easily find the parent table
427 // children of row are cell/any items
428 Q_ASSERT(m_rowIndex >= 0);
429 const int numColumns = table->columnCount();
430 columns = [self populateTableRow:columns count:numColumns];
431 return NSAccessibilityUnignoredChildren(columns);
432 }
433 }
434 return QCocoaAccessible::unignoredChildren(iface);
435}
436
437- (NSArray *) accessibilitySelectedChildren {
438 QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
439 if (!iface || !iface->isValid())
440 return nil;
441
442 QAccessibleSelectionInterface *selection = iface->selectionInterface();
443 if (!selection)
444 return nil;
445
446 const QList<QAccessibleInterface *> selectedList = selection->selectedItems();
447 const qsizetype numSelected = selectedList.size();
448 NSMutableArray<QMacAccessibilityElement *> *selectedChildren =
449 [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:numSelected];
450 for (QAccessibleInterface *selectedChild : selectedList) {
451 if (selectedChild && selectedChild->isValid()) {
452 QAccessible::Id id = QAccessible::uniqueId(selectedChild);
453 QMacAccessibilityElement *element = [QMacAccessibilityElement elementWithId:id];
454 if (element)
455 [selectedChildren addObject:element];
456 }
457 }
458 return NSAccessibilityUnignoredChildren(selectedChildren);
459}
460
461- (id) accessibilityWindow {
462 // We're in the same window as our parent.
463 return [self.accessibilityParent accessibilityWindow];
464}
465
466- (id) accessibilityTopLevelUIElementAttribute {
467 // We're in the same top level element as our parent.
468 return [self.accessibilityParent accessibilityTopLevelUIElementAttribute];
469}
470
471- (NSString *) accessibilityTitle {
472 if (QAccessibleInterface *iface = self.qtInterface) {
473 if (iface->role() == QAccessible::StaticText)
474 return nil;
475 if (self.isManagedByParent)
476 return nil;
477 return iface->text(QAccessible::Name).toNSString();
478 }
479 return nil;
480}
481
482- (BOOL) isAccessibilityEnabled {
483 if (QAccessibleInterface *iface = self.qtInterface)
484 return !iface->state().disabled;
485 return false;
486}
487
488- (id)accessibilityParent {
489 if (synthesizedRole == NSAccessibilityCellRole) {
490 // a synthetic cell without interface - shortcut to the row
491 QMacAccessibilityElement *tableElement =
492 [QMacAccessibilityElement elementWithId:axid];
493 Q_ASSERT(tableElement && tableElement->rows && int(tableElement->rows.count) > m_rowIndex);
494 QMacAccessibilityElement *rowElement = tableElement->rows[m_rowIndex];
495 return rowElement;
496 }
497
498 QAccessibleInterface *iface = self.qtInterface;
499 if (!iface)
500 return nil;
501
502 if (self.isManagedByParent) {
503 // axid is the same for the parent element
504 return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithId:axid]);
505 }
506
507 // macOS expects that the hierarchy is:
508 // App -> Window -> Children
509 // We don't actually have the window reflected properly in QAccessibility.
510 // Check if the parent is the application and then instead return the native window.
511
512 if (QAccessibleInterface *parent = iface->parent()) {
513 if (parent->tableInterface()) {
514 QMacAccessibilityElement *tableElement =
515 [QMacAccessibilityElement elementWithInterface:parent];
516
517 // parent of cell should be row
518 int rowIndex = -1;
519 if (m_rowIndex >= 0 && m_columnIndex >= 0)
520 rowIndex = m_rowIndex;
521 else if (QAccessibleTableCellInterface *cell = iface->tableCellInterface())
522 rowIndex = cell->rowIndex();
523 Q_ASSERT(tableElement->rows && int([tableElement->rows count]) > rowIndex);
524 QMacAccessibilityElement *rowElement = tableElement->rows[rowIndex];
525 return NSAccessibilityUnignoredAncestor(rowElement);
526 }
527 if (parent->role() != QAccessible::Application)
528 return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithInterface: parent]);
529 }
530
531 if (QWindow *window = iface->window()) {
532 QPlatformWindow *platformWindow = window->handle();
533 if (platformWindow) {
534 QCocoaWindow *win = static_cast<QCocoaWindow*>(platformWindow);
535 return NSAccessibilityUnignoredAncestor(qnsview_cast(win->view()));
536 }
537 }
538 return nil;
539}
540
541- (NSRect)accessibilityFrame {
542 QAccessibleInterface *iface = self.qtInterface;
543 if (!iface)
544 return NSZeroRect;
545
546 QRect rect;
547 if (self.isManagedByParent) {
548 if (QAccessibleTableInterface *table = iface->tableInterface()) {
549 // Construct the geometry of the Row/Column by looking at the individual table cells
550 // ### Assumes that cells logical coordinates have spatial ordering (e.g finds the
551 // rows width by taking the union between the leftmost item and the rightmost item in
552 // a row).
553 // Otherwise, we have to iterate over *all* cells in a row/columns to
554 // find out the Row/Column geometry
555 const bool isRow = synthesizedRole == NSAccessibilityRowRole;
556 QPoint cellPos;
557 int &row = isRow ? cellPos.ry() : cellPos.rx();
558 int &col = isRow ? cellPos.rx() : cellPos.ry();
559
560 NSUInteger trackIndex = self.accessibilityIndex;
561 if (trackIndex != NSNotFound) {
562 row = int(trackIndex);
563 if (QAccessibleInterface *firstCell = table->cellAt(cellPos.y(), cellPos.x())) {
564 rect = firstCell->rect();
565 col = isRow ? table->columnCount() : table->rowCount();
566 if (col > 1) {
567 --col;
568 if (QAccessibleInterface *lastCell =
569 table->cellAt(cellPos.y(), cellPos.x()))
570 rect = rect.united(lastCell->rect());
571 }
572 }
573 }
574 }
575 } else {
576 rect = iface->rect();
577 }
578
580}
581
582- (NSString*)accessibilityLabel {
583 if (QAccessibleInterface *iface = self.qtInterface)
584 return iface->text(QAccessible::Description).toNSString();
585 qWarning() << "Called accessibilityLabel on invalid object: " << axid;
586 return nil;
587}
588
589- (void)setAccessibilityLabel:(NSString*)label{
590 if (QAccessibleInterface *iface = self.qtInterface)
591 iface->setText(QAccessible::Description, QString::fromNSString(label));
592}
593
594- (id) accessibilityValue {
595 if (QAccessibleInterface *iface = self.qtInterface) {
596 // VoiceOver asks for the value attribute for all elements. Return nil
597 // if we don't want the element to have a value attribute.
598 if (QCocoaAccessible::hasValueAttribute(iface))
599 return QCocoaAccessible::getValueAttribute(iface);
600 }
601 return nil;
602}
603
604- (NSInteger) accessibilityNumberOfCharacters {
605 if (QAccessibleInterface *iface = self.qtInterface) {
606 if (QAccessibleTextInterface *text = iface->textInterface())
607 return text->characterCount();
608 }
609 return 0;
610}
611
612- (NSString *) accessibilitySelectedText {
613 if (QAccessibleInterface *iface = self.qtInterface) {
614 if (QAccessibleTextInterface *text = iface->textInterface()) {
615 int start = 0;
616 int end = 0;
617 text->selection(0, &start, &end);
618 return text->text(start, end).toNSString();
619 }
620 }
621 return nil;
622}
623
624- (NSRange) accessibilitySelectedTextRange {
625 QAccessibleInterface *iface = self.qtInterface;
626 if (!iface)
627 return NSRange();
628 if (QAccessibleTextInterface *text = iface->textInterface()) {
629 int start = 0;
630 int end = 0;
631 if (text->selectionCount() > 0) {
632 text->selection(0, &start, &end);
633 } else {
634 start = text->cursorPosition();
635 end = start;
636 }
637 return NSMakeRange(quint32(start), quint32(end - start));
638 }
639 return NSMakeRange(0, 0);
640}
641
642- (NSInteger)accessibilityLineForIndex:(NSInteger)index {
643 QAccessibleInterface *iface = self.qtInterface;
644 if (!iface)
645 return 0;
646 if (QAccessibleTextInterface *text = iface->textInterface()) {
647 QString textToPos = text->text(0, index);
648 return textToPos.count('\n');
649 }
650 return 0;
651}
652
653- (NSRange)accessibilityVisibleCharacterRange {
654 QAccessibleInterface *iface = self.qtInterface;
655 if (!iface)
656 return NSRange();
657 // FIXME This is not correct and may impact performance for big texts
658 if (QAccessibleTextInterface *text = iface->textInterface())
659 return NSMakeRange(0, static_cast<uint>(text->characterCount()));
660 return NSMakeRange(0, static_cast<uint>(iface->text(QAccessible::Name).length()));
661}
662
663- (NSInteger) accessibilityInsertionPointLineNumber {
664 QAccessibleInterface *iface = self.qtInterface;
665 if (!iface)
666 return 0;
667 if (QAccessibleTextInterface *text = iface->textInterface()) {
668 int position = text->cursorPosition();
669 return [self accessibilityLineForIndex:position];
670 }
671 return 0;
672}
673
674- (NSArray *)accessibilityParameterizedAttributeNames {
675
676 QAccessibleInterface *iface = self.qtInterface;
677 if (!iface) {
678 qWarning() << "Called attribute on invalid object: " << axid;
679 return nil;
680 }
681
682 if (iface->textInterface()) {
683 return @[
684 NSAccessibilityStringForRangeParameterizedAttribute,
685 NSAccessibilityLineForIndexParameterizedAttribute,
686 NSAccessibilityRangeForLineParameterizedAttribute,
687 NSAccessibilityRangeForPositionParameterizedAttribute,
688// NSAccessibilityRangeForIndexParameterizedAttribute,
689 NSAccessibilityBoundsForRangeParameterizedAttribute,
690// NSAccessibilityRTFForRangeParameterizedAttribute,
691 NSAccessibilityStyleRangeForIndexParameterizedAttribute,
692 NSAccessibilityAttributedStringForRangeParameterizedAttribute
693 ];
694 }
695
696 return nil;
697}
698
699- (id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter {
700 QAccessibleInterface *iface = self.qtInterface;
701 if (!iface) {
702 qWarning() << "Called attribute on invalid object: " << axid;
703 return nil;
704 }
705
706 if (!iface->textInterface())
707 return nil;
708
709 if ([attribute isEqualToString: NSAccessibilityStringForRangeParameterizedAttribute]) {
710 NSRange range = [parameter rangeValue];
711 QString text = iface->textInterface()->text(range.location, range.location + range.length);
712 return text.toNSString();
713 }
714 if ([attribute isEqualToString: NSAccessibilityLineForIndexParameterizedAttribute]) {
715 int index = [parameter intValue];
716 if (index < 0 || index > iface->textInterface()->characterCount())
717 return nil;
718 int line = 0; // true for all single line edits
719 if (iface->state().multiLine) {
720 line = -1;
721 convertLineOffset(iface->textInterface(), &line, &index);
722 }
723 return @(line);
724 }
725 if ([attribute isEqualToString: NSAccessibilityRangeForLineParameterizedAttribute]) {
726 int line = [parameter intValue];
727 if (line < 0)
728 return nil;
729 int lineOffset = -1;
730 NSUInteger startOffset = 0;
731 NSUInteger endOffset = 0;
732 convertLineOffset(iface->textInterface(), &line, &lineOffset, &startOffset, &endOffset);
733 return [NSValue valueWithRange:NSMakeRange(startOffset, endOffset - startOffset)];
734 }
735 if ([attribute isEqualToString: NSAccessibilityBoundsForRangeParameterizedAttribute]) {
736 NSRange range = [parameter rangeValue];
737 QRect firstRect = iface->textInterface()->characterRect(range.location);
738 QRectF rect;
739 if (range.length > 0) {
740 NSUInteger position = range.location + range.length - 1;
741 if (position > range.location && iface->textInterface()->text(position, position + 1) == QStringLiteral("\n"))
742 --position;
743 QRect lastRect = iface->textInterface()->characterRect(position);
744 rect = firstRect.united(lastRect);
745 } else {
746 rect = firstRect;
747 rect.setWidth(1);
748 }
749 return [NSValue valueWithRect:QCocoaScreen::mapToNative(rect)];
750 }
751 if ([attribute isEqualToString: NSAccessibilityAttributedStringForRangeParameterizedAttribute]) {
752 NSRange range = [parameter rangeValue];
753 QString text = iface->textInterface()->text(range.location, range.location + range.length);
754 return [[NSAttributedString alloc] initWithString:text.toNSString()];
755 } else if ([attribute isEqualToString: NSAccessibilityRangeForPositionParameterizedAttribute]) {
756 QPoint point = QCocoaScreen::mapFromNative([parameter pointValue]).toPoint();
757 int offset = iface->textInterface()->offsetAtPoint(point);
758 return [NSValue valueWithRange:NSMakeRange(static_cast<NSUInteger>(offset), 1)];
759 } else if ([attribute isEqualToString: NSAccessibilityStyleRangeForIndexParameterizedAttribute]) {
760 int start = 0;
761 int end = 0;
762 iface->textInterface()->attributes([parameter intValue], &start, &end);
763 return [NSValue valueWithRange:NSMakeRange(static_cast<NSUInteger>(start), static_cast<NSUInteger>(end - start))];
764 }
765 return nil;
766}
767
768- (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute {
769 QAccessibleInterface *iface = self.qtInterface;
770 if (!iface)
771 return NO;
772
773 if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
774 return iface->state().focusable ? YES : NO;
775 } else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
776 if (iface->textInterface() && iface->state().editable)
777 return YES;
778 if (iface->valueInterface())
779 return YES;
780 return NO;
781 } else if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
782 return iface->textInterface() ? YES : NO;
783 }
784 return NO;
785}
786
787- (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute {
788 QAccessibleInterface *iface = self.qtInterface;
789 if (!iface)
790 return;
791 if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
792 if (QAccessibleActionInterface *action = iface->actionInterface())
793 action->doAction(QAccessibleActionInterface::setFocusAction());
794 } else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
795 if (iface->textInterface()) {
796 QString text = QString::fromNSString((NSString *)value);
797 iface->setText(QAccessible::Value, text);
798 } else if (QAccessibleValueInterface *valueIface = iface->valueInterface()) {
799 double val = [value doubleValue];
800 valueIface->setCurrentValue(val);
801 }
802 } else if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
803 if (QAccessibleTextInterface *text = iface->textInterface()) {
804 NSRange range = [value rangeValue];
805 if (range.length > 0)
806 text->setSelection(0, range.location, range.location + range.length);
807 else
808 text->setCursorPosition(range.location);
809 }
810 }
811}
812
813// actions
814
815- (NSArray *)accessibilityActionNames {
816 NSMutableArray *nsActions = [[NSMutableArray new] autorelease];
817 QAccessibleInterface *iface = self.qtInterface;
818 if (!iface)
819 return nsActions;
820
821 const QStringList &supportedActionNames = QAccessibleBridgeUtils::effectiveActionNames(iface);
822 for (const QString &qtAction : supportedActionNames) {
823 NSString *nsAction = QCocoaAccessible::getTranslatedAction(qtAction);
824 if (nsAction)
825 [nsActions addObject : nsAction];
826 }
827
828 return nsActions;
829}
830
831- (NSString *)accessibilityActionDescription:(NSString *)action {
832 QAccessibleInterface *iface = self.qtInterface;
833 if (!iface)
834 return nil; // FIXME is that the right return type??
835 QString qtAction = QCocoaAccessible::translateAction(action, iface);
836 QString description;
837 // Return a description from the action interface if this action is not known to the OS.
838 if (qtAction.isEmpty()) {
839 if (QAccessibleActionInterface *actionInterface = iface->actionInterface()) {
840 qtAction = QString::fromNSString((NSString *)action);
841 description = actionInterface->localizedActionDescription(qtAction);
842 }
843 } else {
844 description = qAccessibleLocalizedActionDescription(qtAction);
845 }
846 return description.toNSString();
847}
848
849- (void)accessibilityPerformAction:(NSString *)action {
850 if (QAccessibleInterface *iface = self.qtInterface) {
851 const QString qtAction = QCocoaAccessible::translateAction(action, iface);
853 }
854}
855
856// misc
857
858- (BOOL)accessibilityIsIgnored {
859 // Short-cut for placeholders and synthesized elements. Working around a bug
860 // that corrups lists returned by NSAccessibilityUnignoredChildren, otherwise
861 // we could ignore rows and columns that are outside the table.
862 if (self.isManagedByParent)
863 return false;
864
865 if (QAccessibleInterface *iface = self.qtInterface)
866 return QCocoaAccessible::shouldBeIgnored(iface);
867 return true;
868}
869
870- (id)accessibilityHitTest:(NSPoint)point {
871 QAccessibleInterface *iface = self.qtInterface;
872 if (!iface) {
873// qDebug("Hit test: INVALID");
874 return NSAccessibilityUnignoredAncestor(self);
875 }
876
877 QPointF screenPoint = QCocoaScreen::mapFromNative(point);
878 QAccessibleInterface *childInterface = iface->childAt(screenPoint.x(), screenPoint.y());
879 // No child found, meaning we hit this element.
880 if (!childInterface || !childInterface->isValid())
881 return NSAccessibilityUnignoredAncestor(self);
882
883 // find the deepest child at the point
884 QAccessibleInterface *childOfChildInterface = nullptr;
885 do {
886 childOfChildInterface = childInterface->childAt(screenPoint.x(), screenPoint.y());
887 if (childOfChildInterface && childOfChildInterface->isValid())
888 childInterface = childOfChildInterface;
889 } while (childOfChildInterface && childOfChildInterface->isValid());
890
891 // hit a child, forward to child accessible interface.
892 QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithInterface:childInterface];
893 if (accessibleElement)
894 return NSAccessibilityUnignoredAncestor(accessibleElement);
895 return NSAccessibilityUnignoredAncestor(self);
896}
897
898- (id)accessibilityFocusedUIElement {
899 QAccessibleInterface *iface = self.qtInterface;
900 if (!iface) {
901 qWarning("FocusedUIElement for INVALID");
902 return nil;
903 }
904
905 QAccessibleInterface *childInterface = iface->focusChild();
906 if (childInterface && childInterface->isValid()) {
907 QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithInterface:childInterface];
908 return NSAccessibilityUnignoredAncestor(accessibleElement);
909 }
910
911 return NSAccessibilityUnignoredAncestor(self);
912}
913
914- (NSString *) accessibilityHelp {
915 if (QAccessibleInterface *iface = self.qtInterface) {
916 const QString helpText = iface->text(QAccessible::Help);
917 if (!helpText.isEmpty())
918 return helpText.toNSString();
919 }
920 return nil;
921}
922
923/*
924 * Support for table
925 */
926- (NSInteger) accessibilityIndex {
927 NSInteger index = 0;
928 if (synthesizedRole == NSAccessibilityCellRole)
929 return m_columnIndex;
930 if (QAccessibleInterface *iface = self.qtInterface) {
931 if (self.isManagedByParent) {
932 // axid matches the parent table axid so that we can easily find the parent table
933 // children of row are cell/any items
934 if (QAccessibleTableInterface *table = iface->tableInterface()) {
935 if (m_rowIndex >= 0)
936 index = NSInteger(m_rowIndex);
937 else if (m_columnIndex >= 0)
938 index = NSInteger(m_columnIndex);
939 }
940 }
941 }
942 return index;
943}
944
945- (NSArray *) accessibilityRows {
946 if (!synthesizedRole && rows) {
947 QAccessibleInterface *iface = self.qtInterface;
948 if (iface && iface->tableInterface())
949 return NSAccessibilityUnignoredChildren(rows);
950 }
951 return nil;
952}
953
954- (NSArray *) accessibilityColumns {
955 if (!synthesizedRole && columns) {
956 QAccessibleInterface *iface = self.qtInterface;
957 if (iface && iface->tableInterface())
958 return NSAccessibilityUnignoredChildren(columns);
959 }
960 return nil;
961}
962
963@end
964
965#endif // QT_CONFIG(accessibility)
966
static bool isEqual(const aiUVTransform &a, const aiUVTransform &b)
The QAccessible class provides enums and static functions related to accessibility.
static CGPoint mapToNative(const QPointF &pos, QCocoaScreen *screen=QCocoaScreen::primaryScreen())
static QPointF mapFromNative(CGPoint pos, QCocoaScreen *screen=QCocoaScreen::primaryScreen())
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
The QPlatformWindow class provides an abstraction for top-level windows.
\inmodule QtCore\reentrant
Definition qpoint.h:214
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:333
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:338
constexpr QPoint toPoint() const
Rounds the coordinates of this point to the nearest integer, and returns a QPoint object with the rou...
Definition qpoint.h:394
\inmodule QtCore\reentrant
Definition qpoint.h:23
constexpr int & ry() noexcept
Returns a reference to the y coordinate of this point.
Definition qpoint.h:157
constexpr int & rx() noexcept
Returns a reference to the x coordinate of this point.
Definition qpoint.h:152
constexpr int x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:127
constexpr int y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:132
\inmodule QtCore\reentrant
Definition qrect.h:483
\inmodule QtCore\reentrant
Definition qrect.h:30
QRect united(const QRect &other) const noexcept
Definition qrect.h:419
\inmodule QtCore
\inmodule QtCore
Definition qstringview.h:76
qsizetype count(QChar c, Qt::CaseSensitivity cs=Qt::CaseSensitive) const noexcept
constexpr QStringView left(qsizetype n) const noexcept
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
qsizetype count(QChar c, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4732
\inmodule QtGui
Definition qwindow.h:63
QHash< int, QWidget * > hash
[35multi]
QString text
QCache< int, Employee > cache
[0]
rect
[4]
QStringList effectiveActionNames(QAccessibleInterface *iface)
bool performEffectiveAction(QAccessibleInterface *iface, const QString &actionName)
constexpr QBindableInterface iface
Definition qproperty.h:664
QString self
Definition language.cpp:57
QNSView * qnsview_cast(NSView *view)
Returns the view cast to a QNSview if possible.
long NSInteger
unsigned long NSUInteger
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
EGLOutputLayerEXT EGLint attribute
#define qWarning
Definition qlogging.h:162
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
GLuint index
[2]
GLuint GLuint end
GLenum GLuint GLenum GLsizei length
GLenum GLuint id
[7]
GLenum GLenum GLsizei count
GLuint object
[3]
GLsizei range
GLuint GLsizei const GLchar * label
[43]
GLuint start
GLenum GLuint GLintptr offset
GLfloat n
GLuint GLfloat * val
GLenum array
GLenum GLenum GLsizei void * row
GLenum GLenum GLsizei void * table
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define QStringLiteral(str)
static QString helpText(const QCommandLineParser &p)
Definition main.cpp:664
unsigned int quint32
Definition qtypes.h:45
ptrdiff_t qsizetype
Definition qtypes.h:70
unsigned int uint
Definition qtypes.h:29
QWidget * win
Definition settings.cpp:6
QSharedPointer< T > other(t)
[5]
QItemSelection * selection
[0]
aWidget window() -> setWindowTitle("New Window Title")
[2]
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent