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
5#include "qwasmwindow.h"
7
8#include <QRectF>
9#include <QLoggingCategory>
10#include <qguiapplication.h>
11#include <qwindow.h>
12#include <qpa/qplatforminputcontext.h>
13#include <qpa/qwindowsysteminterface.h>
14#if QT_CONFIG(clipboard)
15#include <QClipboard>
16#endif
17#include <QtGui/qtextobject.h>
18
20
21Q_LOGGING_CATEGORY(qLcQpaWasmInputContext, "qt.qpa.wasm.inputcontext")
22
23using namespace qstdweb;
24
25void QWasmInputContext::inputCallback(emscripten::val event)
26{
27 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "isComposing : " << event["isComposing"].as<bool>();
28
29 emscripten::val inputType = event["inputType"];
30 if (inputType.isNull() || inputType.isUndefined())
31 return;
32 const auto inputTypeString = inputType.as<std::string>();
33
34 emscripten::val inputData = event["data"];
35 QString inputStr = (!inputData.isNull() && !inputData.isUndefined())
36 ? QString::fromEcmaString(inputData) : QString();
37
38 // There are many inputTypes for InputEvent
39 // https://www.w3.org/TR/input-events-1/
40 // Some of them should be implemented here later.
41 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "inputType : " << inputTypeString;
42 if (!inputTypeString.compare("deleteContentBackward")) {
43 QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier);
44 QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyRelease, Qt::Key_Backspace, Qt::NoModifier);
45 event.call<void>("stopImmediatePropagation");
46 return;
47 } else if (!inputTypeString.compare("deleteContentForward")) {
48 QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_Delete, Qt::NoModifier);
49 QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyRelease, Qt::Key_Delete, Qt::NoModifier);
50 event.call<void>("stopImmediatePropagation");
51 return;
52 } else if (!inputTypeString.compare("insertCompositionText")) {
53 qCDebug(qLcQpaWasmInputContext) << "inputString : " << inputStr;
54 insertPreedit();
55 event.call<void>("stopImmediatePropagation");
56 return;
57 } else if (!inputTypeString.compare("insertReplacementText")) {
58 qCDebug(qLcQpaWasmInputContext) << "inputString : " << inputStr;
59 //auto ranges = event.call<emscripten::val>("getTargetRanges");
60 //qCDebug(qLcQpaWasmInputContext) << ranges["length"].as<int>();
61 // WA For Korean IME
62 // insertReplacementText should have targetRanges but
63 // Safari cannot have it and just it seems to be supposed
64 // to replace previous input.
65 insertText(inputStr, true);
66
67 event.call<void>("stopImmediatePropagation");
68 return;
69 } else if (!inputTypeString.compare("deleteCompositionText")) {
70 setPreeditString("", 0);
71 insertPreedit();
72 event.call<void>("stopImmediatePropagation");
73 return;
74 } else if (!inputTypeString.compare("insertFromComposition")) {
75 setPreeditString(inputStr, 0);
76 insertPreedit();
77 event.call<void>("stopImmediatePropagation");
78 return;
79 } else if (!inputTypeString.compare("insertText")) {
80 insertText(inputStr);
81 event.call<void>("stopImmediatePropagation");
82#if QT_CONFIG(clipboard)
83 } else if (!inputTypeString.compare("insertFromPaste")) {
84 insertText(QGuiApplication::clipboard()->text());
85 event.call<void>("stopImmediatePropagation");
86 // These can be supported here,
87 // But now, keyCallback in QWasmWindow
88 // will take them as exceptions.
89 //} else if (!inputTypeString.compare("deleteByCut")) {
90#endif
91 } else {
92 qCWarning(qLcQpaWasmInputContext) << Q_FUNC_INFO << "inputType \"" <<
93 inputType.as<std::string>() << "\" is not supported in Qt yet";
94 }
95}
96
97void QWasmInputContext::compositionEndCallback(emscripten::val event)
98{
99 const auto inputStr = QString::fromEcmaString(event["data"]);
100 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << inputStr;
101
102 if (preeditString().isEmpty())
103 return;
104
105 if (inputStr != preeditString()) {
106 qCWarning(qLcQpaWasmInputContext) << Q_FUNC_INFO
107 << "Composition string" << inputStr
108 << "is differ from" << preeditString();
109 }
110 commitPreeditAndClear();
111}
112
113void QWasmInputContext::compositionStartCallback(emscripten::val event)
114{
115 Q_UNUSED(event);
116 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
117
118 // Do nothing when starting composition
119}
120
121/*
122// Test implementation
123static void beforeInputCallback(emscripten::val event)
124{
125 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
126
127 auto ranges = event.call<emscripten::val>("getTargetRanges");
128 auto length = ranges["length"].as<int>();
129 for (auto i = 0; i < length; i++) {
130 qCDebug(qLcQpaWasmInputContext) << ranges.call<emscripten::val>("get", i)["startOffset"].as<int>();
131 qCDebug(qLcQpaWasmInputContext) << ranges.call<emscripten::val>("get", i)["endOffset"].as<int>();
132 }
133}
134*/
135
136void QWasmInputContext::compositionUpdateCallback(emscripten::val event)
137{
138 const auto compositionStr = QString::fromEcmaString(event["data"]);
139 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << compositionStr;
140
141 // WA for IOS.
142 // Not sure now because I cannot test it anymore.
143// int replaceSize = 0;
144// emscripten::val win = emscripten::val::global("window");
145// emscripten::val sel = win.call<emscripten::val>("getSelection");
146// if (!sel.isNull() && !sel.isUndefined()
147// && sel["rangeCount"].as<int>() > 0) {
148// QInputMethodQueryEvent queryEvent(Qt::ImQueryAll);
149// QCoreApplication::sendEvent(QGuiApplication::focusObject(), &queryEvent);
150// qCDebug(qLcQpaWasmInputContext) << "Qt surrounding text: " << queryEvent.value(Qt::ImSurroundingText).toString();
151// qCDebug(qLcQpaWasmInputContext) << "Qt current selection: " << queryEvent.value(Qt::ImCurrentSelection).toString();
152// qCDebug(qLcQpaWasmInputContext) << "Qt text before cursor: " << queryEvent.value(Qt::ImTextBeforeCursor).toString();
153// qCDebug(qLcQpaWasmInputContext) << "Qt text after cursor: " << queryEvent.value(Qt::ImTextAfterCursor).toString();
154//
155// const QString &selectedStr = QString::fromEcmaString(sel.call<emscripten::val>("toString"));
156// const auto &preeditStr = preeditString();
157// qCDebug(qLcQpaWasmInputContext) << "Selection.type : " << sel["type"].as<std::string>();
158// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "Selected: " << selectedStr;
159// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "PreeditString: " << preeditStr;
160// if (!sel["type"].as<std::string>().compare("Range")) {
161// QString surroundingTextBeforeCursor = queryEvent.value(Qt::ImTextBeforeCursor).toString();
162// if (surroundingTextBeforeCursor.endsWith(selectedStr)) {
163// replaceSize = selectedStr.size();
164// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "Current Preedit: " << preeditStr << replaceSize;
165// }
166// }
167// emscripten::val range = sel.call<emscripten::val>("getRangeAt", 0);
168// qCDebug(qLcQpaWasmInputContext) << "Range.startOffset : " << range["startOffset"].as<int>();
169// qCDebug(qLcQpaWasmInputContext) << "Range.endOffset : " << range["endOffset"].as<int>();
170// }
171//
172// setPreeditString(compositionStr, replaceSize);
173 setPreeditString(compositionStr, 0);
174}
175
176QWasmInputContext::QWasmInputContext()
177{
178 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
179}
180
181QWasmInputContext::~QWasmInputContext()
182{
183}
184
185void QWasmInputContext::update(Qt::InputMethodQueries queries)
186{
187 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << queries;
188
189 if ((queries & Qt::ImEnabled) && (inputMethodAccepted() != m_inputMethodAccepted)) {
190 if (m_focusObject && !m_preeditString.isEmpty())
191 commitPreeditAndClear();
192 updateInputElement();
193 }
194 QPlatformInputContext::update(queries);
195}
196
197void QWasmInputContext::showInputPanel()
198{
199 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
200
201 // Note: showInputPanel not necessarily called, we shall
202 // still accept input if we have a focus object and
203 // inputMethodAccepted().
204 updateInputElement();
205}
206
207void QWasmInputContext::updateGeometry()
208{
209 if (QWasmAccessibility::isEnabled())
210 return;
211
212 if (m_inputElement.isNull())
213 return;
214
215 const QWindow *focusWindow = QGuiApplication::focusWindow();
216 if (!m_focusObject || !focusWindow || !m_inputMethodAccepted) {
217 m_inputElement["style"].set("left", "0px");
218 m_inputElement["style"].set("top", "0px");
219 } else {
220 Q_ASSERT(focusWindow);
221 Q_ASSERT(m_focusObject);
222 Q_ASSERT(m_inputMethodAccepted);
223
224 const QRect inputItemRectangle = QPlatformInputContext::inputItemRectangle().toRect();
225 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "propagating inputItemRectangle:" << inputItemRectangle;
226 m_inputElement["style"].set("left", std::to_string(inputItemRectangle.x()) + "px");
227 m_inputElement["style"].set("top", std::to_string(inputItemRectangle.y()) + "px");
228 m_inputElement["style"].set("width", std::to_string(inputItemRectangle.width()) + "px");
229 m_inputElement["style"].set("height", std::to_string(inputItemRectangle.height()) + "px");
230 }
231}
232
233void QWasmInputContext::updateInputElement()
234{
235 m_inputMethodAccepted = inputMethodAccepted();
236
237 if (QWasmAccessibility::isEnabled())
238 return;
239
240 // Mobile devices can dismiss keyboard/IME and focus is still on input.
241 // Successive clicks on the same input should open the keyboard/IME.
242 updateGeometry();
243
244 // If there is no focus object, or no visible input panel, remove focus
245 QWasmWindow *focusWindow = QWasmWindow::fromWindow(QGuiApplication::focusWindow());
246 if (!m_focusObject || !focusWindow || !m_inputMethodAccepted) {
247 if (!m_inputElement.isNull()) {
248 m_inputElement.set("value", "");
249 m_inputElement.set("inputMode", std::string("none"));
250 }
251
252 if (focusWindow) {
253 focusWindow->focus();
254 } else {
255 if (!m_inputElement.isNull())
256 m_inputElement.call<void>("blur");
257 }
258
259 m_inputElement = emscripten::val::null();
260 return;
261 }
262
263 Q_ASSERT(focusWindow);
264 Q_ASSERT(m_focusObject);
265 Q_ASSERT(m_inputMethodAccepted);
266
267 m_inputElement = focusWindow->inputElement();
268
269 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << QRectF::fromDOMRect(m_inputElement.call<emscripten::val>("getBoundingClientRect"));
270
271 // Set the text input
272 QInputMethodQueryEvent queryEvent(Qt::ImQueryAll);
273 QCoreApplication::sendEvent(m_focusObject, &queryEvent);
274 qCDebug(qLcQpaWasmInputContext) << "Qt surrounding text: " << queryEvent.value(Qt::ImSurroundingText).toString();
275 qCDebug(qLcQpaWasmInputContext) << "Qt current selection: " << queryEvent.value(Qt::ImCurrentSelection).toString();
276 qCDebug(qLcQpaWasmInputContext) << "Qt text before cursor: " << queryEvent.value(Qt::ImTextBeforeCursor).toString();
277 qCDebug(qLcQpaWasmInputContext) << "Qt text after cursor: " << queryEvent.value(Qt::ImTextAfterCursor).toString();
278 qCDebug(qLcQpaWasmInputContext) << "Qt cursor position: " << queryEvent.value(Qt::ImCursorPosition).toInt();
279 qCDebug(qLcQpaWasmInputContext) << "Qt anchor position: " << queryEvent.value(Qt::ImAnchorPosition).toInt();
280
281 m_inputElement.set("value", queryEvent.value(Qt::ImSurroundingText).toString().toStdString());
282
283 m_inputElement.set("selectionStart", queryEvent.value(Qt::ImAnchorPosition).toUInt());
284 m_inputElement.set("selectionEnd", queryEvent.value(Qt::ImCursorPosition).toUInt());
285
286 QInputMethodQueryEvent query((Qt::InputMethodQueries(Qt::ImHints)));
287 QCoreApplication::sendEvent(m_focusObject, &query);
288 if (Qt::InputMethodHints(query.value(Qt::ImHints).toInt()).testFlag(Qt::ImhHiddenText))
289 m_inputElement.set("type", "password");
290 else
291 m_inputElement.set("type", "text");
292
293 m_inputElement.set("inputMode", std::string("text"));
294
295 m_inputElement.call<void>("focus");
296}
297
298void QWasmInputContext::setFocusObject(QObject *object)
299{
300 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << object << inputMethodAccepted();
301
302 // Commit the previous composition before change m_focusObject
303 if (m_focusObject && !m_preeditString.isEmpty())
304 commitPreeditAndClear();
305
306 m_focusObject = object;
307
308 updateInputElement();
309 QPlatformInputContext::setFocusObject(object);
310}
311
312void QWasmInputContext::hideInputPanel()
313{
314 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
315
316 // hide only if m_focusObject does not exist
317 if (!m_focusObject)
318 updateInputElement();
319}
320
321void QWasmInputContext::setPreeditString(QString preeditStr, int replaceSize)
322{
323 m_preeditString = preeditStr;
324 m_replaceSize = replaceSize;
325}
326
327void QWasmInputContext::insertPreedit()
328{
329 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << m_preeditString;
330
331 QList<QInputMethodEvent::Attribute> attributes;
332 {
333 QInputMethodEvent::Attribute attr_cursor(QInputMethodEvent::Cursor,
334 m_preeditString.length(),
335 1);
336 attributes.append(attr_cursor);
337
338 QTextCharFormat format;
339 format.setFontUnderline(true);
340 format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
341 QInputMethodEvent::Attribute attr_format(QInputMethodEvent::TextFormat,
342 0,
343 m_preeditString.length(), format);
344 attributes.append(attr_format);
345 }
346
347 QInputMethodEvent e(m_preeditString, attributes);
348 if (m_replaceSize > 0)
349 e.setCommitString("", -m_replaceSize, m_replaceSize);
350 QCoreApplication::sendEvent(m_focusObject, &e);
351}
352
353void QWasmInputContext::commitPreeditAndClear()
354{
355 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << m_preeditString;
356
357 if (m_preeditString.isEmpty())
358 return;
359 QInputMethodEvent e;
360 e.setCommitString(m_preeditString);
361 m_preeditString.clear();
362 QCoreApplication::sendEvent(m_focusObject, &e);
363}
364
365void QWasmInputContext::insertText(QString inputStr, bool replace)
366{
367 Q_UNUSED(replace);
368 if (!inputStr.isEmpty()) {
369 const int replaceLen = 0;
370 QInputMethodEvent e;
371 e.setCommitString(inputStr, -replaceLen, replaceLen);
372 QCoreApplication::sendEvent(m_focusObject, &e);
373 }
374}
375
376QT_END_NAMESPACE
friend class QWasmCompositor