From eaee2bd0d048367f6bd331fdb4a177004848d77e Mon Sep 17 00:00:00 2001 From: Frederik Gladhorn Date: Wed, 5 Feb 2014 11:44:42 +0100 Subject: Accessibility: Use factory function instead of plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This simplifies deployment and makes instantiating accessible interfaces faster since no plugin needs to be loaded. [ChangeLog][QtWidgets] Accessibility for widgets is now included in the widget library instead of being a separate plugin. For static builds this means that libqtaccessiblewidgets is no longer required. Task-number: QTBUG-32888 Change-Id: Ie7347898868fd67c1fc568f0d251cbd2ef71e041 Reviewed-by: Jan Arve Sæther --- src/widgets/accessible/itemviews.cpp | 1222 ++++++++++++++++++++++++++++++++++ 1 file changed, 1222 insertions(+) create mode 100644 src/widgets/accessible/itemviews.cpp (limited to 'src/widgets/accessible/itemviews.cpp') diff --git a/src/widgets/accessible/itemviews.cpp b/src/widgets/accessible/itemviews.cpp new file mode 100644 index 0000000000..81736fc1c6 --- /dev/null +++ b/src/widgets/accessible/itemviews.cpp @@ -0,0 +1,1222 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "itemviews.h" + +#include +#include +#include +#include +#include + +#ifndef QT_NO_ACCESSIBILITY + +QT_BEGIN_NAMESPACE + +QString qt_accStripAmp(const QString &text); + +#ifndef QT_NO_ITEMVIEWS +/* +Implementation of the IAccessible2 table2 interface. Much simpler than +the other table interfaces since there is only the main table and cells: + +TABLE/LIST/TREE + |- HEADER CELL + |- CELL + |- CELL + ... +*/ + + +QAbstractItemView *QAccessibleTable::view() const +{ + return qobject_cast(object()); +} + +int QAccessibleTable::logicalIndex(const QModelIndex &index) const +{ + if (!view()->model() || !index.isValid()) + return -1; + int vHeader = verticalHeader() ? 1 : 0; + int hHeader = horizontalHeader() ? 1 : 0; + return (index.row() + hHeader)*(index.model()->columnCount() + vHeader) + (index.column() + vHeader); +} + +QAccessibleTable::QAccessibleTable(QWidget *w) + : QAccessibleObject(w) +{ + Q_ASSERT(view()); + + if (qobject_cast(view())) { + m_role = QAccessible::Table; + } else if (qobject_cast(view())) { + m_role = QAccessible::Tree; + } else if (qobject_cast(view())) { + m_role = QAccessible::List; + } else { + // is this our best guess? + m_role = QAccessible::Table; + } +} + +bool QAccessibleTable::isValid() const +{ + return (view() && !qobject_cast(view())->d_func()->data.in_destructor); +} + +QAccessibleTable::~QAccessibleTable() +{ + Q_FOREACH (QAccessible::Id id, childToId.values()) + QAccessible::deleteAccessibleInterface(id); +} + +QHeaderView *QAccessibleTable::horizontalHeader() const +{ + QHeaderView *header = 0; + if (false) { +#ifndef QT_NO_TABLEVIEW + } else if (const QTableView *tv = qobject_cast(view())) { + header = tv->horizontalHeader(); +#endif +#ifndef QT_NO_TREEVIEW + } else if (const QTreeView *tv = qobject_cast(view())) { + header = tv->header(); +#endif + } + return header; +} + +QHeaderView *QAccessibleTable::verticalHeader() const +{ + QHeaderView *header = 0; + if (false) { +#ifndef QT_NO_TABLEVIEW + } else if (const QTableView *tv = qobject_cast(view())) { + header = tv->verticalHeader(); +#endif + } + return header; +} + +QAccessibleInterface *QAccessibleTable::cellAt(int row, int column) const +{ + if (!view()->model()) + return 0; + Q_ASSERT(role() != QAccessible::Tree); + QModelIndex index = view()->model()->index(row, column, view()->rootIndex()); + if (!index.isValid()) { + qWarning() << "QAccessibleTable::cellAt: invalid index: " << index << " for " << view(); + return 0; + } + return child(logicalIndex(index)); +} + +QAccessibleInterface *QAccessibleTable::caption() const +{ + return 0; +} + +QString QAccessibleTable::columnDescription(int column) const +{ + if (!view()->model()) + return QString(); + return view()->model()->headerData(column, Qt::Horizontal).toString(); +} + +int QAccessibleTable::columnCount() const +{ + if (!view()->model()) + return 0; + return view()->model()->columnCount(); +} + +int QAccessibleTable::rowCount() const +{ + if (!view()->model()) + return 0; + return view()->model()->rowCount(); +} + +int QAccessibleTable::selectedCellCount() const +{ + if (!view()->selectionModel()) + return 0; + return view()->selectionModel()->selectedIndexes().count(); +} + +int QAccessibleTable::selectedColumnCount() const +{ + if (!view()->selectionModel()) + return 0; + return view()->selectionModel()->selectedColumns().count(); +} + +int QAccessibleTable::selectedRowCount() const +{ + if (!view()->selectionModel()) + return 0; + return view()->selectionModel()->selectedRows().count(); +} + +QString QAccessibleTable::rowDescription(int row) const +{ + if (!view()->model()) + return QString(); + return view()->model()->headerData(row, Qt::Vertical).toString(); +} + +QList QAccessibleTable::selectedCells() const +{ + QList cells; + if (!view()->selectionModel()) + return cells; + Q_FOREACH (const QModelIndex &index, view()->selectionModel()->selectedIndexes()) + cells.append(child(logicalIndex(index))); + return cells; +} + +QList QAccessibleTable::selectedColumns() const +{ + if (!view()->selectionModel()) + return QList(); + QList columns; + Q_FOREACH (const QModelIndex &index, view()->selectionModel()->selectedColumns()) { + columns.append(index.column()); + } + return columns; +} + +QList QAccessibleTable::selectedRows() const +{ + if (!view()->selectionModel()) + return QList(); + QList rows; + Q_FOREACH (const QModelIndex &index, view()->selectionModel()->selectedRows()) { + rows.append(index.row()); + } + return rows; +} + +QAccessibleInterface *QAccessibleTable::summary() const +{ + return 0; +} + +bool QAccessibleTable::isColumnSelected(int column) const +{ + if (!view()->selectionModel()) + return false; + return view()->selectionModel()->isColumnSelected(column, QModelIndex()); +} + +bool QAccessibleTable::isRowSelected(int row) const +{ + if (!view()->selectionModel()) + return false; + return view()->selectionModel()->isRowSelected(row, QModelIndex()); +} + +bool QAccessibleTable::selectRow(int row) +{ + if (!view()->model() || !view()->selectionModel()) + return false; + QModelIndex index = view()->model()->index(row, 0, view()->rootIndex()); + + if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectColumns) + return false; + + switch (view()->selectionMode()) { + case QAbstractItemView::NoSelection: + return false; + case QAbstractItemView::SingleSelection: + if (view()->selectionBehavior() != QAbstractItemView::SelectRows && columnCount() > 1 ) + return false; + view()->clearSelection(); + break; + case QAbstractItemView::ContiguousSelection: + if ((!row || !view()->selectionModel()->isRowSelected(row - 1, view()->rootIndex())) + && !view()->selectionModel()->isRowSelected(row + 1, view()->rootIndex())) + view()->clearSelection(); + break; + default: + break; + } + + view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); + return true; +} + +bool QAccessibleTable::selectColumn(int column) +{ + if (!view()->model() || !view()->selectionModel()) + return false; + QModelIndex index = view()->model()->index(0, column, view()->rootIndex()); + + if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectRows) + return false; + + switch (view()->selectionMode()) { + case QAbstractItemView::NoSelection: + return false; + case QAbstractItemView::SingleSelection: + if (view()->selectionBehavior() != QAbstractItemView::SelectColumns && rowCount() > 1) + return false; + case QAbstractItemView::ContiguousSelection: + if ((!column || !view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex())) + && !view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) + view()->clearSelection(); + break; + default: + break; + } + + view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Columns); + return true; +} + +bool QAccessibleTable::unselectRow(int row) +{ + if (!view()->model() || !view()->selectionModel()) + return false; + + QModelIndex index = view()->model()->index(row, 0, view()->rootIndex()); + if (!index.isValid()) + return false; + + QItemSelection selection(index, index); + + switch (view()->selectionMode()) { + case QAbstractItemView::SingleSelection: + //In SingleSelection and ContiguousSelection once an item + //is selected, there's no way for the user to unselect all items + if (selectedRowCount() == 1) + return false; + break; + case QAbstractItemView::ContiguousSelection: + if (selectedRowCount() == 1) + return false; + + if ((!row || view()->selectionModel()->isRowSelected(row - 1, view()->rootIndex())) + && view()->selectionModel()->isRowSelected(row + 1, view()->rootIndex())) { + //If there are rows selected both up the current row and down the current rown, + //the ones which are down the current row will be deselected + selection = QItemSelection(index, view()->model()->index(rowCount() - 1, 0, view()->rootIndex())); + } + default: + break; + } + + view()->selectionModel()->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Rows); + return true; +} + +bool QAccessibleTable::unselectColumn(int column) +{ + if (!view()->model() || !view()->selectionModel()) + return false; + + QModelIndex index = view()->model()->index(0, column, view()->rootIndex()); + if (!index.isValid()) + return false; + + QItemSelection selection(index, index); + + switch (view()->selectionMode()) { + case QAbstractItemView::SingleSelection: + //In SingleSelection and ContiguousSelection once an item + //is selected, there's no way for the user to unselect all items + if (selectedColumnCount() == 1) + return false; + break; + case QAbstractItemView::ContiguousSelection: + if (selectedColumnCount() == 1) + return false; + + if ((!column || view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex())) + && view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) { + //If there are columns selected both at the left of the current row and at the right + //of the current rown, the ones which are at the right will be deselected + selection = QItemSelection(index, view()->model()->index(0, columnCount() - 1, view()->rootIndex())); + } + default: + break; + } + + view()->selectionModel()->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Columns); + return true; +} + +QAccessible::Role QAccessibleTable::role() const +{ + return m_role; +} + +QAccessible::State QAccessibleTable::state() const +{ + return QAccessible::State(); +} + +QAccessibleInterface *QAccessibleTable::childAt(int x, int y) const +{ + QPoint viewportOffset = view()->viewport()->mapTo(view(), QPoint(0,0)); + QPoint indexPosition = view()->mapFromGlobal(QPoint(x, y) - viewportOffset); + // FIXME: if indexPosition < 0 in one coordinate, return header + + QModelIndex index = view()->indexAt(indexPosition); + if (index.isValid()) { + return child(logicalIndex(index)); + } + return 0; +} + +int QAccessibleTable::childCount() const +{ + if (!view()->model()) + return 0; + int vHeader = verticalHeader() ? 1 : 0; + int hHeader = horizontalHeader() ? 1 : 0; + return (view()->model()->rowCount()+hHeader) * (view()->model()->columnCount()+vHeader); +} + +int QAccessibleTable::indexOfChild(const QAccessibleInterface *iface) const +{ + if (!view()->model()) + return -1; + QAccessibleInterface *parent = iface->parent(); + if (parent->object() != view()) + return -1; + + Q_ASSERT(iface->role() != QAccessible::TreeItem); // should be handled by tree class + if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) { + const QAccessibleTableCell* cell = static_cast(iface); + return logicalIndex(cell->m_index); + } else if (iface->role() == QAccessible::ColumnHeader){ + const QAccessibleTableHeaderCell* cell = static_cast(iface); + return cell->index + (verticalHeader() ? 1 : 0); + } else if (iface->role() == QAccessible::RowHeader){ + const QAccessibleTableHeaderCell* cell = static_cast(iface); + return (cell->index + 1) * (view()->model()->columnCount() + 1); + } else if (iface->role() == QAccessible::Pane) { + return 0; // corner button + } else { + qWarning() << "WARNING QAccessibleTable::indexOfChild Fix my children..." + << iface->role() << iface->text(QAccessible::Name); + } + // FIXME: we are in denial of our children. this should stop. + return -1; +} + +QString QAccessibleTable::text(QAccessible::Text t) const +{ + if (t == QAccessible::Description) + return view()->accessibleDescription(); + return view()->accessibleName(); +} + +QRect QAccessibleTable::rect() const +{ + if (!view()->isVisible()) + return QRect(); + QPoint pos = view()->mapToGlobal(QPoint(0, 0)); + return QRect(pos.x(), pos.y(), view()->width(), view()->height()); +} + +QAccessibleInterface *QAccessibleTable::parent() const +{ + if (view() && view()->parent()) { + if (qstrcmp("QComboBoxPrivateContainer", view()->parent()->metaObject()->className()) == 0) { + return QAccessible::queryAccessibleInterface(view()->parent()->parent()); + } + return QAccessible::queryAccessibleInterface(view()->parent()); + } + return 0; +} + +QAccessibleInterface *QAccessibleTable::child(int logicalIndex) const +{ + if (!view()->model()) + return 0; + + if (childToId.contains(logicalIndex)) { + QAccessible::Id id = childToId.value(logicalIndex); + return QAccessible::accessibleInterface(id); + } + + int vHeader = verticalHeader() ? 1 : 0; + int hHeader = horizontalHeader() ? 1 : 0; + + int columns = view()->model()->columnCount() + vHeader; + + int row = logicalIndex / columns; + int column = logicalIndex % columns; + + QAccessibleInterface *iface = 0; + + if (vHeader) { + if (column == 0) { + if (hHeader && row == 0) { + iface = new QAccessibleTableCornerButton(view()); + } else { + iface = new QAccessibleTableHeaderCell(view(), row - hHeader, Qt::Vertical); + } + } + --column; + } + if (!iface && hHeader) { + if (row == 0) { + iface = new QAccessibleTableHeaderCell(view(), column, Qt::Horizontal); + } + --row; + } + + if (!iface) { + QModelIndex index = view()->model()->index(row, column, view()->rootIndex()); + if (!index.isValid()) { + qWarning() << "QAccessibleTable::child: Invalid index at: " << row << column; + return 0; + } + iface = new QAccessibleTableCell(view(), index, cellRole()); + } + + QAccessible::registerAccessibleInterface(iface); + childToId.insert(logicalIndex, QAccessible::uniqueId(iface)); + return iface; +} + +void *QAccessibleTable::interface_cast(QAccessible::InterfaceType t) +{ + if (t == QAccessible::TableInterface) + return static_cast(this); + return 0; +} + +void QAccessibleTable::modelChange(QAccessibleTableModelChangeEvent *event) +{ + // if there is no cache yet, we don't update anything + if (childToId.isEmpty()) + return; + + switch (event->modelChangeType()) { + case QAccessibleTableModelChangeEvent::ModelReset: + Q_FOREACH (QAccessible::Id id, childToId.values()) + QAccessible::deleteAccessibleInterface(id); + childToId.clear(); + break; + + // rows are inserted: move every row after that + case QAccessibleTableModelChangeEvent::RowsInserted: + case QAccessibleTableModelChangeEvent::ColumnsInserted: { + int newRows = event->lastRow() - event->firstRow() + 1; + int newColumns = event->lastColumn() - event->firstColumn() + 1; + + ChildCache newCache; + ChildCache::ConstIterator iter = childToId.constBegin(); + + while (iter != childToId.constEnd()) { + QAccessible::Id id = iter.value(); + QAccessibleInterface *iface = QAccessible::accessibleInterface(id); + Q_ASSERT(iface); + if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) { + Q_ASSERT(iface->tableCellInterface()); + QAccessibleTableCell *cell = static_cast(iface->tableCellInterface()); + if (event->modelChangeType() == QAccessibleTableModelChangeEvent::RowsInserted + && cell->m_index.row() >= event->firstRow()) { + int newRow = cell->m_index.row() + newRows; + cell->m_index = cell->m_index.sibling(newRow, cell->m_index.column()); + } else if (event->modelChangeType() == QAccessibleTableModelChangeEvent::ColumnsInserted + && cell->m_index.column() >= event->firstColumn()) { + int newColumn = cell->m_index.column() + newColumns; + cell->m_index = cell->m_index.sibling(cell->m_index.row(), newColumn); + } + } else if (event->modelChangeType() == QAccessibleTableModelChangeEvent::RowsInserted + && iface->role() == QAccessible::RowHeader) { + QAccessibleTableHeaderCell *cell = static_cast(iface); + if (cell->index >= event->firstRow()) { + cell->index += newRows; + } + } else if (event->modelChangeType() == QAccessibleTableModelChangeEvent::ColumnsInserted + && iface->role() == QAccessible::ColumnHeader) { + QAccessibleTableHeaderCell *cell = static_cast(iface); + if (cell->index >= event->firstColumn()) { + cell->index += newColumns; + } + } + if (indexOfChild(iface) >= 0) { + newCache.insert(indexOfChild(iface), id); + } else { + // ### This should really not happen, + // but it might if the view has a root index set. + // This needs to be fixed. + QAccessible::deleteAccessibleInterface(id); + } + ++iter; + } + childToId = newCache; + break; + } + + case QAccessibleTableModelChangeEvent::ColumnsRemoved: + case QAccessibleTableModelChangeEvent::RowsRemoved: { + int deletedColumns = event->lastColumn() - event->firstColumn() + 1; + int deletedRows = event->lastRow() - event->firstRow() + 1; + ChildCache newCache; + ChildCache::ConstIterator iter = childToId.constBegin(); + while (iter != childToId.constEnd()) { + QAccessible::Id id = iter.value(); + QAccessibleInterface *iface = QAccessible::accessibleInterface(id); + Q_ASSERT(iface); + if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) { + Q_ASSERT(iface->tableCellInterface()); + QAccessibleTableCell *cell = static_cast(iface->tableCellInterface()); + if (event->modelChangeType() == QAccessibleTableModelChangeEvent::RowsRemoved) { + if (cell->m_index.row() < event->firstRow()) { + newCache.insert(indexOfChild(cell), id); + } else if (cell->m_index.row() > event->lastRow()) { + int newRow = cell->m_index.row() - deletedRows; + cell->m_index = cell->m_index.sibling(newRow, cell->m_index.column()); + newCache.insert(indexOfChild(cell), id); + } else { + QAccessible::deleteAccessibleInterface(id); + } + } else if (event->modelChangeType() == QAccessibleTableModelChangeEvent::ColumnsRemoved) { + if (cell->m_index.column() < event->firstColumn()) { + newCache.insert(indexOfChild(cell), id); + } else if (cell->m_index.column() > event->lastColumn()) { + int newColumn = cell->m_index.column() - deletedColumns; + cell->m_index = cell->m_index.sibling(cell->m_index.row(), newColumn); + newCache.insert(indexOfChild(cell), id); + } else { + QAccessible::deleteAccessibleInterface(id); + } + } + } else if (event->modelChangeType() == QAccessibleTableModelChangeEvent::RowsRemoved + && iface->role() == QAccessible::RowHeader) { + QAccessibleTableHeaderCell *cell = static_cast(iface); + if (cell->index < event->firstRow()) { + newCache.insert(indexOfChild(cell), id); + } else if (cell->index > event->lastRow()) { + cell->index -= deletedRows; + newCache.insert(indexOfChild(cell), id); + } else { + QAccessible::deleteAccessibleInterface(id); + } + } else if (event->modelChangeType() == QAccessibleTableModelChangeEvent::ColumnsRemoved + && iface->role() == QAccessible::ColumnHeader) { + QAccessibleTableHeaderCell *cell = static_cast(iface); + if (cell->index < event->firstColumn()) { + newCache.insert(indexOfChild(cell), id); + } else if (cell->index > event->lastColumn()) { + cell->index -= deletedColumns; + newCache.insert(indexOfChild(cell), id); + } else { + QAccessible::deleteAccessibleInterface(id); + } + } + ++iter; + } + childToId = newCache; + break; + } + + case QAccessibleTableModelChangeEvent::DataChanged: + // nothing to do in this case + break; + } +} + +// TREE VIEW + +QModelIndex QAccessibleTree::indexFromLogical(int row, int column) const +{ + if (!isValid() || !view()->model()) + return QModelIndex(); + + const QTreeView *treeView = qobject_cast(view()); + if ((row < 0) || (column < 0) || (treeView->d_func()->viewItems.count() <= row)) { + qWarning() << "QAccessibleTree::indexFromLogical: invalid index: " << row << column << " for " << treeView; + return QModelIndex(); + } + QModelIndex modelIndex = treeView->d_func()->viewItems.at(row).index; + + if (modelIndex.isValid() && column > 0) { + modelIndex = view()->model()->index(modelIndex.row(), column, modelIndex.parent()); + } + return modelIndex; +} + +QAccessibleInterface *QAccessibleTree::childAt(int x, int y) const +{ + if (!view()->model()) + return 0; + QPoint viewportOffset = view()->viewport()->mapTo(view(), QPoint(0,0)); + QPoint indexPosition = view()->mapFromGlobal(QPoint(x, y) - viewportOffset); + + QModelIndex index = view()->indexAt(indexPosition); + if (!index.isValid()) + return 0; + + const QTreeView *treeView = qobject_cast(view()); + int row = treeView->d_func()->viewIndex(index) + (horizontalHeader() ? 1 : 0); + int column = index.column(); + + int i = row * view()->model()->columnCount() + column; + return child(i); +} + +int QAccessibleTree::childCount() const +{ + const QTreeView *treeView = qobject_cast(view()); + Q_ASSERT(treeView); + if (!view()->model()) + return 0; + + int hHeader = horizontalHeader() ? 1 : 0; + return (treeView->d_func()->viewItems.count() + hHeader)* view()->model()->columnCount(); +} + + +QAccessibleInterface *QAccessibleTree::child(int logicalIndex) const +{ + if (logicalIndex < 0 || !view()->model() || !view()->model()->columnCount()) + return 0; + + QAccessibleInterface *iface = 0; + int index = logicalIndex; + + if (horizontalHeader()) { + if (index < view()->model()->columnCount()) { + iface = new QAccessibleTableHeaderCell(view(), index, Qt::Horizontal); + } else { + index -= view()->model()->columnCount(); + } + } + + if (!iface) { + int row = index / view()->model()->columnCount(); + int column = index % view()->model()->columnCount(); + QModelIndex modelIndex = indexFromLogical(row, column); + if (!modelIndex.isValid()) + return 0; + iface = new QAccessibleTableCell(view(), modelIndex, cellRole()); + } + QAccessible::registerAccessibleInterface(iface); + // ### FIXME: get interfaces from the cache instead of re-creating them + return iface; +} + +int QAccessibleTree::rowCount() const +{ + const QTreeView *treeView = qobject_cast(view()); + Q_ASSERT(treeView); + return treeView->d_func()->viewItems.count(); +} + +int QAccessibleTree::indexOfChild(const QAccessibleInterface *iface) const +{ + if (!view()->model()) + return -1; + QAccessibleInterface *parent = iface->parent(); + if (parent->object() != view()) + return -1; + + if (iface->role() == QAccessible::TreeItem) { + const QAccessibleTableCell* cell = static_cast(iface); + const QTreeView *treeView = qobject_cast(view()); + Q_ASSERT(treeView); + int row = treeView->d_func()->viewIndex(cell->m_index) + (horizontalHeader() ? 1 : 0); + int column = cell->m_index.column(); + + int index = row * view()->model()->columnCount() + column; + return index; + } else if (iface->role() == QAccessible::ColumnHeader){ + const QAccessibleTableHeaderCell* cell = static_cast(iface); + return cell->index; + } else { + qWarning() << "WARNING QAccessibleTable::indexOfChild invalid child" + << iface->role() << iface->text(QAccessible::Name); + } + // FIXME: add scrollbars and don't just ignore them + return -1; +} + +QAccessibleInterface *QAccessibleTree::cellAt(int row, int column) const +{ + QModelIndex index = indexFromLogical(row, column); + if (!index.isValid()) { + qWarning() << "Requested invalid tree cell: " << row << column; + return 0; + } + const QTreeView *treeView = qobject_cast(view()); + Q_ASSERT(treeView); + int logicalIndex = treeView->d_func()->accessibleTable2Index(index); + + return child(logicalIndex); // FIXME ### new QAccessibleTableCell(view(), index, cellRole()); +} + +QString QAccessibleTree::rowDescription(int) const +{ + return QString(); // no headers for rows in trees +} + +bool QAccessibleTree::isRowSelected(int row) const +{ + if (!view()->selectionModel()) + return false; + QModelIndex index = indexFromLogical(row); + return view()->selectionModel()->isRowSelected(index.row(), index.parent()); +} + +bool QAccessibleTree::selectRow(int row) +{ + if (!view()->selectionModel()) + return false; + QModelIndex index = indexFromLogical(row); + + if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectColumns) + return false; + + switch (view()->selectionMode()) { + case QAbstractItemView::NoSelection: + return false; + case QAbstractItemView::SingleSelection: + if ((view()->selectionBehavior() != QAbstractItemView::SelectRows) && (columnCount() > 1)) + return false; + view()->clearSelection(); + break; + case QAbstractItemView::ContiguousSelection: + if ((!row || !view()->selectionModel()->isRowSelected(row - 1, view()->rootIndex())) + && !view()->selectionModel()->isRowSelected(row + 1, view()->rootIndex())) + view()->clearSelection(); + break; + default: + break; + } + + view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); + return true; +} + +// TABLE CELL + +QAccessibleTableCell::QAccessibleTableCell(QAbstractItemView *view_, const QModelIndex &index_, QAccessible::Role role_) + : /* QAccessibleSimpleEditableTextInterface(this), */ view(view_), m_index(index_), m_role(role_) +{ + if (!index_.isValid()) + qWarning() << "QAccessibleTableCell::QAccessibleTableCell with invalid index: " << index_; +} + +void *QAccessibleTableCell::interface_cast(QAccessible::InterfaceType t) +{ + if (t == QAccessible::TableCellInterface) + return static_cast(this); + if (t == QAccessible::ActionInterface) + return static_cast(this); + return 0; +} + +int QAccessibleTableCell::columnExtent() const { return 1; } +int QAccessibleTableCell::rowExtent() const { return 1; } + +QList QAccessibleTableCell::rowHeaderCells() const +{ + QList headerCell; + if (verticalHeader()) { + // FIXME + headerCell.append(new QAccessibleTableHeaderCell(view, m_index.row(), Qt::Vertical)); + } + return headerCell; +} + +QList QAccessibleTableCell::columnHeaderCells() const +{ + QList headerCell; + if (horizontalHeader()) { + // FIXME + headerCell.append(new QAccessibleTableHeaderCell(view, m_index.column(), Qt::Horizontal)); + } + return headerCell; +} + +QHeaderView *QAccessibleTableCell::horizontalHeader() const +{ + QHeaderView *header = 0; + + if (false) { +#ifndef QT_NO_TABLEVIEW + } else if (const QTableView *tv = qobject_cast(view)) { + header = tv->horizontalHeader(); +#endif +#ifndef QT_NO_TREEVIEW + } else if (const QTreeView *tv = qobject_cast(view)) { + header = tv->header(); +#endif + } + + return header; +} + +QHeaderView *QAccessibleTableCell::verticalHeader() const +{ + QHeaderView *header = 0; +#ifndef QT_NO_TABLEVIEW + if (const QTableView *tv = qobject_cast(view)) + header = tv->verticalHeader(); +#endif + return header; +} + +int QAccessibleTableCell::columnIndex() const +{ + return m_index.column(); +} + +int QAccessibleTableCell::rowIndex() const +{ + if (role() == QAccessible::TreeItem) { + const QTreeView *treeView = qobject_cast(view); + Q_ASSERT(treeView); + int row = treeView->d_func()->viewIndex(m_index); + return row; + } + return m_index.row(); +} + +bool QAccessibleTableCell::isSelected() const +{ + return view->selectionModel()->isSelected(m_index); +} + +QStringList QAccessibleTableCell::actionNames() const +{ + QStringList names; + names << toggleAction(); + return names; +} + +void QAccessibleTableCell::doAction(const QString& actionName) +{ + if (actionName == toggleAction()) { + if (isSelected()) + unselectCell(); + else + selectCell(); + } +} + +QStringList QAccessibleTableCell::keyBindingsForAction(const QString &) const +{ + return QStringList(); +} + + +void QAccessibleTableCell::selectCell() +{ + QAbstractItemView::SelectionMode selectionMode = view->selectionMode(); + if (!m_index.isValid() || (selectionMode == QAbstractItemView::NoSelection)) + return; + Q_ASSERT(table()); + QAccessibleTableInterface *cellTable = table()->tableInterface(); + + switch (view->selectionBehavior()) { + case QAbstractItemView::SelectItems: + break; + case QAbstractItemView::SelectColumns: + if (cellTable) + cellTable->selectColumn(m_index.column()); + return; + case QAbstractItemView::SelectRows: + if (cellTable) + cellTable->selectRow(m_index.row()); + return; + } + + if (selectionMode == QAbstractItemView::SingleSelection) { + view->clearSelection(); + } + + view->selectionModel()->select(m_index, QItemSelectionModel::Select); +} + +void QAccessibleTableCell::unselectCell() +{ + + QAbstractItemView::SelectionMode selectionMode = view->selectionMode(); + if (!m_index.isValid() || (selectionMode & QAbstractItemView::NoSelection)) + return; + + QAccessibleTableInterface *cellTable = table()->tableInterface(); + + switch (view->selectionBehavior()) { + case QAbstractItemView::SelectItems: + break; + case QAbstractItemView::SelectColumns: + if (cellTable) + cellTable->unselectColumn(m_index.column()); + return; + case QAbstractItemView::SelectRows: + if (cellTable) + cellTable->unselectRow(m_index.row()); + return; + } + + //If the mode is not MultiSelection or ExtendedSelection and only + //one cell is selected it cannot be unselected by the user + if ((selectionMode != QAbstractItemView::MultiSelection) + && (selectionMode != QAbstractItemView::ExtendedSelection) + && (view->selectionModel()->selectedIndexes().count() <= 1)) + return; + + view->selectionModel()->select(m_index, QItemSelectionModel::Deselect); +} + +QAccessibleInterface *QAccessibleTableCell::table() const +{ + return QAccessible::queryAccessibleInterface(view); +} + +QAccessible::Role QAccessibleTableCell::role() const +{ + return m_role; +} + +QAccessible::State QAccessibleTableCell::state() const +{ + QAccessible::State st; + QRect globalRect = view->rect(); + globalRect.translate(view->mapToGlobal(QPoint(0,0))); + if (!globalRect.intersects(rect())) + st.invisible = true; + + if (view->selectionModel()->isSelected(m_index)) + st.selected = true; + if (view->selectionModel()->currentIndex() == m_index) + st.focused = true; + if (m_index.model()->data(m_index, Qt::CheckStateRole).toInt() == Qt::Checked) + st.checked = true; + + Qt::ItemFlags flags = m_index.flags(); + if (flags & Qt::ItemIsSelectable) { + st.selectable = true; + st.focusable = true; + if (view->selectionMode() == QAbstractItemView::MultiSelection) + st.multiSelectable = true; + if (view->selectionMode() == QAbstractItemView::ExtendedSelection) + st.extSelectable = true; + } + if (m_role == QAccessible::TreeItem) { + const QTreeView *treeView = qobject_cast(view); + if (treeView->model()->hasChildren(m_index)) + st.expandable = true; + if (treeView->isExpanded(m_index)) + st.expanded = true; + } + return st; +} + + +QRect QAccessibleTableCell::rect() const +{ + QRect r; + r = view->visualRect(m_index); + + if (!r.isNull()) { + r.translate(view->viewport()->mapTo(view, QPoint(0,0))); + r.translate(view->mapToGlobal(QPoint(0, 0))); + } + return r; +} + +QString QAccessibleTableCell::text(QAccessible::Text t) const +{ + QAbstractItemModel *model = view->model(); + QString value; + switch (t) { + case QAccessible::Name: + value = model->data(m_index, Qt::AccessibleTextRole).toString(); + if (value.isEmpty()) + value = model->data(m_index, Qt::DisplayRole).toString(); + break; + case QAccessible::Description: + value = model->data(m_index, Qt::AccessibleDescriptionRole).toString(); + break; + default: + break; + } + return value; +} + +void QAccessibleTableCell::setText(QAccessible::Text /*t*/, const QString &text) +{ + if (!(m_index.flags() & Qt::ItemIsEditable)) + return; + view->model()->setData(m_index, text); +} + +bool QAccessibleTableCell::isValid() const +{ + return view && view->model() && m_index.isValid(); +} + +QAccessibleInterface *QAccessibleTableCell::parent() const +{ + return QAccessible::queryAccessibleInterface(view); +} + +QAccessibleInterface *QAccessibleTableCell::child(int) const +{ + return 0; +} + +QAccessibleTableHeaderCell::QAccessibleTableHeaderCell(QAbstractItemView *view_, int index_, Qt::Orientation orientation_) + : view(view_), index(index_), orientation(orientation_) +{ + Q_ASSERT(index_ >= 0); +} + +QAccessible::Role QAccessibleTableHeaderCell::role() const +{ + if (orientation == Qt::Horizontal) + return QAccessible::ColumnHeader; + return QAccessible::RowHeader; +} + +QAccessible::State QAccessibleTableHeaderCell::state() const +{ + QAccessible::State s; + if (QHeaderView *h = headerView()) { + s.invisible = !h->testAttribute(Qt::WA_WState_Visible); + s.disabled = !h->isEnabled(); + } + return s; +} + +QRect QAccessibleTableHeaderCell::rect() const +{ + QHeaderView *header = 0; + if (false) { +#ifndef QT_NO_TABLEVIEW + } else if (const QTableView *tv = qobject_cast(view)) { + if (orientation == Qt::Horizontal) { + header = tv->horizontalHeader(); + } else { + header = tv->verticalHeader(); + } +#endif +#ifndef QT_NO_TREEVIEW + } else if (const QTreeView *tv = qobject_cast(view)) { + header = tv->header(); +#endif + } + if (!header) + return QRect(); + QPoint zero = header->mapToGlobal(QPoint(0, 0)); + int sectionSize = header->sectionSize(index); + int sectionPos = header->sectionPosition(index); + return orientation == Qt::Horizontal + ? QRect(zero.x() + sectionPos, zero.y(), sectionSize, header->height()) + : QRect(zero.x(), zero.y() + sectionPos, header->width(), sectionSize); +} + +QString QAccessibleTableHeaderCell::text(QAccessible::Text t) const +{ + QAbstractItemModel *model = view->model(); + QString value; + switch (t) { + case QAccessible::Name: + value = model->headerData(index, orientation, Qt::AccessibleTextRole).toString(); + if (value.isEmpty()) + value = model->headerData(index, orientation, Qt::DisplayRole).toString(); + break; + case QAccessible::Description: + value = model->headerData(index, orientation, Qt::AccessibleDescriptionRole).toString(); + break; + default: + break; + } + return value; +} + +void QAccessibleTableHeaderCell::setText(QAccessible::Text, const QString &) +{ + return; +} + +bool QAccessibleTableHeaderCell::isValid() const +{ + return view && view->model() && (index >= 0) + && ((orientation == Qt::Horizontal) ? (index < view->model()->columnCount()) : (index < view->model()->rowCount())); +} + +QAccessibleInterface *QAccessibleTableHeaderCell::parent() const +{ + return QAccessible::queryAccessibleInterface(view); +} + +QAccessibleInterface *QAccessibleTableHeaderCell::child(int) const +{ + return 0; +} + +QHeaderView *QAccessibleTableHeaderCell::headerView() const +{ + QHeaderView *header = 0; + if (false) { +#ifndef QT_NO_TABLEVIEW + } else if (const QTableView *tv = qobject_cast(view)) { + if (orientation == Qt::Horizontal) { + header = tv->horizontalHeader(); + } else { + header = tv->verticalHeader(); + } +#endif +#ifndef QT_NO_TREEVIEW + } else if (const QTreeView *tv = qobject_cast(view)) { + header = tv->header(); +#endif + } + return header; +} + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY -- cgit v1.2.3