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