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());
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*>(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
111bool QAccessibleTable::isValid() const
112{
113 return view() && !qt_widget_private(view())->data.in_destructor;
114}
115
116QAccessibleTable::~QAccessibleTable()
117{
118 for (QAccessible::Id id : qAsConst(childToId))
119 QAccessible::deleteAccessibleInterface(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*>(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
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*>(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, 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
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(column, 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(row, 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(selectedIndexes.size());
224 for (const QModelIndex &index : selectedIndexes)
225 cells.append(child(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(selectedColumns.size());
236 for (const QModelIndex &index : selectedColumns)
237 columns.append(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(selectedRows.size());
249 for (const QModelIndex &index : selectedRows)
250 rows.append(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, QModelIndex());
265}
266
267bool QAccessibleTable::isRowSelected(int row) const
268{
269 if (!view()->selectionModel())
270 return false;
271 return view()->selectionModel()->isRowSelected(row, 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, 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
304bool 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
333bool 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
369bool 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
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(indexPosition);
422 if (index.isValid()) {
423 return child(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(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
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("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
491QAccessibleInterface *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
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(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
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*>(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
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(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
695int 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
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*>(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*>(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
770QAccessibleInterface *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
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(index.row(), 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 - 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
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(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(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*>(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
888QHeaderView *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
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*>(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
920bool QAccessibleTableCell::isSelected() const
921{
922 if (!isValid())
923 return false;
924 return view->selectionModel()->isSelected(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 (isSelected())
938 unselectCell();
939 else
940 selectCell();
941 }
942}
943
944QStringList QAccessibleTableCell::keyBindingsForAction(const QString &) const
945{
946 return QStringList();
947}
948
949
950void QAccessibleTableCell::selectCell()
951{
952 if (!isValid())
953 return;
954 QAbstractItemView::SelectionMode selectionMode = view->selectionMode();
955 if (selectionMode == QAbstractItemView::NoSelection)
956 return;
957 Q_ASSERT(table());
958 QAccessibleTableInterface *cellTable = table()->tableInterface();
959
960 switch (view->selectionBehavior()) {
961 case QAbstractItemView::SelectItems:
962 break;
963 case QAbstractItemView::SelectColumns:
964 if (cellTable)
965 cellTable->selectColumn(m_index.column());
966 return;
967 case QAbstractItemView::SelectRows:
968 if (cellTable)
969 cellTable->selectRow(m_index.row());
970 return;
971 }
972
973 if (selectionMode == QAbstractItemView::SingleSelection) {
974 view->clearSelection();
975 }
976
977 view->selectionModel()->select(m_index, QItemSelectionModel::Select);
978}
979
980void QAccessibleTableCell::unselectCell()
981{
982 if (!isValid())
983 return;
984 QAbstractItemView::SelectionMode selectionMode = view->selectionMode();
985 if (selectionMode == QAbstractItemView::NoSelection)
986 return;
987
988 QAccessibleTableInterface *cellTable = table()->tableInterface();
989
990 switch (view->selectionBehavior()) {
991 case QAbstractItemView::SelectItems:
992 break;
993 case QAbstractItemView::SelectColumns:
994 if (cellTable)
995 cellTable->unselectColumn(m_index.column());
996 return;
997 case QAbstractItemView::SelectRows:
998 if (cellTable)
999 cellTable->unselectRow(m_index.row());
1000 return;
1001 }
1002
1003 //If the mode is not MultiSelection or ExtendedSelection and only
1004 //one cell is selected it cannot be unselected by the user
1005 if ((selectionMode != QAbstractItemView::MultiSelection)
1006 && (selectionMode != QAbstractItemView::ExtendedSelection)
1007 && (view->selectionModel()->selectedIndexes().count() <= 1))
1008 return;
1009
1010 view->selectionModel()->select(m_index, QItemSelectionModel::Deselect);
1011}
1012
1013QAccessibleInterface *QAccessibleTableCell::table() const
1014{
1015 return QAccessible::queryAccessibleInterface(view);
1016}
1017
1018QAccessible::Role QAccessibleTableCell::role() const
1019{
1020 return m_role;
1021}
1022
1023QAccessible::State QAccessibleTableCell::state() const
1024{
1025 QAccessible::State st;
1026 if (!isValid())
1027 return st;
1028
1029 QRect globalRect = view->rect();
1030 globalRect.translate(view->mapToGlobal(QPoint(0,0)));
1031 if (!globalRect.intersects(rect()))
1032 st.invisible = true;
1033
1034 if (view->selectionModel()->isSelected(m_index))
1035 st.selected = true;
1036 if (view->selectionModel()->currentIndex() == m_index)
1037 st.focused = true;
1038
1039 QVariant checkState = m_index.model()->data(m_index, Qt::CheckStateRole);
1040 if (checkState.toInt() == Qt::Checked)
1041 st.checked = true;
1042
1043 Qt::ItemFlags flags = m_index.flags();
1044 if ((flags & Qt::ItemIsUserCheckable) && checkState.isValid())
1045 st.checkable = true;
1046 if (flags & Qt::ItemIsSelectable) {
1047 st.selectable = true;
1048 st.focusable = true;
1049 if (view->selectionMode() == QAbstractItemView::MultiSelection)
1050 st.multiSelectable = true;
1051 if (view->selectionMode() == QAbstractItemView::ExtendedSelection)
1052 st.extSelectable = true;
1053 }
1054#if QT_CONFIG(treeview)
1055 if (m_role == QAccessible::TreeItem) {
1056 const QTreeView *treeView = qobject_cast<const QTreeView*>(view);
1057 if (treeView->model()->hasChildren(m_index))
1058 st.expandable = true;
1059 if (treeView->isExpanded(m_index))
1060 st.expanded = true;
1061 }
1062#endif
1063 return st;
1064}
1065
1066
1067QRect QAccessibleTableCell::rect() const
1068{
1069 QRect r;
1070 if (!isValid())
1071 return r;
1072 r = view->visualRect(m_index);
1073
1074 if (!r.isNull()) {
1075 r.translate(view->viewport()->mapTo(view, QPoint(0,0)));
1076 r.translate(view->mapToGlobal(QPoint(0, 0)));
1077 }
1078 return r;
1079}
1080
1081QString QAccessibleTableCell::text(QAccessible::Text t) const
1082{
1083 QString value;
1084 if (!isValid())
1085 return value;
1086 QAbstractItemModel *model = view->model();
1087 switch (t) {
1088 case QAccessible::Name:
1089 value = model->data(m_index, Qt::AccessibleTextRole).toString();
1090 if (value.isEmpty())
1091 value = model->data(m_index, Qt::DisplayRole).toString();
1092 break;
1093 case QAccessible::Description:
1094 value = model->data(m_index, Qt::AccessibleDescriptionRole).toString();
1095 break;
1096 default:
1097 break;
1098 }
1099 return value;
1100}
1101
1102void QAccessibleTableCell::setText(QAccessible::Text /*t*/, const QString &text)
1103{
1104 if (!isValid() || !(m_index.flags() & Qt::ItemIsEditable))
1105 return;
1106 view->model()->setData(m_index, text);
1107}
1108
1109bool QAccessibleTableCell::isValid() const
1110{
1111 return view && !qt_widget_private(view)->data.in_destructor
1112 && view->model() && m_index.isValid();
1113}
1114
1115QAccessibleInterface *QAccessibleTableCell::parent() const
1116{
1117 return QAccessible::queryAccessibleInterface(view);
1118}
1119
1120QAccessibleInterface *QAccessibleTableCell::child(int) const
1121{
1122 return nullptr;
1123}
1124
1125QAccessibleTableHeaderCell::QAccessibleTableHeaderCell(QAbstractItemView *view_, int index_, Qt::Orientation orientation_)
1126 : view(view_), index(index_), orientation(orientation_)
1127{
1128 Q_ASSERT(index_ >= 0);
1129}
1130
1131QAccessible::Role QAccessibleTableHeaderCell::role() const
1132{
1133 if (orientation == Qt::Horizontal)
1134 return QAccessible::ColumnHeader;
1135 return QAccessible::RowHeader;
1136}
1137
1138QAccessible::State QAccessibleTableHeaderCell::state() const
1139{
1140 QAccessible::State s;
1141 if (QHeaderView *h = headerView()) {
1142 s.invisible = !h->testAttribute(Qt::WA_WState_Visible);
1143 s.disabled = !h->isEnabled();
1144 }
1145 return s;
1146}
1147
1148QRect QAccessibleTableHeaderCell::rect() const
1149{
1150 QHeaderView *header = nullptr;
1151 if (false) {
1152#if QT_CONFIG(tableview)
1153 } else if (const QTableView *tv = qobject_cast<const QTableView*>(view)) {
1154 if (orientation == Qt::Horizontal) {
1155 header = tv->horizontalHeader();
1156 } else {
1157 header = tv->verticalHeader();
1158 }
1159#endif
1160#if QT_CONFIG(treeview)
1161 } else if (const QTreeView *tv = qobject_cast<const QTreeView*>(view)) {
1162 header = tv->header();
1163#endif
1164 }
1165 if (!header)
1166 return QRect();
1167 QPoint zero = header->mapToGlobal(QPoint(0, 0));
1168 int sectionSize = header->sectionSize(index);
1169 int sectionPos = header->sectionPosition(index);
1170 return orientation == Qt::Horizontal
1171 ? QRect(zero.x() + sectionPos, zero.y(), sectionSize, header->height())
1172 : QRect(zero.x(), zero.y() + sectionPos, header->width(), sectionSize);
1173}
1174
1175QString QAccessibleTableHeaderCell::text(QAccessible::Text t) const
1176{
1177 QAbstractItemModel *model = view->model();
1178 QString value;
1179 switch (t) {
1180 case QAccessible::Name:
1181 value = model->headerData(index, orientation, Qt::AccessibleTextRole).toString();
1182 if (value.isEmpty())
1183 value = model->headerData(index, orientation, Qt::DisplayRole).toString();
1184 break;
1185 case QAccessible::Description:
1186 value = model->headerData(index, orientation, Qt::AccessibleDescriptionRole).toString();
1187 break;
1188 default:
1189 break;
1190 }
1191 return value;
1192}
1193
1194void QAccessibleTableHeaderCell::setText(QAccessible::Text, const QString &)
1195{
1196 return;
1197}
1198
1199bool QAccessibleTableHeaderCell::isValid() const
1200{
1201 return view && !qt_widget_private(view)->data.in_destructor
1202 && view->model() && (index >= 0)
1203 && ((orientation == Qt::Horizontal) ? (index < view->model()->columnCount()) : (index < view->model()->rowCount()));
1204}
1205
1206QAccessibleInterface *QAccessibleTableHeaderCell::parent() const
1207{
1208 return QAccessible::queryAccessibleInterface(view);
1209}
1210
1211QAccessibleInterface *QAccessibleTableHeaderCell::child(int) const
1212{
1213 return nullptr;
1214}
1215
1216QHeaderView *QAccessibleTableHeaderCell::headerView() const
1217{
1218 QHeaderView *header = nullptr;
1219 if (false) {
1220#if QT_CONFIG(tableview)
1221 } else if (const QTableView *tv = qobject_cast<const QTableView*>(view)) {
1222 if (orientation == Qt::Horizontal) {
1223 header = tv->horizontalHeader();
1224 } else {
1225 header = tv->verticalHeader();
1226 }
1227#endif
1228#if QT_CONFIG(treeview)
1229 } else if (const QTreeView *tv = qobject_cast<const QTreeView*>(view)) {
1230 header = tv->header();
1231#endif
1232 }
1233 return header;
1234}
1235
1236QT_END_NAMESPACE
1237
1238#endif // QT_NO_ACCESSIBILITY
1239