1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #include "accessibilityscenemanager.h"
30 
AccessibilitySceneManager()31 AccessibilitySceneManager::AccessibilitySceneManager()
32 {
33     m_window = 0;
34     m_view = 0;
35     m_scene = 0;
36     m_rootItem = 0;
37     m_optionsWidget = 0;
38     m_selectedObject = 0;
39 }
40 
populateAccessibilityScene()41 void AccessibilitySceneManager::populateAccessibilityScene()
42 {
43     m_scene->clear();
44     m_graphicsItems.clear();
45 
46     QAccessibleInterface * rootInterface = m_window->accessibleRoot();
47     if (!rootInterface)
48         return;
49 
50     populateAccessibilityScene(rootInterface, m_scene);
51 }
52 
updateAccessibilitySceneItemFlags()53 void AccessibilitySceneManager::updateAccessibilitySceneItemFlags()
54 {
55     qDebug() << "update";
56     foreach (QObject *object, m_graphicsItems.keys()) {
57         if (!object)
58             continue;
59         QAccessibleInterface *interface = QAccessible::queryAccessibleInterface(object);
60         if (!interface)
61             continue;
62         updateItemFlags(m_graphicsItems.value(object), interface);
63     }
64 }
65 
populateAccessibilityTreeScene()66 void AccessibilitySceneManager::populateAccessibilityTreeScene()
67 {
68     m_treeScene->clear();
69     QAccessibleInterface * rootInterface = m_window->accessibleRoot();
70     if (!rootInterface) {
71         qWarning("QWindow::accessibleRoot returned 0");
72         return;
73     }
74 
75     populateAccessibilityTreeScene(rootInterface);
76 }
77 
handleUpdate(QAccessibleEvent * event)78 void AccessibilitySceneManager::handleUpdate(QAccessibleEvent *event)
79 {
80     QObject *object = event->object();
81     QAccessible::Event type = event->type();
82 
83     QAccessibleInterface *interface = QAccessible::queryAccessibleInterface(object);
84     if (!interface)
85         return;
86 
87     QString name = interface->text(QAccessible::Name);
88 
89     if (type == QAccessible::ObjectCreated) {
90   //      qDebug() << "ObjectCreated" << object << name;
91         populateAccessibilityScene(interface, m_scene);
92     }
93 
94     QGraphicsRectItem *item = m_graphicsItems.value(object);
95 
96     if (!item) {
97 //        qDebug() << "populateAccessibilityScene failed for" << object;
98         return;
99     }
100 
101     if (type == QAccessible::LocationChanged) {
102 
103         //if (name.startsWith("List"))
104             qDebug() << "locationChange" << object << name << interface->rect();
105 
106         updateItem(item, interface);
107         for (int i = 0; i < interface->childCount(); ++i) {
108            QAccessibleInterface *child = interface->child(i);
109            if (child) {
110                updateItem(m_graphicsItems.value(child->object()), child);
111             }
112         }
113 
114     } else if (type == QAccessible::ObjectDestroyed) {
115 //        qDebug() << "ObjectDestroyed" << object << name;
116         delete m_graphicsItems.value(object);
117         m_graphicsItems.remove(object);
118         m_animatedObjects.remove(object);
119         if (object == m_selectedObject) {
120             m_selectedObject = 0;
121         }
122     } else if (type == QAccessible::ObjectHide) {
123 //        qDebug() << "ObjectCreated Hide" << object;
124         updateItemFlags(item, interface);
125     } else if (type == QAccessible::ObjectShow) {
126 //        qDebug() << "ObjectCreated Show" << object;
127         updateItemFlags(item, interface);
128     } else if (type == QAccessible::ScrollingStart) {
129         qDebug() << "ObjectCreated ScrollingStart" << object;
130         for (int i = 0; i < interface->childCount(); ++i) {
131             QAccessibleInterface *child = interface->child(i);
132             if (child) {
133                 m_animatedObjects.insert(child->object());
134             }
135         }
136     } else if (type == QAccessible::ScrollingEnd) {
137         // qDebug() << "ObjectCreated ScrollingEnd" << object;
138         foreach (QObject *object, m_animatedObjects) {
139             updateItem(m_graphicsItems.value(object), interface);
140         }
141         m_animatedObjects.clear();
142 
143     } else {
144 //        qDebug() << "other update" << object;
145     }
146 }
147 
setSelected(QObject * object)148 void AccessibilitySceneManager::setSelected(QObject *object)
149 {
150     m_scene->update(); // scedule update
151 
152     // clear existing selection
153     if (m_selectedObject) {
154         QObject *previousSelectedObject = m_selectedObject;
155         m_selectedObject = 0;
156         updateItem(previousSelectedObject);
157     }
158 
159     m_selectedObject = object;
160     updateItem(object);
161 
162     populateAccessibilityTreeScene();
163 }
164 
changeScale(int)165 void AccessibilitySceneManager::changeScale(int)
166 {
167     // No QGraphicsView::setScale :(
168 
169     //m_view->scale(scale / 10.0, scale / 10.0);
170     //if (m_rootItem)
171     //    m_view->ensureVisible(m_rootItem);
172 }
173 
updateItems(QObject * root)174 void AccessibilitySceneManager::updateItems(QObject *root)
175 {
176     QAccessibleInterface *interface = QAccessible::queryAccessibleInterface(root);
177     if (!interface)
178         return;
179     updateItem(m_graphicsItems.value(root), interface);
180 
181     for (int i = 0; i < interface->childCount(); ++i) {
182         QAccessibleInterface *child = interface->child(i);
183         updateItems(child->object());
184     }
185 }
186 
updateItem(QObject * object)187 void AccessibilitySceneManager::updateItem(QObject *object)
188 {
189     if (!object)
190         return;
191 
192     QAccessibleInterface *interface = QAccessible::queryAccessibleInterface(object);
193     if (!interface)
194         return;
195 
196     updateItem(m_graphicsItems.value(object), interface);
197 }
198 
updateItem(QGraphicsRectItem * item,QAccessibleInterface * interface)199 void AccessibilitySceneManager::updateItem(QGraphicsRectItem *item, QAccessibleInterface *interface)
200 {
201     if (!item)
202         return;
203 
204     QRect rect = interface->rect();
205     item->setPos(rect.topLeft());
206     item->setRect(QRect(QPoint(0,0), rect.size()));
207 
208     updateItemFlags(item, interface);
209 }
210 
updateItemFlags(QGraphicsRectItem * item,QAccessibleInterface * interface)211 void AccessibilitySceneManager::updateItemFlags(QGraphicsRectItem *item, QAccessibleInterface *interface)
212 {
213   //  qDebug() << "udpateItemFlags" << interface << interface->object();
214 
215     bool shouldShow = true;
216 
217     if (m_optionsWidget->hideInvisibleItems()) {
218         if (isHidden(interface)) {
219             shouldShow = false;
220         }
221     }
222 
223     if (m_optionsWidget->hideOffscreenItems()) {
224         if (interface->state().offscreen) {
225             shouldShow = false;
226         }
227     }
228 
229     if (m_optionsWidget->hidePaneItems()) {
230         if (interface->role() & QAccessible::Pane) {
231             shouldShow = false;
232         }
233     }
234 
235     if (m_optionsWidget->hideNullObjectItems()) {
236         if (interface->object() == 0) {
237             shouldShow = false;
238         }
239     }
240 
241     if (m_optionsWidget->hideNullRectItems()) {
242         if (interface->rect().isNull()) {
243             shouldShow = false;
244         }
245     }
246 
247     item->setVisible(shouldShow);
248 
249     if (interface->object() && interface->object() == m_selectedObject)
250         item->setBrush(QColor(Qt::yellow));
251     else
252         item->setBrush(QColor(Qt::white));
253 
254     m_view->update();
255 }
256 
processInterface(QAccessibleInterface * interface,QGraphicsScene * scene)257 QGraphicsRectItem * AccessibilitySceneManager::processInterface(QAccessibleInterface * interface, QGraphicsScene *scene)
258 {
259     // Process this interface
260 
261     QGraphicsRectItem * item = new QGraphicsRectItem();
262     scene->addItem(item);
263     if (!m_rootItem)
264         m_rootItem = item;
265 
266     QString name = interface->text(QAccessible::Name);
267     QString description; // = interface->text(QAccessibleInterface::Description, child);
268     QString role = translateRole(interface->role());
269     int childCount = interface->childCount();
270 
271     /* qDebug() << "name:" << name << "local pos" <<
272                interface->rect(0) << "description" << description << "childCount" << childCount;
273 */
274 
275     updateItem(item, interface);
276 
277     QGraphicsSimpleTextItem * textItem = new QGraphicsSimpleTextItem();
278     textItem->setParentItem(item);
279     textItem->setPos(QPoint(5, 5));
280 
281     QString text;
282     text.append("Name: " + name + " ");
283     if (!description.isEmpty())
284         text.append("Description: " + description + " ");
285     text.append("Role: " + role + " ");
286     if (childCount > 0)
287         text.append("ChildCount: " + QString::number(childCount) + " ");
288     textItem->setText(text);
289 
290     QFont font;
291     font.setPointSize(10);
292  //   font.setPointSize(14);
293     textItem->setFont(font);
294 
295     return item;
296 }
297 
populateAccessibilityScene(QAccessibleInterface * interface,QGraphicsScene * scene)298 void AccessibilitySceneManager::populateAccessibilityScene(QAccessibleInterface * interface, QGraphicsScene *scene)
299 {
300     if (!interface)
301         return;
302 
303     QGraphicsRectItem *item = processInterface(interface, scene);
304 
305     QObject *object = interface->object();
306     if (object) {
307         m_graphicsItems.insert(object, item);
308     }
309 
310     for (int i = 0; i < interface->childCount(); ++i) {
311         QAccessibleInterface *child = interface->child(i);
312         updateItems(child->object());
313         populateAccessibilityScene(child, scene);
314     }
315 }
316 
computeLevels(QAccessibleInterface * interface,int level)317 AccessibilitySceneManager::TreeItem AccessibilitySceneManager::computeLevels(QAccessibleInterface * interface, int level)
318 {
319     if (interface == 0)
320         return TreeItem();
321 
322     TreeItem currentLevel;
323 
324     int usedChildren = 0;
325     for (int i = 0; i < interface->childCount(); ++i) {
326         QAccessibleInterface *child = interface->child(i);
327         if (child != 0) {
328             ++usedChildren;
329             TreeItem childLevel = computeLevels(child, level + 1);
330             currentLevel.children.append(childLevel);
331             currentLevel.width += childLevel.width + m_treeItemHorizontalPadding;
332         }
333     }
334 
335     // leaf node case
336     if (usedChildren == 0) {
337         currentLevel.width = m_treeItemWidth + m_treeItemHorizontalPadding;
338     }
339 
340     // capture information:
341     currentLevel.name = interface->text(QAccessible::Name);
342     currentLevel.description += interface->text(QAccessible::DebugDescription);
343     currentLevel.role = translateRole(interface->role());
344     currentLevel.rect = interface->rect();
345     currentLevel.state = interface->state();
346     currentLevel.object = interface->object();
347 
348     return currentLevel;
349 }
350 
populateAccessibilityTreeScene(QAccessibleInterface * interface)351 void AccessibilitySceneManager::populateAccessibilityTreeScene(QAccessibleInterface * interface)
352 {
353     if (!interface)
354         return;
355 
356     // set some layout metrics:
357     m_treeItemWidth = 90;
358     m_treeItemHorizontalPadding = 10;
359     m_treeItemHeight = 60;
360     m_treeItemVerticalPadding = 30;
361 
362     // We want to draw the accessibility hiearchy as a vertical
363     // tree, growing from the root node at the top.
364 
365     // First, figure out the number of levels and the width of each level:
366     m_rootTreeItem = computeLevels(interface, 0);
367 
368     // create graphics items for each tree item
369     addGraphicsItems(m_rootTreeItem, 0, 0);
370 }
371 
addGraphicsItems(AccessibilitySceneManager::TreeItem item,int row,int xPos)372 void AccessibilitySceneManager::addGraphicsItems(AccessibilitySceneManager::TreeItem item, int row, int xPos)
373 {
374     //qDebug() << "add graphics item" << row << item.name << item.role << xPos << item.width << item.children.count();
375 
376     int yPos = row * (m_treeItemHeight + m_treeItemVerticalPadding);
377 
378     // Process this interface
379     QGraphicsRectItem * graphicsItem = new QGraphicsRectItem();
380     graphicsItem->setPos(xPos, yPos);
381     graphicsItem->setRect(0, 0, m_treeItemWidth, m_treeItemHeight);
382     graphicsItem->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
383 
384     if (item.object && item.object == m_selectedObject)
385         graphicsItem->setBrush(QColor(Qt::yellow));
386     else
387         graphicsItem->setBrush(QColor(Qt::white));
388 
389     if (item.state.offscreen) {
390         QPen linePen;
391         linePen.setStyle(Qt::DashLine);
392         graphicsItem->setPen(linePen);
393     }
394 
395     m_treeScene->addItem(graphicsItem);
396 
397     QGraphicsTextItem * textItem = new QGraphicsTextItem();
398     textItem->setParentItem(graphicsItem);
399     textItem->setPos(QPoint(0, 0));
400 
401     QFont font;
402     font.setPointSize(8);
403     textItem->setFont(font);
404 
405     QString text;
406     text += item.name + "\n";
407     text += item.role + "\n";
408     text += item.description.split(QLatin1Char(' '), Qt::SkipEmptyParts).join("\n") + "\n";
409     text += "P:" + QString::number(item.rect.x()) + " " + QString::number(item.rect.y()) + " ";
410     text += "S:" + QString::number(item.rect.width()) + " " + QString::number(item.rect.height()) + "\n";
411 
412     textItem->setPlainText(text);
413 
414     // recurse to children
415     int childIndex = 0;
416     int childCount = item.children.count();
417     int segmentSize = item.width / qMax(1, childCount);
418     int segmentCenterOffset = segmentSize / 2;
419     int segmentsStart = xPos - (item.width / 2);
420     foreach (TreeItem child, item.children) {
421         // spread the children out, covering the width, centered on xPos
422         int segmentPosition = segmentsStart + (segmentSize * childIndex) + segmentCenterOffset;
423         addGraphicsItems(child, row + 1, segmentPosition);
424         ++childIndex;
425     }
426 
427     // add lines from parents to kids
428     int boxBottom = yPos + m_treeItemHeight;
429     int boxMiddleX = xPos + m_treeItemWidth / 2;
430     int yBottomMiddle = boxBottom + m_treeItemVerticalPadding / 2;
431     int boxTop = yPos;
432     int yTopMiddle = boxTop - m_treeItemVerticalPadding / 2;
433 
434     if (row > 0) {
435         QGraphicsLineItem *childVerticalStem = new QGraphicsLineItem();
436         childVerticalStem->setLine(boxMiddleX, yTopMiddle, boxMiddleX, boxTop);
437         m_treeScene->addItem(childVerticalStem);
438     }
439 
440     if (childCount > 0) {
441         QGraphicsLineItem *parentVerticalStem = new QGraphicsLineItem();
442         parentVerticalStem->setLine(boxMiddleX, boxBottom, boxMiddleX, yBottomMiddle);
443         m_treeScene->addItem(parentVerticalStem);
444     }
445 
446     if (childCount > 1) {
447         QGraphicsLineItem *horizontalStem = new QGraphicsLineItem();
448         // match the end points with the horizontal lines
449         int lineStartX = segmentsStart + segmentCenterOffset + m_treeItemWidth / 2;
450         int lineStopX = segmentsStart + segmentSize * (childCount -1) + segmentCenterOffset + m_treeItemWidth / 2;
451         horizontalStem->setLine(lineStartX, yBottomMiddle, lineStopX , yBottomMiddle);
452         m_treeScene->addItem(horizontalStem);
453     }
454 }
455 
isHidden(QAccessibleInterface * interface)456 bool AccessibilitySceneManager::isHidden(QAccessibleInterface *interface)
457 {
458     QAccessibleInterface *current = interface;
459     while (current) {
460 
461         if (current->state().invisible) {
462             return true;
463         }
464 
465         current = current->parent();
466     }
467 
468     return false;
469 }
470