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