1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the plugins of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "itemviews_p.h"
41
42#include <qheaderview.h>
43#if QT_CONFIG(tableview)
44#include <qtableview.h>
45#endif
46#if QT_CONFIG(listview)
47#include <qlistview.h>
48#endif
49#if QT_CONFIG(treeview)
50#include <qtreeview.h>
51#include <private/qtreeview_p.h>
52#endif
53#include <private/qwidget_p.h>
54
55#ifndef QT_NO_ACCESSIBILITY
56
57QT_BEGIN_NAMESPACE
58
59/*
60Implementation of the IAccessible2 table2 interface. Much simpler than
61the other table interfaces since there is only the main table and cells:
62
63TABLE/LIST/TREE
64 |- HEADER CELL
65 |- CELL
66 |- CELL
67 ...
68*/
69
70
71QAbstractItemView *QAccessibleTable::view() const
72{
73 return qobject_cast<QAbstractItemView*>(object: object());
74}
75
76int QAccessibleTable::logicalIndex(const QModelIndex &index) const
77{
78 if (!view()->model() || !index.isValid())
79 return -1;
80 int vHeader = verticalHeader() ? 1 : 0;
81 int hHeader = horizontalHeader() ? 1 : 0;
82 return (index.row() + hHeader)*(index.model()->columnCount() + vHeader) + (index.column() + vHeader);
83}
84
85QAccessibleTable::QAccessibleTable(QWidget *w)
86 : QAccessibleObject(w)
87{
88 Q_ASSERT(view());
89
90#if QT_CONFIG(tableview)
91 if (qobject_cast<const QTableView*>(object: view())) {
92 m_role = QAccessible::Table;
93 } else
94#endif
95#if QT_CONFIG(treeview)
96 if (qobject_cast<const QTreeView*>(object: view())) {
97 m_role = QAccessible::Tree;
98 } else
99#endif
100#if QT_CONFIG(listview)
101 if (qobject_cast<const QListView*>(object: view())) {
102 m_role = QAccessible::List;
103 } else
104#endif
105 {
106 // is this our best guess?
107 m_role = QAccessible::Table;
108 }
109}
110
111bool QAccessibleTable::isValid() const
112{
113 return view() && !qt_widget_private(widget: view())->data.in_destructor;
114}
115
116QAccessibleTable::~QAccessibleTable()
117{
118 for (QAccessible::Id id : qAsConst(t&: childToId))
119 QAccessible::deleteAccessibleInterface(uniqueId: id);
120}
121
122QHeaderView *QAccessibleTable::horizontalHeader() const
123{
124 QHeaderView *header = nullptr;
125 if (false) {
126#if QT_CONFIG(tableview)
127 } else if (const QTableView *tv = qobject_cast<const QTableView*>(object: view())) {
128 header = tv->horizontalHeader();
129#endif
130#if QT_CONFIG(treeview)
131 } else if (const QTreeView *tv = qobject_cast<const QTreeView*>(object: view())) {
132 header = tv->header();
133#endif
134 }
135 return header;
136}
137
138QHeaderView *QAccessibleTable::verticalHeader() const
139{
140 QHeaderView *header = nullptr;
141 if (false) {
142#if QT_CONFIG(tableview)
143 } else if (const QTableView *tv = qobject_cast<const QTableView*>(object: view())) {
144 header = tv->verticalHeader();
145#endif
146 }
147 return header;
148}
149
150QAccessibleInterface *QAccessibleTable::cellAt(int row, int column) const
151{
152 if (!view()->model())
153 return nullptr;
154 Q_ASSERT(role() != QAccessible::Tree);
155 QModelIndex index = view()->model()->index(row, column, parent: view()->rootIndex());
156 if (Q_UNLIKELY(!index.isValid())) {
157 qWarning() << "QAccessibleTable::cellAt: invalid index: " << index << " for " << view();
158 return nullptr;
159 }
160 return child(index: logicalIndex(index));
161}
162
163QAccessibleInterface *QAccessibleTable::caption() const
164{
165 return nullptr;
166}
167
168QString QAccessibleTable::columnDescription(int column) const
169{
170 if (!view()->model())
171 return QString();
172 return view()->model()->headerData(section: column, orientation: Qt::Horizontal).toString();
173}
174
175int QAccessibleTable::columnCount() const
176{
177 if (!view()->model())
178 return 0;
179 return view()->model()->columnCount();
180}
181
182int QAccessibleTable::rowCount() const
183{
184 if (!view()->model())
185 return 0;
186 return view()->model()->rowCount();
187}
188
189int QAccessibleTable::selectedCellCount() const
190{
191 if (!view()->selectionModel())
192 return 0;
193 return view()->selectionModel()->selectedIndexes().count();
194}
195
196int QAccessibleTable::selectedColumnCount() const
197{
198 if (!view()->selectionModel())
199 return 0;
200 return view()->selectionModel()->selectedColumns().count();
201}
202
203int QAccessibleTable::selectedRowCount() const
204{
205 if (!view()->selectionModel())
206 return 0;
207 return view()->selectionModel()->selectedRows().count();
208}
209
210QString QAccessibleTable::rowDescription(int row) const
211{
212 if (!view()->model())
213 return QString();
214 return view()->model()->headerData(section: row, orientation: Qt::Vertical).toString();
215}
216
217QList<QAccessibleInterface *> QAccessibleTable::selectedCells() const
218{
219 QList<QAccessibleInterface*> cells;
220 if (!view()->selectionModel())
221 return cells;
222 const QModelIndexList selectedIndexes = view()->selectionModel()->selectedIndexes();
223 cells.reserve(alloc: selectedIndexes.size());
224 for (const QModelIndex &index : selectedIndexes)
225 cells.append(t: child(index: logicalIndex(index)));
226 return cells;
227}
228
229QList<int> QAccessibleTable::selectedColumns() const
230{
231 if (!view()->selectionModel())
232 return QList<int>();
233 QList<int> columns;
234 const QModelIndexList selectedColumns = view()->selectionModel()->selectedColumns();
235 columns.reserve(alloc: selectedColumns.size());
236 for (const QModelIndex &index : selectedColumns)
237 columns.append(t: index.column());
238
239 return columns;
240}
241
242QList<int> QAccessibleTable::selectedRows() const
243{
244 if (!view()->selectionModel())
245 return QList<int>();
246 QList<int> rows;
247 const QModelIndexList selectedRows = view()->selectionModel()->selectedRows();
248 rows.reserve(alloc: selectedRows.size());
249 for (const QModelIndex &index : selectedRows)
250 rows.append(t: index.row());
251
252 return rows;
253}
254
255QAccessibleInterface *QAccessibleTable::summary() const
256{
257 return nullptr;
258}
259
260bool QAccessibleTable::isColumnSelected(int column) const
261{
262 if (!view()->selectionModel())
263 return false;
264 return view()->selectionModel()->isColumnSelected(column, parent: QModelIndex());
265}
266
267bool QAccessibleTable::isRowSelected(int row) const
268{
269 if (!view()->selectionModel())
270 return false;
271 return view()->selectionModel()->isRowSelected(row, parent: QModelIndex());
272}
273
274bool QAccessibleTable::selectRow(int row)
275{
276 if (!view()->model() || !view()->selectionModel())
277 return false;
278 QModelIndex index = view()->model()->index(row, column: 0, parent: view()->rootIndex());
279
280 if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectColumns)
281 return false;
282
283 switch (view()->selectionMode()) {
284 case QAbstractItemView::NoSelection:
285 return false;
286 case QAbstractItemView::SingleSelection:
287 if (view()->selectionBehavior() != QAbstractItemView::SelectRows && columnCount() > 1 )
288 return false;
289 view()->clearSelection();
290 break;
291 case QAbstractItemView::ContiguousSelection:
292 if ((!row || !view()->selectionModel()->isRowSelected(row: row - 1, parent: view()->rootIndex()))
293 && !view()->selectionModel()->isRowSelected(row: row + 1, parent: view()->rootIndex()))
294 view()->clearSelection();
295 break;
296 default:
297 break;
298 }
299
300 view()->selectionModel()->select(index, command: QItemSelectionModel::Select | QItemSelectionModel::Rows);
301 return true;
302}
303
304bool QAccessibleTable::selectColumn(int column)
305{
306 if (!view()->model() || !view()->selectionModel())
307 return false;
308 QModelIndex index = view()->model()->index(row: 0, column, parent: view()->rootIndex());
309
310 if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectRows)
311 return false;
312
313 switch (view()->selectionMode()) {
314 case QAbstractItemView::NoSelection:
315 return false;
316 case QAbstractItemView::SingleSelection:
317 if (view()->selectionBehavior() != QAbstractItemView::SelectColumns && rowCount() > 1)
318 return false;
319 Q_FALLTHROUGH();
320 case QAbstractItemView::ContiguousSelection:
321 if ((!column || !view()->selectionModel()->isColumnSelected(column: column - 1, parent: view()->rootIndex()))
322 && !view()->selectionModel()->isColumnSelected(column: column + 1, parent: view()->rootIndex()))
323 view()->clearSelection();
324 break;
325 default:
326 break;
327 }
328
329 view()->selectionModel()->select(index, command: QItemSelectionModel::Select | QItemSelectionModel::Columns);
330 return true;
331}
332
333bool QAccessibleTable::unselectRow(int row)
334{
335 if (!view()->model() || !view()->selectionModel())
336 return false;
337
338 QModelIndex index = view()->model()->index(row, column: 0, parent: view()->rootIndex());
339 if (!index.isValid())
340 return false;
341
342 QItemSelection selection(index, index);
343
344 switch (view()->selectionMode()) {
345 case QAbstractItemView::SingleSelection:
346 //In SingleSelection and ContiguousSelection once an item
347 //is selected, there's no way for the user to unselect all items
348 if (selectedRowCount() == 1)
349 return false;
350 break;
351 case QAbstractItemView::ContiguousSelection:
352 if (selectedRowCount() == 1)
353 return false;
354
355 if ((!row || view()->selectionModel()->isRowSelected(row: row - 1, parent: view()->rootIndex()))
356 && view()->selectionModel()->isRowSelected(row: row + 1, parent: view()->rootIndex())) {
357 //If there are rows selected both up the current row and down the current rown,
358 //the ones which are down the current row will be deselected
359 selection = QItemSelection(index, view()->model()->index(row: rowCount() - 1, column: 0, parent: view()->rootIndex()));
360 }
361 default:
362 break;
363 }
364
365 view()->selectionModel()->select(selection, command: QItemSelectionModel::Deselect | QItemSelectionModel::Rows);
366 return true;
367}
368
369bool QAccessibleTable::unselectColumn(int column)
370{
371 if (!view()->model() || !view()->selectionModel())
372 return false;
373
374 QModelIndex index = view()->model()->index(row: 0, column, parent: view()->rootIndex());
375 if (!index.isValid())
376 return false;
377
378 QItemSelection selection(index, index);
379
380 switch (view()->selectionMode()) {
381 case QAbstractItemView::SingleSelection:
382 //In SingleSelection and ContiguousSelection once an item
383 //is selected, there's no way for the user to unselect all items
384 if (selectedColumnCount() == 1)
385 return false;
386 break;
387 case QAbstractItemView::ContiguousSelection:
388 if (selectedColumnCount() == 1)
389 return false;
390
391 if ((!column || view()->selectionModel()->isColumnSelected(column: column - 1, parent: view()->rootIndex()))
392 && view()->selectionModel()->isColumnSelected(column: column + 1, parent: view()->rootIndex())) {
393 //If there are columns selected both at the left of the current row and at the right
394 //of the current rown, the ones which are at the right will be deselected
395 selection = QItemSelection(index, view()->model()->index(row: 0, column: columnCount() - 1, parent: view()->rootIndex()));
396 }
397 default:
398 break;
399 }
400
401 view()->selectionModel()->select(selection, command: QItemSelectionModel::Deselect | QItemSelectionModel::Columns);
402 return true;
403}
404
405QAccessible::Role QAccessibleTable::role() const
406{
407 return m_role;
408}
409
410QAccessible::State QAccessibleTable::state() const
411{
412 return QAccessible::State();
413}
414
415QAccessibleInterface *QAccessibleTable::childAt(int x, int y) const
416{
417 QPoint viewportOffset = view()->viewport()->mapTo(view(), QPoint(0,0));
418 QPoint indexPosition = view()->mapFromGlobal(QPoint(x, y) - viewportOffset);
419 // FIXME: if indexPosition < 0 in one coordinate, return header
420
421 QModelIndex index = view()->indexAt(point: indexPosition);
422 if (index.isValid()) {
423 return child(index: logicalIndex(index));
424 }
425 return nullptr;
426}
427
428int QAccessibleTable::childCount() const
429{
430 if (!view()->model())
431 return 0;
432 int vHeader = verticalHeader() ? 1 : 0;
433 int hHeader = horizontalHeader() ? 1 : 0;
434 return (view()->model()->rowCount()+hHeader) * (view()->model()->columnCount()+vHeader);
435}
436
437int QAccessibleTable::indexOfChild(const QAccessibleInterface *iface) const
438{
439 if (!view()->model())
440 return -1;
441 QAccessibleInterface *parent = iface->parent();
442 if (parent->object() != view())
443 return -1;
444
445 Q_ASSERT(iface->role() != QAccessible::TreeItem); // should be handled by tree class
446 if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) {
447 const QAccessibleTableCell* cell = static_cast<const QAccessibleTableCell*>(iface);
448 return logicalIndex(index: cell->m_index);
449 } else if (iface->role() == QAccessible::ColumnHeader){
450 const QAccessibleTableHeaderCell* cell = static_cast<const QAccessibleTableHeaderCell*>(iface);
451 return cell->index + (verticalHeader() ? 1 : 0);
452 } else if (iface->role() == QAccessible::RowHeader){
453 const QAccessibleTableHeaderCell* cell = static_cast<const QAccessibleTableHeaderCell*>(iface);
454 return (cell->index + 1) * (view()->model()->columnCount() + 1);
455 } else if (iface->role() == QAccessible::Pane) {
456 return 0; // corner button
457 } else {
458 qWarning() << "WARNING QAccessibleTable::indexOfChild Fix my children..."
459 << iface->role() << iface->text(t: QAccessible::Name);
460 }
461 // FIXME: we are in denial of our children. this should stop.
462 return -1;
463}
464
465QString QAccessibleTable::text(QAccessible::Text t) const
466{
467 if (t == QAccessible::Description)
468 return view()->accessibleDescription();
469 return view()->accessibleName();
470}
471
472QRect QAccessibleTable::rect() const
473{
474 if (!view()->isVisible())
475 return QRect();
476 QPoint pos = view()->mapToGlobal(QPoint(0, 0));
477 return QRect(pos.x(), pos.y(), view()->width(), view()->height());
478}
479
480QAccessibleInterface *QAccessibleTable::parent() const
481{
482 if (view() && view()->parent()) {
483 if (qstrcmp(str1: "QComboBoxPrivateContainer", str2: view()->parent()->metaObject()->className()) == 0) {
484 return QAccessible::queryAccessibleInterface(view()->parent()->parent());
485 }
486 return QAccessible::queryAccessibleInterface(view()->parent());
487 }
488 return nullptr;
489}
490
491QAccessibleInterface *QAccessibleTable::child(int logicalIndex) const
492{
493 if (!view()->model())
494 return nullptr;
495
496 auto id = childToId.constFind(akey: logicalIndex);
497 if (id != childToId.constEnd())
498 return QAccessible::accessibleInterface(uniqueId: id.value());
499
500 int vHeader = verticalHeader() ? 1 : 0;
501 int hHeader = horizontalHeader() ? 1 : 0;
502
503 int columns = view()->model()->columnCount() + vHeader;
504
505 int row = logicalIndex / columns;
506 int column = logicalIndex % columns;
507
508 QAccessibleInterface *iface = nullptr;
509
510 if (vHeader) {
511 if (column == 0) {
512 if (hHeader && row == 0) {
513 iface = new QAccessibleTableCornerButton(view());
514 } else {
515 iface = new QAccessibleTableHeaderCell(view(), row - hHeader, Qt::Vertical);
516 }
517 }
518 --column;
519 }
520 if (!iface && hHeader) {
521 if (row == 0) {
522 iface = new QAccessibleTableHeaderCell(view(), column, Qt::Horizontal);
523 }
524 --row;
525 }
526
527 if (!iface) {
528 QModelIndex index = view()->model()->index(row, column, parent: view()->rootIndex());
529 if (Q_UNLIKELY(!index.isValid())) {
530 qWarning(msg: "QAccessibleTable::child: Invalid index at: %d %d", row, column);
531 return nullptr;
532 }
533 iface = new QAccessibleTableCell(view(), index, cellRole());
534 }
535
536 QAccessible::registerAccessibleInterface(iface);
537 childToId.insert(akey: logicalIndex, avalue: QAccessible::uniqueId(iface));
538 return iface;
539}
540
541void *QAccessibleTable::interface_cast(QAccessible::InterfaceType t)
542{
543 if (t == QAccessible::TableInterface)
544 return static_cast<QAccessibleTableInterface*>(this);
545 return nullptr;
546}
547
548void QAccessibleTable::modelChange(QAccessibleTableModelChangeEvent *event)
549{
550 // if there is no cache yet, we don't update anything
551 if (childToId.isEmpty())
552 return;
553
554 switch (event->modelChangeType()) {
555 case QAccessibleTableModelChangeEvent::ModelReset:
556 for (QAccessible::Id id : qAsConst(t&: childToId))
557 QAccessible::deleteAccessibleInterface(uniqueId: id);
558 childToId.clear();
559 break;
560
561 // rows are inserted: move every row after that
562 case QAccessibleTableModelChangeEvent::RowsInserted:
563 case QAccessibleTableModelChangeEvent::ColumnsInserted: {
564 int newRows = event->lastRow() - event->firstRow() + 1;
565 int newColumns = event->lastColumn() - event->firstColumn() + 1;
566
567 ChildCache newCache;
568 ChildCache::ConstIterator iter = childToId.constBegin();
569
570 while (iter != childToId.constEnd()) {
571 QAccessible::Id id = iter.value();
572 QAccessibleInterface *iface = QAccessible::accessibleInterface(uniqueId: id);
573 Q_ASSERT(iface);
574 if (event->modelChangeType() == QAccessibleTableModelChangeEvent::RowsInserted
575 && iface->role() == QAccessible::RowHeader) {
576 QAccessibleTableHeaderCell *cell = static_cast<QAccessibleTableHeaderCell*>(iface);
577 if (cell->index >= event->firstRow()) {
578 cell->index += newRows;
579 }
580 } else if (event->modelChangeType() == QAccessibleTableModelChangeEvent::ColumnsInserted
581 && iface->role() == QAccessible::ColumnHeader) {
582 QAccessibleTableHeaderCell *cell = static_cast<QAccessibleTableHeaderCell*>(iface);
583 if (cell->index >= event->firstColumn()) {
584 cell->index += newColumns;
585 }
586 }
587 if (indexOfChild(iface) >= 0) {
588 newCache.insert(akey: indexOfChild(iface), avalue: id);
589 } else {
590 // ### This should really not happen,
591 // but it might if the view has a root index set.
592 // This needs to be fixed.
593 QAccessible::deleteAccessibleInterface(uniqueId: id);
594 }
595 ++iter;
596 }
597 childToId = newCache;
598 break;
599 }
600
601 case QAccessibleTableModelChangeEvent::ColumnsRemoved:
602 case QAccessibleTableModelChangeEvent::RowsRemoved: {
603 int deletedColumns = event->lastColumn() - event->firstColumn() + 1;
604 int deletedRows = event->lastRow() - event->firstRow() + 1;
605 ChildCache newCache;
606 ChildCache::ConstIterator iter = childToId.constBegin();
607 while (iter != childToId.constEnd()) {
608 QAccessible::Id id = iter.value();
609 QAccessibleInterface *iface = QAccessible::accessibleInterface(uniqueId: id);
610 Q_ASSERT(iface);
611 if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) {
612 Q_ASSERT(iface->tableCellInterface());
613 QAccessibleTableCell *cell = static_cast<QAccessibleTableCell*>(iface->tableCellInterface());
614 // Since it is a QPersistentModelIndex, we only need to check if it is valid
615 if (cell->m_index.isValid())
616 newCache.insert(akey: indexOfChild(iface: cell), avalue: id);
617 else
618 QAccessible::deleteAccessibleInterface(uniqueId: id);
619 } else if (event->modelChangeType() == QAccessibleTableModelChangeEvent::RowsRemoved
620 && iface->role() == QAccessible::RowHeader) {
621 QAccessibleTableHeaderCell *cell = static_cast<QAccessibleTableHeaderCell*>(iface);
622 if (cell->index < event->firstRow()) {
623 newCache.insert(akey: indexOfChild(iface: cell), avalue: id);
624 } else if (cell->index > event->lastRow()) {
625 cell->index -= deletedRows;
626 newCache.insert(akey: indexOfChild(iface: cell), avalue: id);
627 } else {
628 QAccessible::deleteAccessibleInterface(uniqueId: id);
629 }
630 } else if (event->modelChangeType() == QAccessibleTableModelChangeEvent::ColumnsRemoved
631 && iface->role() == QAccessible::ColumnHeader) {
632 QAccessibleTableHeaderCell *cell = static_cast<QAccessibleTableHeaderCell*>(iface);
633 if (cell->index < event->firstColumn()) {
634 newCache.insert(akey: indexOfChild(iface: cell), avalue: id);
635 } else if (cell->index > event->lastColumn()) {
636 cell->index -= deletedColumns;
637 newCache.insert(akey: indexOfChild(iface: cell), avalue: id);
638 } else {
639 QAccessible::deleteAccessibleInterface(uniqueId: id);
640 }
641 }
642 ++iter;
643 }
644 childToId = newCache;
645 break;
646 }
647
648 case QAccessibleTableModelChangeEvent::DataChanged:
649 // nothing to do in this case
650 break;
651 }
652}
653
654#if QT_CONFIG(treeview)
655
656// TREE VIEW
657
658QModelIndex QAccessibleTree::indexFromLogical(int row, int column) const
659{
660 if (!isValid() || !view()->model())
661 return QModelIndex();
662
663 const QTreeView *treeView = qobject_cast<const QTreeView*>(object: view());
664 if (Q_UNLIKELY(row < 0 || column < 0 || treeView->d_func()->viewItems.count() <= row)) {
665 qWarning() << "QAccessibleTree::indexFromLogical: invalid index: " << row << column << " for " << treeView;
666 return QModelIndex();
667 }
668 QModelIndex modelIndex = treeView->d_func()->viewItems.at(i: row).index;
669
670 if (modelIndex.isValid() && column > 0) {
671 modelIndex = view()->model()->index(row: modelIndex.row(), column, parent: modelIndex.parent());
672 }
673 return modelIndex;
674}
675
676QAccessibleInterface *QAccessibleTree::childAt(int x, int y) const
677{
678 if (!view()->model())
679 return nullptr;
680 QPoint viewportOffset = view()->viewport()->mapTo(view(), QPoint(0,0));
681 QPoint indexPosition = view()->mapFromGlobal(QPoint(x, y) - viewportOffset);
682
683 QModelIndex index = view()->indexAt(point: indexPosition);
684 if (!index.isValid())
685 return nullptr;
686
687 const QTreeView *treeView = qobject_cast<const QTreeView*>(object: view());
688 int row = treeView->d_func()->viewIndex(index) + (horizontalHeader() ? 1 : 0);
689 int column = index.column();
690
691 int i = row * view()->model()->columnCount() + column;
692 return child(index: i);
693}
694
695int QAccessibleTree::childCount() const
696{
697 const QTreeView *treeView = qobject_cast<const QTreeView*>(object: view());
698 Q_ASSERT(treeView);
699 if (!view()->model())
700 return 0;
701
702 int hHeader = horizontalHeader() ? 1 : 0;
703 return (treeView->d_func()->viewItems.count() + hHeader)* view()->model()->columnCount();
704}
705
706QAccessibleInterface *QAccessibleTree::child(int logicalIndex) const
707{
708 if (logicalIndex < 0 || !view()->model() || !view()->model()->columnCount())
709 return nullptr;
710
711 QAccessibleInterface *iface = nullptr;
712 int index = logicalIndex;
713
714 if (horizontalHeader()) {
715 if (index < view()->model()->columnCount()) {
716 iface = new QAccessibleTableHeaderCell(view(), index, Qt::Horizontal);
717 } else {
718 index -= view()->model()->columnCount();
719 }
720 }
721
722 if (!iface) {
723 int row = index / view()->model()->columnCount();
724 int column = index % view()->model()->columnCount();
725 QModelIndex modelIndex = indexFromLogical(row, column);
726 if (!modelIndex.isValid())
727 return nullptr;
728 iface = new QAccessibleTableCell(view(), modelIndex, cellRole());
729 }
730 QAccessible::registerAccessibleInterface(iface);
731 // ### FIXME: get interfaces from the cache instead of re-creating them
732 return iface;
733}
734
735int QAccessibleTree::rowCount() const
736{
737 const QTreeView *treeView = qobject_cast<const QTreeView*>(object: view());
738 Q_ASSERT(treeView);
739 return treeView->d_func()->viewItems.count();
740}
741
742int QAccessibleTree::indexOfChild(const QAccessibleInterface *iface) const
743{
744 if (!view()->model())
745 return -1;
746 QAccessibleInterface *parent = iface->parent();
747 if (parent->object() != view())
748 return -1;
749
750 if (iface->role() == QAccessible::TreeItem) {
751 const QAccessibleTableCell* cell = static_cast<const QAccessibleTableCell*>(iface);
752 const QTreeView *treeView = qobject_cast<const QTreeView*>(object: view());
753 Q_ASSERT(treeView);
754 int row = treeView->d_func()->viewIndex(index: cell->m_index) + (horizontalHeader() ? 1 : 0);
755 int column = cell->m_index.column();
756
757 int index = row * view()->model()->columnCount() + column;
758 return index;
759 } else if (iface->role() == QAccessible::ColumnHeader){
760 const QAccessibleTableHeaderCell* cell = static_cast<const QAccessibleTableHeaderCell*>(iface);
761 return cell->index;
762 } else {
763 qWarning() << "WARNING QAccessibleTable::indexOfChild invalid child"
764 << iface->role() << iface->text(t: QAccessible::Name);
765 }
766 // FIXME: add scrollbars and don't just ignore them
767 return -1;
768}
769
770QAccessibleInterface *QAccessibleTree::cellAt(int row, int column) const
771{
772 QModelIndex index = indexFromLogical(row, column);
773 if (Q_UNLIKELY(!index.isValid())) {
774 qWarning(msg: "Requested invalid tree cell: %d %d", row, column);
775 return nullptr;
776 }
777 const QTreeView *treeView = qobject_cast<const QTreeView*>(object: view());
778 Q_ASSERT(treeView);
779 int logicalIndex = treeView->d_func()->accessibleTable2Index(index);
780
781 return child(logicalIndex); // FIXME ### new QAccessibleTableCell(view(), index, cellRole());
782}
783
784QString QAccessibleTree::rowDescription(int) const
785{
786 return QString(); // no headers for rows in trees
787}
788
789bool QAccessibleTree::isRowSelected(int row) const
790{
791 if (!view()->selectionModel())
792 return false;
793 QModelIndex index = indexFromLogical(row);
794 return view()->selectionModel()->isRowSelected(row: index.row(), parent: index.parent());
795}
796
797bool QAccessibleTree::selectRow(int row)
798{
799 if (!view()->selectionModel())
800 return false;
801 QModelIndex index = indexFromLogical(row);
802
803 if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectColumns)
804 return false;
805
806 switch (view()->selectionMode()) {
807 case QAbstractItemView::NoSelection:
808 return false;
809 case QAbstractItemView::SingleSelection:
810 if ((view()->selectionBehavior() != QAbstractItemView::SelectRows) && (columnCount() > 1))
811 return false;
812 view()->clearSelection();
813 break;
814 case QAbstractItemView::ContiguousSelection:
815 if ((!row || !view()->selectionModel()->isRowSelected(row: row - 1, parent: view()->rootIndex()))
816 && !view()->selectionModel()->isRowSelected(row: row + 1, parent: view()->rootIndex()))
817 view()->clearSelection();
818 break;
819 default:
820 break;
821 }
822
823 view()->selectionModel()->select(index, command: QItemSelectionModel::Select | QItemSelectionModel::Rows);
824 return true;
825}
826
827#endif // QT_CONFIG(treeview)
828
829// TABLE CELL
830
831QAccessibleTableCell::QAccessibleTableCell(QAbstractItemView *view_, const QModelIndex &index_, QAccessible::Role role_)
832 : /* QAccessibleSimpleEditableTextInterface(this), */ view(view_), m_index(index_), m_role(role_)
833{
834 if (Q_UNLIKELY(!index_.isValid()))
835 qWarning() << "QAccessibleTableCell::QAccessibleTableCell with invalid index: " << index_;
836}
837
838void *QAccessibleTableCell::interface_cast(QAccessible::InterfaceType t)
839{
840 if (t == QAccessible::TableCellInterface)
841 return static_cast<QAccessibleTableCellInterface*>(this);
842 if (t == QAccessible::ActionInterface)
843 return static_cast<QAccessibleActionInterface*>(this);
844 return nullptr;
845}
846
847int QAccessibleTableCell::columnExtent() const { return 1; }
848int QAccessibleTableCell::rowExtent() const { return 1; }
849
850QList<QAccessibleInterface*> QAccessibleTableCell::rowHeaderCells() const
851{
852 QList<QAccessibleInterface*> headerCell;
853 if (verticalHeader()) {
854 // FIXME
855 headerCell.append(t: new QAccessibleTableHeaderCell(view, m_index.row(), Qt::Vertical));
856 }
857 return headerCell;
858}
859
860QList<QAccessibleInterface*> QAccessibleTableCell::columnHeaderCells() const
861{
862 QList<QAccessibleInterface*> headerCell;
863 if (horizontalHeader()) {
864 // FIXME
865 headerCell.append(t: new QAccessibleTableHeaderCell(view, m_index.column(), Qt::Horizontal));
866 }
867 return headerCell;
868}
869
870QHeaderView *QAccessibleTableCell::horizontalHeader() const
871{
872 QHeaderView *header = nullptr;
873
874 if (false) {
875#if QT_CONFIG(tableview)
876 } else if (const QTableView *tv = qobject_cast<const QTableView*>(object: view)) {
877 header = tv->horizontalHeader();
878#endif
879#if QT_CONFIG(treeview)
880 } else if (const QTreeView *tv = qobject_cast<const QTreeView*>(object: view)) {
881 header = tv->header();
882#endif
883 }
884
885 return header;
886}
887
888QHeaderView *QAccessibleTableCell::verticalHeader() const
889{
890 QHeaderView *header = nullptr;
891#if QT_CONFIG(tableview)
892 if (const QTableView *tv = qobject_cast<const QTableView*>(object: view))
893 header = tv->verticalHeader();
894#endif
895 return header;
896}
897
898int QAccessibleTableCell::columnIndex() const
899{
900 if (!isValid())
901 return -1;
902 return m_index.column();
903}
904
905int QAccessibleTableCell::rowIndex() const
906{
907 if (!isValid())
908 return -1;
909#if QT_CONFIG(treeview)
910 if (role() == QAccessible::TreeItem) {
911 const QTreeView *treeView = qobject_cast<const QTreeView*>(object: view);
912 Q_ASSERT(treeView);
913 int row = treeView->d_func()->viewIndex(index: m_index);
914 return row;
915 }
916#endif
917 return m_index.row();
918}
919
920bool QAccessibleTableCell::isSelected() const
921{
922 if (!isValid())
923 return false;
924 return view->selectionModel()->isSelected(index: m_index);
925}
926
927QStringList QAccessibleTableCell::actionNames() const
928{
929 QStringList names;
930 names << toggleAction();
931 return names;
932}
933
934void QAccessibleTableCell::doAction(const QString& actionName)
935{
936 if (actionName == toggleAction()) {
937#if defined(Q_OS_ANDROID)
938 QAccessibleInterface *parentInterface = parent();
939 while (parentInterface){
940 if (parentInterface->role() == QAccessible::ComboBox) {
941 selectCell();
942 parentInterface->actionInterface()->doAction(pressAction());
943 return;
944 } else {
945 parentInterface = parentInterface->parent();
946 }
947 }
948#endif
949 if (isSelected()) {
950 unselectCell();
951 } else {
952 selectCell();
953 }
954 }
955}
956
957QStringList QAccessibleTableCell::keyBindingsForAction(const QString &) const
958{
959 return QStringList();
960}
961
962
963void QAccessibleTableCell::selectCell()
964{
965 if (!isValid())
966 return;
967 QAbstractItemView::SelectionMode selectionMode = view->selectionMode();
968 if (selectionMode == QAbstractItemView::NoSelection)
969 return;
970 Q_ASSERT(table());
971 QAccessibleTableInterface *cellTable = table()->tableInterface();
972
973 switch (view->selectionBehavior()) {
974 case QAbstractItemView::SelectItems:
975 break;
976 case QAbstractItemView::SelectColumns:
977 if (cellTable)
978 cellTable->selectColumn(column: m_index.column());
979 return;
980 case QAbstractItemView::SelectRows:
981 if (cellTable)
982 cellTable->selectRow(row: m_index.row());
983 return;
984 }
985
986 if (selectionMode == QAbstractItemView::SingleSelection) {
987 view->clearSelection();
988 }
989
990 view->selectionModel()->select(index: m_index, command: QItemSelectionModel::Select);
991}
992
993void QAccessibleTableCell::unselectCell()
994{
995 if (!isValid())
996 return;
997 QAbstractItemView::SelectionMode selectionMode = view->selectionMode();
998 if (selectionMode == QAbstractItemView::NoSelection)
999 return;
1000
1001 QAccessibleTableInterface *cellTable = table()->tableInterface();
1002
1003 switch (view->selectionBehavior()) {
1004 case QAbstractItemView::SelectItems:
1005 break;
1006 case QAbstractItemView::SelectColumns:
1007 if (cellTable)
1008 cellTable->unselectColumn(column: m_index.column());
1009 return;
1010 case QAbstractItemView::SelectRows:
1011 if (cellTable)
1012 cellTable->unselectRow(row: m_index.row());
1013 return;
1014 }
1015
1016 //If the mode is not MultiSelection or ExtendedSelection and only
1017 //one cell is selected it cannot be unselected by the user
1018 if ((selectionMode != QAbstractItemView::MultiSelection)
1019 && (selectionMode != QAbstractItemView::ExtendedSelection)
1020 && (view->selectionModel()->selectedIndexes().count() <= 1))
1021 return;
1022
1023 view->selectionModel()->select(index: m_index, command: QItemSelectionModel::Deselect);
1024}
1025
1026QAccessibleInterface *QAccessibleTableCell::table() const
1027{
1028 return QAccessible::queryAccessibleInterface(view);
1029}
1030
1031QAccessible::Role QAccessibleTableCell::role() const
1032{
1033 return m_role;
1034}
1035
1036QAccessible::State QAccessibleTableCell::state() const
1037{
1038 QAccessible::State st;
1039 if (!isValid())
1040 return st;
1041
1042 QRect globalRect = view->rect();
1043 globalRect.translate(p: view->mapToGlobal(QPoint(0,0)));
1044 if (!globalRect.intersects(r: rect()))
1045 st.invisible = true;
1046
1047 if (view->selectionModel()->isSelected(index: m_index))
1048 st.selected = true;
1049 if (view->selectionModel()->currentIndex() == m_index)
1050 st.focused = true;
1051
1052 QVariant checkState = m_index.model()->data(index: m_index, role: Qt::CheckStateRole);
1053 if (checkState.toInt() == Qt::Checked)
1054 st.checked = true;
1055
1056 Qt::ItemFlags flags = m_index.flags();
1057 if ((flags & Qt::ItemIsUserCheckable) && checkState.isValid())
1058 st.checkable = true;
1059 if (flags & Qt::ItemIsSelectable) {
1060 st.selectable = true;
1061 st.focusable = true;
1062 if (view->selectionMode() == QAbstractItemView::MultiSelection)
1063 st.multiSelectable = true;
1064 if (view->selectionMode() == QAbstractItemView::ExtendedSelection)
1065 st.extSelectable = true;
1066 }
1067#if QT_CONFIG(treeview)
1068 if (m_role == QAccessible::TreeItem) {
1069 const QTreeView *treeView = qobject_cast<const QTreeView*>(object: view);
1070 if (treeView->model()->hasChildren(parent: m_index))
1071 st.expandable = true;
1072 if (treeView->isExpanded(index: m_index))
1073 st.expanded = true;
1074 }
1075#endif
1076 return st;
1077}
1078
1079
1080QRect QAccessibleTableCell::rect() const
1081{
1082 QRect r;
1083 if (!isValid())
1084 return r;
1085 r = view->visualRect(index: m_index);
1086
1087 if (!r.isNull()) {
1088 r.translate(p: view->viewport()->mapTo(view, QPoint(0,0)));
1089 r.translate(p: view->mapToGlobal(QPoint(0, 0)));
1090 }
1091 return r;
1092}
1093
1094QString QAccessibleTableCell::text(QAccessible::Text t) const
1095{
1096 QString value;
1097 if (!isValid())
1098 return value;
1099 QAbstractItemModel *model = view->model();
1100 switch (t) {
1101 case QAccessible::Name:
1102 value = model->data(index: m_index, role: Qt::AccessibleTextRole).toString();
1103 if (value.isEmpty())
1104 value = model->data(index: m_index, role: Qt::DisplayRole).toString();
1105 break;
1106 case QAccessible::Description:
1107 value = model->data(index: m_index, role: Qt::AccessibleDescriptionRole).toString();
1108 break;
1109 default:
1110 break;
1111 }
1112 return value;
1113}
1114
1115void QAccessibleTableCell::setText(QAccessible::Text /*t*/, const QString &text)
1116{
1117 if (!isValid() || !(m_index.flags() & Qt::ItemIsEditable))
1118 return;
1119 view->model()->setData(index: m_index, value: text);
1120}
1121
1122bool QAccessibleTableCell::isValid() const
1123{
1124 return view && !qt_widget_private(widget: view)->data.in_destructor
1125 && view->model() && m_index.isValid();
1126}
1127
1128QAccessibleInterface *QAccessibleTableCell::parent() const
1129{
1130 return QAccessible::queryAccessibleInterface(view);
1131}
1132
1133QAccessibleInterface *QAccessibleTableCell::child(int) const
1134{
1135 return nullptr;
1136}
1137
1138QAccessibleTableHeaderCell::QAccessibleTableHeaderCell(QAbstractItemView *view_, int index_, Qt::Orientation orientation_)
1139 : view(view_), index(index_), orientation(orientation_)
1140{
1141 Q_ASSERT(index_ >= 0);
1142}
1143
1144QAccessible::Role QAccessibleTableHeaderCell::role() const
1145{
1146 if (orientation == Qt::Horizontal)
1147 return QAccessible::ColumnHeader;
1148 return QAccessible::RowHeader;
1149}
1150
1151QAccessible::State QAccessibleTableHeaderCell::state() const
1152{
1153 QAccessible::State s;
1154 if (QHeaderView *h = headerView()) {
1155 s.invisible = !h->testAttribute(attribute: Qt::WA_WState_Visible);
1156 s.disabled = !h->isEnabled();
1157 }
1158 return s;
1159}
1160
1161QRect QAccessibleTableHeaderCell::rect() const
1162{
1163 QHeaderView *header = nullptr;
1164 if (false) {
1165#if QT_CONFIG(tableview)
1166 } else if (const QTableView *tv = qobject_cast<const QTableView*>(object: view)) {
1167 if (orientation == Qt::Horizontal) {
1168 header = tv->horizontalHeader();
1169 } else {
1170 header = tv->verticalHeader();
1171 }
1172#endif
1173#if QT_CONFIG(treeview)
1174 } else if (const QTreeView *tv = qobject_cast<const QTreeView*>(object: view)) {
1175 header = tv->header();
1176#endif
1177 }
1178 if (!header)
1179 return QRect();
1180 QPoint zero = header->mapToGlobal(QPoint(0, 0));
1181 int sectionSize = header->sectionSize(logicalIndex: index);
1182 int sectionPos = header->sectionPosition(logicalIndex: index);
1183 return orientation == Qt::Horizontal
1184 ? QRect(zero.x() + sectionPos, zero.y(), sectionSize, header->height())
1185 : QRect(zero.x(), zero.y() + sectionPos, header->width(), sectionSize);
1186}
1187
1188QString QAccessibleTableHeaderCell::text(QAccessible::Text t) const
1189{
1190 QAbstractItemModel *model = view->model();
1191 QString value;
1192 switch (t) {
1193 case QAccessible::Name:
1194 value = model->headerData(section: index, orientation, role: Qt::AccessibleTextRole).toString();
1195 if (value.isEmpty())
1196 value = model->headerData(section: index, orientation, role: Qt::DisplayRole).toString();
1197 break;
1198 case QAccessible::Description:
1199 value = model->headerData(section: index, orientation, role: Qt::AccessibleDescriptionRole).toString();
1200 break;
1201 default:
1202 break;
1203 }
1204 return value;
1205}
1206
1207void QAccessibleTableHeaderCell::setText(QAccessible::Text, const QString &)
1208{
1209 return;
1210}
1211
1212bool QAccessibleTableHeaderCell::isValid() const
1213{
1214 return view && !qt_widget_private(widget: view)->data.in_destructor
1215 && view->model() && (index >= 0)
1216 && ((orientation == Qt::Horizontal) ? (index < view->model()->columnCount()) : (index < view->model()->rowCount()));
1217}
1218
1219QAccessibleInterface *QAccessibleTableHeaderCell::parent() const
1220{
1221 return QAccessible::queryAccessibleInterface(view);
1222}
1223
1224QAccessibleInterface *QAccessibleTableHeaderCell::child(int) const
1225{
1226 return nullptr;
1227}
1228
1229QHeaderView *QAccessibleTableHeaderCell::headerView() const
1230{
1231 QHeaderView *header = nullptr;
1232 if (false) {
1233#if QT_CONFIG(tableview)
1234 } else if (const QTableView *tv = qobject_cast<const QTableView*>(object: view)) {
1235 if (orientation == Qt::Horizontal) {
1236 header = tv->horizontalHeader();
1237 } else {
1238 header = tv->verticalHeader();
1239 }
1240#endif
1241#if QT_CONFIG(treeview)
1242 } else if (const QTreeView *tv = qobject_cast<const QTreeView*>(object: view)) {
1243 header = tv->header();
1244#endif
1245 }
1246 return header;
1247}
1248
1249QT_END_NAMESPACE
1250
1251#endif // QT_NO_ACCESSIBILITY
1252

source code of qtbase/src/widgets/accessible/itemviews.cpp