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 plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
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 Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qaccessiblewidgets_p.h"
41 #include "qabstracttextdocumentlayout.h"
42 #include "qapplication.h"
43 #include "qclipboard.h"
44 #include "qtextdocument.h"
45 #include "qtextobject.h"
46 #if QT_CONFIG(textedit)
47 #include "qplaintextedit.h"
48 #include "qtextedit.h"
49 #include "private/qtextedit_p.h"
50 #endif
51 #include "qtextboundaryfinder.h"
52 #if QT_CONFIG(scrollbar)
53 #include "qscrollbar.h"
54 #endif
55 #include "qdebug.h"
56 #include <QApplication>
57 #if QT_CONFIG(stackedwidget)
58 #include <QStackedWidget>
59 #endif
60 #if QT_CONFIG(toolbox)
61 #include <QToolBox>
62 #endif
63 #if QT_CONFIG(mdiarea)
64 #include <QMdiArea>
65 #include <QMdiSubWindow>
66 #endif
67 #if QT_CONFIG(dialogbuttonbox)
68 #include <QDialogButtonBox>
69 #endif
70 #include <limits.h>
71 #if QT_CONFIG(rubberband)
72 #include <QRubberBand>
73 #endif
74 #if QT_CONFIG(textbrowser)
75 #include <QTextBrowser>
76 #endif
77 #if QT_CONFIG(calendarwidget)
78 #include <QCalendarWidget>
79 #endif
80 #if QT_CONFIG(itemviews)
81 #include <QAbstractItemView>
82 #endif
83 #if QT_CONFIG(dockwidget)
84 #include <QDockWidget>
85 #include <private/qdockwidget_p.h>
86 #endif
87 #if QT_CONFIG(mainwindow)
88 #include <QMainWindow>
89 #endif
90 #include <QFocusFrame>
91 #if QT_CONFIG(menu)
92 #include <QMenu>
93 #endif
94 
95 #ifndef QT_NO_ACCESSIBILITY
96 
97 QT_BEGIN_NAMESPACE
98 
99 QString qt_accStripAmp(const QString &text);
100 QString qt_accHotKey(const QString &text);
101 
childWidgets(const QWidget * widget)102 QList<QWidget*> childWidgets(const QWidget *widget)
103 {
104     QList<QWidget*> widgets;
105     if (!widget)
106         return widgets;
107     for (QObject *o : widget->children()) {
108         QWidget *w = qobject_cast<QWidget *>(o);
109         if (!w)
110             continue;
111         QString objectName = w->objectName();
112         if (!w->isWindow()
113               && !qobject_cast<QFocusFrame*>(w)
114 #if QT_CONFIG(menu)
115               && !qobject_cast<QMenu*>(w)
116 #endif
117               && objectName != QLatin1String("qt_rubberband")
118               && objectName != QLatin1String("qt_qmainwindow_extended_splitter")) {
119             widgets.append(w);
120         }
121     }
122     return widgets;
123 }
124 
125 #if QT_CONFIG(textedit) && !defined(QT_NO_CURSOR)
126 
QAccessiblePlainTextEdit(QWidget * o)127 QAccessiblePlainTextEdit::QAccessiblePlainTextEdit(QWidget* o)
128   :QAccessibleTextWidget(o)
129 {
130     Q_ASSERT(widget()->inherits("QPlainTextEdit"));
131 }
132 
plainTextEdit() const133 QPlainTextEdit* QAccessiblePlainTextEdit::plainTextEdit() const
134 {
135     return static_cast<QPlainTextEdit *>(widget());
136 }
137 
text(QAccessible::Text t) const138 QString QAccessiblePlainTextEdit::text(QAccessible::Text t) const
139 {
140     if (t == QAccessible::Value)
141         return plainTextEdit()->toPlainText();
142 
143     return QAccessibleWidget::text(t);
144 }
145 
setText(QAccessible::Text t,const QString & text)146 void QAccessiblePlainTextEdit::setText(QAccessible::Text t, const QString &text)
147 {
148     if (t != QAccessible::Value) {
149         QAccessibleWidget::setText(t, text);
150         return;
151     }
152     if (plainTextEdit()->isReadOnly())
153         return;
154 
155     plainTextEdit()->setPlainText(text);
156 }
157 
state() const158 QAccessible::State QAccessiblePlainTextEdit::state() const
159 {
160     QAccessible::State st = QAccessibleTextWidget::state();
161     if (plainTextEdit()->isReadOnly())
162         st.readOnly = true;
163     else
164         st.editable = true;
165     return st;
166 }
167 
interface_cast(QAccessible::InterfaceType t)168 void *QAccessiblePlainTextEdit::interface_cast(QAccessible::InterfaceType t)
169 {
170     if (t == QAccessible::TextInterface)
171         return static_cast<QAccessibleTextInterface*>(this);
172     else if (t == QAccessible::EditableTextInterface)
173         return static_cast<QAccessibleEditableTextInterface*>(this);
174     return QAccessibleWidget::interface_cast(t);
175 }
176 
scrollBarPosition() const177 QPoint QAccessiblePlainTextEdit::scrollBarPosition() const
178 {
179     QPoint result;
180     result.setX(plainTextEdit()->horizontalScrollBar() ? plainTextEdit()->horizontalScrollBar()->sliderPosition() : 0);
181     result.setY(plainTextEdit()->verticalScrollBar() ? plainTextEdit()->verticalScrollBar()->sliderPosition() : 0);
182     return result;
183 }
184 
textCursor() const185 QTextCursor QAccessiblePlainTextEdit::textCursor() const
186 {
187     return plainTextEdit()->textCursor();
188 }
189 
setTextCursor(const QTextCursor & textCursor)190 void QAccessiblePlainTextEdit::setTextCursor(const QTextCursor &textCursor)
191 {
192     plainTextEdit()->setTextCursor(textCursor);
193 }
194 
textDocument() const195 QTextDocument* QAccessiblePlainTextEdit::textDocument() const
196 {
197     return plainTextEdit()->document();
198 }
199 
viewport() const200 QWidget* QAccessiblePlainTextEdit::viewport() const
201 {
202     return plainTextEdit()->viewport();
203 }
204 
scrollToSubstring(int startIndex,int endIndex)205 void QAccessiblePlainTextEdit::scrollToSubstring(int startIndex, int endIndex)
206 {
207     //TODO: Not implemented
208     Q_UNUSED(startIndex);
209     Q_UNUSED(endIndex);
210 }
211 
212 
213 /*!
214   \class QAccessibleTextEdit
215   \brief The QAccessibleTextEdit class implements the QAccessibleInterface for richtext editors.
216   \internal
217 */
218 
219 /*!
220   \fn QAccessibleTextEdit::QAccessibleTextEdit(QWidget *widget)
221 
222   Constructs a QAccessibleTextEdit object for a \a widget.
223 */
QAccessibleTextEdit(QWidget * o)224 QAccessibleTextEdit::QAccessibleTextEdit(QWidget *o)
225 : QAccessibleTextWidget(o, QAccessible::EditableText)
226 {
227     Q_ASSERT(widget()->inherits("QTextEdit"));
228 }
229 
230 /*! Returns the text edit. */
textEdit() const231 QTextEdit *QAccessibleTextEdit::textEdit() const
232 {
233     return static_cast<QTextEdit *>(widget());
234 }
235 
textCursor() const236 QTextCursor QAccessibleTextEdit::textCursor() const
237 {
238     return textEdit()->textCursor();
239 }
240 
textDocument() const241 QTextDocument *QAccessibleTextEdit::textDocument() const
242 {
243     return textEdit()->document();
244 }
245 
setTextCursor(const QTextCursor & textCursor)246 void QAccessibleTextEdit::setTextCursor(const QTextCursor &textCursor)
247 {
248     textEdit()->setTextCursor(textCursor);
249 }
250 
viewport() const251 QWidget *QAccessibleTextEdit::viewport() const
252 {
253     return textEdit()->viewport();
254 }
255 
scrollBarPosition() const256 QPoint QAccessibleTextEdit::scrollBarPosition() const
257 {
258     QPoint result;
259     result.setX(textEdit()->horizontalScrollBar() ? textEdit()->horizontalScrollBar()->sliderPosition() : 0);
260     result.setY(textEdit()->verticalScrollBar() ? textEdit()->verticalScrollBar()->sliderPosition() : 0);
261     return result;
262 }
263 
text(QAccessible::Text t) const264 QString QAccessibleTextEdit::text(QAccessible::Text t) const
265 {
266     if (t == QAccessible::Value)
267         return textEdit()->toPlainText();
268 
269     return QAccessibleWidget::text(t);
270 }
271 
setText(QAccessible::Text t,const QString & text)272 void QAccessibleTextEdit::setText(QAccessible::Text t, const QString &text)
273 {
274     if (t != QAccessible::Value) {
275         QAccessibleWidget::setText(t, text);
276         return;
277     }
278     if (textEdit()->isReadOnly())
279         return;
280 
281     textEdit()->setText(text);
282 }
283 
state() const284 QAccessible::State QAccessibleTextEdit::state() const
285 {
286     QAccessible::State st = QAccessibleTextWidget::state();
287     if (textEdit()->isReadOnly())
288         st.readOnly = true;
289     else
290         st.editable = true;
291     return st;
292 }
293 
interface_cast(QAccessible::InterfaceType t)294 void *QAccessibleTextEdit::interface_cast(QAccessible::InterfaceType t)
295 {
296     if (t == QAccessible::TextInterface)
297         return static_cast<QAccessibleTextInterface*>(this);
298     else if (t == QAccessible::EditableTextInterface)
299         return static_cast<QAccessibleEditableTextInterface*>(this);
300     return QAccessibleWidget::interface_cast(t);
301 }
302 
scrollToSubstring(int startIndex,int endIndex)303 void QAccessibleTextEdit::scrollToSubstring(int startIndex, int endIndex)
304 {
305     QTextEdit *edit = textEdit();
306 
307     QTextCursor cursor = textCursor();
308     cursor.setPosition(startIndex);
309     QRect r = edit->cursorRect(cursor);
310 
311     cursor.setPosition(endIndex);
312     r.setBottomRight(edit->cursorRect(cursor).bottomRight());
313 
314     r.moveTo(r.x() + edit->horizontalScrollBar()->value(),
315              r.y() + edit->verticalScrollBar()->value());
316 
317     // E V I L, but ensureVisible is not public
318     if (Q_UNLIKELY(!QMetaObject::invokeMethod(edit, "_q_ensureVisible", Q_ARG(QRectF, r))))
319         qWarning("AccessibleTextEdit::scrollToSubstring failed!");
320 }
321 
322 #endif // QT_CONFIG(textedit) && QT_NO_CURSOR
323 
324 #if QT_CONFIG(stackedwidget)
325 // ======================= QAccessibleStackedWidget ======================
QAccessibleStackedWidget(QWidget * widget)326 QAccessibleStackedWidget::QAccessibleStackedWidget(QWidget *widget)
327     : QAccessibleWidget(widget, QAccessible::LayeredPane)
328 {
329     Q_ASSERT(qobject_cast<QStackedWidget *>(widget));
330 }
331 
childAt(int x,int y) const332 QAccessibleInterface *QAccessibleStackedWidget::childAt(int x, int y) const
333 {
334     if (!stackedWidget()->isVisible())
335         return nullptr;
336     QWidget *currentWidget = stackedWidget()->currentWidget();
337     if (!currentWidget)
338         return nullptr;
339     QPoint position = currentWidget->mapFromGlobal(QPoint(x, y));
340     if (currentWidget->rect().contains(position))
341         return child(stackedWidget()->currentIndex());
342     return nullptr;
343 }
344 
childCount() const345 int QAccessibleStackedWidget::childCount() const
346 {
347     return stackedWidget()->count();
348 }
349 
indexOfChild(const QAccessibleInterface * child) const350 int QAccessibleStackedWidget::indexOfChild(const QAccessibleInterface *child) const
351 {
352     if (!child)
353         return -1;
354 
355     QWidget *widget = qobject_cast<QWidget*>(child->object());
356     return stackedWidget()->indexOf(widget);
357 }
358 
child(int index) const359 QAccessibleInterface *QAccessibleStackedWidget::child(int index) const
360 {
361     if (index < 0 || index >= stackedWidget()->count())
362         return nullptr;
363     return QAccessible::queryAccessibleInterface(stackedWidget()->widget(index));
364 }
365 
stackedWidget() const366 QStackedWidget *QAccessibleStackedWidget::stackedWidget() const
367 {
368     return static_cast<QStackedWidget *>(object());
369 }
370 #endif // QT_CONFIG(stackedwidget)
371 
372 #if QT_CONFIG(toolbox)
373 // ======================= QAccessibleToolBox ======================
QAccessibleToolBox(QWidget * widget)374 QAccessibleToolBox::QAccessibleToolBox(QWidget *widget)
375     : QAccessibleWidget(widget, QAccessible::LayeredPane)
376 {
377     Q_ASSERT(qobject_cast<QToolBox *>(widget));
378 }
379 
toolBox() const380 QToolBox * QAccessibleToolBox::toolBox() const
381 {
382     return static_cast<QToolBox *>(object());
383 }
384 #endif // QT_CONFIG(toolbox)
385 
386 // ======================= QAccessibleMdiArea ======================
387 #if QT_CONFIG(mdiarea)
QAccessibleMdiArea(QWidget * widget)388 QAccessibleMdiArea::QAccessibleMdiArea(QWidget *widget)
389     : QAccessibleWidget(widget, QAccessible::LayeredPane)
390 {
391     Q_ASSERT(qobject_cast<QMdiArea *>(widget));
392 }
393 
childCount() const394 int QAccessibleMdiArea::childCount() const
395 {
396     return mdiArea()->subWindowList().count();
397 }
398 
child(int index) const399 QAccessibleInterface *QAccessibleMdiArea::child(int index) const
400 {
401     QList<QMdiSubWindow *> subWindows = mdiArea()->subWindowList();
402     QWidget *targetObject = subWindows.value(index);
403     if (!targetObject)
404        return nullptr;
405     return QAccessible::queryAccessibleInterface(targetObject);
406 }
407 
408 
indexOfChild(const QAccessibleInterface * child) const409 int QAccessibleMdiArea::indexOfChild(const QAccessibleInterface *child) const
410 {
411     if (!child || !child->object() || mdiArea()->subWindowList().isEmpty())
412         return -1;
413     if (QMdiSubWindow *window = qobject_cast<QMdiSubWindow *>(child->object())) {
414         return mdiArea()->subWindowList().indexOf(window);
415     }
416     return -1;
417 }
418 
mdiArea() const419 QMdiArea *QAccessibleMdiArea::mdiArea() const
420 {
421     return static_cast<QMdiArea *>(object());
422 }
423 
424 // ======================= QAccessibleMdiSubWindow ======================
QAccessibleMdiSubWindow(QWidget * widget)425 QAccessibleMdiSubWindow::QAccessibleMdiSubWindow(QWidget *widget)
426     : QAccessibleWidget(widget, QAccessible::Window)
427 {
428     Q_ASSERT(qobject_cast<QMdiSubWindow *>(widget));
429 }
430 
text(QAccessible::Text textType) const431 QString QAccessibleMdiSubWindow::text(QAccessible::Text textType) const
432 {
433     if (textType == QAccessible::Name) {
434         QString title = mdiSubWindow()->windowTitle();
435         title.replace(QLatin1String("[*]"), QLatin1String(""));
436         return title;
437     }
438     return QAccessibleWidget::text(textType);
439 }
440 
setText(QAccessible::Text textType,const QString & text)441 void QAccessibleMdiSubWindow::setText(QAccessible::Text textType, const QString &text)
442 {
443     if (textType == QAccessible::Name)
444         mdiSubWindow()->setWindowTitle(text);
445     else
446         QAccessibleWidget::setText(textType, text);
447 }
448 
state() const449 QAccessible::State QAccessibleMdiSubWindow::state() const
450 {
451     QAccessible::State state;
452     state.focusable = true;
453     if (!mdiSubWindow()->isMaximized()) {
454         state.movable = true;
455         state.sizeable = true;
456     }
457     if (mdiSubWindow()->isAncestorOf(QApplication::focusWidget())
458             || QApplication::focusWidget() == mdiSubWindow())
459         state.focused = true;
460     if (!mdiSubWindow()->isVisible())
461         state.invisible = true;
462     if (const QWidget *parent = mdiSubWindow()->parentWidget())
463         if (!parent->contentsRect().contains(mdiSubWindow()->geometry()))
464             state.offscreen = true;
465     if (!mdiSubWindow()->isEnabled())
466         state.disabled = true;
467     return state;
468 }
469 
childCount() const470 int QAccessibleMdiSubWindow::childCount() const
471 {
472     if (mdiSubWindow()->widget())
473         return 1;
474     return 0;
475 }
476 
child(int index) const477 QAccessibleInterface *QAccessibleMdiSubWindow::child(int index) const
478 {
479     QMdiSubWindow *source = mdiSubWindow();
480     if (index != 0 || !source->widget())
481         return nullptr;
482 
483     return QAccessible::queryAccessibleInterface(source->widget());
484 }
485 
indexOfChild(const QAccessibleInterface * child) const486 int QAccessibleMdiSubWindow::indexOfChild(const QAccessibleInterface *child) const
487 {
488     if (child && child->object() && child->object() == mdiSubWindow()->widget())
489         return 0;
490     return -1;
491 }
492 
rect() const493 QRect QAccessibleMdiSubWindow::rect() const
494 {
495     if (mdiSubWindow()->isHidden())
496         return QRect();
497     if (!mdiSubWindow()->parent())
498         return QAccessibleWidget::rect();
499     const QPoint pos = mdiSubWindow()->mapToGlobal(QPoint(0, 0));
500     return QRect(pos, mdiSubWindow()->size());
501 }
502 
mdiSubWindow() const503 QMdiSubWindow *QAccessibleMdiSubWindow::mdiSubWindow() const
504 {
505     return static_cast<QMdiSubWindow *>(object());
506 }
507 #endif // QT_CONFIG(mdiarea)
508 
509 #if QT_CONFIG(dialogbuttonbox)
510 // ======================= QAccessibleDialogButtonBox ======================
QAccessibleDialogButtonBox(QWidget * widget)511 QAccessibleDialogButtonBox::QAccessibleDialogButtonBox(QWidget *widget)
512     : QAccessibleWidget(widget, QAccessible::Grouping)
513 {
514     Q_ASSERT(qobject_cast<QDialogButtonBox*>(widget));
515 }
516 
517 #endif // QT_CONFIG(dialogbuttonbox)
518 
519 #if QT_CONFIG(textbrowser) && !defined(QT_NO_CURSOR)
QAccessibleTextBrowser(QWidget * widget)520 QAccessibleTextBrowser::QAccessibleTextBrowser(QWidget *widget)
521     : QAccessibleTextEdit(widget)
522 {
523     Q_ASSERT(qobject_cast<QTextBrowser *>(widget));
524 }
525 
role() const526 QAccessible::Role QAccessibleTextBrowser::role() const
527 {
528     return QAccessible::StaticText;
529 }
530 #endif // QT_CONFIG(textbrowser) && QT_NO_CURSOR
531 
532 #if QT_CONFIG(calendarwidget)
533 // ===================== QAccessibleCalendarWidget ========================
QAccessibleCalendarWidget(QWidget * widget)534 QAccessibleCalendarWidget::QAccessibleCalendarWidget(QWidget *widget)
535     : QAccessibleWidget(widget, QAccessible::Table)
536 {
537     Q_ASSERT(qobject_cast<QCalendarWidget *>(widget));
538 }
539 
childCount() const540 int QAccessibleCalendarWidget::childCount() const
541 {
542    return calendarWidget()->isNavigationBarVisible() ? 2 : 1;
543 }
544 
indexOfChild(const QAccessibleInterface * child) const545 int QAccessibleCalendarWidget::indexOfChild(const QAccessibleInterface *child) const
546 {
547     if (!child || !child->object() || childCount() <= 0)
548         return -1;
549     if (qobject_cast<QAbstractItemView *>(child->object()))
550         return childCount() - 1; // FIXME
551     return 0;
552 }
553 
child(int index) const554 QAccessibleInterface *QAccessibleCalendarWidget::child(int index) const
555 {
556     if (index < 0 || index >= childCount())
557         return nullptr;
558 
559     if (childCount() > 1 && index == 0)
560         return QAccessible::queryAccessibleInterface(navigationBar());
561 
562     return QAccessible::queryAccessibleInterface(calendarView());
563 }
564 
calendarWidget() const565 QCalendarWidget *QAccessibleCalendarWidget::calendarWidget() const
566 {
567     return static_cast<QCalendarWidget *>(object());
568 }
569 
calendarView() const570 QAbstractItemView *QAccessibleCalendarWidget::calendarView() const
571 {
572     for (QObject *child : calendarWidget()->children()) {
573         if (child->objectName() == QLatin1String("qt_calendar_calendarview"))
574             return static_cast<QAbstractItemView *>(child);
575     }
576     return nullptr;
577 }
578 
navigationBar() const579 QWidget *QAccessibleCalendarWidget::navigationBar() const
580 {
581     for (QObject *child : calendarWidget()->children()) {
582         if (child->objectName() == QLatin1String("qt_calendar_navigationbar"))
583             return static_cast<QWidget *>(child);
584     }
585     return nullptr;
586 }
587 #endif // QT_CONFIG(calendarwidget)
588 
589 #if QT_CONFIG(dockwidget)
590 
591 // Dock Widget - order of children:
592 // - Content widget
593 // - Float button
594 // - Close button
595 // If there is a custom title bar widget, that one becomes child 1, after the content 0
596 // (in that case the buttons are ignored)
QAccessibleDockWidget(QWidget * widget)597 QAccessibleDockWidget::QAccessibleDockWidget(QWidget *widget)
598     : QAccessibleWidget(widget, QAccessible::Window)
599 {
600 }
601 
dockWidgetLayout() const602 QDockWidgetLayout *QAccessibleDockWidget::dockWidgetLayout() const
603 {
604     return qobject_cast<QDockWidgetLayout*>(dockWidget()->layout());
605 }
606 
childCount() const607 int QAccessibleDockWidget::childCount() const
608 {
609     if (dockWidget()->titleBarWidget()) {
610         return dockWidget()->widget() ? 2 : 1;
611     }
612     return dockWidgetLayout()->count();
613 }
614 
child(int index) const615 QAccessibleInterface *QAccessibleDockWidget::child(int index) const
616 {
617     if (dockWidget()->titleBarWidget()) {
618         if ((!dockWidget()->widget() && index == 0) || (index == 1))
619             return QAccessible::queryAccessibleInterface(dockWidget()->titleBarWidget());
620         if (index == 0)
621             return QAccessible::queryAccessibleInterface(dockWidget()->widget());
622     } else {
623         QLayoutItem *item = dockWidgetLayout()->itemAt(index);
624         if (item)
625             return QAccessible::queryAccessibleInterface(item->widget());
626     }
627     return nullptr;
628 }
629 
indexOfChild(const QAccessibleInterface * child) const630 int QAccessibleDockWidget::indexOfChild(const QAccessibleInterface *child) const
631 {
632     if (!child || !child->object() || child->object()->parent() != object())
633         return -1;
634 
635     if (dockWidget()->titleBarWidget() == child->object()) {
636         return dockWidget()->widget() ? 1 : 0;
637     }
638 
639     return dockWidgetLayout()->indexOf(qobject_cast<QWidget*>(child->object()));
640 }
641 
rect() const642 QRect QAccessibleDockWidget::rect() const
643 {
644     QRect rect;
645 
646     if (dockWidget()->isFloating()) {
647         rect = dockWidget()->frameGeometry();
648     } else {
649         rect = dockWidget()->rect();
650         rect.moveTopLeft(dockWidget()->mapToGlobal(rect.topLeft()));
651     }
652 
653     return rect;
654 }
655 
dockWidget() const656 QDockWidget *QAccessibleDockWidget::dockWidget() const
657 {
658     return static_cast<QDockWidget *>(object());
659 }
660 
text(QAccessible::Text t) const661 QString QAccessibleDockWidget::text(QAccessible::Text t) const
662 {
663     if (t == QAccessible::Name) {
664         return qt_accStripAmp(dockWidget()->windowTitle());
665     } else if (t == QAccessible::Accelerator) {
666         return qt_accHotKey(dockWidget()->windowTitle());
667     }
668     return QString();
669 }
670 #endif // QT_CONFIG(dockwidget)
671 
672 #ifndef QT_NO_CURSOR
673 
QAccessibleTextWidget(QWidget * o,QAccessible::Role r,const QString & name)674 QAccessibleTextWidget::QAccessibleTextWidget(QWidget *o, QAccessible::Role r, const QString &name):
675     QAccessibleWidget(o, r, name)
676 {
677 
678 }
679 
state() const680 QAccessible::State QAccessibleTextWidget::state() const
681 {
682     QAccessible::State s = QAccessibleWidget::state();
683     s.selectableText = true;
684     s.multiLine = true;
685     return s;
686 }
687 
characterRect(int offset) const688 QRect QAccessibleTextWidget::characterRect(int offset) const
689 {
690     QTextBlock block = textDocument()->findBlock(offset);
691     if (!block.isValid())
692         return QRect();
693 
694     QTextLayout *layout = block.layout();
695     QPointF layoutPosition = layout->position();
696     int relativeOffset = offset - block.position();
697     QTextLine line = layout->lineForTextPosition(relativeOffset);
698 
699     QRect r;
700 
701     if (line.isValid()) {
702         qreal x = line.cursorToX(relativeOffset);
703 
704         QTextCharFormat format;
705         QTextBlock::iterator iter = block.begin();
706         if (iter.atEnd())
707             format = block.charFormat();
708         else {
709             while (!iter.atEnd() && !iter.fragment().contains(offset))
710                 ++iter;
711             if (iter.atEnd()) // newline should have same format as preceding character
712                 --iter;
713             format = iter.fragment().charFormat();
714         }
715 
716         QFontMetrics fm(format.font());
717         const QString ch = text(offset, offset + 1);
718         if (!ch.isEmpty()) {
719             int w = fm.horizontalAdvance(ch);
720             int h = fm.height();
721             r = QRect(layoutPosition.x() + x, layoutPosition.y() + line.y() + line.ascent() + fm.descent() - h,
722                       w, h);
723             r.moveTo(viewport()->mapToGlobal(r.topLeft()));
724         }
725         r.translate(-scrollBarPosition());
726     }
727 
728     return r;
729 }
730 
offsetAtPoint(const QPoint & point) const731 int QAccessibleTextWidget::offsetAtPoint(const QPoint &point) const
732 {
733     QPoint p = viewport()->mapFromGlobal(point);
734     // convert to document coordinates
735     p += scrollBarPosition();
736     return textDocument()->documentLayout()->hitTest(p, Qt::ExactHit);
737 }
738 
selectionCount() const739 int QAccessibleTextWidget::selectionCount() const
740 {
741     return textCursor().hasSelection() ? 1 : 0;
742 }
743 
744 namespace {
745 /*!
746     \internal
747     \brief Helper class for AttributeFormatter
748 
749     This class is returned from AttributeFormatter's indexing operator to act
750     as a proxy for the following assignment.
751 
752     It uses perfect forwarding in its assignment operator to amend the RHS
753     with the formatting of the key, using QStringBuilder. Consequently, the
754     RHS can be anything that QStringBuilder supports.
755 */
756 class AttributeFormatterRef {
757     QString &string;
758     const char *key;
759     friend class AttributeFormatter;
AttributeFormatterRef(QString & string,const char * key)760     AttributeFormatterRef(QString &string, const char *key) : string(string), key(key) {}
761 public:
762     template <typename RHS>
operator =(RHS && rhs)763     void operator=(RHS &&rhs)
764     { string += QLatin1String(key) + QLatin1Char(':') + std::forward<RHS>(rhs) + QLatin1Char(';'); }
765 };
766 
767 /*!
768     \internal
769     \brief Small string-builder class that supports a map-like API to serialize key-value pairs.
770     \code
771     AttributeFormatter attrs;
772     attrs["foo"] = QLatinString("hello") + world + QLatin1Char('!');
773     \endcode
774     The key type is always \c{const char*}, and the right-hand-side can
775     be any QStringBuilder expression.
776 
777     Breaking it down, this class provides the indexing operator, stores
778     the key in an instance of, and then returns, AttributeFormatterRef,
779     which is the class that provides the assignment part of the operation.
780 */
781 class AttributeFormatter {
782     QString string;
783 public:
operator [](const char * key)784     AttributeFormatterRef operator[](const char *key)
785     { return AttributeFormatterRef(string, key); }
786 
toFormatted() const787     QString toFormatted() const { return string; }
788 };
789 } // unnamed namespace
790 
attributes(int offset,int * startOffset,int * endOffset) const791 QString QAccessibleTextWidget::attributes(int offset, int *startOffset, int *endOffset) const
792 {
793     /* The list of attributes can be found at:
794      http://linuxfoundation.org/collaborate/workgroups/accessibility/iaccessible2/textattributes
795     */
796 
797     // IAccessible2 defines -1 as length and -2 as cursor position
798     if (offset == -2)
799         offset = cursorPosition();
800 
801     const int charCount = characterCount();
802 
803     // -1 doesn't make much sense here, but it's better to return something
804     // screen readers may ask for text attributes at the cursor pos which may be equal to length
805     if (offset == -1 || offset == charCount)
806         offset = charCount - 1;
807 
808     if (offset < 0 || offset > charCount) {
809         *startOffset = -1;
810         *endOffset = -1;
811         return QString();
812     }
813 
814 
815     QTextCursor cursor = textCursor();
816     cursor.setPosition(offset);
817     QTextBlock block = cursor.block();
818 
819     int blockStart = block.position();
820     int blockEnd = blockStart + block.length();
821 
822     QTextBlock::iterator iter = block.begin();
823     int lastFragmentIndex = blockStart;
824     while (!iter.atEnd()) {
825         QTextFragment f = iter.fragment();
826         if (f.contains(offset))
827             break;
828         lastFragmentIndex = f.position() + f.length();
829         ++iter;
830     }
831 
832     QTextCharFormat charFormat;
833     if (!iter.atEnd()) {
834         QTextFragment fragment = iter.fragment();
835         charFormat = fragment.charFormat();
836         int pos = fragment.position();
837         // text block and fragment may overlap, use the smallest common range
838         *startOffset = qMax(pos, blockStart);
839         *endOffset = qMin(pos + fragment.length(), blockEnd);
840     } else {
841         charFormat = block.charFormat();
842         *startOffset = lastFragmentIndex;
843         *endOffset = blockEnd;
844     }
845     Q_ASSERT(*startOffset <= offset);
846     Q_ASSERT(*endOffset >= offset);
847 
848     QTextBlockFormat blockFormat = cursor.blockFormat();
849 
850     const QFont charFormatFont = charFormat.font();
851 
852     AttributeFormatter attrs;
853     QString family = charFormatFont.family();
854     if (!family.isEmpty()) {
855         family = family.replace('\\', QLatin1String("\\\\"));
856         family = family.replace(':', QLatin1String("\\:"));
857         family = family.replace(',', QLatin1String("\\,"));
858         family = family.replace('=', QLatin1String("\\="));
859         family = family.replace(';', QLatin1String("\\;"));
860         family = family.replace('\"', QLatin1String("\\\""));
861         attrs["font-family"] = QLatin1Char('"') + family + QLatin1Char('"');
862     }
863 
864     int fontSize = int(charFormatFont.pointSize());
865     if (fontSize)
866         attrs["font-size"] = QString::fromLatin1("%1pt").arg(fontSize);
867 
868     //Different weight values are not handled
869     attrs["font-weight"] = QString::fromLatin1(charFormatFont.weight() > QFont::Normal ? "bold" : "normal");
870 
871     QFont::Style style = charFormatFont.style();
872     attrs["font-style"] = QString::fromLatin1((style == QFont::StyleItalic) ? "italic" : ((style == QFont::StyleOblique) ? "oblique": "normal"));
873 
874     QTextCharFormat::UnderlineStyle underlineStyle = charFormat.underlineStyle();
875     if (underlineStyle == QTextCharFormat::NoUnderline && charFormatFont.underline()) // underline could still be set in the default font
876         underlineStyle = QTextCharFormat::SingleUnderline;
877     QString underlineStyleValue;
878     switch (underlineStyle) {
879         case QTextCharFormat::NoUnderline:
880             break;
881         case QTextCharFormat::SingleUnderline:
882             underlineStyleValue = QStringLiteral("solid");
883             break;
884         case QTextCharFormat::DashUnderline:
885             underlineStyleValue = QStringLiteral("dash");
886             break;
887         case QTextCharFormat::DotLine:
888             underlineStyleValue = QStringLiteral("dash");
889             break;
890         case QTextCharFormat::DashDotLine:
891             underlineStyleValue = QStringLiteral("dot-dash");
892             break;
893         case QTextCharFormat::DashDotDotLine:
894             underlineStyleValue = QStringLiteral("dot-dot-dash");
895             break;
896         case QTextCharFormat::WaveUnderline:
897             underlineStyleValue = QStringLiteral("wave");
898             break;
899         case QTextCharFormat::SpellCheckUnderline:
900             underlineStyleValue = QStringLiteral("wave"); // this is not correct, but provides good approximation at least
901             break;
902         default:
903             qWarning() << "Unknown QTextCharFormat::​UnderlineStyle value " << underlineStyle << " could not be translated to IAccessible2 value";
904             break;
905     }
906     if (!underlineStyleValue.isNull()) {
907         attrs["text-underline-style"] = underlineStyleValue;
908         attrs["text-underline-type"] = QStringLiteral("single"); // if underlineStyleValue is set, there is an underline, and Qt does not support other than single ones
909     } // else both are "none" which is the default - no need to set them
910 
911     if (block.textDirection() == Qt::RightToLeft)
912         attrs["writing-mode"] = QStringLiteral("rl");
913 
914     QTextCharFormat::VerticalAlignment alignment = charFormat.verticalAlignment();
915     attrs["text-position"] = QString::fromLatin1((alignment == QTextCharFormat::AlignSubScript) ? "sub" : ((alignment == QTextCharFormat::AlignSuperScript) ? "super" : "baseline" ));
916 
917     QBrush background = charFormat.background();
918     if (background.style() == Qt::SolidPattern) {
919         attrs["background-color"] = QString::fromLatin1("rgb(%1,%2,%3)").arg(background.color().red()).arg(background.color().green()).arg(background.color().blue());
920     }
921 
922     QBrush foreground = charFormat.foreground();
923     if (foreground.style() == Qt::SolidPattern) {
924         attrs["color"] = QString::fromLatin1("rgb(%1,%2,%3)").arg(foreground.color().red()).arg(foreground.color().green()).arg(foreground.color().blue());
925     }
926 
927     switch (blockFormat.alignment() & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignHCenter | Qt::AlignJustify)) {
928     case Qt::AlignLeft:
929         attrs["text-align"] = QStringLiteral("left");
930         break;
931     case Qt::AlignRight:
932         attrs["text-align"] = QStringLiteral("right");
933         break;
934     case Qt::AlignHCenter:
935         attrs["text-align"] = QStringLiteral("center");
936         break;
937     case Qt::AlignJustify:
938         attrs["text-align"] = QStringLiteral("justify");
939         break;
940     }
941 
942     return attrs.toFormatted();
943 }
944 
cursorPosition() const945 int QAccessibleTextWidget::cursorPosition() const
946 {
947     return textCursor().position();
948 }
949 
selection(int selectionIndex,int * startOffset,int * endOffset) const950 void QAccessibleTextWidget::selection(int selectionIndex, int *startOffset, int *endOffset) const
951 {
952     *startOffset = *endOffset = 0;
953     QTextCursor cursor = textCursor();
954 
955     if (selectionIndex != 0 || !cursor.hasSelection())
956         return;
957 
958     *startOffset = cursor.selectionStart();
959     *endOffset = cursor.selectionEnd();
960 }
961 
text(int startOffset,int endOffset) const962 QString QAccessibleTextWidget::text(int startOffset, int endOffset) const
963 {
964     QTextCursor cursor(textCursor());
965 
966     cursor.setPosition(startOffset, QTextCursor::MoveAnchor);
967     cursor.setPosition(endOffset, QTextCursor::KeepAnchor);
968 
969     return cursor.selectedText().replace(QChar(QChar::ParagraphSeparator), QLatin1Char('\n'));
970 }
971 
scrollBarPosition() const972 QPoint QAccessibleTextWidget::scrollBarPosition() const
973 {
974     return QPoint(0, 0);
975 }
976 
977 
textBeforeOffset(int offset,QAccessible::TextBoundaryType boundaryType,int * startOffset,int * endOffset) const978 QString QAccessibleTextWidget::textBeforeOffset(int offset, QAccessible::TextBoundaryType boundaryType,
979                                                 int *startOffset, int *endOffset) const
980 {
981     Q_ASSERT(startOffset);
982     Q_ASSERT(endOffset);
983 
984     QTextCursor cursor = textCursor();
985     cursor.setPosition(offset);
986     QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
987     cursor.setPosition(boundaries.first - 1);
988     boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
989 
990     *startOffset = boundaries.first;
991     *endOffset = boundaries.second;
992 
993     return text(boundaries.first, boundaries.second);
994  }
995 
996 
textAfterOffset(int offset,QAccessible::TextBoundaryType boundaryType,int * startOffset,int * endOffset) const997 QString QAccessibleTextWidget::textAfterOffset(int offset, QAccessible::TextBoundaryType boundaryType,
998                                               int *startOffset, int *endOffset) const
999 {
1000     Q_ASSERT(startOffset);
1001     Q_ASSERT(endOffset);
1002 
1003     QTextCursor cursor = textCursor();
1004     cursor.setPosition(offset);
1005     QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
1006     cursor.setPosition(boundaries.second);
1007     boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
1008 
1009     *startOffset = boundaries.first;
1010     *endOffset = boundaries.second;
1011 
1012     return text(boundaries.first, boundaries.second);
1013 }
1014 
textAtOffset(int offset,QAccessible::TextBoundaryType boundaryType,int * startOffset,int * endOffset) const1015 QString QAccessibleTextWidget::textAtOffset(int offset, QAccessible::TextBoundaryType boundaryType,
1016                                             int *startOffset, int *endOffset) const
1017 {
1018     Q_ASSERT(startOffset);
1019     Q_ASSERT(endOffset);
1020 
1021     QTextCursor cursor = textCursor();
1022     cursor.setPosition(offset);
1023     QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
1024 
1025     *startOffset = boundaries.first;
1026     *endOffset = boundaries.second;
1027 
1028     return text(boundaries.first, boundaries.second);
1029 }
1030 
setCursorPosition(int position)1031 void QAccessibleTextWidget::setCursorPosition(int position)
1032 {
1033     QTextCursor cursor = textCursor();
1034     cursor.setPosition(position);
1035     setTextCursor(cursor);
1036 }
1037 
addSelection(int startOffset,int endOffset)1038 void QAccessibleTextWidget::addSelection(int startOffset, int endOffset)
1039 {
1040     setSelection(0, startOffset, endOffset);
1041 }
1042 
removeSelection(int selectionIndex)1043 void QAccessibleTextWidget::removeSelection(int selectionIndex)
1044 {
1045     if (selectionIndex != 0)
1046         return;
1047 
1048     QTextCursor cursor = textCursor();
1049     cursor.clearSelection();
1050     setTextCursor(cursor);
1051 }
1052 
setSelection(int selectionIndex,int startOffset,int endOffset)1053 void QAccessibleTextWidget::setSelection(int selectionIndex, int startOffset, int endOffset)
1054 {
1055     if (selectionIndex != 0)
1056         return;
1057 
1058     QTextCursor cursor = textCursor();
1059     cursor.setPosition(startOffset, QTextCursor::MoveAnchor);
1060     cursor.setPosition(endOffset, QTextCursor::KeepAnchor);
1061     setTextCursor(cursor);
1062 }
1063 
characterCount() const1064 int QAccessibleTextWidget::characterCount() const
1065 {
1066     QTextCursor cursor = textCursor();
1067     cursor.movePosition(QTextCursor::End);
1068     return cursor.position();
1069 }
1070 
textCursorForRange(int startOffset,int endOffset) const1071 QTextCursor QAccessibleTextWidget::textCursorForRange(int startOffset, int endOffset) const
1072 {
1073     QTextCursor cursor = textCursor();
1074     cursor.setPosition(startOffset, QTextCursor::MoveAnchor);
1075     cursor.setPosition(endOffset, QTextCursor::KeepAnchor);
1076 
1077     return cursor;
1078 }
1079 
deleteText(int startOffset,int endOffset)1080 void QAccessibleTextWidget::deleteText(int startOffset, int endOffset)
1081 {
1082     QTextCursor cursor = textCursorForRange(startOffset, endOffset);
1083     cursor.removeSelectedText();
1084 }
1085 
insertText(int offset,const QString & text)1086 void QAccessibleTextWidget::insertText(int offset, const QString &text)
1087 {
1088     QTextCursor cursor = textCursor();
1089     cursor.setPosition(offset);
1090     cursor.insertText(text);
1091 }
1092 
replaceText(int startOffset,int endOffset,const QString & text)1093 void QAccessibleTextWidget::replaceText(int startOffset, int endOffset, const QString &text)
1094 {
1095     QTextCursor cursor = textCursorForRange(startOffset, endOffset);
1096     cursor.removeSelectedText();
1097     cursor.insertText(text);
1098 }
1099 #endif // QT_NO_CURSOR
1100 
1101 
1102 #if QT_CONFIG(mainwindow)
QAccessibleMainWindow(QWidget * widget)1103 QAccessibleMainWindow::QAccessibleMainWindow(QWidget *widget)
1104     : QAccessibleWidget(widget, QAccessible::Window) { }
1105 
child(int index) const1106 QAccessibleInterface *QAccessibleMainWindow::child(int index) const
1107 {
1108     QList<QWidget*> kids = childWidgets(mainWindow());
1109     if (index >= 0 && index < kids.count()) {
1110         return QAccessible::queryAccessibleInterface(kids.at(index));
1111     }
1112     return nullptr;
1113 }
1114 
childCount() const1115 int QAccessibleMainWindow::childCount() const
1116 {
1117     QList<QWidget*> kids = childWidgets(mainWindow());
1118     return kids.count();
1119 }
1120 
indexOfChild(const QAccessibleInterface * iface) const1121 int QAccessibleMainWindow::indexOfChild(const QAccessibleInterface *iface) const
1122 {
1123     QList<QWidget*> kids = childWidgets(mainWindow());
1124     return kids.indexOf(static_cast<QWidget*>(iface->object()));
1125 }
1126 
childAt(int x,int y) const1127 QAccessibleInterface *QAccessibleMainWindow::childAt(int x, int y) const
1128 {
1129     QWidget *w = widget();
1130     if (!w->isVisible())
1131         return nullptr;
1132     QPoint gp = w->mapToGlobal(QPoint(0, 0));
1133     if (!QRect(gp.x(), gp.y(), w->width(), w->height()).contains(x, y))
1134         return nullptr;
1135 
1136     const QWidgetList kids = childWidgets(mainWindow());
1137     QPoint rp = mainWindow()->mapFromGlobal(QPoint(x, y));
1138     for (QWidget *child : kids) {
1139         if (!child->isWindow() && !child->isHidden() && child->geometry().contains(rp)) {
1140             return QAccessible::queryAccessibleInterface(child);
1141         }
1142     }
1143     return nullptr;
1144 }
1145 
mainWindow() const1146 QMainWindow *QAccessibleMainWindow::mainWindow() const
1147 {
1148     return qobject_cast<QMainWindow *>(object());
1149 }
1150 
1151 #endif // QT_CONFIG(mainwindow)
1152 
1153 QT_END_NAMESPACE
1154 
1155 #endif // QT_NO_ACCESSIBILITY
1156