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