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