Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 : #include "diagnosticaccessiblemodel.h"
23 :
24 : #include <QAccessible>
25 : #include <QMetaEnum>
26 :
27 : #include "muse_framework_config.h"
28 : #include "accessibility/internal/accessibilitycontroller.h"
29 :
30 : #include "log.h"
31 :
32 : using namespace muse::diagnostics;
33 : using namespace muse::accessibility;
34 :
35 0 : DiagnosticAccessibleModel::DiagnosticAccessibleModel(QObject* parent)
36 0 : : QAbstractItemModel(parent), muse::Contextable(muse::iocCtxForQmlObject(this))
37 : {
38 0 : m_refresher.setInterval(1000);
39 0 : m_refresher.setSingleShot(false);
40 0 : connect(&m_refresher, &QTimer::timeout, [this]() {
41 0 : reload();
42 0 : });
43 0 : }
44 :
45 0 : DiagnosticAccessibleModel::~DiagnosticAccessibleModel()
46 : {
47 0 : delete m_rootItem;
48 0 : }
49 :
50 0 : void DiagnosticAccessibleModel::init()
51 : {
52 : #ifdef MUSE_MODULE_ACCESSIBILITY
53 0 : AccessibilityController* accessController = dynamic_cast<AccessibilityController*>(accessibilityController().get());
54 0 : AccessibilityController::Item rootItem = accessController->findItem(accessController);
55 0 : m_accessibleRootObject = rootItem.object;
56 :
57 0 : accessController->eventSent().onReceive(this, [this](QAccessibleEvent* ev) { onAccessibleEvent(ev); });
58 : #endif
59 0 : }
60 :
61 : #ifdef MUSE_MODULE_ACCESSIBILITY
62 0 : static void debug_dumpItem(QAccessibleInterface* item, QTextStream& stream, QString& level)
63 : {
64 0 : QAccessibleInterface* prn = item->parent();
65 0 : stream << level
66 0 : << item->text(QAccessible::Name)
67 0 : << " parent: " << (prn ? prn->text(QAccessible::Name) : QString("null"))
68 0 : << " childCount: " << item->childCount()
69 0 : << Qt::endl;
70 :
71 0 : QAccessible::State st = item->state();
72 0 : stream << level
73 0 : << "state:"
74 0 : << " invisible: " << st.invisible
75 0 : << " invalid: " << st.invalid
76 0 : << " disabled: " << st.disabled
77 0 : << " active: " << st.active
78 0 : << " focusable: " << st.focusable
79 0 : << " focused: " << st.focused
80 0 : << Qt::endl;
81 :
82 0 : level += " ";
83 0 : int count = item->childCount();
84 0 : for (int i = 0; i < count; ++i) {
85 0 : QAccessibleInterface* ch = item->child(i);
86 0 : debug_dumpItem(ch, stream, level);
87 : }
88 0 : level.chop(2);
89 0 : }
90 :
91 0 : static void debug_dumpTree(QAccessibleInterface* item)
92 : {
93 0 : QString str;
94 0 : QTextStream stream(&str);
95 0 : QString level;
96 :
97 0 : debug_dumpItem(item, stream, level);
98 :
99 0 : stream.flush();
100 0 : LOGI() << "================================================\n";
101 0 : std::cout << str.toStdString() << '\n';
102 0 : }
103 :
104 : #endif
105 :
106 0 : void DiagnosticAccessibleModel::dumpTree()
107 : {
108 : #ifdef MUSE_MODULE_ACCESSIBILITY
109 0 : AccessibilityController* accessController = dynamic_cast<AccessibilityController*>(accessibilityController().get());
110 0 : AccessibilityController::Item rootItem = accessController->findItem(accessController);
111 :
112 0 : debug_dumpTree(rootItem.iface);
113 : #endif
114 0 : }
115 :
116 0 : QModelIndex DiagnosticAccessibleModel::index(int row, int column, const QModelIndex& parent) const
117 : {
118 0 : if (!m_rootItem) {
119 0 : return QModelIndex();
120 : }
121 :
122 0 : if (!hasIndex(row, column, parent)) {
123 0 : return QModelIndex();
124 : }
125 :
126 0 : Item* parentItem = nullptr;
127 :
128 0 : if (!parent.isValid()) {
129 0 : parentItem = m_rootItem;
130 : } else {
131 0 : parentItem = itemByModelIndex(parent);
132 : }
133 :
134 0 : if (!parentItem) {
135 0 : return QModelIndex();
136 : }
137 :
138 0 : Item* childItem = parentItem->child(row);
139 0 : if (childItem) {
140 0 : return createIndex(row, column, childItem->key());
141 : }
142 :
143 0 : return QModelIndex();
144 : }
145 :
146 0 : QModelIndex DiagnosticAccessibleModel::parent(const QModelIndex& child) const
147 : {
148 0 : if (!m_rootItem) {
149 0 : return QModelIndex();
150 : }
151 :
152 0 : if (!child.isValid()) {
153 0 : return QModelIndex();
154 : }
155 :
156 0 : Item* childItem = itemByModelIndex(child);
157 0 : if (!childItem) {
158 0 : return QModelIndex();
159 : }
160 :
161 0 : Item* parentItem = childItem->parent();
162 0 : if (parentItem == m_rootItem) {
163 0 : return QModelIndex();
164 : }
165 :
166 0 : return createIndex(parentItem->row(), 0, parentItem->key());
167 : }
168 :
169 0 : int DiagnosticAccessibleModel::rowCount(const QModelIndex& parent) const
170 : {
171 0 : if (!m_rootItem) {
172 0 : return 0;
173 : }
174 :
175 0 : Item* parentItem = nullptr;
176 :
177 0 : if (!parent.isValid()) {
178 0 : parentItem = m_rootItem;
179 : } else {
180 0 : parentItem = itemByModelIndex(parent);
181 : }
182 :
183 0 : if (!parentItem) {
184 0 : return 0;
185 : }
186 :
187 0 : return parentItem->childCount();
188 : }
189 :
190 0 : int DiagnosticAccessibleModel::columnCount(const QModelIndex&) const
191 : {
192 0 : return 1;
193 : }
194 :
195 0 : QVariant DiagnosticAccessibleModel::data(const QModelIndex& index, int role) const
196 : {
197 0 : if (!index.isValid() && role != rItemData) {
198 0 : return QVariant();
199 : }
200 :
201 0 : Item* item = itemByModelIndex(index);
202 0 : if (!item) {
203 0 : return QVariant();
204 : }
205 :
206 0 : return item->data();
207 : }
208 :
209 0 : QHash<int, QByteArray> DiagnosticAccessibleModel::roleNames() const
210 : {
211 0 : return { { rItemData, "itemData" } };
212 0 : }
213 :
214 0 : void DiagnosticAccessibleModel::onAccessibleEvent(QAccessibleEvent* ev)
215 : {
216 0 : LOGD() << "event: " << ev->type();
217 0 : if (ev->type() == QAccessible::ObjectCreated || ev->type() == QAccessible::ObjectDestroyed) {
218 : // todo
219 : } else {
220 0 : onItemChanged(ev->object());
221 : }
222 0 : }
223 :
224 0 : void DiagnosticAccessibleModel::onItemChanged(QObject* accessibleObject)
225 : {
226 0 : if (!m_rootItem) {
227 0 : return;
228 : }
229 :
230 0 : QAccessibleInterface* iface = QAccessible::queryAccessibleInterface(accessibleObject);
231 0 : IF_ASSERT_FAILED(iface) {
232 0 : return;
233 : }
234 :
235 0 : Item* item = findItemForIface(iface, m_rootItem);
236 0 : if (!item) {
237 0 : LOGE() << "not found item: " << iface->text(QAccessible::Name);
238 0 : return;
239 : }
240 :
241 0 : QVariant newData = makeData(iface);
242 0 : item->setData(newData);
243 :
244 0 : QModelIndex index = createIndex(item->row(), 0, item->key());
245 :
246 0 : QAccessible::State st = iface->state();
247 0 : if (st.focused) {
248 0 : emit focusedItem(index);
249 : }
250 :
251 0 : emit dataChanged(index, index, { rItemData });
252 0 : }
253 :
254 0 : DiagnosticAccessibleModel::Item* DiagnosticAccessibleModel::findItemForIface(const QAccessibleInterface* iface, Item* rootItem) const
255 : {
256 0 : if (!rootItem) {
257 0 : return nullptr;
258 : }
259 :
260 0 : if (rootItem->iface() == iface) {
261 0 : return rootItem;
262 : }
263 :
264 0 : for (int i = 0; i < rootItem->childCount(); ++i) {
265 0 : Item* it = findItemForIface(iface, rootItem->child(i));
266 0 : if (it) {
267 0 : return it;
268 : }
269 : }
270 :
271 0 : return nullptr;
272 : }
273 :
274 0 : DiagnosticAccessibleModel::Item* DiagnosticAccessibleModel::createItem(Item* parent)
275 : {
276 0 : Item* item = new Item(parent);
277 0 : m_allItems.insert(item->key(), item);
278 0 : return item;
279 : }
280 :
281 0 : DiagnosticAccessibleModel::Item* DiagnosticAccessibleModel::itemByModelIndex(const QModelIndex& index) const
282 : {
283 0 : return m_allItems.value(index.internalId(), nullptr);
284 : }
285 :
286 0 : void DiagnosticAccessibleModel::reload()
287 : {
288 0 : IF_ASSERT_FAILED(m_accessibleRootObject) {
289 0 : return;
290 : }
291 :
292 0 : QAccessibleInterface* iface = QAccessible::queryAccessibleInterface(m_accessibleRootObject);
293 0 : IF_ASSERT_FAILED(iface) {
294 0 : return;
295 : }
296 :
297 0 : m_refresher.stop();
298 :
299 0 : emit beforeReload();
300 0 : beginResetModel();
301 :
302 0 : m_allItems.clear();
303 0 : delete m_rootItem;
304 0 : m_rootItem = createItem(nullptr);
305 0 : load(iface, m_rootItem);
306 :
307 0 : endResetModel();
308 0 : emit afterReload();
309 :
310 0 : if (m_isAutoRefresh) {
311 0 : m_refresher.start();
312 : }
313 : }
314 :
315 0 : QVariant DiagnosticAccessibleModel::makeData(const QAccessibleInterface* iface) const
316 : {
317 0 : QAccessibleInterface* prn = iface->parent();
318 :
319 0 : static const QMetaEnum roleEnum = QMetaEnum::fromType<QAccessible::Role>();
320 :
321 0 : QVariantMap itemData;
322 0 : itemData["role"] = QString(roleEnum.valueToKey(iface->role()));
323 0 : itemData["name"] = iface->text(QAccessible::Name);
324 0 : itemData["parent"] = prn ? prn->text(QAccessible::Name) : QString("null");
325 0 : itemData["children"] = iface->childCount();
326 :
327 0 : QAccessible::State st = iface->state();
328 0 : QVariantMap state;
329 0 : state["invisible"] = st.invisible;
330 0 : state["invalid"] = st.invalid;
331 0 : state["disabled"] = st.disabled;
332 0 : state["active"] = st.active;
333 0 : state["focusable"] = st.focusable;
334 0 : state["focused"] = st.focused;
335 0 : itemData["state"] = state;
336 :
337 0 : return itemData;
338 0 : }
339 :
340 0 : void DiagnosticAccessibleModel::load(QAccessibleInterface* iface, Item* parent)
341 : {
342 0 : Item* item = createItem(parent);
343 0 : item->setIface(iface);
344 0 : item->setData(makeData(iface));
345 :
346 0 : int childCount = iface->childCount();
347 0 : for (int i = 0; i < childCount; ++i) {
348 0 : QAccessibleInterface* child = iface->child(i);
349 0 : load(child, item);
350 : }
351 0 : }
352 :
353 0 : void DiagnosticAccessibleModel::setIsAutoRefresh(bool isAutoRefresh)
354 : {
355 0 : if (m_isAutoRefresh == isAutoRefresh) {
356 0 : return;
357 : }
358 :
359 0 : m_isAutoRefresh = isAutoRefresh;
360 0 : emit isAutoRefreshChanged();
361 :
362 0 : if (isAutoRefresh) {
363 0 : m_refresher.start();
364 : }
365 : }
366 :
367 0 : bool DiagnosticAccessibleModel::isAutoRefresh() const
368 : {
369 0 : return m_isAutoRefresh;
370 : }
|