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(qobject_cast<QPlainTextEdit *>(widget()));
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(qobject_cast<QTextEdit *>(widget()));
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 attrs["text-line-through-type"] = charFormatFont.strikeOut() ? "single"_L1 : "none"_L1;
843
844 QTextCharFormat::UnderlineStyle underlineStyle = charFormat.underlineStyle();
845 if (underlineStyle == QTextCharFormat::NoUnderline && charFormatFont.underline()) // underline could still be set in the default font
846 underlineStyle = QTextCharFormat::SingleUnderline;
847 QString underlineStyleValue;
848 switch (underlineStyle) {
849 case QTextCharFormat::NoUnderline:
850 break;
851 case QTextCharFormat::SingleUnderline:
852 underlineStyleValue = QStringLiteral("solid");
853 break;
854 case QTextCharFormat::DashUnderline:
855 underlineStyleValue = QStringLiteral("dash");
856 break;
857 case QTextCharFormat::DotLine:
858 underlineStyleValue = QStringLiteral("dash");
859 break;
860 case QTextCharFormat::DashDotLine:
861 underlineStyleValue = QStringLiteral("dot-dash");
862 break;
863 case QTextCharFormat::DashDotDotLine:
864 underlineStyleValue = QStringLiteral("dot-dot-dash");
865 break;
866 case QTextCharFormat::WaveUnderline:
867 underlineStyleValue = QStringLiteral("wave");
868 break;
869 case QTextCharFormat::SpellCheckUnderline:
870 underlineStyleValue = QStringLiteral("wave"); // this is not correct, but provides good approximation at least
871 break;
872 default:
873 qWarning() << "Unknown QTextCharFormat::UnderlineStyle value " << underlineStyle << " could not be translated to IAccessible2 value";
874 break;
875 }
876 if (!underlineStyleValue.isNull()) {
877 attrs["text-underline-style"] = underlineStyleValue;
878 attrs["text-underline-type"] = QStringLiteral("single"); // if underlineStyleValue is set, there is an underline, and Qt does not support other than single ones
879 } // else both are "none" which is the default - no need to set them
880
881 if (block.textDirection() == Qt::RightToLeft)
882 attrs["writing-mode"] = QStringLiteral("rl");
883
884 QTextCharFormat::VerticalAlignment alignment = charFormat.verticalAlignment();
885 attrs["text-position"] = QString::fromLatin1(ba: (alignment == QTextCharFormat::AlignSubScript) ? "sub" : ((alignment == QTextCharFormat::AlignSuperScript) ? "super" : "baseline" ));
886
887 QBrush background = charFormat.background();
888 if (background.style() == Qt::SolidPattern) {
889 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());
890 }
891
892 QBrush foreground = charFormat.foreground();
893 if (foreground.style() == Qt::SolidPattern) {
894 attrs["color"] = QString::fromLatin1(ba: "rgb(%1,%2,%3)").arg(a: foreground.color().red()).arg(a: foreground.color().green()).arg(a: foreground.color().blue());
895 }
896
897 switch (blockFormat.alignment() & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignHCenter | Qt::AlignJustify)) {
898 case Qt::AlignLeft:
899 attrs["text-align"] = QStringLiteral("left");
900 break;
901 case Qt::AlignRight:
902 attrs["text-align"] = QStringLiteral("right");
903 break;
904 case Qt::AlignHCenter:
905 attrs["text-align"] = QStringLiteral("center");
906 break;
907 case Qt::AlignJustify:
908 attrs["text-align"] = QStringLiteral("justify");
909 break;
910 }
911
912 return attrs.toFormatted();
913}
914
915int QAccessibleTextWidget::cursorPosition() const
916{
917 return textCursor().position();
918}
919
920void QAccessibleTextWidget::selection(int selectionIndex, int *startOffset, int *endOffset) const
921{
922 *startOffset = *endOffset = 0;
923 QTextCursor cursor = textCursor();
924
925 if (selectionIndex != 0 || !cursor.hasSelection())
926 return;
927
928 *startOffset = cursor.selectionStart();
929 *endOffset = cursor.selectionEnd();
930}
931
932QString QAccessibleTextWidget::text(int startOffset, int endOffset) const
933{
934 QTextCursor cursor(textCursor());
935
936 cursor.setPosition(pos: startOffset, mode: QTextCursor::MoveAnchor);
937 cursor.setPosition(pos: endOffset, mode: QTextCursor::KeepAnchor);
938
939 return cursor.selectedText().replace(before: QChar(QChar::ParagraphSeparator), after: u'\n');
940}
941
942QPoint QAccessibleTextWidget::scrollBarPosition() const
943{
944 return QPoint(0, 0);
945}
946
947
948QString QAccessibleTextWidget::textBeforeOffset(int offset, QAccessible::TextBoundaryType boundaryType,
949 int *startOffset, int *endOffset) const
950{
951 Q_ASSERT(startOffset);
952 Q_ASSERT(endOffset);
953
954 QTextCursor cursor = textCursor();
955 cursor.setPosition(pos: offset);
956 QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
957 cursor.setPosition(pos: boundaries.first - 1);
958 boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
959
960 *startOffset = boundaries.first;
961 *endOffset = boundaries.second;
962
963 return text(startOffset: boundaries.first, endOffset: boundaries.second);
964 }
965
966
967QString QAccessibleTextWidget::textAfterOffset(int offset, QAccessible::TextBoundaryType boundaryType,
968 int *startOffset, int *endOffset) const
969{
970 Q_ASSERT(startOffset);
971 Q_ASSERT(endOffset);
972
973 QTextCursor cursor = textCursor();
974 cursor.setPosition(pos: offset);
975 QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
976 cursor.setPosition(pos: boundaries.second);
977 boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
978
979 *startOffset = boundaries.first;
980 *endOffset = boundaries.second;
981
982 return text(startOffset: boundaries.first, endOffset: boundaries.second);
983}
984
985QString QAccessibleTextWidget::textAtOffset(int offset, QAccessible::TextBoundaryType boundaryType,
986 int *startOffset, int *endOffset) const
987{
988 Q_ASSERT(startOffset);
989 Q_ASSERT(endOffset);
990
991 QTextCursor cursor = textCursor();
992 cursor.setPosition(pos: offset);
993 QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType);
994
995 *startOffset = boundaries.first;
996 *endOffset = boundaries.second;
997
998 return text(startOffset: boundaries.first, endOffset: boundaries.second);
999}
1000
1001void QAccessibleTextWidget::setCursorPosition(int position)
1002{
1003 QTextCursor cursor = textCursor();
1004 cursor.setPosition(pos: position);
1005 setTextCursor(cursor);
1006}
1007
1008void QAccessibleTextWidget::addSelection(int startOffset, int endOffset)
1009{
1010 setSelection(selectionIndex: 0, startOffset, endOffset);
1011}
1012
1013void QAccessibleTextWidget::removeSelection(int selectionIndex)
1014{
1015 if (selectionIndex != 0)
1016 return;
1017
1018 QTextCursor cursor = textCursor();
1019 cursor.clearSelection();
1020 setTextCursor(cursor);
1021}
1022
1023void QAccessibleTextWidget::setSelection(int selectionIndex, int startOffset, int endOffset)
1024{
1025 if (selectionIndex != 0)
1026 return;
1027
1028 QTextCursor cursor = textCursor();
1029 cursor.setPosition(pos: startOffset, mode: QTextCursor::MoveAnchor);
1030 cursor.setPosition(pos: endOffset, mode: QTextCursor::KeepAnchor);
1031 setTextCursor(cursor);
1032}
1033
1034int QAccessibleTextWidget::characterCount() const
1035{
1036 QTextCursor cursor = textCursor();
1037 cursor.movePosition(op: QTextCursor::End);
1038 return cursor.position();
1039}
1040
1041QTextCursor QAccessibleTextWidget::textCursorForRange(int startOffset, int endOffset) const
1042{
1043 QTextCursor cursor = textCursor();
1044 cursor.setPosition(pos: startOffset, mode: QTextCursor::MoveAnchor);
1045 cursor.setPosition(pos: endOffset, mode: QTextCursor::KeepAnchor);
1046
1047 return cursor;
1048}
1049
1050void QAccessibleTextWidget::deleteText(int startOffset, int endOffset)
1051{
1052 QTextCursor cursor = textCursorForRange(startOffset, endOffset);
1053 cursor.removeSelectedText();
1054}
1055
1056void QAccessibleTextWidget::insertText(int offset, const QString &text)
1057{
1058 QTextCursor cursor = textCursor();
1059 cursor.setPosition(pos: offset);
1060 cursor.insertText(text);
1061}
1062
1063void QAccessibleTextWidget::replaceText(int startOffset, int endOffset, const QString &text)
1064{
1065 QTextCursor cursor = textCursorForRange(startOffset, endOffset);
1066 cursor.removeSelectedText();
1067 cursor.insertText(text);
1068}
1069#endif // QT_NO_CURSOR
1070
1071
1072#if QT_CONFIG(mainwindow)
1073QAccessibleMainWindow::QAccessibleMainWindow(QWidget *widget)
1074 : QAccessibleWidget(widget, QAccessible::Window) { }
1075
1076QAccessibleInterface *QAccessibleMainWindow::child(int index) const
1077{
1078 QList<QWidget*> kids = _q_ac_childWidgets(widget: mainWindow());
1079 if (index >= 0 && index < kids.size()) {
1080 return QAccessible::queryAccessibleInterface(kids.at(i: index));
1081 }
1082 return nullptr;
1083}
1084
1085int QAccessibleMainWindow::childCount() const
1086{
1087 QList<QWidget*> kids = _q_ac_childWidgets(widget: mainWindow());
1088 return kids.size();
1089}
1090
1091int QAccessibleMainWindow::indexOfChild(const QAccessibleInterface *iface) const
1092{
1093 QList<QWidget*> kids = _q_ac_childWidgets(widget: mainWindow());
1094 return kids.indexOf(t: static_cast<QWidget*>(iface->object()));
1095}
1096
1097QAccessibleInterface *QAccessibleMainWindow::childAt(int x, int y) const
1098{
1099 QWidget *w = widget();
1100 if (!w->isVisible())
1101 return nullptr;
1102 QPoint gp = w->mapToGlobal(QPoint(0, 0));
1103 if (!QRect(gp.x(), gp.y(), w->width(), w->height()).contains(ax: x, ay: y))
1104 return nullptr;
1105
1106 const QWidgetList kids = _q_ac_childWidgets(widget: mainWindow());
1107 QPoint rp = mainWindow()->mapFromGlobal(QPoint(x, y));
1108 for (QWidget *child : kids) {
1109 if (!child->isWindow() && !child->isHidden() && child->geometry().contains(p: rp)) {
1110 return QAccessible::queryAccessibleInterface(child);
1111 }
1112 }
1113 return nullptr;
1114}
1115
1116QMainWindow *QAccessibleMainWindow::mainWindow() const
1117{
1118 return qobject_cast<QMainWindow *>(object: object());
1119}
1120
1121#endif // QT_CONFIG(mainwindow)
1122
1123QT_END_NAMESPACE
1124
1125#endif // QT_CONFIG(accessibility)
1126

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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