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