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