1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
|
/*
* SPDX-FileCopyrightText: 2012 Amandeep Singh <[email protected]>
* SPDX-FileCopyrightText: 2024 Felix Ernst <[email protected]>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "kitemlistdelegateaccessible.h"
#include "kitemviews/kfileitemlistwidget.h"
#include "kitemviews/kfileitemmodel.h"
#include "kitemviews/kitemlistcontroller.h"
#include "kitemviews/kitemlistselectionmanager.h"
#include "kitemviews/kitemlistview.h"
#include "kitemviews/private/kitemlistviewlayouter.h"
#include <KLocalizedString>
#include <QGraphicsScene>
#include <QGraphicsView>
KItemListDelegateAccessible::KItemListDelegateAccessible(KItemListView *view, int index)
: m_view(view)
, m_index(index)
{
Q_ASSERT(index >= 0 && index < view->model()->count());
}
void *KItemListDelegateAccessible::interface_cast(QAccessible::InterfaceType type)
{
if (type == QAccessible::TableCellInterface) {
return static_cast<QAccessibleTableCellInterface *>(this);
}
return nullptr;
}
int KItemListDelegateAccessible::columnExtent() const
{
return 1;
}
int KItemListDelegateAccessible::rowExtent() const
{
return 1;
}
QList<QAccessibleInterface *> KItemListDelegateAccessible::rowHeaderCells() const
{
return QList<QAccessibleInterface *>();
}
QList<QAccessibleInterface *> KItemListDelegateAccessible::columnHeaderCells() const
{
return QList<QAccessibleInterface *>();
}
int KItemListDelegateAccessible::columnIndex() const
{
return m_view->m_layouter->itemColumn(m_index);
}
int KItemListDelegateAccessible::rowIndex() const
{
return m_view->m_layouter->itemRow(m_index);
}
bool KItemListDelegateAccessible::isSelected() const
{
return m_view->controller()->selectionManager()->isSelected(m_index);
}
QAccessibleInterface *KItemListDelegateAccessible::table() const
{
return QAccessible::queryAccessibleInterface(m_view);
}
QAccessible::Role KItemListDelegateAccessible::role() const
{
return QAccessible::ListItem; // We could also return "Cell" here which would then announce the exact row and column of the item. However, different from
// applications that actually have a strong cell workflow -- like LibreOfficeCalc -- we have no advantage of announcing the row or column aside from us
// generally being interested in announcing that users in Icon View mode need to use the Left and Right arrow keys to arrive at every item. There are ways
// for users to figure this out regardless by paying attention to the index that is being announced for each list item. In KitemListViewAccessible in icon
// view mode it is also mentioned that the items are positioned in a grid, so the two-dimensionality should be clear enough.
}
QAccessible::State KItemListDelegateAccessible::state() const
{
QAccessible::State state;
state.selectable = true;
if (isSelected()) {
state.selected = true;
}
state.focusable = true;
if (m_view->controller()->selectionManager()->currentItem() == m_index) {
state.focused = true;
state.active = true;
}
if (m_view->controller()->selectionBehavior() == KItemListController::MultiSelection) {
state.multiSelectable = true;
}
if (m_view->supportsItemExpanding() && m_view->model()->isExpandable(m_index)) {
state.expandable = true;
state.expanded = m_view->model()->isExpanded(m_index);
state.collapsed = !state.expanded;
}
return state;
}
bool KItemListDelegateAccessible::isExpandable() const
{
return m_view->model()->isExpandable(m_index);
}
QRect KItemListDelegateAccessible::rect() const
{
QRect rect = m_view->itemRect(m_index).toRect();
if (rect.isNull()) {
return QRect();
}
rect.translate(m_view->mapToScene(QPointF(0.0, 0.0)).toPoint());
rect.translate(m_view->scene()->views()[0]->mapToGlobal(QPoint(0, 0)));
return rect;
}
QString KItemListDelegateAccessible::text(QAccessible::Text t) const
{
const QHash<QByteArray, QVariant> data = m_view->model()->data(m_index);
switch (t) {
case QAccessible::Name: {
return data["text"].toString();
}
case QAccessible::Description: {
QString description;
if (data["isHidden"].toBool()) {
description += i18nc("@info", "hidden");
}
QString mimeType{data["type"].toString()};
if (mimeType.isEmpty()) {
const KFileItemModel *model = qobject_cast<KFileItemModel *>(m_view->model());
if (model) {
mimeType = model->fileItem(m_index).mimeComment();
}
Q_ASSERT_X(!mimeType.isEmpty(), "KItemListDelegateAccessible::text", "Unable to retrieve mime type.");
}
if (data["isLink"].toBool()) {
QString linkDestination{data["destination"].toString()};
if (linkDestination.isEmpty()) {
const KFileItemModel *model = qobject_cast<KFileItemModel *>(m_view->model());
if (model) {
linkDestination = model->fileItem(m_index).linkDest();
}
Q_ASSERT_X(!linkDestination.isEmpty(), "KItemListDelegateAccessible::text", "Unable to retrieve link destination.");
}
description += i18nc("@info enumeration saying this is a link to $1, %1 is mimeType", ", link to %1 at %2", mimeType, linkDestination);
} else {
description += i18nc("@info enumeration, %1 is mimeType", ", %1", mimeType);
}
const QList<QByteArray> additionallyShownInformation{m_view->visibleRoles()};
const KItemModelBase *model = m_view->model();
for (const auto &roleInformation : additionallyShownInformation) {
if (roleInformation == "text") {
continue;
}
KFileItemListWidgetInformant informant;
const auto roleText{informant.roleText(roleInformation, data, KFileItemListWidgetInformant::ForUsageAs::SpokenText)};
if (roleText.isEmpty()) {
continue; // No need to announce roles which are empty for this item.
}
description +=
// i18n: The text starts with a comma because multiple occurences of this text can follow after each others as an enumeration.
// Normally it would make sense to have a colon between property and value to make the relation between the property and its property value
// clear, however this is accessible text that will be read out by screen readers. That's why there is only a space between the two here,
// because screen readers would read the colon literally as "colon", which is just a waste of time for users who might go through a list of
// hundreds of items. So, if you want to add any more punctation there to improve structure, try to make sure that it will not lead to annoying
// announcements when read out by a screen reader.
i18nc("@info accessibility enumeration, %1 is property, %2 is value", ", %1 %2", model->roleDescription(roleInformation), roleText);
}
return description;
}
default:
break;
}
return QString();
}
void KItemListDelegateAccessible::setText(QAccessible::Text, const QString &)
{
}
QAccessibleInterface *KItemListDelegateAccessible::child(int) const
{
return nullptr;
}
bool KItemListDelegateAccessible::isValid() const
{
return m_view && (m_index >= 0) && (m_index < m_view->model()->count());
}
QAccessibleInterface *KItemListDelegateAccessible::childAt(int, int) const
{
return nullptr;
}
int KItemListDelegateAccessible::childCount() const
{
return 0;
}
int KItemListDelegateAccessible::indexOfChild(const QAccessibleInterface *child) const
{
Q_UNUSED(child)
return -1;
}
QAccessibleInterface *KItemListDelegateAccessible::parent() const
{
return QAccessible::queryAccessibleInterface(m_view);
}
int KItemListDelegateAccessible::index() const
{
return m_index;
}
QObject *KItemListDelegateAccessible::object() const
{
return nullptr;
}
|