Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwasminputcontext.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6#include "qwasmwindow.h"
8
9#include <QRectF>
10#include <QLoggingCategory>
11#include <qguiapplication.h>
12#include <qwindow.h>
13#include <qpa/qplatforminputcontext.h>
14#include <qpa/qwindowsysteminterface.h>
15#if QT_CONFIG(clipboard)
16#include <QClipboard>
17#endif
18#include <QtGui/qtextobject.h>
19
21
22Q_LOGGING_CATEGORY(qLcQpaWasmInputContext, "qt.qpa.wasm.inputcontext")
23
24using namespace qstdweb;
25
26void QWasmInputContext::inputCallback(emscripten::val event)
27{
28 emscripten::val inputType = event["inputType"];
29 if (inputType.isNull() || inputType.isUndefined())
30 return;
31 const auto inputTypeString = inputType.as<std::string>();
32
33 emscripten::val inputData = event["data"];
34 QString inputStr = (!inputData.isNull() && !inputData.isUndefined())
35 ? QString::fromEcmaString(inputData) : QString();
36
37
38 // also do dataTransfer
39 // containing rich text
40 emscripten::val dataTr = event["dataTransfer"];
41 if (!dataTr.isUndefined() && !dataTr.isNull()) {
42 qCDebug(qLcQpaWasmInputContext) << "inputEvent dataTransfer" << inputStr;
43 inputStr = QString::fromStdString(dataTr.
44 call<emscripten::val>("getData",
45 std::string("text")).as<std::string>());
46 }
47
48 // There are many inputTypes for InputEvent
49 // https://www.w3.org/TR/input-events-1/
50 // Some of them should be implemented here later.
51 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "inputType : " << inputTypeString;
52 if (!inputTypeString.compare("deleteContentBackward")) {
53
54 QInputMethodQueryEvent queryEvent(Qt::ImQueryAll);
55 QCoreApplication::sendEvent(m_focusObject, &queryEvent);
56 int cursorPosition = queryEvent.value(Qt::ImCursorPosition).toInt();
57 int deleteLength = rangesPair.second - rangesPair.first;
58 int deleteFrom = -1;
59
60 if (cursorPosition >= rangesPair.first) {
61 deleteFrom = -(cursorPosition - rangesPair.first);
62 }
63 QInputMethodEvent e;
64 e.setCommitString(QString(), deleteFrom, deleteLength);
65 QCoreApplication::sendEvent(m_focusObject, &e);
66
67 rangesPair.first = 0;
68 rangesPair.second = 0;
69
70 event.call<void>("stopImmediatePropagation");
71 return;
72 } else if (!inputTypeString.compare("deleteContentForward")) {
73 QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_Delete, Qt::NoModifier);
74 QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyRelease, Qt::Key_Delete, Qt::NoModifier);
75 event.call<void>("stopImmediatePropagation");
76 return;
77 } else if (!inputTypeString.compare("insertCompositionText")) {
78 qCDebug(qLcQpaWasmInputContext) << "insertCompositionText : " << inputStr;
79 event.call<void>("stopImmediatePropagation");
80
81 QInputMethodQueryEvent queryEvent(Qt::ImQueryAll);
82 QCoreApplication::sendEvent(m_focusObject, &queryEvent);
83
84 int qCursorPosition = queryEvent.value(Qt::ImCursorPosition).toInt() ;
85 int replaceIndex = (qCursorPosition - rangesPair.first);
86 int replaceLength = rangesPair.second - rangesPair.first;
87
88 setPreeditString(inputStr, replaceIndex);
89 insertPreedit(replaceLength);
90
91 rangesPair.first = 0;
92 rangesPair.second = 0;
93 event.call<void>("stopImmediatePropagation");
94 return;
95 } else if (!inputTypeString.compare("insertReplacementText")) {
96 // the previous input string up to the space, needs replaced with this
97 // used on iOS when continuing composition after focus change
98 // there's no range given
99
100 qCDebug(qLcQpaWasmInputContext) << "insertReplacementText >>>>" << "inputString : " << inputStr;
101 emscripten::val ranges = event.call<emscripten::val>("getTargetRanges");
102
103 m_preeditString.clear();
104 std::string elementString = m_inputElement["value"].as<std::string>();
105 QInputMethodQueryEvent queryEvent(Qt::ImQueryAll);
106 QCoreApplication::sendEvent(m_focusObject, &queryEvent);
107 QString textFieldString = queryEvent.value(Qt::ImTextBeforeCursor).toString();
108 int qCursorPosition = queryEvent.value(Qt::ImCursorPosition).toInt();
109
110 if (rangesPair.first != 0 || rangesPair.second != 0) {
111
112 int replaceIndex = (qCursorPosition - rangesPair.first);
113 int replaceLength = rangesPair.second - rangesPair.first;
114 replaceText(inputStr, -replaceIndex, replaceLength);
115 rangesPair.first = 0;
116 rangesPair.second = 0;
117
118 } else {
119 int spaceIndex = textFieldString.lastIndexOf(' ') + 1;
120 int replaceIndex = (qCursorPosition - spaceIndex);
121
122 replaceText(inputStr, -replaceIndex, replaceIndex);
123 }
124
125 event.call<void>("stopImmediatePropagation");
126
127 return;
128 } else if (!inputTypeString.compare("deleteCompositionText")) {
129 setPreeditString("", 0);
130 insertPreedit();
131 event.call<void>("stopImmediatePropagation");
132 return;
133 } else if (!inputTypeString.compare("insertFromComposition")) {
134 setPreeditString(inputStr, 0);
135 insertPreedit();
136 event.call<void>("stopImmediatePropagation");
137 return;
138 } else if (!inputTypeString.compare("insertText")) {
139 if ((rangesPair.first != 0 || rangesPair.second != 0)
140 && rangesPair.first != rangesPair.second) {
141
142 QInputMethodQueryEvent queryEvent(Qt::ImQueryAll);
143 QCoreApplication::sendEvent(m_focusObject, &queryEvent);
144
145 int qCursorPosition = queryEvent.value(Qt::ImCursorPosition).toInt();
146 int replaceIndex = (qCursorPosition - rangesPair.first);
147 int replaceLength = rangesPair.second - rangesPair.first;
148
149 replaceText(inputStr, -replaceIndex, replaceLength);
150
151 rangesPair.first = 0;
152 rangesPair.second = 0;
153
154 } else {
155 insertText(inputStr);
156 }
157
158 event.call<void>("stopImmediatePropagation");
159 } else if (!inputTypeString.compare("insertFromPaste")) {
160 insertText(inputStr);
161 event.call<void>("stopImmediatePropagation");
162 // These can be supported here,
163 // But now, keyCallback in QWasmWindow
164 // will take them as exceptions.
165 //} else if (!inputTypeString.compare("deleteByCut")) {
166 } else {
167 qCWarning(qLcQpaWasmInputContext) << Q_FUNC_INFO << "inputType \"" <<
168 inputType.as<std::string>() << "\" is not supported in Qt yet";
169 }
170}
171
172void QWasmInputContext::compositionEndCallback(emscripten::val event)
173{
174 const auto inputStr = QString::fromEcmaString(event["data"]);
175
176 if (preeditString().isEmpty()) // we get final results from inputCallback
177 return;
178
179 if (inputStr != preeditString()) {
180 qCWarning(qLcQpaWasmInputContext) << Q_FUNC_INFO
181 << "Composition string" << inputStr
182 << "is differ from" << preeditString();
183 }
184 commitPreeditAndClear();
185}
186
187void QWasmInputContext::compositionStartCallback(emscripten::val event)
188{
189 Q_UNUSED(event);
190
191 // Do nothing when starting composition
192}
193
194void QWasmInputContext::compositionUpdateCallback(emscripten::val event)
195{
196 const auto compositionStr = QString::fromEcmaString(event["data"]);
197 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << compositionStr;
198
199 setPreeditString(compositionStr, 0);
200}
201
202void QWasmInputContext::beforeInputCallback(emscripten::val event)
203{
204 emscripten::val ranges = event.call<emscripten::val>("getTargetRanges");
205
206 auto length = ranges["length"].as<int>();
207 for (auto i = 0; i < length; i++) {
208 emscripten::val range = ranges[i];
209 qCDebug(qLcQpaWasmInputContext) << "startOffset" << range["startOffset"].as<int>();
210 qCDebug(qLcQpaWasmInputContext) << "endOffset" << range["endOffset"].as<int>();
211 rangesPair.first = range["startOffset"].as<int>();
212 rangesPair.second = range["endOffset"].as<int>();
213 }
214}
215
216QWasmInputContext::QWasmInputContext()
217{
218 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
219}
220
221QWasmInputContext::~QWasmInputContext()
222{
223}
224
225void QWasmInputContext::update(Qt::InputMethodQueries queries)
226{
227 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << queries;
228
229 if ((queries & Qt::ImEnabled) && (inputMethodAccepted() != m_inputMethodAccepted)) {
230 if (m_focusObject && !m_preeditString.isEmpty())
231 commitPreeditAndClear();
232 updateInputElement();
233 }
234 QPlatformInputContext::update(queries);
235}
236
237void QWasmInputContext::showInputPanel()
238{
239 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
240
241 // Note: showInputPanel not necessarily called, we shall
242 // still accept input if we have a focus object and
243 // inputMethodAccepted().
244 updateInputElement();
245}
246
247void QWasmInputContext::updateGeometry()
248{
249 if (QWasmAccessibility::isEnabled())
250 return;
251
252 if (m_inputElement.isNull())
253 return;
254
255 const QWindow *focusWindow = QGuiApplication::focusWindow();
256 if (!m_focusObject || !focusWindow || !m_inputMethodAccepted) {
257 m_inputElement["style"].set("left", "0px");
258 m_inputElement["style"].set("top", "0px");
259 } else {
260 Q_ASSERT(focusWindow);
261 Q_ASSERT(m_focusObject);
262 Q_ASSERT(m_inputMethodAccepted);
263
264 const QRect inputItemRectangle = QPlatformInputContext::inputItemRectangle().toRect();
265 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "propagating inputItemRectangle:" << inputItemRectangle;
266 m_inputElement["style"].set("left", std::to_string(inputItemRectangle.x()) + "px");
267 m_inputElement["style"].set("top", std::to_string(inputItemRectangle.y()) + "px");
268 m_inputElement["style"].set("width", "1px");
269 m_inputElement["style"].set("height", "1px");
270 }
271}
272
273void QWasmInputContext::updateInputElement()
274{
275 m_inputMethodAccepted = inputMethodAccepted();
276
277 if (QWasmAccessibility::isEnabled())
278 return;
279
280 // Mobile devices can dismiss keyboard/IME and focus is still on input.
281 // Successive clicks on the same input should open the keyboard/IME.
282 updateGeometry();
283
284 // If there is no focus object, or no visible input panel, remove focus
285 QWasmWindow *focusWindow = QWasmWindow::fromWindow(QGuiApplication::focusWindow());
286 if (!m_focusObject || !focusWindow || !m_inputMethodAccepted) {
287 if (!m_inputElement.isNull()) {
288 m_inputElement.set("value", "");
289 m_inputElement.set("inputMode", std::string("none"));
290 }
291
292 if (focusWindow) {
293 focusWindow->focus();
294 } else {
295 if (!m_inputElement.isNull())
296 m_inputElement.call<void>("blur");
297 }
298
299 m_inputElement = emscripten::val::null();
300 return;
301 }
302
303 Q_ASSERT(focusWindow);
304 Q_ASSERT(m_focusObject);
305 Q_ASSERT(m_inputMethodAccepted);
306
307 m_inputElement = focusWindow->inputElement();
308
309 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << QRectF::fromDOMRect(m_inputElement.call<emscripten::val>("getBoundingClientRect"));
310
311 // Set the text input
312 QInputMethodQueryEvent queryEvent(Qt::ImQueryAll);
313 QCoreApplication::sendEvent(m_focusObject, &queryEvent);
314 qCDebug(qLcQpaWasmInputContext) << "Qt surrounding text: " << queryEvent.value(Qt::ImSurroundingText).toString();
315 qCDebug(qLcQpaWasmInputContext) << "Qt current selection: " << queryEvent.value(Qt::ImCurrentSelection).toString();
316 qCDebug(qLcQpaWasmInputContext) << "Qt text before cursor: " << queryEvent.value(Qt::ImTextBeforeCursor).toString();
317 qCDebug(qLcQpaWasmInputContext) << "Qt text after cursor: " << queryEvent.value(Qt::ImTextAfterCursor).toString();
318 qCDebug(qLcQpaWasmInputContext) << "Qt cursor position: " << queryEvent.value(Qt::ImCursorPosition).toInt();
319 qCDebug(qLcQpaWasmInputContext) << "Qt anchor position: " << queryEvent.value(Qt::ImAnchorPosition).toInt();
320
321 m_inputElement.set("value", queryEvent.value(Qt::ImSurroundingText).toString().toStdString());
322
323 m_inputElement.set("selectionStart", queryEvent.value(Qt::ImAnchorPosition).toUInt());
324 m_inputElement.set("selectionEnd", queryEvent.value(Qt::ImCursorPosition).toUInt());
325
326 QInputMethodQueryEvent query((Qt::InputMethodQueries(Qt::ImHints)));
327 QCoreApplication::sendEvent(m_focusObject, &query);
328 if (Qt::InputMethodHints(query.value(Qt::ImHints).toInt()).testFlag(Qt::ImhHiddenText))
329 m_inputElement.set("type", "password");
330 else
331 m_inputElement.set("type", "text");
332
333// change inputmode to suit Qt imhints
334
335 Qt::InputMethodHints imHints = static_cast<Qt::InputMethodHints>(query.value(Qt::ImHints).toInt());
336 std::string inMode = "text";
337
338 if (imHints & Qt::ImhDigitsOnly)
339 inMode = "numeric";
340 if (imHints & Qt::ImhFormattedNumbersOnly)
341 inMode = "decimal";
342 if (imHints & Qt::ImhDialableCharactersOnly)
343 inMode = "tel";
344 if (imHints & Qt::ImhEmailCharactersOnly)
345 inMode = "email";
346 if (imHints & Qt::ImhUrlCharactersOnly)
347 inMode = "url";
348 // search ??
349 m_inputElement.set("inputMode",inMode);
350
351 m_inputElement.call<void>("focus");
352}
353
354void QWasmInputContext::setFocusObject(QObject *object)
355{
356 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << object << inputMethodAccepted();
357
358 // Commit the previous composition before change m_focusObject
359 if (m_focusObject && !m_preeditString.isEmpty())
360 commitPreeditAndClear();
361
362 m_focusObject = object;
363
364 updateInputElement();
365 QPlatformInputContext::setFocusObject(object);
366}
367
368void QWasmInputContext::hideInputPanel()
369{
370 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
371
372 // hide only if m_focusObject does not exist
373 if (!m_focusObject)
374 updateInputElement();
375}
376
377void QWasmInputContext::setPreeditString(QString preeditStr, int replaceSize)
378{
379 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << preeditStr << replaceSize;
380 m_preeditString = preeditStr;
381 m_replaceIndex = replaceSize;
382}
383
384void QWasmInputContext::insertPreedit(int replaceLength)
385{
386 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << m_preeditString;
387 if (replaceLength == 0)
388 replaceLength = m_preeditString.length();
389
390 QList<QInputMethodEvent::Attribute> attributes;
391 {
392 QInputMethodEvent::Attribute attr_cursor(QInputMethodEvent::Cursor,
393 0,
394 1);
395 attributes.append(attr_cursor);
396
397 QTextCharFormat format;
398 format.setFontUnderline(true);
399 format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
400 QInputMethodEvent::Attribute attr_format(QInputMethodEvent::TextFormat,
401 0,
402 replaceLength, format);
403 attributes.append(attr_format);
404 }
405
406 QInputMethodEvent e(m_preeditString, attributes);
407 if (m_replaceIndex > 0)
408 e.setCommitString("", -m_replaceIndex, replaceLength);
409 QCoreApplication::sendEvent(m_focusObject, &e);
410}
411
412void QWasmInputContext::commitPreeditAndClear()
413{
414 if (m_preeditString.isEmpty())
415 return;
416 QInputMethodEvent e;
417 e.setCommitString(m_preeditString);
418 m_preeditString.clear();
419 QCoreApplication::sendEvent(m_focusObject, &e);
420}
421
422void QWasmInputContext::insertText(QString inputStr, bool replace)
423{ // commitString
424 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << inputStr << replace;
425 Q_UNUSED(replace);
426 if (!inputStr.isEmpty()) {
427 const int replaceLen = 0;
428 QInputMethodEvent e;
429 e.setCommitString(inputStr, -replaceLen, replaceLen);
430 QCoreApplication::sendEvent(m_focusObject, &e);
431 }
432}
433
434/* This will replace the text in the focusobject at replaceFrom position, and replaceSize length
435 with the text in inputStr. */
436
437 void QWasmInputContext::replaceText(QString inputStr, int replaceFrom, int replaceSize)
438 {
439 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << inputStr << replaceFrom << replaceSize;
440
441 QList<QInputMethodEvent::Attribute> attributes;
442 {
443 QInputMethodEvent::Attribute attr_cursor(QInputMethodEvent::Cursor,
444 0, // start
445 1); // length
446 attributes.append(attr_cursor);
447
448 QTextCharFormat format;
449 format.setFontUnderline(true);
450 format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
451 QInputMethodEvent::Attribute attr_format(QInputMethodEvent::TextFormat,
452 0,
453 replaceSize,
454 format);
455 attributes.append(attr_format);
456 }
457
458 QInputMethodEvent e1(QString(), attributes);
459 e1.setCommitString(inputStr, replaceFrom, replaceSize);
460 QCoreApplication::sendEvent(m_focusObject, &e1);
461
462 m_preeditString.clear();
463 }
464
465QT_END_NAMESPACE
static QWasmWindow * fromWindow(const QWindow *window)
friend class QWasmCompositor
Combined button and popup list for selecting options.