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 | |
61 | QT_BEGIN_NAMESPACE |
62 | |
63 | using namespace Qt::StringLiterals; |
64 | |
65 | QString qt_accStripAmp(const QString &text); |
66 | QString qt_accHotKey(const QString &text); |
67 | |
68 | QWidgetList _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 | |
95 | QAccessiblePlainTextEdit::QAccessiblePlainTextEdit(QWidget* o) |
96 | :QAccessibleTextWidget(o) |
97 | { |
98 | Q_ASSERT(widget()->inherits("QPlainTextEdit" )); |
99 | } |
100 | |
101 | QPlainTextEdit* QAccessiblePlainTextEdit::plainTextEdit() const |
102 | { |
103 | return static_cast<QPlainTextEdit *>(widget()); |
104 | } |
105 | |
106 | QString QAccessiblePlainTextEdit::text(QAccessible::Text t) const |
107 | { |
108 | if (t == QAccessible::Value) |
109 | return plainTextEdit()->toPlainText(); |
110 | |
111 | return QAccessibleWidget::text(t); |
112 | } |
113 | |
114 | void 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 | |
126 | QAccessible::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 | |
136 | void *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 | |
145 | QPoint 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 | |
153 | QTextCursor QAccessiblePlainTextEdit::textCursor() const |
154 | { |
155 | return plainTextEdit()->textCursor(); |
156 | } |
157 | |
158 | void QAccessiblePlainTextEdit::setTextCursor(const QTextCursor &textCursor) |
159 | { |
160 | plainTextEdit()->setTextCursor(textCursor); |
161 | } |
162 | |
163 | QTextDocument* QAccessiblePlainTextEdit::textDocument() const |
164 | { |
165 | return plainTextEdit()->document(); |
166 | } |
167 | |
168 | QWidget* QAccessiblePlainTextEdit::viewport() const |
169 | { |
170 | return plainTextEdit()->viewport(); |
171 | } |
172 | |
173 | void 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 | */ |
192 | QAccessibleTextEdit::QAccessibleTextEdit(QWidget *o) |
193 | : QAccessibleTextWidget(o, QAccessible::EditableText) |
194 | { |
195 | Q_ASSERT(widget()->inherits("QTextEdit" )); |
196 | } |
197 | |
198 | /*! Returns the text edit. */ |
199 | QTextEdit *QAccessibleTextEdit::textEdit() const |
200 | { |
201 | return static_cast<QTextEdit *>(widget()); |
202 | } |
203 | |
204 | QTextCursor QAccessibleTextEdit::textCursor() const |
205 | { |
206 | return textEdit()->textCursor(); |
207 | } |
208 | |
209 | QTextDocument *QAccessibleTextEdit::textDocument() const |
210 | { |
211 | return textEdit()->document(); |
212 | } |
213 | |
214 | void QAccessibleTextEdit::setTextCursor(const QTextCursor &textCursor) |
215 | { |
216 | textEdit()->setTextCursor(textCursor); |
217 | } |
218 | |
219 | QWidget *QAccessibleTextEdit::viewport() const |
220 | { |
221 | return textEdit()->viewport(); |
222 | } |
223 | |
224 | QPoint 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 | |
232 | QString QAccessibleTextEdit::text(QAccessible::Text t) const |
233 | { |
234 | if (t == QAccessible::Value) |
235 | return textEdit()->toPlainText(); |
236 | |
237 | return QAccessibleWidget::text(t); |
238 | } |
239 | |
240 | void 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 | |
252 | QAccessible::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 | |
262 | void *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 | |
271 | void 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 ====================== |
294 | QAccessibleStackedWidget::QAccessibleStackedWidget(QWidget *widget) |
295 | : QAccessibleWidget(widget, QAccessible::LayeredPane) |
296 | { |
297 | Q_ASSERT(qobject_cast<QStackedWidget *>(widget)); |
298 | } |
299 | |
300 | QAccessibleInterface *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 | |
313 | int QAccessibleStackedWidget::childCount() const |
314 | { |
315 | return stackedWidget()->count(); |
316 | } |
317 | |
318 | int 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 | |
327 | QAccessibleInterface *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 | |
334 | QStackedWidget *QAccessibleStackedWidget::stackedWidget() const |
335 | { |
336 | return static_cast<QStackedWidget *>(object()); |
337 | } |
338 | #endif // QT_CONFIG(stackedwidget) |
339 | |
340 | #if QT_CONFIG(toolbox) |
341 | // ======================= QAccessibleToolBox ====================== |
342 | QAccessibleToolBox::QAccessibleToolBox(QWidget *widget) |
343 | : QAccessibleWidget(widget, QAccessible::LayeredPane) |
344 | { |
345 | Q_ASSERT(qobject_cast<QToolBox *>(widget)); |
346 | } |
347 | |
348 | QToolBox * QAccessibleToolBox::toolBox() const |
349 | { |
350 | return static_cast<QToolBox *>(object()); |
351 | } |
352 | #endif // QT_CONFIG(toolbox) |
353 | |
354 | // ======================= QAccessibleMdiArea ====================== |
355 | #if QT_CONFIG(mdiarea) |
356 | QAccessibleMdiArea::QAccessibleMdiArea(QWidget *widget) |
357 | : QAccessibleWidget(widget, QAccessible::LayeredPane) |
358 | { |
359 | Q_ASSERT(qobject_cast<QMdiArea *>(widget)); |
360 | } |
361 | |
362 | int QAccessibleMdiArea::childCount() const |
363 | { |
364 | return mdiArea()->subWindowList().size(); |
365 | } |
366 | |
367 | QAccessibleInterface *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 | |
377 | int 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 | |
387 | QMdiArea *QAccessibleMdiArea::mdiArea() const |
388 | { |
389 | return static_cast<QMdiArea *>(object()); |
390 | } |
391 | |
392 | // ======================= QAccessibleMdiSubWindow ====================== |
393 | QAccessibleMdiSubWindow::QAccessibleMdiSubWindow(QWidget *widget) |
394 | : QAccessibleWidget(widget, QAccessible::Window) |
395 | { |
396 | Q_ASSERT(qobject_cast<QMdiSubWindow *>(widget)); |
397 | } |
398 | |
399 | QString 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 | |
409 | void 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 | |
417 | QAccessible::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 | |
438 | int QAccessibleMdiSubWindow::childCount() const |
439 | { |
440 | if (mdiSubWindow()->widget()) |
441 | return 1; |
442 | return 0; |
443 | } |
444 | |
445 | QAccessibleInterface *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 | |
454 | int QAccessibleMdiSubWindow::indexOfChild(const QAccessibleInterface *child) const |
455 | { |
456 | if (child && child->object() && child->object() == mdiSubWindow()->widget()) |
457 | return 0; |
458 | return -1; |
459 | } |
460 | |
461 | QRect 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 | |
471 | QMdiSubWindow *QAccessibleMdiSubWindow::mdiSubWindow() const |
472 | { |
473 | return static_cast<QMdiSubWindow *>(object()); |
474 | } |
475 | #endif // QT_CONFIG(mdiarea) |
476 | |
477 | #if QT_CONFIG(dialogbuttonbox) |
478 | // ======================= QAccessibleDialogButtonBox ====================== |
479 | QAccessibleDialogButtonBox::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) |
488 | QAccessibleTextBrowser::QAccessibleTextBrowser(QWidget *widget) |
489 | : QAccessibleTextEdit(widget) |
490 | { |
491 | Q_ASSERT(qobject_cast<QTextBrowser *>(widget)); |
492 | } |
493 | |
494 | QAccessible::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 ======================== |
502 | QAccessibleCalendarWidget::QAccessibleCalendarWidget(QWidget *widget) |
503 | : QAccessibleWidget(widget, QAccessible::Table) |
504 | { |
505 | Q_ASSERT(qobject_cast<QCalendarWidget *>(widget)); |
506 | } |
507 | |
508 | int QAccessibleCalendarWidget::childCount() const |
509 | { |
510 | return calendarWidget()->isNavigationBarVisible() ? 2 : 1; |
511 | } |
512 | |
513 | int 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 | |
522 | QAccessibleInterface *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 | |
533 | QCalendarWidget *QAccessibleCalendarWidget::calendarWidget() const |
534 | { |
535 | return static_cast<QCalendarWidget *>(object()); |
536 | } |
537 | |
538 | QAbstractItemView *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 | |
547 | QWidget *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) |
565 | QAccessibleDockWidget::QAccessibleDockWidget(QWidget *widget) |
566 | : QAccessibleWidget(widget, QAccessible::Window) |
567 | { |
568 | } |
569 | |
570 | QDockWidgetLayout *QAccessibleDockWidget::dockWidgetLayout() const |
571 | { |
572 | return qobject_cast<QDockWidgetLayout*>(object: dockWidget()->layout()); |
573 | } |
574 | |
575 | int QAccessibleDockWidget::childCount() const |
576 | { |
577 | if (dockWidget()->titleBarWidget()) { |
578 | return dockWidget()->widget() ? 2 : 1; |
579 | } |
580 | return dockWidgetLayout()->count(); |
581 | } |
582 | |
583 | QAccessibleInterface *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 | |
598 | int 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 | |
610 | QRect 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 | |
624 | QDockWidget *QAccessibleDockWidget::dockWidget() const |
625 | { |
626 | return static_cast<QDockWidget *>(object()); |
627 | } |
628 | |
629 | QString 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 | |
642 | QAccessibleTextWidget::QAccessibleTextWidget(QWidget *o, QAccessible::Role r, const QString &name): |
643 | QAccessibleWidget(o, r, name) |
644 | { |
645 | |
646 | } |
647 | |
648 | QAccessible::State QAccessibleTextWidget::state() const |
649 | { |
650 | QAccessible::State s = QAccessibleWidget::state(); |
651 | s.selectableText = true; |
652 | s.multiLine = true; |
653 | return s; |
654 | } |
655 | |
656 | QRect 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 | |
699 | int 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 | |
707 | int QAccessibleTextWidget::selectionCount() const |
708 | { |
709 | return textCursor().hasSelection() ? 1 : 0; |
710 | } |
711 | |
712 | namespace { |
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 | */ |
724 | class AttributeFormatterRef { |
725 | QString &string; |
726 | const char *key; |
727 | friend class AttributeFormatter; |
728 | AttributeFormatterRef(QString &string, const char *key) : string(string), key(key) {} |
729 | public: |
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 | */ |
749 | class AttributeFormatter { |
750 | QString string; |
751 | public: |
752 | AttributeFormatterRef operator[](const char *key) |
753 | { return AttributeFormatterRef(string, key); } |
754 | |
755 | QString toFormatted() const { return string; } |
756 | }; |
757 | } // unnamed namespace |
758 | |
759 | QString QAccessibleTextWidget::attributes(int offset, int *startOffset, int *endOffset) const |
760 | { |
761 | /* The list of attributes can be found at: |
762 | http://linuxfoundation.org/collaborate/workgroups/accessibility/iaccessible2/textattributes |
763 | */ |
764 | |
765 | // IAccessible2 defines -1 as length and -2 as cursor position |
766 | if (offset == -2) |
767 | offset = cursorPosition(); |
768 | |
769 | const int charCount = characterCount(); |
770 | |
771 | // -1 doesn't make much sense here, but it's better to return something |
772 | // screen readers may ask for text attributes at the cursor pos which may be equal to length |
773 | if (offset == -1 || offset == charCount) |
774 | offset = charCount - 1; |
775 | |
776 | if (offset < 0 || offset > charCount) { |
777 | *startOffset = -1; |
778 | *endOffset = -1; |
779 | return QString(); |
780 | } |
781 | |
782 | |
783 | QTextCursor cursor = textCursor(); |
784 | cursor.setPosition(pos: offset); |
785 | QTextBlock block = cursor.block(); |
786 | |
787 | int blockStart = block.position(); |
788 | int blockEnd = blockStart + block.length(); |
789 | |
790 | QTextBlock::iterator iter = block.begin(); |
791 | int lastFragmentIndex = blockStart; |
792 | while (!iter.atEnd()) { |
793 | QTextFragment f = iter.fragment(); |
794 | if (f.contains(position: offset)) |
795 | break; |
796 | lastFragmentIndex = f.position() + f.length(); |
797 | ++iter; |
798 | } |
799 | |
800 | QTextCharFormat charFormat; |
801 | if (!iter.atEnd()) { |
802 | QTextFragment fragment = iter.fragment(); |
803 | charFormat = fragment.charFormat(); |
804 | int pos = fragment.position(); |
805 | // text block and fragment may overlap, use the smallest common range |
806 | *startOffset = qMax(a: pos, b: blockStart); |
807 | *endOffset = qMin(a: pos + fragment.length(), b: blockEnd); |
808 | } else { |
809 | charFormat = block.charFormat(); |
810 | *startOffset = lastFragmentIndex; |
811 | *endOffset = blockEnd; |
812 | } |
813 | Q_ASSERT(*startOffset <= offset); |
814 | Q_ASSERT(*endOffset >= offset); |
815 | |
816 | QTextBlockFormat blockFormat = cursor.blockFormat(); |
817 | |
818 | const QFont charFormatFont = charFormat.font(); |
819 | |
820 | AttributeFormatter attrs; |
821 | QString family = charFormatFont.families().value(i: 0, defaultValue: QString()); |
822 | if (!family.isEmpty()) { |
823 | family = family.replace(c: u'\\', after: "\\\\"_L1 ); |
824 | family = family.replace(c: u':', after: "\\:"_L1 ); |
825 | family = family.replace(c: u',', after: "\\,"_L1 ); |
826 | family = family.replace(c: u'=', after: "\\="_L1 ); |
827 | family = family.replace(c: u';', after: "\\;"_L1 ); |
828 | family = family.replace(c: u'\"', after: "\\\""_L1 ); |
829 | attrs["font-family" ] = u'"' + family + u'"'; |
830 | } |
831 | |
832 | int fontSize = int(charFormatFont.pointSize()); |
833 | if (fontSize) |
834 | attrs["font-size" ] = QString::fromLatin1(ba: "%1pt" ).arg(a: fontSize); |
835 | |
836 | //Different weight values are not handled |
837 | attrs["font-weight" ] = QString::fromLatin1(ba: charFormatFont.weight() > QFont::Normal ? "bold" : "normal" ); |
838 | |
839 | QFont::Style style = charFormatFont.style(); |
840 | attrs["font-style" ] = QString::fromLatin1(ba: (style == QFont::StyleItalic) ? "italic" : ((style == QFont::StyleOblique) ? "oblique" : "normal" )); |
841 | |
842 | QTextCharFormat::UnderlineStyle underlineStyle = charFormat.underlineStyle(); |
843 | if (underlineStyle == QTextCharFormat::NoUnderline && charFormatFont.underline()) // underline could still be set in the default font |
844 | underlineStyle = QTextCharFormat::SingleUnderline; |
845 | QString underlineStyleValue; |
846 | switch (underlineStyle) { |
847 | case QTextCharFormat::NoUnderline: |
848 | break; |
849 | case QTextCharFormat::SingleUnderline: |
850 | underlineStyleValue = QStringLiteral("solid" ); |
851 | break; |
852 | case QTextCharFormat::DashUnderline: |
853 | underlineStyleValue = QStringLiteral("dash" ); |
854 | break; |
855 | case QTextCharFormat::DotLine: |
856 | underlineStyleValue = QStringLiteral("dash" ); |
857 | break; |
858 | case QTextCharFormat::DashDotLine: |
859 | underlineStyleValue = QStringLiteral("dot-dash" ); |
860 | break; |
861 | case QTextCharFormat::DashDotDotLine: |
862 | underlineStyleValue = QStringLiteral("dot-dot-dash" ); |
863 | break; |
864 | case QTextCharFormat::WaveUnderline: |
865 | underlineStyleValue = QStringLiteral("wave" ); |
866 | break; |
867 | case QTextCharFormat::SpellCheckUnderline: |
868 | underlineStyleValue = QStringLiteral("wave" ); // this is not correct, but provides good approximation at least |
869 | break; |
870 | default: |
871 | qWarning() << "Unknown QTextCharFormat::UnderlineStyle value " << underlineStyle << " could not be translated to IAccessible2 value" ; |
872 | break; |
873 | } |
874 | if (!underlineStyleValue.isNull()) { |
875 | attrs["text-underline-style" ] = underlineStyleValue; |
876 | attrs["text-underline-type" ] = QStringLiteral("single" ); // if underlineStyleValue is set, there is an underline, and Qt does not support other than single ones |
877 | } // else both are "none" which is the default - no need to set them |
878 | |
879 | if (block.textDirection() == Qt::RightToLeft) |
880 | attrs["writing-mode" ] = QStringLiteral("rl" ); |
881 | |
882 | QTextCharFormat::VerticalAlignment alignment = charFormat.verticalAlignment(); |
883 | attrs["text-position" ] = QString::fromLatin1(ba: (alignment == QTextCharFormat::AlignSubScript) ? "sub" : ((alignment == QTextCharFormat::AlignSuperScript) ? "super" : "baseline" )); |
884 | |
885 | QBrush background = charFormat.background(); |
886 | if (background.style() == Qt::SolidPattern) { |
887 | attrs["background-color" ] = QString::fromLatin1(ba: "rgb(%1,%2,%3)" ).arg(a: background.color().red()).arg(a: background.color().green()).arg(a: background.color().blue()); |
888 | } |
889 | |
890 | QBrush foreground = charFormat.foreground(); |
891 | if (foreground.style() == Qt::SolidPattern) { |
892 | attrs["color" ] = QString::fromLatin1(ba: "rgb(%1,%2,%3)" ).arg(a: foreground.color().red()).arg(a: foreground.color().green()).arg(a: foreground.color().blue()); |
893 | } |
894 | |
895 | switch (blockFormat.alignment() & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignHCenter | Qt::AlignJustify)) { |
896 | case Qt::AlignLeft: |
897 | attrs["text-align" ] = QStringLiteral("left" ); |
898 | break; |
899 | case Qt::AlignRight: |
900 | attrs["text-align" ] = QStringLiteral("right" ); |
901 | break; |
902 | case Qt::AlignHCenter: |
903 | attrs["text-align" ] = QStringLiteral("center" ); |
904 | break; |
905 | case Qt::AlignJustify: |
906 | attrs["text-align" ] = QStringLiteral("justify" ); |
907 | break; |
908 | } |
909 | |
910 | return attrs.toFormatted(); |
911 | } |
912 | |
913 | int QAccessibleTextWidget::cursorPosition() const |
914 | { |
915 | return textCursor().position(); |
916 | } |
917 | |
918 | void QAccessibleTextWidget::selection(int selectionIndex, int *startOffset, int *endOffset) const |
919 | { |
920 | *startOffset = *endOffset = 0; |
921 | QTextCursor cursor = textCursor(); |
922 | |
923 | if (selectionIndex != 0 || !cursor.hasSelection()) |
924 | return; |
925 | |
926 | *startOffset = cursor.selectionStart(); |
927 | *endOffset = cursor.selectionEnd(); |
928 | } |
929 | |
930 | QString QAccessibleTextWidget::text(int startOffset, int endOffset) const |
931 | { |
932 | QTextCursor cursor(textCursor()); |
933 | |
934 | cursor.setPosition(pos: startOffset, mode: QTextCursor::MoveAnchor); |
935 | cursor.setPosition(pos: endOffset, mode: QTextCursor::KeepAnchor); |
936 | |
937 | return cursor.selectedText().replace(before: QChar(QChar::ParagraphSeparator), after: u'\n'); |
938 | } |
939 | |
940 | QPoint QAccessibleTextWidget::scrollBarPosition() const |
941 | { |
942 | return QPoint(0, 0); |
943 | } |
944 | |
945 | |
946 | QString QAccessibleTextWidget::textBeforeOffset(int offset, QAccessible::TextBoundaryType boundaryType, |
947 | int *startOffset, int *endOffset) const |
948 | { |
949 | Q_ASSERT(startOffset); |
950 | Q_ASSERT(endOffset); |
951 | |
952 | QTextCursor cursor = textCursor(); |
953 | cursor.setPosition(pos: offset); |
954 | QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType); |
955 | cursor.setPosition(pos: boundaries.first - 1); |
956 | boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType); |
957 | |
958 | *startOffset = boundaries.first; |
959 | *endOffset = boundaries.second; |
960 | |
961 | return text(startOffset: boundaries.first, endOffset: boundaries.second); |
962 | } |
963 | |
964 | |
965 | QString QAccessibleTextWidget::textAfterOffset(int offset, QAccessible::TextBoundaryType boundaryType, |
966 | int *startOffset, int *endOffset) const |
967 | { |
968 | Q_ASSERT(startOffset); |
969 | Q_ASSERT(endOffset); |
970 | |
971 | QTextCursor cursor = textCursor(); |
972 | cursor.setPosition(pos: offset); |
973 | QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType); |
974 | cursor.setPosition(pos: boundaries.second); |
975 | boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType); |
976 | |
977 | *startOffset = boundaries.first; |
978 | *endOffset = boundaries.second; |
979 | |
980 | return text(startOffset: boundaries.first, endOffset: boundaries.second); |
981 | } |
982 | |
983 | QString QAccessibleTextWidget::textAtOffset(int offset, QAccessible::TextBoundaryType boundaryType, |
984 | int *startOffset, int *endOffset) const |
985 | { |
986 | Q_ASSERT(startOffset); |
987 | Q_ASSERT(endOffset); |
988 | |
989 | QTextCursor cursor = textCursor(); |
990 | cursor.setPosition(pos: offset); |
991 | QPair<int, int> boundaries = QAccessible::qAccessibleTextBoundaryHelper(cursor, boundaryType); |
992 | |
993 | *startOffset = boundaries.first; |
994 | *endOffset = boundaries.second; |
995 | |
996 | return text(startOffset: boundaries.first, endOffset: boundaries.second); |
997 | } |
998 | |
999 | void QAccessibleTextWidget::setCursorPosition(int position) |
1000 | { |
1001 | QTextCursor cursor = textCursor(); |
1002 | cursor.setPosition(pos: position); |
1003 | setTextCursor(cursor); |
1004 | } |
1005 | |
1006 | void QAccessibleTextWidget::addSelection(int startOffset, int endOffset) |
1007 | { |
1008 | setSelection(selectionIndex: 0, startOffset, endOffset); |
1009 | } |
1010 | |
1011 | void QAccessibleTextWidget::removeSelection(int selectionIndex) |
1012 | { |
1013 | if (selectionIndex != 0) |
1014 | return; |
1015 | |
1016 | QTextCursor cursor = textCursor(); |
1017 | cursor.clearSelection(); |
1018 | setTextCursor(cursor); |
1019 | } |
1020 | |
1021 | void QAccessibleTextWidget::setSelection(int selectionIndex, int startOffset, int endOffset) |
1022 | { |
1023 | if (selectionIndex != 0) |
1024 | return; |
1025 | |
1026 | QTextCursor cursor = textCursor(); |
1027 | cursor.setPosition(pos: startOffset, mode: QTextCursor::MoveAnchor); |
1028 | cursor.setPosition(pos: endOffset, mode: QTextCursor::KeepAnchor); |
1029 | setTextCursor(cursor); |
1030 | } |
1031 | |
1032 | int QAccessibleTextWidget::characterCount() const |
1033 | { |
1034 | QTextCursor cursor = textCursor(); |
1035 | cursor.movePosition(op: QTextCursor::End); |
1036 | return cursor.position(); |
1037 | } |
1038 | |
1039 | QTextCursor QAccessibleTextWidget::textCursorForRange(int startOffset, int endOffset) const |
1040 | { |
1041 | QTextCursor cursor = textCursor(); |
1042 | cursor.setPosition(pos: startOffset, mode: QTextCursor::MoveAnchor); |
1043 | cursor.setPosition(pos: endOffset, mode: QTextCursor::KeepAnchor); |
1044 | |
1045 | return cursor; |
1046 | } |
1047 | |
1048 | void QAccessibleTextWidget::deleteText(int startOffset, int endOffset) |
1049 | { |
1050 | QTextCursor cursor = textCursorForRange(startOffset, endOffset); |
1051 | cursor.removeSelectedText(); |
1052 | } |
1053 | |
1054 | void QAccessibleTextWidget::insertText(int offset, const QString &text) |
1055 | { |
1056 | QTextCursor cursor = textCursor(); |
1057 | cursor.setPosition(pos: offset); |
1058 | cursor.insertText(text); |
1059 | } |
1060 | |
1061 | void QAccessibleTextWidget::replaceText(int startOffset, int endOffset, const QString &text) |
1062 | { |
1063 | QTextCursor cursor = textCursorForRange(startOffset, endOffset); |
1064 | cursor.removeSelectedText(); |
1065 | cursor.insertText(text); |
1066 | } |
1067 | #endif // QT_NO_CURSOR |
1068 | |
1069 | |
1070 | #if QT_CONFIG(mainwindow) |
1071 | QAccessibleMainWindow::QAccessibleMainWindow(QWidget *widget) |
1072 | : QAccessibleWidget(widget, QAccessible::Window) { } |
1073 | |
1074 | QAccessibleInterface *QAccessibleMainWindow::child(int index) const |
1075 | { |
1076 | QList<QWidget*> kids = _q_ac_childWidgets(widget: mainWindow()); |
1077 | if (index >= 0 && index < kids.size()) { |
1078 | return QAccessible::queryAccessibleInterface(kids.at(i: index)); |
1079 | } |
1080 | return nullptr; |
1081 | } |
1082 | |
1083 | int QAccessibleMainWindow::childCount() const |
1084 | { |
1085 | QList<QWidget*> kids = _q_ac_childWidgets(widget: mainWindow()); |
1086 | return kids.size(); |
1087 | } |
1088 | |
1089 | int QAccessibleMainWindow::indexOfChild(const QAccessibleInterface *iface) const |
1090 | { |
1091 | QList<QWidget*> kids = _q_ac_childWidgets(widget: mainWindow()); |
1092 | return kids.indexOf(t: static_cast<QWidget*>(iface->object())); |
1093 | } |
1094 | |
1095 | QAccessibleInterface *QAccessibleMainWindow::childAt(int x, int y) const |
1096 | { |
1097 | QWidget *w = widget(); |
1098 | if (!w->isVisible()) |
1099 | return nullptr; |
1100 | QPoint gp = w->mapToGlobal(QPoint(0, 0)); |
1101 | if (!QRect(gp.x(), gp.y(), w->width(), w->height()).contains(ax: x, ay: y)) |
1102 | return nullptr; |
1103 | |
1104 | const QWidgetList kids = _q_ac_childWidgets(widget: mainWindow()); |
1105 | QPoint rp = mainWindow()->mapFromGlobal(QPoint(x, y)); |
1106 | for (QWidget *child : kids) { |
1107 | if (!child->isWindow() && !child->isHidden() && child->geometry().contains(p: rp)) { |
1108 | return QAccessible::queryAccessibleInterface(child); |
1109 | } |
1110 | } |
1111 | return nullptr; |
1112 | } |
1113 | |
1114 | QMainWindow *QAccessibleMainWindow::mainWindow() const |
1115 | { |
1116 | return qobject_cast<QMainWindow *>(object: object()); |
1117 | } |
1118 | |
1119 | #endif // QT_CONFIG(mainwindow) |
1120 | |
1121 | QT_END_NAMESPACE |
1122 | |
1123 | #endif // QT_CONFIG(accessibility) |
1124 | |