1221 lines
38 KiB
C++
1221 lines
38 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** 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 The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
|
** packaging of this file. Please review the following information to
|
|
** ensure the GNU Lesser General Public License version 3 requirements
|
|
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 2.0 or (at your option) the GNU General
|
|
** Public license version 3 or any later version approved by the KDE Free
|
|
** Qt Foundation. The licenses are as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
|
** https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "itemviews_p.h"
|
|
|
|
#include <qheaderview.h>
|
|
#if QT_CONFIG(tableview)
|
|
#include <qtableview.h>
|
|
#endif
|
|
#include <qlistview.h>
|
|
#if QT_CONFIG(treeview)
|
|
#include <qtreeview.h>
|
|
#include <private/qtreeview_p.h>
|
|
#endif
|
|
#include <private/qwidget_p.h>
|
|
|
|
#ifndef QT_NO_ACCESSIBILITY
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
#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<QAbstractItemView*>(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 QT_CONFIG(tableview)
|
|
if (qobject_cast<const QTableView*>(view())) {
|
|
m_role = QAccessible::Table;
|
|
} else
|
|
#endif
|
|
#if QT_CONFIG(treeview)
|
|
if (qobject_cast<const QTreeView*>(view())) {
|
|
m_role = QAccessible::Tree;
|
|
} else
|
|
#endif
|
|
#ifndef QT_NO_LISTVIEW
|
|
if (qobject_cast<const QListView*>(view())) {
|
|
m_role = QAccessible::List;
|
|
} else
|
|
#endif
|
|
{
|
|
// is this our best guess?
|
|
m_role = QAccessible::Table;
|
|
}
|
|
}
|
|
|
|
bool QAccessibleTable::isValid() const
|
|
{
|
|
return (view() && !qobject_cast<QWidget*>(view())->d_func()->data.in_destructor);
|
|
}
|
|
|
|
QAccessibleTable::~QAccessibleTable()
|
|
{
|
|
Q_FOREACH (QAccessible::Id id, childToId)
|
|
QAccessible::deleteAccessibleInterface(id);
|
|
}
|
|
|
|
QHeaderView *QAccessibleTable::horizontalHeader() const
|
|
{
|
|
QHeaderView *header = 0;
|
|
if (false) {
|
|
#if QT_CONFIG(tableview)
|
|
} else if (const QTableView *tv = qobject_cast<const QTableView*>(view())) {
|
|
header = tv->horizontalHeader();
|
|
#endif
|
|
#if QT_CONFIG(treeview)
|
|
} else if (const QTreeView *tv = qobject_cast<const QTreeView*>(view())) {
|
|
header = tv->header();
|
|
#endif
|
|
}
|
|
return header;
|
|
}
|
|
|
|
QHeaderView *QAccessibleTable::verticalHeader() const
|
|
{
|
|
QHeaderView *header = 0;
|
|
if (false) {
|
|
#if QT_CONFIG(tableview)
|
|
} else if (const QTableView *tv = qobject_cast<const QTableView*>(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 (Q_UNLIKELY(!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<QAccessibleInterface *> QAccessibleTable::selectedCells() const
|
|
{
|
|
QList<QAccessibleInterface*> cells;
|
|
if (!view()->selectionModel())
|
|
return cells;
|
|
const QModelIndexList selectedIndexes = view()->selectionModel()->selectedIndexes();
|
|
cells.reserve(selectedIndexes.size());
|
|
Q_FOREACH (const QModelIndex &index, selectedIndexes)
|
|
cells.append(child(logicalIndex(index)));
|
|
return cells;
|
|
}
|
|
|
|
QList<int> QAccessibleTable::selectedColumns() const
|
|
{
|
|
if (!view()->selectionModel())
|
|
return QList<int>();
|
|
QList<int> columns;
|
|
const QModelIndexList selectedColumns = view()->selectionModel()->selectedColumns();
|
|
columns.reserve(selectedColumns.size());
|
|
Q_FOREACH (const QModelIndex &index, selectedColumns)
|
|
columns.append(index.column());
|
|
|
|
return columns;
|
|
}
|
|
|
|
QList<int> QAccessibleTable::selectedRows() const
|
|
{
|
|
if (!view()->selectionModel())
|
|
return QList<int>();
|
|
QList<int> rows;
|
|
const QModelIndexList selectedRows = view()->selectionModel()->selectedRows();
|
|
rows.reserve(selectedRows.size());
|
|
Q_FOREACH (const QModelIndex &index, 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;
|
|
Q_FALLTHROUGH();
|
|
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<const QAccessibleTableCell*>(iface);
|
|
return logicalIndex(cell->m_index);
|
|
} else if (iface->role() == QAccessible::ColumnHeader){
|
|
const QAccessibleTableHeaderCell* cell = static_cast<const QAccessibleTableHeaderCell*>(iface);
|
|
return cell->index + (verticalHeader() ? 1 : 0);
|
|
} else if (iface->role() == QAccessible::RowHeader){
|
|
const QAccessibleTableHeaderCell* cell = static_cast<const QAccessibleTableHeaderCell*>(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;
|
|
|
|
auto id = childToId.constFind(logicalIndex);
|
|
if (id != childToId.constEnd())
|
|
return QAccessible::accessibleInterface(id.value());
|
|
|
|
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 (Q_UNLIKELY(!index.isValid())) {
|
|
qWarning("QAccessibleTable::child: Invalid index at: %d %d", 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<QAccessibleTableInterface*>(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)
|
|
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 (event->modelChangeType() == QAccessibleTableModelChangeEvent::RowsInserted
|
|
&& iface->role() == QAccessible::RowHeader) {
|
|
QAccessibleTableHeaderCell *cell = static_cast<QAccessibleTableHeaderCell*>(iface);
|
|
if (cell->index >= event->firstRow()) {
|
|
cell->index += newRows;
|
|
}
|
|
} else if (event->modelChangeType() == QAccessibleTableModelChangeEvent::ColumnsInserted
|
|
&& iface->role() == QAccessible::ColumnHeader) {
|
|
QAccessibleTableHeaderCell *cell = static_cast<QAccessibleTableHeaderCell*>(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<QAccessibleTableCell*>(iface->tableCellInterface());
|
|
// Since it is a QPersistentModelIndex, we only need to check if it is valid
|
|
if (cell->m_index.isValid())
|
|
newCache.insert(indexOfChild(cell), id);
|
|
else
|
|
QAccessible::deleteAccessibleInterface(id);
|
|
} else if (event->modelChangeType() == QAccessibleTableModelChangeEvent::RowsRemoved
|
|
&& iface->role() == QAccessible::RowHeader) {
|
|
QAccessibleTableHeaderCell *cell = static_cast<QAccessibleTableHeaderCell*>(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<QAccessibleTableHeaderCell*>(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;
|
|
}
|
|
}
|
|
|
|
#if QT_CONFIG(treeview)
|
|
|
|
// TREE VIEW
|
|
|
|
QModelIndex QAccessibleTree::indexFromLogical(int row, int column) const
|
|
{
|
|
if (!isValid() || !view()->model())
|
|
return QModelIndex();
|
|
|
|
const QTreeView *treeView = qobject_cast<const QTreeView*>(view());
|
|
if (Q_UNLIKELY(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<const QTreeView*>(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<const QTreeView*>(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<const QTreeView*>(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<const QAccessibleTableCell*>(iface);
|
|
const QTreeView *treeView = qobject_cast<const QTreeView*>(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<const QAccessibleTableHeaderCell*>(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 (Q_UNLIKELY(!index.isValid())) {
|
|
qWarning("Requested invalid tree cell: %d %d", row, column);
|
|
return 0;
|
|
}
|
|
const QTreeView *treeView = qobject_cast<const QTreeView*>(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;
|
|
}
|
|
|
|
#endif // QT_CONFIG(treeview)
|
|
|
|
// TABLE CELL
|
|
|
|
QAccessibleTableCell::QAccessibleTableCell(QAbstractItemView *view_, const QModelIndex &index_, QAccessible::Role role_)
|
|
: /* QAccessibleSimpleEditableTextInterface(this), */ view(view_), m_index(index_), m_role(role_)
|
|
{
|
|
if (Q_UNLIKELY(!index_.isValid()))
|
|
qWarning() << "QAccessibleTableCell::QAccessibleTableCell with invalid index: " << index_;
|
|
}
|
|
|
|
void *QAccessibleTableCell::interface_cast(QAccessible::InterfaceType t)
|
|
{
|
|
if (t == QAccessible::TableCellInterface)
|
|
return static_cast<QAccessibleTableCellInterface*>(this);
|
|
if (t == QAccessible::ActionInterface)
|
|
return static_cast<QAccessibleActionInterface*>(this);
|
|
return 0;
|
|
}
|
|
|
|
int QAccessibleTableCell::columnExtent() const { return 1; }
|
|
int QAccessibleTableCell::rowExtent() const { return 1; }
|
|
|
|
QList<QAccessibleInterface*> QAccessibleTableCell::rowHeaderCells() const
|
|
{
|
|
QList<QAccessibleInterface*> headerCell;
|
|
if (verticalHeader()) {
|
|
// FIXME
|
|
headerCell.append(new QAccessibleTableHeaderCell(view, m_index.row(), Qt::Vertical));
|
|
}
|
|
return headerCell;
|
|
}
|
|
|
|
QList<QAccessibleInterface*> QAccessibleTableCell::columnHeaderCells() const
|
|
{
|
|
QList<QAccessibleInterface*> 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) {
|
|
#if QT_CONFIG(tableview)
|
|
} else if (const QTableView *tv = qobject_cast<const QTableView*>(view)) {
|
|
header = tv->horizontalHeader();
|
|
#endif
|
|
#if QT_CONFIG(treeview)
|
|
} else if (const QTreeView *tv = qobject_cast<const QTreeView*>(view)) {
|
|
header = tv->header();
|
|
#endif
|
|
}
|
|
|
|
return header;
|
|
}
|
|
|
|
QHeaderView *QAccessibleTableCell::verticalHeader() const
|
|
{
|
|
QHeaderView *header = 0;
|
|
#if QT_CONFIG(tableview)
|
|
if (const QTableView *tv = qobject_cast<const QTableView*>(view))
|
|
header = tv->verticalHeader();
|
|
#endif
|
|
return header;
|
|
}
|
|
|
|
int QAccessibleTableCell::columnIndex() const
|
|
{
|
|
return m_index.column();
|
|
}
|
|
|
|
int QAccessibleTableCell::rowIndex() const
|
|
{
|
|
#if QT_CONFIG(treeview)
|
|
if (role() == QAccessible::TreeItem) {
|
|
const QTreeView *treeView = qobject_cast<const QTreeView*>(view);
|
|
Q_ASSERT(treeView);
|
|
int row = treeView->d_func()->viewIndex(m_index);
|
|
return row;
|
|
}
|
|
#endif
|
|
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;
|
|
if (!view)
|
|
return 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 QT_CONFIG(treeview)
|
|
if (m_role == QAccessible::TreeItem) {
|
|
const QTreeView *treeView = qobject_cast<const QTreeView*>(view);
|
|
if (treeView->model()->hasChildren(m_index))
|
|
st.expandable = true;
|
|
if (treeView->isExpanded(m_index))
|
|
st.expanded = true;
|
|
}
|
|
#endif
|
|
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) {
|
|
#if QT_CONFIG(tableview)
|
|
} else if (const QTableView *tv = qobject_cast<const QTableView*>(view)) {
|
|
if (orientation == Qt::Horizontal) {
|
|
header = tv->horizontalHeader();
|
|
} else {
|
|
header = tv->verticalHeader();
|
|
}
|
|
#endif
|
|
#if QT_CONFIG(treeview)
|
|
} else if (const QTreeView *tv = qobject_cast<const QTreeView*>(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) {
|
|
#if QT_CONFIG(tableview)
|
|
} else if (const QTableView *tv = qobject_cast<const QTableView*>(view)) {
|
|
if (orientation == Qt::Horizontal) {
|
|
header = tv->horizontalHeader();
|
|
} else {
|
|
header = tv->verticalHeader();
|
|
}
|
|
#endif
|
|
#if QT_CONFIG(treeview)
|
|
} else if (const QTreeView *tv = qobject_cast<const QTreeView*>(view)) {
|
|
header = tv->header();
|
|
#endif
|
|
}
|
|
return header;
|
|
}
|
|
|
|
#endif // QT_NO_ITEMVIEWS
|
|
|
|
QT_END_NAMESPACE
|
|
|
|
#endif // QT_NO_ACCESSIBILITY
|