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

source code of qtbase/src/widgets/accessible/qaccessiblewidgets.cpp