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
57 QT_BEGIN_NAMESPACE
58
59 /*
60 Implementation of the IAccessible2 table2 interface. Much simpler than
61 the other table interfaces since there is only the main table and cells:
62
63 TABLE/LIST/TREE
64 |- HEADER CELL
65 |- CELL
66 |- CELL
67 ...
68 */
69
70
view() const71 QAbstractItemView *QAccessibleTable::view() const
72 {
73 return qobject_cast<QAbstractItemView*>(object());
74 }
75
logicalIndex(const QModelIndex & index) const76 int 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
QAccessibleTable(QWidget * w)85 QAccessibleTable::QAccessibleTable(QWidget *w)
86 : QAccessibleObject(w)
87 {
88 Q_ASSERT(view());
89
90 #if QT_CONFIG(tableview)
91 if (qobject_cast<const QTableView*>(view())) {
92 m_role = QAccessible::Table;
93 } else
94 #endif
95 #if QT_CONFIG(treeview)
96 if (qobject_cast<const QTreeView*>(view())) {
97 m_role = QAccessible::Tree;
98 } else
99 #endif
100 #if QT_CONFIG(listview)
101 if (qobject_cast<const QListView*>(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
isValid() const111 bool QAccessibleTable::isValid() const
112 {
113 return view() && !qt_widget_private(view())->data.in_destructor;
114 }
115
~QAccessibleTable()116 QAccessibleTable::~QAccessibleTable()
117 {
118 for (QAccessible::Id id : qAsConst(childToId))
119 QAccessible::deleteAccessibleInterface(id);
120 }
121
horizontalHeader() const122 QHeaderView *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*>(view())) {
128 header = tv->horizontalHeader();
129 #endif
130 #if QT_CONFIG(treeview)
131 } else if (const QTreeView *tv = qobject_cast<const QTreeView*>(view())) {
132 header = tv->header();
133 #endif
134 }
135 return header;
136 }
137
verticalHeader() const138 QHeaderView *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*>(view())) {
144 header = tv->verticalHeader();
145 #endif
146 }
147 return header;
148 }
149
cellAt(int row,int column) const150 QAccessibleInterface *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, view()->rootIndex());
156 if (Q_UNLIKELY(!index.isValid())) {
157 qWarning() << "QAccessibleTable::cellAt: invalid index: " << index << " for " << view();
158 return nullptr;
159 }
160 return child(logicalIndex(index));
161 }
162
caption() const163 QAccessibleInterface *QAccessibleTable::caption() const
164 {
165 return nullptr;
166 }
167
columnDescription(int column) const168 QString QAccessibleTable::columnDescription(int column) const
169 {
170 if (!view()->model())
171 return QString();
172 return view()->model()->headerData(column, Qt::Horizontal).toString();
173 }
174
columnCount() const175 int QAccessibleTable::columnCount() const
176 {
177 if (!view()->model())
178 return 0;
179 return view()->model()->columnCount();
180 }
181
rowCount() const182 int QAccessibleTable::rowCount() const
183 {
184 if (!view()->model())
185 return 0;
186 return view()->model()->rowCount();
187 }
188
selectedCellCount() const189 int QAccessibleTable::selectedCellCount() const
190 {
191 if (!view()->selectionModel())
192 return 0;
193 return view()->selectionModel()->selectedIndexes().count();
194 }
195
selectedColumnCount() const196 int QAccessibleTable::selectedColumnCount() const
197 {
198 if (!view()->selectionModel())
199 return 0;
200 return view()->selectionModel()->selectedColumns().count();
201 }
202
selectedRowCount() const203 int QAccessibleTable::selectedRowCount() const
204 {
205 if (!view()->selectionModel())
206 return 0;
207 return view()->selectionModel()->selectedRows().count();
208 }
209
rowDescription(int row) const210 QString QAccessibleTable::rowDescription(int row) const
211 {
212 if (!view()->model())
213 return QString();
214 return view()->model()->headerData(row, Qt::Vertical).toString();
215 }
216
selectedCells() const217 QList<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(selectedIndexes.size());
224 for (const QModelIndex &index : selectedIndexes)
225 cells.append(child(logicalIndex(index)));
226 return cells;
227 }
228
selectedColumns() const229 QList<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(selectedColumns.size());
236 for (const QModelIndex &index : selectedColumns)
237 columns.append(index.column());
238
239 return columns;
240 }
241
selectedRows() const242 QList<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(selectedRows.size());
249 for (const QModelIndex &index : selectedRows)
250 rows.append(index.row());
251
252 return rows;
253 }
254
summary() const255 QAccessibleInterface *QAccessibleTable::summary() const
256 {
257 return nullptr;
258 }
259
isColumnSelected(int column) const260 bool QAccessibleTable::isColumnSelected(int column) const
261 {
262 if (!view()->selectionModel())
263 return false;
264 return view()->selectionModel()->isColumnSelected(column, QModelIndex());
265 }
266
isRowSelected(int row) const267 bool QAccessibleTable::isRowSelected(int row) const
268 {
269 if (!view()->selectionModel())
270 return false;
271 return view()->selectionModel()->isRowSelected(row, QModelIndex());
272 }
273
selectRow(int row)274 bool QAccessibleTable::selectRow(int row)
275 {
276 if (!view()->model() || !view()->selectionModel())
277 return false;
278 QModelIndex index = view()->model()->index(row, 0, 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 - 1, view()->rootIndex()))
293 && !view()->selectionModel()->isRowSelected(row + 1, view()->rootIndex()))
294 view()->clearSelection();
295 break;
296 default:
297 break;
298 }
299
300 view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows);
301 return true;
302 }
303
selectColumn(int column)304 bool QAccessibleTable::selectColumn(int column)
305 {
306 if (!view()->model() || !view()->selectionModel())
307 return false;
308 QModelIndex index = view()->model()->index(0, column, 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 - 1, view()->rootIndex()))
322 && !view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex()))
323 view()->clearSelection();
324 break;
325 default:
326 break;
327 }
328
329 view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Columns);
330 return true;
331 }
332
unselectRow(int row)333 bool QAccessibleTable::unselectRow(int row)
334 {
335 if (!view()->model() || !view()->selectionModel())
336 return false;
337
338 QModelIndex index = view()->model()->index(row, 0, 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 - 1, view()->rootIndex()))
356 && view()->selectionModel()->isRowSelected(row + 1, 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(rowCount() - 1, 0, view()->rootIndex()));
360 }
361 default:
362 break;
363 }
364
365 view()->selectionModel()->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Rows);
366 return true;
367 }
368
unselectColumn(int column)369 bool QAccessibleTable::unselectColumn(int column)
370 {
371 if (!view()->model() || !view()->selectionModel())
372 return false;
373
374 QModelIndex index = view()->model()->index(0, column, 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 - 1, view()->rootIndex()))
392 && view()->selectionModel()->isColumnSelected(column + 1, 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(0, columnCount() - 1, view()->rootIndex()));
396 }
397 default:
398 break;
399 }
400
401 view()->selectionModel()->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Columns);
402 return true;
403 }
404
role() const405 QAccessible::Role QAccessibleTable::role() const
406 {
407 return m_role;
408 }
409
state() const410 QAccessible::State QAccessibleTable::state() const
411 {
412 return QAccessible::State();
413 }
414
childAt(int x,int y) const415 QAccessibleInterface *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(indexPosition);
422 if (index.isValid()) {
423 return child(logicalIndex(index));
424 }
425 return nullptr;
426 }
427
childCount() const428 int 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
indexOfChild(const QAccessibleInterface * iface) const437 int 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(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(QAccessible::Name);
460 }
461 // FIXME: we are in denial of our children. this should stop.
462 return -1;
463 }
464
text(QAccessible::Text t) const465 QString QAccessibleTable::text(QAccessible::Text t) const
466 {
467 if (t == QAccessible::Description)
468 return view()->accessibleDescription();
469 return view()->accessibleName();
470 }
471
rect() const472 QRect 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
parent() const480 QAccessibleInterface *QAccessibleTable::parent() const
481 {
482 if (view() && view()->parent()) {
483 if (qstrcmp("QComboBoxPrivateContainer", 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
child(int logicalIndex) const491 QAccessibleInterface *QAccessibleTable::child(int logicalIndex) const
492 {
493 if (!view()->model())
494 return nullptr;
495
496 auto id = childToId.constFind(logicalIndex);
497 if (id != childToId.constEnd())
498 return QAccessible::accessibleInterface(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, view()->rootIndex());
529 if (Q_UNLIKELY(!index.isValid())) {
530 qWarning("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(logicalIndex, QAccessible::uniqueId(iface));
538 return iface;
539 }
540
interface_cast(QAccessible::InterfaceType t)541 void *QAccessibleTable::interface_cast(QAccessible::InterfaceType t)
542 {
543 if (t == QAccessible::TableInterface)
544 return static_cast<QAccessibleTableInterface*>(this);
545 return nullptr;
546 }
547
modelChange(QAccessibleTableModelChangeEvent * event)548 void 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(childToId))
557 QAccessible::deleteAccessibleInterface(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(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(indexOfChild(iface), 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(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(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(indexOfChild(cell), id);
617 else
618 QAccessible::deleteAccessibleInterface(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(indexOfChild(cell), id);
624 } else if (cell->index > event->lastRow()) {
625 cell->index -= deletedRows;
626 newCache.insert(indexOfChild(cell), id);
627 } else {
628 QAccessible::deleteAccessibleInterface(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(indexOfChild(cell), id);
635 } else if (cell->index > event->lastColumn()) {
636 cell->index -= deletedColumns;
637 newCache.insert(indexOfChild(cell), id);
638 } else {
639 QAccessible::deleteAccessibleInterface(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
indexFromLogical(int row,int column) const658 QModelIndex 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*>(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(row).index;
669
670 if (modelIndex.isValid() && column > 0) {
671 modelIndex = view()->model()->index(modelIndex.row(), column, modelIndex.parent());
672 }
673 return modelIndex;
674 }
675
childAt(int x,int y) const676 QAccessibleInterface *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(indexPosition);
684 if (!index.isValid())
685 return nullptr;
686
687 const QTreeView *treeView = qobject_cast<const QTreeView*>(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(i);
693 }
694
childCount() const695 int QAccessibleTree::childCount() const
696 {
697 const QTreeView *treeView = qobject_cast<const QTreeView*>(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
child(int logicalIndex) const706 QAccessibleInterface *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
rowCount() const735 int QAccessibleTree::rowCount() const
736 {
737 const QTreeView *treeView = qobject_cast<const QTreeView*>(view());
738 Q_ASSERT(treeView);
739 return treeView->d_func()->viewItems.count();
740 }
741
indexOfChild(const QAccessibleInterface * iface) const742 int 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*>(view());
753 Q_ASSERT(treeView);
754 int row = treeView->d_func()->viewIndex(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(QAccessible::Name);
765 }
766 // FIXME: add scrollbars and don't just ignore them
767 return -1;
768 }
769
cellAt(int row,int column) const770 QAccessibleInterface *QAccessibleTree::cellAt(int row, int column) const
771 {
772 QModelIndex index = indexFromLogical(row, column);
773 if (Q_UNLIKELY(!index.isValid())) {
774 qWarning("Requested invalid tree cell: %d %d", row, column);
775 return nullptr;
776 }
777 const QTreeView *treeView = qobject_cast<const QTreeView*>(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
rowDescription(int) const784 QString QAccessibleTree::rowDescription(int) const
785 {
786 return QString(); // no headers for rows in trees
787 }
788
isRowSelected(int row) const789 bool QAccessibleTree::isRowSelected(int row) const
790 {
791 if (!view()->selectionModel())
792 return false;
793 QModelIndex index = indexFromLogical(row);
794 return view()->selectionModel()->isRowSelected(index.row(), index.parent());
795 }
796
selectRow(int row)797 bool 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 - 1, view()->rootIndex()))
816 && !view()->selectionModel()->isRowSelected(row + 1, view()->rootIndex()))
817 view()->clearSelection();
818 break;
819 default:
820 break;
821 }
822
823 view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows);
824 return true;
825 }
826
827 #endif // QT_CONFIG(treeview)
828
829 // TABLE CELL
830
QAccessibleTableCell(QAbstractItemView * view_,const QModelIndex & index_,QAccessible::Role role_)831 QAccessibleTableCell::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
interface_cast(QAccessible::InterfaceType t)838 void *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
columnExtent() const847 int QAccessibleTableCell::columnExtent() const { return 1; }
rowExtent() const848 int QAccessibleTableCell::rowExtent() const { return 1; }
849
rowHeaderCells() const850 QList<QAccessibleInterface*> QAccessibleTableCell::rowHeaderCells() const
851 {
852 QList<QAccessibleInterface*> headerCell;
853 if (verticalHeader()) {
854 // FIXME
855 headerCell.append(new QAccessibleTableHeaderCell(view, m_index.row(), Qt::Vertical));
856 }
857 return headerCell;
858 }
859
columnHeaderCells() const860 QList<QAccessibleInterface*> QAccessibleTableCell::columnHeaderCells() const
861 {
862 QList<QAccessibleInterface*> headerCell;
863 if (horizontalHeader()) {
864 // FIXME
865 headerCell.append(new QAccessibleTableHeaderCell(view, m_index.column(), Qt::Horizontal));
866 }
867 return headerCell;
868 }
869
horizontalHeader() const870 QHeaderView *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*>(view)) {
877 header = tv->horizontalHeader();
878 #endif
879 #if QT_CONFIG(treeview)
880 } else if (const QTreeView *tv = qobject_cast<const QTreeView*>(view)) {
881 header = tv->header();
882 #endif
883 }
884
885 return header;
886 }
887
verticalHeader() const888 QHeaderView *QAccessibleTableCell::verticalHeader() const
889 {
890 QHeaderView *header = nullptr;
891 #if QT_CONFIG(tableview)
892 if (const QTableView *tv = qobject_cast<const QTableView*>(view))
893 header = tv->verticalHeader();
894 #endif
895 return header;
896 }
897
columnIndex() const898 int QAccessibleTableCell::columnIndex() const
899 {
900 if (!isValid())
901 return -1;
902 return m_index.column();
903 }
904
rowIndex() const905 int 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*>(view);
912 Q_ASSERT(treeView);
913 int row = treeView->d_func()->viewIndex(m_index);
914 return row;
915 }
916 #endif
917 return m_index.row();
918 }
919
isSelected() const920 bool QAccessibleTableCell::isSelected() const
921 {
922 if (!isValid())
923 return false;
924 return view->selectionModel()->isSelected(m_index);
925 }
926
actionNames() const927 QStringList QAccessibleTableCell::actionNames() const
928 {
929 QStringList names;
930 names << toggleAction();
931 return names;
932 }
933
doAction(const QString & actionName)934 void 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
keyBindingsForAction(const QString &) const957 QStringList QAccessibleTableCell::keyBindingsForAction(const QString &) const
958 {
959 return QStringList();
960 }
961
962
selectCell()963 void 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(m_index.column());
979 return;
980 case QAbstractItemView::SelectRows:
981 if (cellTable)
982 cellTable->selectRow(m_index.row());
983 return;
984 }
985
986 if (selectionMode == QAbstractItemView::SingleSelection) {
987 view->clearSelection();
988 }
989
990 view->selectionModel()->select(m_index, QItemSelectionModel::Select);
991 }
992
unselectCell()993 void 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(m_index.column());
1009 return;
1010 case QAbstractItemView::SelectRows:
1011 if (cellTable)
1012 cellTable->unselectRow(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(m_index, QItemSelectionModel::Deselect);
1024 }
1025
table() const1026 QAccessibleInterface *QAccessibleTableCell::table() const
1027 {
1028 return QAccessible::queryAccessibleInterface(view);
1029 }
1030
role() const1031 QAccessible::Role QAccessibleTableCell::role() const
1032 {
1033 return m_role;
1034 }
1035
state() const1036 QAccessible::State QAccessibleTableCell::state() const
1037 {
1038 QAccessible::State st;
1039 if (!isValid())
1040 return st;
1041
1042 QRect globalRect = view->rect();
1043 globalRect.translate(view->mapToGlobal(QPoint(0,0)));
1044 if (!globalRect.intersects(rect()))
1045 st.invisible = true;
1046
1047 if (view->selectionModel()->isSelected(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(m_index, 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*>(view);
1070 if (treeView->model()->hasChildren(m_index))
1071 st.expandable = true;
1072 if (treeView->isExpanded(m_index))
1073 st.expanded = true;
1074 }
1075 #endif
1076 return st;
1077 }
1078
1079
rect() const1080 QRect QAccessibleTableCell::rect() const
1081 {
1082 QRect r;
1083 if (!isValid())
1084 return r;
1085 r = view->visualRect(m_index);
1086
1087 if (!r.isNull()) {
1088 r.translate(view->viewport()->mapTo(view, QPoint(0,0)));
1089 r.translate(view->mapToGlobal(QPoint(0, 0)));
1090 }
1091 return r;
1092 }
1093
text(QAccessible::Text t) const1094 QString 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(m_index, Qt::AccessibleTextRole).toString();
1103 if (value.isEmpty())
1104 value = model->data(m_index, Qt::DisplayRole).toString();
1105 break;
1106 case QAccessible::Description:
1107 value = model->data(m_index, Qt::AccessibleDescriptionRole).toString();
1108 break;
1109 default:
1110 break;
1111 }
1112 return value;
1113 }
1114
setText(QAccessible::Text,const QString & text)1115 void QAccessibleTableCell::setText(QAccessible::Text /*t*/, const QString &text)
1116 {
1117 if (!isValid() || !(m_index.flags() & Qt::ItemIsEditable))
1118 return;
1119 view->model()->setData(m_index, text);
1120 }
1121
isValid() const1122 bool QAccessibleTableCell::isValid() const
1123 {
1124 return view && !qt_widget_private(view)->data.in_destructor
1125 && view->model() && m_index.isValid();
1126 }
1127
parent() const1128 QAccessibleInterface *QAccessibleTableCell::parent() const
1129 {
1130 return QAccessible::queryAccessibleInterface(view);
1131 }
1132
child(int) const1133 QAccessibleInterface *QAccessibleTableCell::child(int) const
1134 {
1135 return nullptr;
1136 }
1137
QAccessibleTableHeaderCell(QAbstractItemView * view_,int index_,Qt::Orientation orientation_)1138 QAccessibleTableHeaderCell::QAccessibleTableHeaderCell(QAbstractItemView *view_, int index_, Qt::Orientation orientation_)
1139 : view(view_), index(index_), orientation(orientation_)
1140 {
1141 Q_ASSERT(index_ >= 0);
1142 }
1143
role() const1144 QAccessible::Role QAccessibleTableHeaderCell::role() const
1145 {
1146 if (orientation == Qt::Horizontal)
1147 return QAccessible::ColumnHeader;
1148 return QAccessible::RowHeader;
1149 }
1150
state() const1151 QAccessible::State QAccessibleTableHeaderCell::state() const
1152 {
1153 QAccessible::State s;
1154 if (QHeaderView *h = headerView()) {
1155 s.invisible = !h->testAttribute(Qt::WA_WState_Visible);
1156 s.disabled = !h->isEnabled();
1157 }
1158 return s;
1159 }
1160
rect() const1161 QRect 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*>(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*>(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(index);
1182 int sectionPos = header->sectionPosition(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
text(QAccessible::Text t) const1188 QString 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(index, orientation, Qt::AccessibleTextRole).toString();
1195 if (value.isEmpty())
1196 value = model->headerData(index, orientation, Qt::DisplayRole).toString();
1197 break;
1198 case QAccessible::Description:
1199 value = model->headerData(index, orientation, Qt::AccessibleDescriptionRole).toString();
1200 break;
1201 default:
1202 break;
1203 }
1204 return value;
1205 }
1206
setText(QAccessible::Text,const QString &)1207 void QAccessibleTableHeaderCell::setText(QAccessible::Text, const QString &)
1208 {
1209 return;
1210 }
1211
isValid() const1212 bool QAccessibleTableHeaderCell::isValid() const
1213 {
1214 return view && !qt_widget_private(view)->data.in_destructor
1215 && view->model() && (index >= 0)
1216 && ((orientation == Qt::Horizontal) ? (index < view->model()->columnCount()) : (index < view->model()->rowCount()));
1217 }
1218
parent() const1219 QAccessibleInterface *QAccessibleTableHeaderCell::parent() const
1220 {
1221 return QAccessible::queryAccessibleInterface(view);
1222 }
1223
child(int) const1224 QAccessibleInterface *QAccessibleTableHeaderCell::child(int) const
1225 {
1226 return nullptr;
1227 }
1228
headerView() const1229 QHeaderView *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*>(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*>(view)) {
1243 header = tv->header();
1244 #endif
1245 }
1246 return header;
1247 }
1248
1249 QT_END_NAMESPACE
1250
1251 #endif // QT_NO_ACCESSIBILITY
1252