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