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
97QT_BEGIN_NAMESPACE
98
99QString qt_accStripAmp(const QString &text);
100QString qt_accHotKey(const QString &text);
101
102QList<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
127QAccessiblePlainTextEdit::QAccessiblePlainTextEdit(QWidget* o)
128 :QAccessibleTextWidget(o)
129{
130 Q_ASSERT(widget()->inherits("QPlainTextEdit"));
131}
132
133QPlainTextEdit* QAccessiblePlainTextEdit::plainTextEdit() const
134{
135 return static_cast<QPlainTextEdit *>(widget());
136}
137
138QString QAccessiblePlainTextEdit::text(QAccessible::Text t) const
139{
140 if (t == QAccessible::Value)
141 return plainTextEdit()->toPlainText();
142
143 return QAccessibleWidget::text(t);
144}
145
146void 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
158QAccessible::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
168void *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
177QPoint 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
185QTextCursor QAccessiblePlainTextEdit::textCursor() const
186{
187 return plainTextEdit()->textCursor();
188}
189
190void QAccessiblePlainTextEdit::setTextCursor(const QTextCursor &textCursor)
191{
192 plainTextEdit()->setTextCursor(textCursor);
193}
194
195QTextDocument* QAccessiblePlainTextEdit::textDocument() const
196{
197 return plainTextEdit()->document();
198}
199
200QWidget* QAccessiblePlainTextEdit::viewport() const
201{
202 return plainTextEdit()->viewport();
203}
204
205void 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*/
224QAccessibleTextEdit::QAccessibleTextEdit(QWidget *o)
225: QAccessibleTextWidget(o, QAccessible::EditableText)
226{
227 Q_ASSERT(widget()->inherits("QTextEdit"));
228}
229
230/*! Returns the text edit. */
231QTextEdit *QAccessibleTextEdit::textEdit() const
232{
233 return static_cast<QTextEdit *>(widget());
234}
235
236QTextCursor QAccessibleTextEdit::textCursor() const
237{
238 return textEdit()->textCursor();
239}
240
241QTextDocument *QAccessibleTextEdit::textDocument() const
242{
243 return textEdit()->document();
244}
245
246void QAccessibleTextEdit::setTextCursor(const QTextCursor &textCursor)
247{
248 textEdit()->setTextCursor(textCursor);
249}
250
251QWidget *QAccessibleTextEdit::viewport() const
252{
253 return textEdit()->viewport();
254}
255
256QPoint 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
264QString QAccessibleTextEdit::text(QAccessible::Text t) const
265{
266 if (t == QAccessible::Value)
267 return textEdit()->toPlainText();
268
269 return QAccessibleWidget::text(t);
270}
271
272void 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
284QAccessible::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
294void *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
303void 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 ======================
326QAccessibleStackedWidget::QAccessibleStackedWidget(QWidget *widget)
327 : QAccessibleWidget(widget, QAccessible::LayeredPane)
328{
329 Q_ASSERT(qobject_cast<QStackedWidget *>(widget));
330}
331
332QAccessibleInterface *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
345int QAccessibleStackedWidget::childCount() const
346{
347 return stackedWidget()->count();
348}
349
350int 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
359QAccessibleInterface *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
366QStackedWidget *QAccessibleStackedWidget::stackedWidget() const
367{
368 return static_cast<QStackedWidget *>(object());
369}
370#endif // QT_CONFIG(stackedwidget)
371
372#if QT_CONFIG(toolbox)
373// ======================= QAccessibleToolBox ======================
374QAccessibleToolBox::QAccessibleToolBox(QWidget *widget)
375 : QAccessibleWidget(widget, QAccessible::LayeredPane)
376{
377 Q_ASSERT(qobject_cast<QToolBox *>(widget));
378}
379
380QToolBox * QAccessibleToolBox::toolBox() const
381{
382 return static_cast<QToolBox *>(object());
383}
384#endif // QT_CONFIG(toolbox)
385
386// ======================= QAccessibleMdiArea ======================
387#if QT_CONFIG(mdiarea)
388QAccessibleMdiArea::QAccessibleMdiArea(QWidget *widget)
389 : QAccessibleWidget(widget, QAccessible::LayeredPane)
390{
391 Q_ASSERT(qobject_cast<QMdiArea *>(widget));
392}
393
394int QAccessibleMdiArea::childCount() const
395{
396 return mdiArea()->subWindowList().count();
397}
398
399QAccessibleInterface *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
409int 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
419QMdiArea *QAccessibleMdiArea::mdiArea() const
420{
421 return static_cast<QMdiArea *>(object());
422}
423
424// ======================= QAccessibleMdiSubWindow ======================
425QAccessibleMdiSubWindow::QAccessibleMdiSubWindow(QWidget *widget)
426 : QAccessibleWidget(widget, QAccessible::Window)
427{
428 Q_ASSERT(qobject_cast<QMdiSubWindow *>(widget));
429}
430
431QString 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
441void 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
449QAccessible::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
470int QAccessibleMdiSubWindow::childCount() const
471{
472 if (mdiSubWindow()->widget())
473 return 1;
474 return 0;
475}
476
477QAccessibleInterface *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
486int QAccessibleMdiSubWindow::indexOfChild(const QAccessibleInterface *child) const
487{
488 if (child && child->object() && child->object() == mdiSubWindow()->widget())
489 return 0;
490 return -1;
491}
492
493QRect 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
503QMdiSubWindow *QAccessibleMdiSubWindow::mdiSubWindow() const
504{
505 return static_cast<QMdiSubWindow *>(object());
506}
507#endif // QT_CONFIG(mdiarea)
508
509#if QT_CONFIG(dialogbuttonbox)
510// ======================= QAccessibleDialogButtonBox ======================
511QAccessibleDialogButtonBox::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)
520QAccessibleTextBrowser::QAccessibleTextBrowser(QWidget *widget)
521 : QAccessibleTextEdit(widget)
522{
523 Q_ASSERT(qobject_cast<QTextBrowser *>(widget));
524}
525
526QAccessible::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 ========================
534QAccessibleCalendarWidget::QAccessibleCalendarWidget(QWidget *widget)
535 : QAccessibleWidget(widget, QAccessible::Table)
536{
537 Q_ASSERT(qobject_cast<QCalendarWidget *>(widget));
538}
539
540int QAccessibleCalendarWidget::childCount() const
541{
542 return calendarWidget()->isNavigationBarVisible() ? 2 : 1;
543}
544
545int 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
554QAccessibleInterface *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
565QCalendarWidget *QAccessibleCalendarWidget::calendarWidget() const
566{
567 return static_cast<QCalendarWidget *>(object());
568}
569
570QAbstractItemView *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
579QWidget *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)
597QAccessibleDockWidget::QAccessibleDockWidget(QWidget *widget)
598 : QAccessibleWidget(widget, QAccessible::Window)
599{
600}
601
602QDockWidgetLayout *QAccessibleDockWidget::dockWidgetLayout() const
603{
604 return qobject_cast<QDockWidgetLayout*>(dockWidget()->layout());
605}
606
607int QAccessibleDockWidget::childCount() const
608{
609 if (dockWidget()->titleBarWidget()) {
610 return dockWidget()->widget() ? 2 : 1;
611 }
612 return dockWidgetLayout()->count();
613}
614
615QAccessibleInterface *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
630int 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
642QRect 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
656QDockWidget *QAccessibleDockWidget::dockWidget() const
657{
658 return static_cast<QDockWidget *>(object());
659}
660
661QString 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
674QAccessibleTextWidget::QAccessibleTextWidget(QWidget *o, QAccessible::Role r, const QString &name):
675 QAccessibleWidget(o, r, name)
676{
677
678}
679
680QAccessible::State QAccessibleTextWidget::state() const
681{
682 QAccessible::State s = QAccessibleWidget::state();
683 s.selectableText = true;
684 s.multiLine = true;
685 return s;
686}
687
688QRect 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
731int 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
739int QAccessibleTextWidget::selectionCount() const
740{
741 return textCursor().hasSelection() ? 1 : 0;
742}
743
744namespace {
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*/
756class AttributeFormatterRef {
757 QString &string;
758 const char *key;
759 friend class AttributeFormatter;
760 AttributeFormatterRef(QString &string, const char *key) : string(string), key(key) {}
761public:
762 template <typename 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*/
781class AttributeFormatter {
782 QString string;
783public:
784 AttributeFormatterRef operator[](const char *key)
785 { return AttributeFormatterRef(string, key); }
786
787 QString toFormatted() const { return string; }
788};
789} // unnamed namespace
790
791QString 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(u'\\', QLatin1String("\\\\"));
856 family = family.replace(u':', QLatin1String("\\:"));
857 family = family.replace(u',', QLatin1String("\\,"));
858 family = family.replace(u'=', QLatin1String("\\="));
859 family = family.replace(u';', QLatin1String("\\;"));
860 family = family.replace(u'\"', 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
945int QAccessibleTextWidget::cursorPosition() const
946{
947 return textCursor().position();
948}
949
950void 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
962QString 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
972QPoint QAccessibleTextWidget::scrollBarPosition() const
973{
974 return QPoint(0, 0);
975}
976
977
978QString 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
997QString 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
1015QString 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
1031void QAccessibleTextWidget::setCursorPosition(int position)
1032{
1033 QTextCursor cursor = textCursor();
1034 cursor.setPosition(position);
1035 setTextCursor(cursor);
1036}
1037
1038void QAccessibleTextWidget::addSelection(int startOffset, int endOffset)
1039{
1040 setSelection(0, startOffset, endOffset);
1041}
1042
1043void QAccessibleTextWidget::removeSelection(int selectionIndex)
1044{
1045 if (selectionIndex != 0)
1046 return;
1047
1048 QTextCursor cursor = textCursor();
1049 cursor.clearSelection();
1050 setTextCursor(cursor);
1051}
1052
1053void 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
1064int QAccessibleTextWidget::characterCount() const
1065{
1066 QTextCursor cursor = textCursor();
1067 cursor.movePosition(QTextCursor::End);
1068 return cursor.position();
1069}
1070
1071QTextCursor 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
1080void QAccessibleTextWidget::deleteText(int startOffset, int endOffset)
1081{
1082 QTextCursor cursor = textCursorForRange(startOffset, endOffset);
1083 cursor.removeSelectedText();
1084}
1085
1086void QAccessibleTextWidget::insertText(int offset, const QString &text)
1087{
1088 QTextCursor cursor = textCursor();
1089 cursor.setPosition(offset);
1090 cursor.insertText(text);
1091}
1092
1093void 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)
1103QAccessibleMainWindow::QAccessibleMainWindow(QWidget *widget)
1104 : QAccessibleWidget(widget, QAccessible::Window) { }
1105
1106QAccessibleInterface *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
1115int QAccessibleMainWindow::childCount() const
1116{
1117 QList<QWidget*> kids = childWidgets(mainWindow());
1118 return kids.count();
1119}
1120
1121int QAccessibleMainWindow::indexOfChild(const QAccessibleInterface *iface) const
1122{
1123 QList<QWidget*> kids = childWidgets(mainWindow());
1124 return kids.indexOf(static_cast<QWidget*>(iface->object()));
1125}
1126
1127QAccessibleInterface *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
1146QMainWindow *QAccessibleMainWindow::mainWindow() const
1147{
1148 return qobject_cast<QMainWindow *>(object());
1149}
1150
1151#endif // QT_CONFIG(mainwindow)
1152
1153QT_END_NAMESPACE
1154
1155#endif // QT_NO_ACCESSIBILITY
1156