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