Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qandroidinputcontext.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
3// Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
4// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5
6#include <android/log.h>
7
9#include "androidjniinput.h"
10#include "androidjnimain.h"
14#include "private/qhighdpiscaling_p.h"
15
16#include <QTextBoundaryFinder>
17#include <QTextCharFormat>
18#include <QtCore/QJniEnvironment>
19#include <QtCore/QJniObject>
20#include <qevent.h>
21#include <qguiapplication.h>
22#include <qinputmethod.h>
23#include <qsharedpointer.h>
24#include <qthread.h>
25#include <qwindow.h>
26#include <qpa/qplatformwindow.h>
27
29
30namespace {
31
33{
34public:
35
37 : m_context(context)
38 {
39 m_context->beginBatchEdit();
40 }
41
43 {
44 m_context->endBatchEdit();
45 }
46
47 BatchEditLock(const BatchEditLock &) = delete;
49
50private:
51
52 QAndroidInputContext *m_context;
53};
54
55} // namespace anonymous
56
58static char const *const QtNativeInputConnectionClassName = "org/qtproject/qt/android/QtNativeInputConnection";
59static char const *const QtExtractedTextClassName = "org/qtproject/qt/android/QtExtractedText";
60static int m_selectHandleWidth = 0;
61static jclass m_extractedTextClass = 0;
62static jmethodID m_classConstructorMethodID = 0;
63static jfieldID m_partialEndOffsetFieldID = 0;
64static jfieldID m_partialStartOffsetFieldID = 0;
65static jfieldID m_selectionEndFieldID = 0;
66static jfieldID m_selectionStartFieldID = 0;
67static jfieldID m_startOffsetFieldID = 0;
68static jfieldID m_textFieldID = 0;
69
70static void runOnQtThread(const std::function<void()> &func)
71{
73 if (!protector.acquire())
74 return;
76}
77
78static jboolean beginBatchEdit(JNIEnv */*env*/, jobject /*thiz*/)
79{
81 return JNI_FALSE;
82
83 qCDebug(lcQpaInputMethods) << "@@@ BEGINBATCH";
84 jboolean res = JNI_FALSE;
85 runOnQtThread([&res]{res = m_androidInputContext->beginBatchEdit();});
86 return res;
87}
88
89static jboolean endBatchEdit(JNIEnv */*env*/, jobject /*thiz*/)
90{
92 return JNI_FALSE;
93
94 qCDebug(lcQpaInputMethods) << "@@@ ENDBATCH";
95
96 jboolean res = JNI_FALSE;
97 runOnQtThread([&res]{res = m_androidInputContext->endBatchEdit();});
98 return res;
99}
100
101
102static jboolean commitText(JNIEnv *env, jobject /*thiz*/, jstring text, jint newCursorPosition)
103{
105 return JNI_FALSE;
106
107 jboolean isCopy;
108 const jchar *jstr = env->GetStringChars(text, &isCopy);
109 QString str(reinterpret_cast<const QChar *>(jstr), env->GetStringLength(text));
110 env->ReleaseStringChars(text, jstr);
111
112 qCDebug(lcQpaInputMethods) << "@@@ COMMIT" << str << newCursorPosition;
113 jboolean res = JNI_FALSE;
114 runOnQtThread([&]{res = m_androidInputContext->commitText(str, newCursorPosition);});
115 return res;
116}
117
118static jboolean deleteSurroundingText(JNIEnv */*env*/, jobject /*thiz*/, jint leftLength, jint rightLength)
119{
121 return JNI_FALSE;
122
123 qCDebug(lcQpaInputMethods) << "@@@ DELETE" << leftLength << rightLength;
124 jboolean res = JNI_FALSE;
125 runOnQtThread([&]{res = m_androidInputContext->deleteSurroundingText(leftLength, rightLength);});
126 return res;
127}
128
129static jboolean finishComposingText(JNIEnv */*env*/, jobject /*thiz*/)
130{
132 return JNI_FALSE;
133
134 qCDebug(lcQpaInputMethods) << "@@@ FINISH";
135 jboolean res = JNI_FALSE;
136 runOnQtThread([&]{res = m_androidInputContext->finishComposingText();});
137 return res;
138}
139
140static jint getCursorCapsMode(JNIEnv */*env*/, jobject /*thiz*/, jint reqModes)
141{
143 return 0;
144
145 jint res = 0;
146 runOnQtThread([&]{res = m_androidInputContext->getCursorCapsMode(reqModes);});
147 return res;
148}
149
150static jobject getExtractedText(JNIEnv *env, jobject /*thiz*/, int hintMaxChars, int hintMaxLines, jint flags)
151{
153 return 0;
154
156 runOnQtThread([&]{extractedText = m_androidInputContext->getExtractedText(hintMaxChars, hintMaxLines, flags);});
157
158 qCDebug(lcQpaInputMethods) << "@@@ GETEX" << hintMaxChars << hintMaxLines << QString::fromLatin1("0x") + QString::number(flags,16) << extractedText.text << "partOff:" << extractedText.partialStartOffset << extractedText.partialEndOffset << "sel:" << extractedText.selectionStart << extractedText.selectionEnd << "offset:" << extractedText.startOffset;
159
160 jobject object = env->NewObject(m_extractedTextClass, m_classConstructorMethodID);
161 env->SetIntField(object, m_partialStartOffsetFieldID, extractedText.partialStartOffset);
162 env->SetIntField(object, m_partialEndOffsetFieldID, extractedText.partialEndOffset);
163 env->SetIntField(object, m_selectionStartFieldID, extractedText.selectionStart);
164 env->SetIntField(object, m_selectionEndFieldID, extractedText.selectionEnd);
165 env->SetIntField(object, m_startOffsetFieldID, extractedText.startOffset);
166 env->SetObjectField(object,
168 env->NewString(reinterpret_cast<const jchar *>(extractedText.text.constData()),
169 jsize(extractedText.text.length())));
170
171 return object;
172}
173
174static jstring getSelectedText(JNIEnv *env, jobject /*thiz*/, jint flags)
175{
177 return 0;
178
180 runOnQtThread([&]{text = m_androidInputContext->getSelectedText(flags);});
181 qCDebug(lcQpaInputMethods) << "@@@ GETSEL" << text;
182 if (text.isEmpty())
183 return 0;
184 return env->NewString(reinterpret_cast<const jchar *>(text.constData()), jsize(text.length()));
185}
186
187static jstring getTextAfterCursor(JNIEnv *env, jobject /*thiz*/, jint length, jint flags)
188{
190 return 0;
191
193 runOnQtThread([&]{text = m_androidInputContext->getTextAfterCursor(length, flags);});
194 qCDebug(lcQpaInputMethods) << "@@@ GETA" << length << text;
195 return env->NewString(reinterpret_cast<const jchar *>(text.constData()), jsize(text.length()));
196}
197
198static jstring getTextBeforeCursor(JNIEnv *env, jobject /*thiz*/, jint length, jint flags)
199{
201 return 0;
202
204 runOnQtThread([&]{text = m_androidInputContext->getTextBeforeCursor(length, flags);});
205 qCDebug(lcQpaInputMethods) << "@@@ GETB" << length << text;
206 return env->NewString(reinterpret_cast<const jchar *>(text.constData()), jsize(text.length()));
207}
208
209static jboolean setComposingText(JNIEnv *env, jobject /*thiz*/, jstring text, jint newCursorPosition)
210{
212 return JNI_FALSE;
213
214 jboolean isCopy;
215 const jchar *jstr = env->GetStringChars(text, &isCopy);
216 QString str(reinterpret_cast<const QChar *>(jstr), env->GetStringLength(text));
217 env->ReleaseStringChars(text, jstr);
218
219 qCDebug(lcQpaInputMethods) << "@@@ SET" << str << newCursorPosition;
220 jboolean res = JNI_FALSE;
221 runOnQtThread([&]{res = m_androidInputContext->setComposingText(str, newCursorPosition);});
222 return res;
223}
224
225static jboolean setComposingRegion(JNIEnv */*env*/, jobject /*thiz*/, jint start, jint end)
226{
228 return JNI_FALSE;
229
230 qCDebug(lcQpaInputMethods) << "@@@ SETR" << start << end;
231 jboolean res = JNI_FALSE;
232 runOnQtThread([&]{res = m_androidInputContext->setComposingRegion(start, end);});
233 return res;
234}
235
236
237static jboolean setSelection(JNIEnv */*env*/, jobject /*thiz*/, jint start, jint end)
238{
240 return JNI_FALSE;
241
242 qCDebug(lcQpaInputMethods) << "@@@ SETSEL" << start << end;
243 jboolean res = JNI_FALSE;
244 runOnQtThread([&]{res = m_androidInputContext->setSelection(start, end);});
245 return res;
246
247}
248
249static jboolean selectAll(JNIEnv */*env*/, jobject /*thiz*/)
250{
252 return JNI_FALSE;
253
254 qCDebug(lcQpaInputMethods) << "@@@ SELALL";
255 jboolean res = JNI_FALSE;
256 runOnQtThread([&]{res = m_androidInputContext->selectAll();});
257 return res;
258}
259
260static jboolean cut(JNIEnv */*env*/, jobject /*thiz*/)
261{
263 return JNI_FALSE;
264
265 qCDebug(lcQpaInputMethods) << "@@@";
266 jboolean res = JNI_FALSE;
268 return res;
269}
270
271static jboolean copy(JNIEnv */*env*/, jobject /*thiz*/)
272{
274 return JNI_FALSE;
275
276 qCDebug(lcQpaInputMethods) << "@@@";
277 jboolean res = JNI_FALSE;
278 runOnQtThread([&]{res = m_androidInputContext->copy();});
279 return res;
280}
281
282static jboolean copyURL(JNIEnv */*env*/, jobject /*thiz*/)
283{
285 return JNI_FALSE;
286
287 qCDebug(lcQpaInputMethods) << "@@@";
288 jboolean res = JNI_FALSE;
289 runOnQtThread([&]{res = m_androidInputContext->copyURL();});
290 return res;
291}
292
293static jboolean paste(JNIEnv */*env*/, jobject /*thiz*/)
294{
296 return JNI_FALSE;
297
298 qCDebug(lcQpaInputMethods) << "@@@ PASTE";
299 jboolean res = JNI_FALSE;
300 runOnQtThread([&]{res = m_androidInputContext->paste();});
301 return res;
302}
303
304static jboolean updateCursorPosition(JNIEnv */*env*/, jobject /*thiz*/)
305{
307 return JNI_FALSE;
308
309 qCDebug(lcQpaInputMethods) << "@@@ UPDATECURSORPOS";
310
311 runOnQtThread([&]{m_androidInputContext->updateCursorPosition();});
312 return true;
313}
314
315static void reportFullscreenMode(JNIEnv */*env*/, jobject /*thiz*/, jboolean enabled)
316{
318 return;
319
320 runOnQtThread([&]{m_androidInputContext->reportFullscreenMode(enabled);});
321}
322
323static jboolean fullscreenMode(JNIEnv */*env*/, jobject /*thiz*/)
324{
325 return m_androidInputContext ? m_androidInputContext->fullscreenMode() : false;
326}
327
328static JNINativeMethod methods[] = {
329 {"beginBatchEdit", "()Z", (void *)beginBatchEdit},
330 {"endBatchEdit", "()Z", (void *)endBatchEdit},
331 {"commitText", "(Ljava/lang/String;I)Z", (void *)commitText},
332 {"deleteSurroundingText", "(II)Z", (void *)deleteSurroundingText},
333 {"finishComposingText", "()Z", (void *)finishComposingText},
334 {"getCursorCapsMode", "(I)I", (void *)getCursorCapsMode},
335 {"getExtractedText", "(III)Lorg/qtproject/qt/android/QtExtractedText;", (void *)getExtractedText},
336 {"getSelectedText", "(I)Ljava/lang/String;", (void *)getSelectedText},
337 {"getTextAfterCursor", "(II)Ljava/lang/String;", (void *)getTextAfterCursor},
338 {"getTextBeforeCursor", "(II)Ljava/lang/String;", (void *)getTextBeforeCursor},
339 {"setComposingText", "(Ljava/lang/String;I)Z", (void *)setComposingText},
340 {"setComposingRegion", "(II)Z", (void *)setComposingRegion},
341 {"setSelection", "(II)Z", (void *)setSelection},
342 {"selectAll", "()Z", (void *)selectAll},
343 {"cut", "()Z", (void *)cut},
344 {"copy", "()Z", (void *)copy},
345 {"copyURL", "()Z", (void *)copyURL},
346 {"paste", "()Z", (void *)paste},
347 {"updateCursorPosition", "()Z", (void *)updateCursorPosition},
348 {"reportFullscreenMode", "(Z)V", (void *)reportFullscreenMode},
349 {"fullscreenMode", "()Z", (void *)fullscreenMode}
350};
351
353{
354 QRect windowRect = QPlatformInputContext::inputItemRectangle().toRect();
355 QPlatformWindow *window = qGuiApp->focusWindow()->handle();
356 return QRect(window->mapToGlobal(windowRect.topLeft()), windowRect.size());
357}
358
361 , m_composingTextStart(-1)
362 , m_composingCursor(-1)
363 , m_handleMode(Hidden)
364 , m_batchEditNestingLevel(0)
365 , m_focusObject(0)
366 , m_fullScreenMode(false)
367{
368 QJniEnvironment env;
369 jclass clazz = env.findClass(QtNativeInputConnectionClassName);
370 if (Q_UNLIKELY(!clazz)) {
371 qCritical() << "Native registration unable to find class '"
373 << '\'';
374 return;
375 }
376
377 if (Q_UNLIKELY(env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0)) {
378 qCritical() << "RegisterNatives failed for '"
380 << '\'';
381 return;
382 }
383
384 clazz = env.findClass(QtExtractedTextClassName);
385 if (Q_UNLIKELY(!clazz)) {
386 qCritical() << "Native registration unable to find class '"
388 << '\'';
389 return;
390 }
391
392 m_extractedTextClass = static_cast<jclass>(env->NewGlobalRef(clazz));
393 m_classConstructorMethodID = env->GetMethodID(m_extractedTextClass, "<init>", "()V");
395 qCritical("GetMethodID failed");
396 return;
397 }
398
399 m_partialEndOffsetFieldID = env->GetFieldID(m_extractedTextClass, "partialEndOffset", "I");
401 qCritical("Can't find field partialEndOffset");
402 return;
403 }
404
405 m_partialStartOffsetFieldID = env->GetFieldID(m_extractedTextClass, "partialStartOffset", "I");
407 qCritical("Can't find field partialStartOffset");
408 return;
409 }
410
411 m_selectionEndFieldID = env->GetFieldID(m_extractedTextClass, "selectionEnd", "I");
413 qCritical("Can't find field selectionEnd");
414 return;
415 }
416
417 m_selectionStartFieldID = env->GetFieldID(m_extractedTextClass, "selectionStart", "I");
419 qCritical("Can't find field selectionStart");
420 return;
421 }
422
423 m_startOffsetFieldID = env->GetFieldID(m_extractedTextClass, "startOffset", "I");
425 qCritical("Can't find field startOffset");
426 return;
427 }
428
429 m_textFieldID = env->GetFieldID(m_extractedTextClass, "text", "Ljava/lang/String;");
431 qCritical("Can't find field text");
432 return;
433 }
434 qRegisterMetaType<QInputMethodEvent *>("QInputMethodEvent*");
435 qRegisterMetaType<QInputMethodQueryEvent *>("QInputMethodQueryEvent*");
437
443 auto im = qGuiApp->inputMethod();
444 if (!im->inputItemClipRectangle().contains(im->anchorRectangle()) ||
445 !im->inputItemClipRectangle().contains(im->cursorRectangle())) {
446 m_handleMode = Hidden;
447 updateSelectionHandles();
448 }
449 });
450 m_hideCursorHandleTimer.setInterval(4000);
451 m_hideCursorHandleTimer.setSingleShot(true);
452 m_hideCursorHandleTimer.setTimerType(Qt::VeryCoarseTimer);
453 connect(&m_hideCursorHandleTimer, &QTimer::timeout, this, [this]{
454 m_handleMode = Hidden;
456 });
457}
458
470
475
476// cursor position getter that also works with editors that have not been updated to the new API
477static inline int getAbsoluteCursorPosition(const QSharedPointer<QInputMethodQueryEvent> &query)
478{
480 return absolutePos.isValid() ? absolutePos.toInt() : query->value(Qt::ImCursorPosition).toInt();
481}
482
483// position of the start of the current block
484static inline int getBlockPosition(const QSharedPointer<QInputMethodQueryEvent> &query)
485{
487 return absolutePos.isValid() ? absolutePos.toInt() - query->value(Qt::ImCursorPosition).toInt() : 0;
488}
489
491{
492 focusObjectStopComposing();
493 clear();
494 m_batchEditNestingLevel = 0;
495 m_handleMode = Hidden;
496 if (qGuiApp->focusObject()) {
497 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(Qt::ImEnabled);
498 if (!query.isNull() && query->value(Qt::ImEnabled).toBool()) {
500 return;
501 }
502 }
504}
505
507{
508 focusObjectStopComposing();
509}
510
512{
513 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
514 if (!query.isNull() && m_batchEditNestingLevel == 0) {
515 const int cursorPos = getAbsoluteCursorPosition(query);
516 const int composeLength = m_composingText.length();
517
518 //Q_ASSERT(m_composingText.isEmpty() == (m_composingTextStart == -1));
519 if (m_composingText.isEmpty() != (m_composingTextStart == -1))
520 qWarning() << "Input method out of sync" << m_composingText << m_composingTextStart;
521
522 int realSelectionStart = cursorPos;
523 int realSelectionEnd = cursorPos;
524
525 int cpos = query->value(Qt::ImCursorPosition).toInt();
526 int anchor = query->value(Qt::ImAnchorPosition).toInt();
527 if (cpos != anchor) {
528 if (!m_composingText.isEmpty()) {
529 qWarning("Selecting text while preediting may give unpredictable results.");
530 focusObjectStopComposing();
531 }
532 int blockPos = getBlockPosition(query);
533 realSelectionStart = blockPos + cpos;
534 realSelectionEnd = blockPos + anchor;
535 }
536 // Qt's idea of the cursor position is the start of the preedit area, so we maintain our own preedit cursor pos
537 if (focusObjectIsComposing())
538 realSelectionStart = realSelectionEnd = m_composingCursor;
539
540 // Some keyboards misbahave when selStart > selEnd
541 if (realSelectionStart > realSelectionEnd)
542 std::swap(realSelectionStart, realSelectionEnd);
543
544 QtAndroidInput::updateSelection(realSelectionStart, realSelectionEnd,
545 m_composingTextStart, m_composingTextStart + composeLength); // pre-edit text
546 }
547}
548
549bool QAndroidInputContext::isImhNoTextHandlesSet()
550{
551 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
552 if (query.isNull())
553 return false;
554 return query->value(Qt::ImHints).toUInt() & Qt::ImhNoTextHandles;
555}
556
558{
559 if (m_fullScreenMode) {
561 return;
562 }
563 static bool noHandles = qEnvironmentVariableIntValue("QT_QPA_NO_TEXT_HANDLES");
564 if (noHandles || !m_focusObject)
565 return;
566
567 if (isImhNoTextHandlesSet()) {
569 return;
570 }
571
572 auto im = qGuiApp->inputMethod();
573
577 QCoreApplication::sendEvent(m_focusObject, &query);
578
579 int cpos = query.value(Qt::ImCursorPosition).toInt();
580 int anchor = query.value(Qt::ImAnchorPosition).toInt();
581 const QVariant readOnlyVariant = query.value(Qt::ImReadOnly);
582 bool readOnly = readOnlyVariant.toBool();
583 QPlatformWindow *qPlatformWindow = qGuiApp->focusWindow()->handle();
584
585 if (!readOnly && ((m_handleMode & 0xff) == Hidden)) {
587 return;
588 }
589
590 if ( cpos == anchor && (!readOnlyVariant.isValid() || readOnly)) {
592 return;
593 }
594
595 if (cpos == anchor || im->anchorRectangle().isNull()) {
596 auto curRect = cursorRectangle();
597 QPoint cursorPointGlobal = qPlatformWindow->mapToGlobal(
598 QPoint(curRect.x() + (curRect.width() / 2), curRect.y() + curRect.height()));
599 QPoint cursorPoint(curRect.center().x(), curRect.bottom());
600 int x = curRect.x();
601 int y = curRect.y();
602
603 // Use x and y for the editMenuPoint from the cursorPointGlobal when the cursor is in the Dialog
604 if (cursorPointGlobal != cursorPoint) {
605 x = cursorPointGlobal.x();
606 y = cursorPointGlobal.y();
607 }
608
609 QPoint editMenuPoint(x, y);
610 m_handleMode &= ShowEditPopup;
611 m_handleMode |= ShowCursor;
612 uint32_t buttons = readOnly ? 0 : EditContext::PasteButton;
613 if (!query.value(Qt::ImSurroundingText).toString().isEmpty())
615 QtAndroidInput::updateHandles(m_handleMode, editMenuPoint, buttons, cursorPointGlobal);
616 m_hideCursorHandleTimer.start();
617
618 return;
619 }
620
621 m_handleMode = ShowSelection | ShowEditPopup ;
622 auto leftRect = cursorRectangle();
623 auto rightRect = anchorRectangle();
624 if (cpos > anchor)
625 std::swap(leftRect, rightRect);
626 //Move the left or right select handle to the center from the screen edge
627 //the select handle is close to or over the screen edge. Otherwise, the
628 //select handle might go out of the screen and it would be impossible to drag.
629 QPoint leftPoint(qPlatformWindow->mapToGlobal(leftRect.bottomLeft().toPoint()));
630 QPoint rightPoint(qPlatformWindow->mapToGlobal(rightRect.bottomRight().toPoint()));
631
633 if (platformIntegration) {
634 if (m_selectHandleWidth == 0)
636
637 int rightSideOfScreen = platformIntegration->screen()->availableGeometry().right();
638 if (leftPoint.x() < m_selectHandleWidth)
639 leftPoint.setX(m_selectHandleWidth);
640
641 if (rightPoint.x() > rightSideOfScreen - m_selectHandleWidth)
642 rightPoint.setX(rightSideOfScreen - m_selectHandleWidth);
643
644 QPoint editPoint(qPlatformWindow->mapToGlobal(leftRect.united(rightRect).topLeft().toPoint()));
645 uint32_t buttons = readOnly ? EditContext::CopyButton | EditContext::SelectAllButton
647
648 QtAndroidInput::updateHandles(m_handleMode, editPoint, buttons, leftPoint, rightPoint,
649 query.value(Qt::ImCurrentSelection).toString().isRightToLeft());
650 m_hideCursorHandleTimer.stop();
651 }
652}
653
654/*
655 Called from Java when a cursor/selection handle was dragged to a new position
656
657 handleId of 1 means the cursor handle, 2 means the left handle, 3 means the right handle
658 */
660{
661 if (m_batchEditNestingLevel != 0) {
662 qWarning() << "QAndroidInputContext::handleLocationChanged returned";
663 return;
664 }
665 QPoint point(x, y);
666
667 // The handle is down of the cursor, but we want the position in the middle.
670 QCoreApplication::sendEvent(m_focusObject, &query);
671 int cpos = query.value(Qt::ImCursorPosition).toInt();
672 int anchor = query.value(Qt::ImAnchorPosition).toInt();
673 auto leftRect = cursorRectangle();
674 auto rightRect = anchorRectangle();
675 if (cpos > anchor)
676 std::swap(leftRect, rightRect);
677
678 // Do not allow dragging left handle below right handle, or right handle above left handle
679 if (handleId == 2 && point.y() > rightRect.center().y()) {
680 point.setY(rightRect.center().y());
681 } else if (handleId == 3 && point.y() < leftRect.center().y()) {
682 point.setY(leftRect.center().y());
683 }
684
685 bool ok;
686 auto object = m_focusObject->parent();
687 int dialogMoveX = 0;
688 while (object) {
689 if (QString::compare(object->metaObject()->className(),
690 "QDialog", Qt::CaseInsensitive) == 0) {
691 dialogMoveX += object->property("x").toInt();
692 }
693 object = object->parent();
694 };
695
696 auto position =
698 const QPointF fixedPosition = QPointF(position.x() - dialogMoveX, position.y());
700 const QTransform mapToLocal = im->inputItemTransform().inverted();
701 const int handlePos = im->queryFocusObject(Qt::ImCursorPosition, mapToLocal.map(fixedPosition)).toInt(&ok);
702
703 if (!ok)
704 return;
705
706 int newCpos = cpos;
707 int newAnchor = anchor;
708 if (newAnchor > newCpos)
709 std::swap(newAnchor, newCpos);
710
711 if (handleId == 1) {
712 newCpos = handlePos;
713 newAnchor = handlePos;
714 } else if (handleId == 2) {
715 newAnchor = handlePos;
716 } else if (handleId == 3) {
717 newCpos = handlePos;
718 }
719
720 /*
721 Do not allow clearing selection by dragging selection handles and do not allow swapping
722 selection handles for consistency with Android's native text editing controls. Ensure that at
723 least one symbol remains selected.
724 */
725 if ((handleId == 2 || handleId == 3) && newCpos <= newAnchor) {
727 query.value(Qt::ImCurrentSelection).toString());
728
729 const int oldSelectionStartPos = qMin(cpos, anchor);
730
731 if (handleId == 2) {
732 finder.toEnd();
733 finder.toPreviousBoundary();
734 newAnchor = finder.position() + oldSelectionStartPos;
735 } else {
736 finder.toStart();
737 finder.toNextBoundary();
738 newCpos = finder.position() + oldSelectionStartPos;
739 }
740 }
741
742 // Check if handle has been dragged far enough
743 if (!focusObjectIsComposing() && newCpos == cpos && newAnchor == anchor)
744 return;
745
746 /*
747 If the editor is currently in composing state, we have to compare newCpos with
748 m_composingCursor instead of cpos. And since there is nothing to compare with newAnchor, we
749 perform the check only when user drags the cursor handle.
750 */
751 if (focusObjectIsComposing() && handleId == 1) {
752 int absoluteCpos = query.value(Qt::ImAbsolutePosition).toInt(&ok);
753 if (!ok)
754 absoluteCpos = cpos;
755 const int blockPos = absoluteCpos - cpos;
756
757 if (blockPos + newCpos == m_composingCursor)
758 return;
759 }
760
761 BatchEditLock batchEditLock(this);
762
763 focusObjectStopComposing();
764
765 QList<QInputMethodEvent::Attribute> attributes;
766 attributes.append({ QInputMethodEvent::Selection, newAnchor, newCpos - newAnchor });
767 if (newCpos != newAnchor)
768 attributes.append({ QInputMethodEvent::Cursor, 0, 0 });
769
770 QInputMethodEvent event(QString(), attributes);
771 QGuiApplication::sendEvent(m_focusObject, &event);
772}
773
775{
776 if (m_focusObject && screenInputItemRectangle().contains(x, y)) {
777 // If the user touch the input rectangle, we can show the cursor handle
778 m_handleMode = ShowCursor;
779 // The VK will appear in a moment, stop the timer
780 m_hideCursorHandleTimer.stop();
781
782 if (focusObjectIsComposing()) {
783 const int curBlockPos = getBlockPosition(
784 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAbsolutePosition));
785 const int touchPosition = curBlockPos
787 if (touchPosition != m_composingCursor)
788 focusObjectStopComposing();
789 }
790
791 // Check if cursor is visible in focused window before updating handles
792 QPlatformWindow *window = qGuiApp->focusWindow()->handle();
793 const QRectF curRect = cursorRectangle();
794 const QPoint cursorGlobalPoint = window->mapToGlobal(QPoint(curRect.x(), curRect.y()));
795 const QRect windowRect = QPlatformInputContext::inputItemClipRectangle().toRect();
796 const QRect windowGlobalRect = QRect(window->mapToGlobal(windowRect.topLeft()), windowRect.size());
797
798 if (windowGlobalRect.contains(cursorGlobalPoint.x(), cursorGlobalPoint.y()))
800 }
801}
802
804{
805 static bool noHandles = qEnvironmentVariableIntValue("QT_QPA_NO_TEXT_HANDLES");
806 if (noHandles)
807 return;
808
809 if (m_focusObject && screenInputItemRectangle().contains(x, y)) {
810 BatchEditLock batchEditLock(this);
811
812 focusObjectStopComposing();
813 const QPointF touchPoint(x, y);
814 setSelectionOnFocusObject(touchPoint, touchPoint);
815
817 QCoreApplication::sendEvent(m_focusObject, &query);
818 int cursor = query.value(Qt::ImCursorPosition).toInt();
819 int anchor = cursor;
820 QString before = query.value(Qt::ImTextBeforeCursor).toString();
821 QString after = query.value(Qt::ImTextAfterCursor).toString();
822 for (const auto &ch : after) {
823 if (!ch.isLetterOrNumber())
824 break;
825 ++anchor;
826 }
827
828 for (auto itch = before.rbegin(); itch != after.rend(); ++itch) {
829 if (!itch->isLetterOrNumber())
830 break;
831 --cursor;
832 }
833 if (cursor == anchor || cursor < 0 || cursor - anchor > 500) {
834 m_handleMode = ShowCursor | ShowEditPopup;
836 return;
837 }
838 QList<QInputMethodEvent::Attribute> imAttributes;
840 imAttributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection, anchor, cursor - anchor, QVariant()));
841 QInputMethodEvent event(QString(), imAttributes);
842 QGuiApplication::sendEvent(m_focusObject, &event);
843
844 m_handleMode = ShowSelection | ShowEditPopup;
846 }
847}
848
850{
851 if (m_handleMode) {
852 // When the user enter text on the keyboard, we hide the cursor handle
853 m_handleMode = Hidden;
855 }
856}
857
859{
860 if (m_handleMode & ShowSelection) {
861 m_handleMode = Hidden;
863 } else {
864 m_hideCursorHandleTimer.start();
865 }
866}
867
868void QAndroidInputContext::update(Qt::InputMethodQueries queries)
869{
870 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(queries);
871 if (query.isNull())
872 return;
873#warning TODO extract the needed data from query
874}
875
877{
878#warning TODO Handle at least QInputMethod::ContextMenu action
879 Q_UNUSED(action);
880 Q_UNUSED(cursorPosition);
881 //### click should be passed to the IM, but in the meantime it's better to ignore it than to do something wrong
882 // if (action == QInputMethod::Click)
883 // commit();
884}
885
890
892{
893 return false;
894}
895
897{
899 connect(qGuiApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(showInputPanelLater(Qt::ApplicationState)));
900 return;
901 }
902 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
903 if (query.isNull())
904 return;
905
906 if (!qGuiApp->focusWindow()->handle())
907 return; // not a real window, probably VR/XR
908
909 disconnect(m_updateCursorPosConnection);
910 m_updateCursorPosConnection = {};
911
912 if (qGuiApp->focusObject()->metaObject()->indexOfSignal("cursorPositionChanged(int,int)") >= 0) // QLineEdit breaks the pattern
913 m_updateCursorPosConnection = connect(qGuiApp->focusObject(), SIGNAL(cursorPositionChanged(int,int)), this, SLOT(updateCursorPosition()));
914 else if (qGuiApp->focusObject()->metaObject()->indexOfSignal("cursorPositionChanged()") >= 0)
915 m_updateCursorPosConnection = connect(qGuiApp->focusObject(), SIGNAL(cursorPositionChanged()), this, SLOT(updateCursorPosition()));
916
918 QtAndroidInput::showSoftwareKeyboard(rect.left(), rect.top(), rect.width(), rect.height(),
919 query->value(Qt::ImHints).toUInt(),
920 query->value(Qt::ImEnterKeyType).toUInt());
921}
922
923void QAndroidInputContext::showInputPanelLater(Qt::ApplicationState state)
924{
926 return;
927 disconnect(qGuiApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(showInputPanelLater(Qt::ApplicationState)));
929}
930
931void QAndroidInputContext::safeCall(const std::function<void()> &func, Qt::ConnectionType conType)
932{
933 if (qGuiApp->thread() == QThread::currentThread())
934 func();
935 else
936 QMetaObject::invokeMethod(this, "safeCall", conType, Q_ARG(std::function<void()>, func));
937}
938
943
948
950{
951 return m_composingText.length();
952}
953
955{
956 m_composingText.clear();
957 m_composingTextStart = -1;
958 m_composingCursor = -1;
959 m_extractedText.clear();
960}
961
962
964{
965 if (object != m_focusObject) {
966 focusObjectStopComposing();
967 m_focusObject = object;
968 reset();
969 }
971}
972
974{
975 ++m_batchEditNestingLevel;
976 return JNI_TRUE;
977}
978
980{
981 if (--m_batchEditNestingLevel == 0) { //ending batch edit mode
982 focusObjectStartComposing();
984 }
985 return JNI_TRUE;
986}
987
988/*
989 Android docs say: This behaves like calling setComposingText(text, newCursorPosition) then
990 finishComposingText().
991*/
992jboolean QAndroidInputContext::commitText(const QString &text, jint newCursorPosition)
993{
994 BatchEditLock batchEditLock(this);
995 return setComposingText(text, newCursorPosition) && finishComposingText();
996}
997
998jboolean QAndroidInputContext::deleteSurroundingText(jint leftLength, jint rightLength)
999{
1000 BatchEditLock batchEditLock(this);
1001
1002 focusObjectStopComposing();
1003
1004 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1005 if (query.isNull())
1006 return JNI_TRUE;
1007
1008 if (leftLength < 0) {
1009 rightLength += -leftLength;
1010 leftLength = 0;
1011 }
1012
1013 const int initialBlockPos = getBlockPosition(query);
1014 const int initialCursorPos = getAbsoluteCursorPosition(query);
1015 const int initialAnchorPos = initialBlockPos + query->value(Qt::ImAnchorPosition).toInt();
1016
1017 /*
1018 According to documentation, we should delete leftLength characters before current selection
1019 and rightLength characters after current selection (without affecting selection). But that is
1020 absolutely not what Android's native EditText does. It deletes leftLength characters before
1021 min(selection start, composing region start) and rightLength characters after max(selection
1022 end, composing region end). There are no known keyboards that depend on this behavior, but
1023 it is better to be consistent with EditText behavior, because there definitely should be no
1024 keyboards that depend on documented behavior.
1025 */
1026 const int leftEnd =
1027 m_composingText.isEmpty()
1028 ? qMin(initialCursorPos, initialAnchorPos)
1029 : qMin(qMin(initialCursorPos, initialAnchorPos), m_composingTextStart);
1030
1031 const int rightBegin =
1032 m_composingText.isEmpty()
1033 ? qMax(initialCursorPos, initialAnchorPos)
1034 : qMax(qMax(initialCursorPos, initialAnchorPos),
1035 m_composingTextStart + m_composingText.length());
1036
1037 int textBeforeCursorLen;
1038 int textAfterCursorLen;
1039
1040 QVariant textBeforeCursor = query->value(Qt::ImTextBeforeCursor);
1041 QVariant textAfterCursor = query->value(Qt::ImTextAfterCursor);
1042 if (textBeforeCursor.isValid() && textAfterCursor.isValid()) {
1043 textBeforeCursorLen = textBeforeCursor.toString().length();
1044 textAfterCursorLen = textAfterCursor.toString().length();
1045 } else {
1046 textBeforeCursorLen = initialCursorPos - initialBlockPos;
1047 textAfterCursorLen =
1048 query->value(Qt::ImSurroundingText).toString().length() - textBeforeCursorLen;
1049 }
1050
1051 leftLength = qMin(qMax(0, textBeforeCursorLen - (initialCursorPos - leftEnd)), leftLength);
1052 rightLength = qMin(qMax(0, textAfterCursorLen - (rightBegin - initialCursorPos)), rightLength);
1053
1054 if (leftLength == 0 && rightLength == 0)
1055 return JNI_TRUE;
1056
1057 if (leftEnd == rightBegin) {
1058 // We have no selection and no composing region; we can do everything using one event
1060 event.setCommitString({}, -leftLength, leftLength + rightLength);
1061 QGuiApplication::sendEvent(m_focusObject, &event);
1062 } else {
1063 if (initialCursorPos != initialAnchorPos) {
1065 { QInputMethodEvent::Selection, initialCursorPos - initialBlockPos, 0 }
1066 });
1067
1068 QGuiApplication::sendEvent(m_focusObject, &event);
1069 }
1070
1071 int currentCursorPos = initialCursorPos;
1072
1073 if (rightLength > 0) {
1075 event.setCommitString({}, rightBegin - currentCursorPos, rightLength);
1076 QGuiApplication::sendEvent(m_focusObject, &event);
1077
1078 currentCursorPos = rightBegin;
1079 }
1080
1081 if (leftLength > 0) {
1082 const int leftBegin = leftEnd - leftLength;
1083
1085 event.setCommitString({}, leftBegin - currentCursorPos, leftLength);
1086 QGuiApplication::sendEvent(m_focusObject, &event);
1087
1088 currentCursorPos = leftBegin;
1089
1090 if (!m_composingText.isEmpty())
1091 m_composingTextStart -= leftLength;
1092 }
1093
1094 // Restore cursor position or selection
1095 if (currentCursorPos != initialCursorPos - leftLength
1096 || initialCursorPos != initialAnchorPos) {
1097 // If we have deleted a newline character, we are now in a new block
1098 const int currentBlockPos = getBlockPosition(
1099 focusObjectInputMethodQuery(Qt::ImAbsolutePosition | Qt::ImCursorPosition));
1100
1102 { QInputMethodEvent::Selection, initialCursorPos - leftLength - currentBlockPos,
1103 initialAnchorPos - initialCursorPos },
1105 });
1106
1107 QGuiApplication::sendEvent(m_focusObject, &event);
1108 }
1109 }
1110
1111 return JNI_TRUE;
1112}
1113
1114// Android docs say the cursor must not move
1116{
1117 BatchEditLock batchEditLock(this);
1118
1119 if (!focusObjectStopComposing())
1120 return JNI_FALSE;
1121
1122 clear();
1123 return JNI_TRUE;
1124}
1125
1127{
1128 m_fullScreenMode = enabled;
1129 BatchEditLock batchEditLock(this);
1130 if (!focusObjectStopComposing())
1131 return;
1132
1133 if (enabled)
1134 m_handleMode = Hidden;
1135
1137}
1138
1139// Called in calling thread's context
1141{
1142 return m_fullScreenMode;
1143}
1144
1145bool QAndroidInputContext::focusObjectIsComposing() const
1146{
1147 return m_composingCursor != -1;
1148}
1149
1150void QAndroidInputContext::focusObjectStartComposing()
1151{
1152 if (focusObjectIsComposing() || m_composingText.isEmpty())
1153 return;
1154
1155 // Composing strings containing newline characters are rare and may cause problems
1156 if (m_composingText.contains(u'\n'))
1157 return;
1158
1159 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1160 if (!query)
1161 return;
1162
1163 if (query->value(Qt::ImCursorPosition).toInt() != query->value(Qt::ImAnchorPosition).toInt())
1164 return;
1165
1166 const int absoluteCursorPos = getAbsoluteCursorPosition(query);
1167 if (absoluteCursorPos < m_composingTextStart
1168 || absoluteCursorPos > m_composingTextStart + m_composingText.length())
1169 return;
1170
1171 m_composingCursor = absoluteCursorPos;
1172
1173 QTextCharFormat underlined;
1174 underlined.setFontUnderline(true);
1175
1176 QInputMethodEvent event(m_composingText, {
1177 { QInputMethodEvent::Cursor, absoluteCursorPos - m_composingTextStart, 1 },
1178 { QInputMethodEvent::TextFormat, 0, int(m_composingText.length()), underlined }
1179 });
1180
1181 event.setCommitString({}, m_composingTextStart - absoluteCursorPos, m_composingText.length());
1182
1183 QGuiApplication::sendEvent(m_focusObject, &event);
1184}
1185
1186bool QAndroidInputContext::focusObjectStopComposing()
1187{
1188 if (!focusObjectIsComposing())
1189 return true; // not composing
1190
1191 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1192 if (query.isNull())
1193 return false;
1194
1195 const int blockPos = getBlockPosition(query);
1196 const int localCursorPos = m_composingCursor - blockPos;
1197
1198 m_composingCursor = -1;
1199
1200 {
1201 // commit the composing test
1202 QList<QInputMethodEvent::Attribute> attributes;
1203 QInputMethodEvent event(QString(), attributes);
1204 event.setCommitString(m_composingText);
1205 sendInputMethodEvent(&event);
1206 }
1207 {
1208 // Moving Qt's cursor to where the preedit cursor used to be
1209 QList<QInputMethodEvent::Attribute> attributes;
1210 attributes.append(
1212 QInputMethodEvent event(QString(), attributes);
1213 sendInputMethodEvent(&event);
1214 }
1215
1216 return true;
1217}
1218
1220{
1221 jint res = 0;
1222 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1223 if (query.isNull())
1224 return res;
1225
1226 const uint qtInputMethodHints = query->value(Qt::ImHints).toUInt();
1227 const int localPos = query->value(Qt::ImCursorPosition).toInt();
1228
1229 bool atWordBoundary =
1230 localPos == 0
1231 && (!focusObjectIsComposing() || m_composingCursor == m_composingTextStart);
1232
1233 if (!atWordBoundary) {
1234 QString surroundingText = query->value(Qt::ImSurroundingText).toString();
1235 surroundingText.truncate(localPos);
1236 if (focusObjectIsComposing())
1237 surroundingText += QStringView{m_composingText}.left(m_composingCursor - m_composingTextStart);
1238 // Add a character to see if it is at the end of the sentence or not
1239 QTextBoundaryFinder finder(QTextBoundaryFinder::Sentence, surroundingText + u'A');
1240 finder.setPosition(surroundingText.length());
1241 if (finder.isAtBoundary())
1242 atWordBoundary = finder.isAtBoundary();
1243 }
1244 if (atWordBoundary && !(qtInputMethodHints & Qt::ImhLowercaseOnly) && !(qtInputMethodHints & Qt::ImhNoAutoUppercase))
1245 res |= CAP_MODE_SENTENCES;
1246
1247 if (qtInputMethodHints & Qt::ImhUppercaseOnly)
1248 res |= CAP_MODE_CHARACTERS;
1249
1250 return res;
1251}
1252
1253
1254
1255const QAndroidInputContext::ExtractedText &QAndroidInputContext::getExtractedText(jint /*hintMaxChars*/, jint /*hintMaxLines*/, jint /*flags*/)
1256{
1257 // Note to self: "if the GET_EXTRACTED_TEXT_MONITOR flag is set, you should be calling
1258 // updateExtractedText(View, int, ExtractedText) whenever you call
1259 // updateSelection(View, int, int, int, int)." QTBUG-37980
1260
1261 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(
1263 if (query.isNull())
1264 return m_extractedText;
1265
1266 const int cursorPos = getAbsoluteCursorPosition(query);
1267 const int blockPos = getBlockPosition(query);
1268
1269 // It is documented that we should try to return hintMaxChars
1270 // characters, but standard Android controls always return all text, and
1271 // there are input methods out there that (surprise) seem to depend on
1272 // what happens in reality rather than what's documented.
1273
1276 if (textBeforeCursor.isValid() && textAfterCursor.isValid()) {
1277 if (focusObjectIsComposing()) {
1278 m_extractedText.text =
1279 textBeforeCursor.toString() + m_composingText + textAfterCursor.toString();
1280 } else {
1281 m_extractedText.text = textBeforeCursor.toString() + textAfterCursor.toString();
1282 }
1283
1284 m_extractedText.startOffset = qMax(0, cursorPos - textBeforeCursor.toString().length());
1285 } else {
1286 m_extractedText.text = focusObjectInputMethodQuery(Qt::ImSurroundingText)
1287 ->value(Qt::ImSurroundingText).toString();
1288
1289 if (focusObjectIsComposing())
1290 m_extractedText.text.insert(cursorPos - blockPos, m_composingText);
1291
1292 m_extractedText.startOffset = blockPos;
1293 }
1294
1295 if (focusObjectIsComposing()) {
1296 m_extractedText.selectionStart = m_composingCursor - m_extractedText.startOffset;
1297 m_extractedText.selectionEnd = m_extractedText.selectionStart;
1298 } else {
1299 m_extractedText.selectionStart = cursorPos - m_extractedText.startOffset;
1300 m_extractedText.selectionEnd =
1301 blockPos + query->value(Qt::ImAnchorPosition).toInt() - m_extractedText.startOffset;
1302
1303 // Some keyboards misbehave when selectionStart > selectionEnd
1304 if (m_extractedText.selectionStart > m_extractedText.selectionEnd)
1305 std::swap(m_extractedText.selectionStart, m_extractedText.selectionEnd);
1306 }
1307
1308 return m_extractedText;
1309}
1310
1312{
1313 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1314 if (query.isNull())
1315 return QString();
1316
1317 return query->value(Qt::ImCurrentSelection).toString();
1318}
1319
1321{
1322 if (length <= 0)
1323 return QString();
1324
1325 QString text;
1326
1328 if (reportedTextAfter.isValid()) {
1329 text = reportedTextAfter.toString();
1330 } else {
1331 // Compatibility code for old controls that do not implement the new API
1332 QSharedPointer<QInputMethodQueryEvent> query =
1333 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImSurroundingText);
1334 if (query) {
1335 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1336 text = query->value(Qt::ImSurroundingText).toString().mid(cursorPos);
1337 }
1338 }
1339
1340 if (focusObjectIsComposing()) {
1341 // Controls do not report preedit text, so we have to add it
1342 const int cursorPosInsidePreedit = m_composingCursor - m_composingTextStart;
1343 text = QStringView{m_composingText}.mid(cursorPosInsidePreedit) + text;
1344 } else {
1345 // We must not return selected text if there is any
1346 QSharedPointer<QInputMethodQueryEvent> query =
1347 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAnchorPosition);
1348 if (query) {
1349 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1350 const int anchorPos = query->value(Qt::ImAnchorPosition).toInt();
1351 if (anchorPos > cursorPos)
1352 text.remove(0, anchorPos - cursorPos);
1353 }
1354 }
1355
1357 return text;
1358}
1359
1361{
1362 if (length <= 0)
1363 return QString();
1364
1365 QString text;
1366
1368 if (reportedTextBefore.isValid()) {
1369 text = reportedTextBefore.toString();
1370 } else {
1371 // Compatibility code for old controls that do not implement the new API
1372 QSharedPointer<QInputMethodQueryEvent> query =
1373 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImSurroundingText);
1374 if (query) {
1375 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1376 text = query->value(Qt::ImSurroundingText).toString().left(cursorPos);
1377 }
1378 }
1379
1380 if (focusObjectIsComposing()) {
1381 // Controls do not report preedit text, so we have to add it
1382 const int cursorPosInsidePreedit = m_composingCursor - m_composingTextStart;
1383 text += QStringView{m_composingText}.left(cursorPosInsidePreedit);
1384 } else {
1385 // We must not return selected text if there is any
1386 QSharedPointer<QInputMethodQueryEvent> query =
1387 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAnchorPosition);
1388 if (query) {
1389 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1390 const int anchorPos = query->value(Qt::ImAnchorPosition).toInt();
1391 if (anchorPos < cursorPos)
1392 text.chop(cursorPos - anchorPos);
1393 }
1394 }
1395
1396 if (text.length() > length)
1397 text = text.right(length);
1398 return text;
1399}
1400
1401/*
1402 Android docs say that this function should:
1403 - remove the current composing text, if there is any
1404 - otherwise remove currently selected text, if there is any
1405 - insert new text in place of old composing text or, if there was none, at current cursor position
1406 - mark the inserted text as composing
1407 - move cursor as specified by newCursorPosition: if > 0, it is relative to the end of inserted
1408 text - 1; if <= 0, it is relative to the start of inserted text
1409 */
1410
1411jboolean QAndroidInputContext::setComposingText(const QString &text, jint newCursorPosition)
1412{
1413 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1414 if (query.isNull())
1415 return JNI_FALSE;
1416
1417 BatchEditLock batchEditLock(this);
1418
1419 const int absoluteCursorPos = getAbsoluteCursorPosition(query);
1420 int absoluteAnchorPos = getBlockPosition(query) + query->value(Qt::ImAnchorPosition).toInt();
1421
1422 auto setCursorPosition = [=]() {
1423 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1424 QInputMethodEvent event({}, { { QInputMethodEvent::Selection, cursorPos, 0 } });
1425 QGuiApplication::sendEvent(m_focusObject, &event);
1426 };
1427
1428 // If we have composing region and selection (and therefore focusObjectIsComposing() == false),
1429 // we must clear selection so that we won't delete it when we will be replacing composing text
1430 if (!m_composingText.isEmpty() && absoluteCursorPos != absoluteAnchorPos) {
1431 setCursorPosition();
1432 absoluteAnchorPos = absoluteCursorPos;
1433 }
1434
1435 // The value of Qt::ImCursorPosition is not updated at the start
1436 // when the first character is added, so we must update it (QTBUG-85090)
1437 if (absoluteCursorPos == 0 && text.length() == 1 && getTextAfterCursor(1,1).length() >= 0) {
1438 setCursorPosition();
1439 }
1440
1441 // If we had no composing region, pretend that we had a zero-length composing region at current
1442 // cursor position to simplify code. Also account for that we must delete selected text if there
1443 // (still) is any.
1444 const int effectiveAbsoluteCursorPos = qMin(absoluteCursorPos, absoluteAnchorPos);
1445 if (m_composingTextStart == -1)
1446 m_composingTextStart = effectiveAbsoluteCursorPos;
1447
1448 const int oldComposingTextLen = m_composingText.length();
1449 m_composingText = text;
1450
1451 const int newAbsoluteCursorPos =
1452 newCursorPosition <= 0
1453 ? m_composingTextStart + newCursorPosition
1454 : m_composingTextStart + m_composingText.length() + newCursorPosition - 1;
1455
1456 const bool focusObjectWasComposing = focusObjectIsComposing();
1457
1458 // Same checks as in focusObjectStartComposing()
1459 if (!m_composingText.isEmpty() && !m_composingText.contains(u'\n')
1460 && newAbsoluteCursorPos >= m_composingTextStart
1461 && newAbsoluteCursorPos <= m_composingTextStart + m_composingText.length())
1462 m_composingCursor = newAbsoluteCursorPos;
1463 else
1464 m_composingCursor = -1;
1465
1466 if (focusObjectIsComposing()) {
1467 QTextCharFormat underlined;
1468 underlined.setFontUnderline(true);
1469
1470 QInputMethodEvent event(m_composingText, {
1471 { QInputMethodEvent::TextFormat, 0, int(m_composingText.length()), underlined },
1472 { QInputMethodEvent::Cursor, m_composingCursor - m_composingTextStart, 1 }
1473 });
1474
1475 if (oldComposingTextLen > 0 && !focusObjectWasComposing) {
1476 event.setCommitString({}, m_composingTextStart - effectiveAbsoluteCursorPos,
1477 oldComposingTextLen);
1478 }
1479 if (m_composingText.isEmpty())
1480 clear();
1481
1482 QGuiApplication::sendEvent(m_focusObject, &event);
1483 } else {
1484 QInputMethodEvent event({}, {});
1485
1486 if (focusObjectWasComposing) {
1487 event.setCommitString(m_composingText);
1488 } else {
1489 event.setCommitString(m_composingText,
1490 m_composingTextStart - effectiveAbsoluteCursorPos,
1491 oldComposingTextLen);
1492 }
1493 if (m_composingText.isEmpty())
1494 clear();
1495
1496 QGuiApplication::sendEvent(m_focusObject, &event);
1497 }
1498
1499 if (!focusObjectIsComposing() && newCursorPosition != 1) {
1500 // Move cursor using a separate event because if we have inserted or deleted a newline
1501 // character, then we are now inside an another block
1502
1503 const int newBlockPos = getBlockPosition(
1504 focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAbsolutePosition));
1505
1507 { QInputMethodEvent::Selection, newAbsoluteCursorPos - newBlockPos, 0 }
1508 });
1509
1510 QGuiApplication::sendEvent(m_focusObject, &event);
1511 }
1512
1513 keyDown();
1514
1515 return JNI_TRUE;
1516}
1517
1518// Android docs say:
1519// * start may be after end, same meaning as if swapped
1520// * this function should not trigger updateSelection, but Android's native EditText does trigger it
1521// * if start == end then we should stop composing
1523{
1524 BatchEditLock batchEditLock(this);
1525
1526 // Qt will not include the current preedit text in the query results, and interprets all
1527 // parameters relative to the text excluding the preedit. The simplest solution is therefore to
1528 // tell Qt that we commit the text before we set the new region. This may cause a little flicker, but is
1529 // much more robust than trying to keep the two different world views in sync
1530
1532
1533 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1534 if (query.isNull())
1535 return JNI_FALSE;
1536
1537 if (start == end)
1538 return JNI_TRUE;
1539 if (start > end)
1540 qSwap(start, end);
1541
1542 QString text = query->value(Qt::ImSurroundingText).toString();
1543 int textOffset = getBlockPosition(query);
1544
1545 if (start < textOffset || end > textOffset + text.length()) {
1546 const int cursorPos = query->value(Qt::ImCursorPosition).toInt();
1547
1548 if (end - textOffset > text.length()) {
1549 const QString after = query->value(Qt::ImTextAfterCursor).toString();
1550 const int additionalSuffixLen = after.length() - (text.length() - cursorPos);
1551
1552 if (additionalSuffixLen > 0)
1553 text += QStringView{after}.right(additionalSuffixLen);
1554 }
1555
1556 if (start < textOffset) {
1557 QString before = query->value(Qt::ImTextBeforeCursor).toString();
1558 before.chop(cursorPos);
1559
1560 if (!before.isEmpty()) {
1561 text = before + text;
1562 textOffset -= before.length();
1563 }
1564 }
1565
1566 if (start < textOffset || end - textOffset > text.length()) {
1567 qCDebug(lcQpaInputMethods) << "Warning: setComposingRegion: failed to retrieve text from composing region";
1568
1569 return JNI_TRUE;
1570 }
1571 }
1572
1573 m_composingText = text.mid(start - textOffset, end - start);
1574 m_composingTextStart = start;
1575
1576 return JNI_TRUE;
1577}
1578
1580{
1581 QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
1582 if (query.isNull())
1583 return JNI_FALSE;
1584
1585 BatchEditLock batchEditLock(this);
1586
1587 int blockPosition = getBlockPosition(query);
1588 int localCursorPos = start - blockPosition;
1589
1590 if (focusObjectIsComposing() && start == end && start >= m_composingTextStart
1591 && start <= m_composingTextStart + m_composingText.length()) {
1592 // not actually changing the selection; just moving the
1593 // preedit cursor
1594 int localOldPos = query->value(Qt::ImCursorPosition).toInt();
1595 int pos = localCursorPos - localOldPos;
1596 QList<QInputMethodEvent::Attribute> attributes;
1598
1599 //but we have to tell Qt about the compose text all over again
1600
1601 // Show compose text underlined
1602 QTextCharFormat underlined;
1603 underlined.setFontUnderline(true);
1604 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat,0, m_composingText.length(),
1605 QVariant(underlined)));
1606 m_composingCursor = start;
1607
1608 QInputMethodEvent event(m_composingText, attributes);
1609 QGuiApplication::sendEvent(m_focusObject, &event);
1610 } else {
1611 // actually changing the selection
1612 focusObjectStopComposing();
1613 QList<QInputMethodEvent::Attribute> attributes;
1615 localCursorPos,
1616 end - start));
1617 QInputMethodEvent event({}, attributes);
1618 QGuiApplication::sendEvent(m_focusObject, &event);
1619 }
1620 return JNI_TRUE;
1621}
1622
1624{
1625 BatchEditLock batchEditLock(this);
1626
1627 focusObjectStopComposing();
1628 m_handleMode = ShowCursor;
1630 return JNI_TRUE;
1631}
1632
1634{
1635 BatchEditLock batchEditLock(this);
1636
1637 // This is probably not what native EditText would do, but normally if there is selection, then
1638 // there will be no composing region
1640
1641 m_handleMode = ShowCursor;
1643 return JNI_TRUE;
1644}
1645
1647{
1648 BatchEditLock batchEditLock(this);
1649
1650 focusObjectStopComposing();
1651 m_handleMode = ShowCursor;
1653 return JNI_TRUE;
1654}
1655
1657{
1658#warning TODO
1659 return JNI_FALSE;
1660}
1661
1663{
1664 BatchEditLock batchEditLock(this);
1665
1666 // TODO: This is not what native EditText does
1668
1669 m_handleMode = ShowCursor;
1671 return JNI_TRUE;
1672}
1673
1675{
1676 for (int i = 0; i < sequence.count(); ++i) {
1677 const QKeyCombination keys = sequence[i];
1678 Qt::Key key = Qt::Key(keys.toCombined() & ~Qt::KeyboardModifierMask);
1679 Qt::KeyboardModifiers mod = Qt::KeyboardModifiers(keys.toCombined() & Qt::KeyboardModifierMask);
1680
1681 QKeyEvent pressEvent(QEvent::KeyPress, key, mod);
1682 QKeyEvent releaseEvent(QEvent::KeyRelease, key, mod);
1683
1684 QGuiApplication::sendEvent(m_focusObject, &pressEvent);
1685 QGuiApplication::sendEvent(m_focusObject, &releaseEvent);
1686 }
1687}
1688
1689QSharedPointer<QInputMethodQueryEvent> QAndroidInputContext::focusObjectInputMethodQuery(Qt::InputMethodQueries queries) {
1690 if (!qGuiApp)
1691 return {};
1692
1693 QObject *focusObject = qGuiApp->focusObject();
1694 if (!focusObject)
1695 return {};
1696
1698 QCoreApplication::sendEvent(focusObject, ret);
1699 return QSharedPointer<QInputMethodQueryEvent>(ret);
1700}
1701
1702void QAndroidInputContext::sendInputMethodEvent(QInputMethodEvent *event)
1703{
1704 if (!qGuiApp)
1705 return;
1706
1707 QObject *focusObject = qGuiApp->focusObject();
1708 if (!focusObject)
1709 return;
1710
1711 QCoreApplication::sendEvent(focusObject, event);
1712}
1713
static JNINativeMethod methods[]
jboolean setSelection(jint start, jint end)
void safeCall(const std::function< void()> &func, Qt::ConnectionType conType=Qt::BlockingQueuedConnection)
jint getCursorCapsMode(jint reqModes)
QString getSelectedText(jint flags)
bool isAnimating() const override
This function can be reimplemented to return true whenever input method is animating shown or hidden.
QString getTextAfterCursor(jint length, jint flags)
void reportFullscreenMode(jboolean enabled)
QRectF keyboardRect() const override
This function can be reimplemented to return virtual keyboard rectangle in currently active window co...
void reset() override
Method to be called when input method needs to be reset.
jboolean setComposingText(const QString &text, jint newCursorPosition)
void hideInputPanel() override
Request to hide input panel.
void setFocusObject(QObject *object) override
This virtual method gets called to notify updated focus to object.
jboolean commitText(const QString &text, jint newCursorPosition)
jboolean setComposingRegion(jint start, jint end)
static QAndroidInputContext * androidInputContext()
void update(Qt::InputMethodQueries queries) override
Notification on editor updates.
QString getTextBeforeCursor(jint length, jint flags)
void sendShortcut(const QKeySequence &)
void invokeAction(QInputMethod::Action action, int cursorPosition) override
Called when the word currently being composed in the input item is tapped by the user.
jboolean deleteSurroundingText(jint leftLength, jint rightLength)
void handleLocationChanged(int handleId, int x, int y)
void showInputPanel() override
Request to show input panel.
const ExtractedText & getExtractedText(jint hintMaxChars, jint hintMaxLines, jint flags)
bool isInputPanelVisible() const override
Returns input panel visibility status.
QRect availableGeometry() const override
Reimplement in subclass to return the pixel geometry of the available space This normally is the desk...
\inmodule QtCore
static bool sendEvent(QObject *receiver, QEvent *event)
Sends event event directly to receiver receiver, using the notify() function.
@ KeyRelease
Definition qcoreevent.h:65
@ KeyPress
Definition qcoreevent.h:64
static Qt::ApplicationState applicationState()
static QWindow * focusWindow()
Returns the QWindow that receives events tied to focus, such as key events.
static QInputMethod * inputMethod()
returns the input method.
The QInputMethodEvent class provides parameters for input method events.
Definition qevent.h:625
The QInputMethodQueryEvent class provides an event sent by the input context to input objects.
Definition qevent.h:679
The QInputMethod class provides access to the active text input method.
Action
Indicates the kind of action performed by the user.
void anchorRectangleChanged()
static QVariant queryFocusObject(Qt::InputMethodQuery query, const QVariant &argument)
Send query to the current focus object with parameters argument and return the result.
void inputItemClipRectangleChanged()
QTransform inputItemTransform() const
Returns the transformation from input item coordinates to the window coordinates.
void cursorRectangleChanged()
\inmodule QtCore
The QKeyEvent class describes a key event.
Definition qevent.h:424
The QKeySequence class encapsulates a key sequence as used by shortcuts.
int count() const
Returns the number of keys in the key sequence.
\inmodule QtCore
Definition qobject.h:103
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:346
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
QVariant property(const char *name) const
Returns the value of the object's name property.
Definition qobject.cpp:4323
The QPlatformInputContext class abstracts the input method dependent data and composing state.
static QRectF anchorRectangle()
QPlatformInputContext::anchorRectangle.
static void setSelectionOnFocusObject(const QPointF &anchorPos, const QPointF &cursorPos)
QPlatformInputContext::setSelectionOnFocusObject.
static QVariant queryFocusObject(Qt::InputMethodQuery query, QPointF position)
QPlatformInputContext::queryFocusObject.
static QRectF inputItemClipRectangle()
QPlatformInputContext::inputItemClipRectangle.
static QRectF cursorRectangle()
QPlatformInputContext::cursorRectangle.
static QRectF inputItemRectangle()
QPlatformInputContext::inputItemRectangle.
The QPlatformWindow class provides an abstraction for top-level windows.
\inmodule QtCore\reentrant
Definition qpoint.h:217
\inmodule QtCore\reentrant
Definition qpoint.h:25
constexpr void setY(int y) noexcept
Sets the y coordinate of this point to the given y coordinate.
Definition qpoint.h:145
constexpr int y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:135
\inmodule QtCore\reentrant
Definition qrect.h:484
\inmodule QtCore\reentrant
Definition qrect.h:30
constexpr int right() const noexcept
Returns the x-coordinate of the rectangle's right edge.
Definition qrect.h:179
\inmodule QtCore
Definition qstringview.h:78
constexpr QStringView right(qsizetype n) const noexcept
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
QString left(qsizetype n) const &
Definition qstring.h:363
void chop(qsizetype n)
Removes n characters from the end of the string.
Definition qstring.cpp:6340
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5871
void truncate(qsizetype pos)
Truncates the string at the given position index.
Definition qstring.cpp:6319
QString mid(qsizetype position, qsizetype n=-1) const &
Definition qstring.cpp:5300
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
void clear()
Clears the contents of the string and makes it null.
Definition qstring.h:1252
const QChar * constData() const
Returns a pointer to the data stored in the QString.
Definition qstring.h:1246
QString right(qsizetype n) const &
Definition qstring.h:375
int compare(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const noexcept
Definition qstring.cpp:6664
QString & insert(qsizetype i, QChar c)
Definition qstring.cpp:3132
bool contains(QChar c, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.h:1369
static QString number(int, int base=10)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:8084
QString & remove(qsizetype i, qsizetype len)
Removes n characters from the string, starting at the given position index, and returns a reference t...
Definition qstring.cpp:3466
reverse_iterator rbegin()
Definition qstring.h:942
qsizetype length() const noexcept
Returns the number of characters in this string.
Definition qstring.h:191
BatchEditLock(const BatchEditLock &)=delete
BatchEditLock(QAndroidInputContext *context)
BatchEditLock & operator=(const BatchEditLock &)=delete
void setFontUnderline(bool underline)
If underline is true, sets the text format's font to be underlined; otherwise it is displayed non-und...
static QThread * currentThread()
Definition qthread.cpp:1039
void setSingleShot(bool singleShot)
Definition qtimer.cpp:552
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
void setInterval(int msec)
Definition qtimer.cpp:579
void stop()
Stops the timer.
Definition qtimer.cpp:267
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
void setTimerType(Qt::TimerType atype)
Definition qtimer.cpp:651
The QTransform class specifies 2D transformations of a coordinate system.
Definition qtransform.h:20
QTransform inverted(bool *invertible=nullptr) const
Returns an inverted copy of this matrix.
\inmodule QtCore
Definition qvariant.h:65
T value() const &
Definition qvariant.h:516
bool isValid() const
Returns true if the storage type of this variant is not QMetaType::UnknownType; otherwise returns fal...
Definition qvariant.h:714
int toInt(bool *ok=nullptr) const
Returns the variant as an int if the variant has userType() \l QMetaType::Int, \l QMetaType::Bool,...
QString toString() const
Returns the variant as a QString if the variant has a userType() including, but not limited to:
QString str
[2]
QString text
QCursor cursor
rect
[4]
else opt state
[0]
T fromNativePixels(const T &value, const C *context)
Combined button and popup list for selecting options.
void updateSelection(int selStart, int selEnd, int candidatesStart, int candidatesEnd)
void showSoftwareKeyboard(int left, int top, int width, int height, int inputHints, int enterKeyType)
bool isSoftwareKeyboardVisible()
void updateHandles(int mode, QPoint editMenuPos, uint32_t editButtons, QPoint cursor, QPoint anchor, bool rtl)
QAndroidPlatformIntegration * androidPlatformIntegration()
@ ImTextBeforeCursor
@ ImSurroundingText
@ ImCursorPosition
@ ImEnterKeyType
@ ImCurrentSelection
@ ImAbsolutePosition
@ ImReadOnly
@ ImAnchorPosition
@ ImHints
@ ImEnabled
@ ImTextAfterCursor
@ VeryCoarseTimer
@ ImhNoTextHandles
@ ImhUppercaseOnly
@ ImhLowercaseOnly
@ ImhNoAutoUppercase
@ KeyboardModifierMask
@ CaseInsensitive
ApplicationState
Definition qnamespace.h:262
@ ApplicationActive
Definition qnamespace.h:266
ConnectionType
@ BlockingQueuedConnection
static void * context
static int getBlockPosition(const QSharedPointer< QInputMethodQueryEvent > &query)
static char const *const QtExtractedTextClassName
static jboolean cut(JNIEnv *, jobject)
static jint getCursorCapsMode(JNIEnv *, jobject, jint reqModes)
static QAndroidInputContext * m_androidInputContext
static jboolean fullscreenMode(JNIEnv *, jobject)
static JNINativeMethod methods[]
static QRect screenInputItemRectangle()
static jboolean finishComposingText(JNIEnv *, jobject)
static jobject getExtractedText(JNIEnv *env, jobject, int hintMaxChars, int hintMaxLines, jint flags)
static char const *const QtNativeInputConnectionClassName
static jfieldID m_partialStartOffsetFieldID
static jboolean copy(JNIEnv *, jobject)
static jstring getTextBeforeCursor(JNIEnv *env, jobject, jint length, jint flags)
static jfieldID m_selectionEndFieldID
static jboolean copyURL(JNIEnv *, jobject)
static jmethodID m_classConstructorMethodID
static jfieldID m_startOffsetFieldID
static void runOnQtThread(const std::function< void()> &func)
static jboolean commitText(JNIEnv *env, jobject, jstring text, jint newCursorPosition)
static jfieldID m_partialEndOffsetFieldID
static jboolean paste(JNIEnv *, jobject)
static jboolean beginBatchEdit(JNIEnv *, jobject)
static jstring getSelectedText(JNIEnv *env, jobject, jint flags)
static jboolean setComposingRegion(JNIEnv *, jobject, jint start, jint end)
static jfieldID m_textFieldID
static jboolean deleteSurroundingText(JNIEnv *, jobject, jint leftLength, jint rightLength)
static jboolean setComposingText(JNIEnv *env, jobject, jstring text, jint newCursorPosition)
static jboolean updateCursorPosition(JNIEnv *, jobject)
static int m_selectHandleWidth
static jstring getTextAfterCursor(JNIEnv *env, jobject, jint length, jint flags)
static int getAbsoluteCursorPosition(const QSharedPointer< QInputMethodQueryEvent > &query)
static void reportFullscreenMode(JNIEnv *, jobject, jboolean enabled)
static jboolean endBatchEdit(JNIEnv *, jobject)
static jclass m_extractedTextClass
static jfieldID m_selectionStartFieldID
static jboolean setSelection(JNIEnv *, jobject, jint start, jint end)
static jboolean selectAll(JNIEnv *, jobject)
#define Q_UNLIKELY(x)
#define qGuiApp
#define qCritical
Definition qlogging.h:167
#define qWarning
Definition qlogging.h:166
#define qCDebug(category,...)
return ret
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
#define SLOT(a)
Definition qobjectdefs.h:52
#define Q_ARG(Type, data)
Definition qobjectdefs.h:63
#define SIGNAL(a)
Definition qobjectdefs.h:53
static bool contains(const QJsonArray &haystack, unsigned needle)
Definition qopengl.cpp:116
GLint GLint GLint GLint GLint x
[0]
GLuint64 key
GLuint GLuint end
GLenum GLuint GLenum GLsizei length
GLuint object
[3]
GLenum GLenum GLsizei const GLuint GLboolean enabled
GLbitfield flags
GLuint start
GLint y
struct _cl_event * event
GLenum query
GLenum func
Definition qopenglext.h:663
GLuint res
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
QT_BEGIN_NAMESPACE constexpr void qSwap(T &value1, T &value2) noexcept(std::is_nothrow_swappable_v< T >)
Definition qswap.h:20
Q_CORE_EXPORT int qEnvironmentVariableIntValue(const char *varName, bool *ok=nullptr) noexcept
#define Q_UNUSED(x)
unsigned int uint
Definition qtypes.h:34
#define enabled
QStringList keys
myObject disconnect()
[26]
aWidget window() -> setWindowTitle("New Window Title")
[2]
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...