10#include <QLoggingCategory>
11#include <qguiapplication.h>
13#include <qpa/qplatforminputcontext.h>
14#include <qpa/qwindowsysteminterface.h>
15#if QT_CONFIG(clipboard)
18#include <QtGui/qtextobject.h>
22Q_LOGGING_CATEGORY(qLcQpaWasmInputContext,
"qt.qpa.wasm.inputcontext")
24using namespace qstdweb;
26void QWasmInputContext::inputCallback(
emscripten::val event)
28 emscripten::val inputType = event[
"inputType"];
29 if (inputType.isNull() || inputType.isUndefined())
31 const auto inputTypeString = inputType.as<
std::string>();
34 QString inputStr = (!inputData.isNull() && !inputData.isUndefined())
35 ? QString::fromEcmaString(inputData) : QString();
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>());
48 if (m_ignoreNextInput) {
49 m_ignoreNextInput =
false;
56 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO <<
"inputType : " << inputTypeString;
58 if (!inputTypeString.compare(
"deleteContentBackward")) {
59 const ResolvedRange resolved = resolveRange(getFirstRange());
61 const int deleteFrom = resolved.replaceFrom <= 0 ? resolved.replaceFrom : -1;
62 commitText(QString(), deleteFrom, resolved.replaceLength);
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 }
else if (!inputTypeString.compare(
"insertCompositionText")) {
68 qCDebug(qLcQpaWasmInputContext) <<
"insertCompositionText : " << inputStr;
69 if (event[
"isComposing"].as<
bool>()) {
70 const CompositionRange range = getFirstRange();
71 setPreeditString(inputStr);
72 insertPreedit(range.end - range.start);
74 }
else if (!inputTypeString.compare(
"insertReplacementText")) {
76 qCDebug(qLcQpaWasmInputContext) <<
"insertReplacementText >>>>" <<
"inputString : " << inputStr;
77 const CompositionRange range = getFirstRange();
80 const ResolvedRange resolved = resolveRange(range);
81 commitText(inputStr, resolved.replaceFrom, resolved.replaceLength);
83 QInputMethodQueryEvent queryEvent(Qt::ImQueryAll);
84 QCoreApplication::sendEvent(m_focusObject, &queryEvent);
85 int qCursorPosition = queryEvent.value(Qt::ImCursorPosition).toInt();
86 QString textFieldString = queryEvent.value(Qt::ImTextBeforeCursor).toString();
88 int spaceIndex = textFieldString.lastIndexOf(
' ') + 1;
89 int replaceIndex = (qCursorPosition - spaceIndex);
90 commitText(inputStr, -replaceIndex, replaceIndex);
92 }
else if (!inputTypeString.compare(
"deleteCompositionText")) {
95 }
else if (!inputTypeString.compare(
"insertFromComposition")) {
96 setPreeditString(inputStr);
98 }
else if (!inputTypeString.compare(
"insertText")) {
99 const CompositionRange range = getFirstRange();
101 if (range.isValid && range.start != range.end) {
102 const ResolvedRange resolved = resolveRange(range);
103 commitText(inputStr, resolved.replaceFrom, resolved.replaceLength);
105 commitText(inputStr);
107 }
else if (!inputTypeString.compare(
"insertFromPaste")) {
108 commitText(inputStr);
114 qCWarning(qLcQpaWasmInputContext) << Q_FUNC_INFO <<
"inputType \"" <<
115 inputType.as<std::string>() <<
"\" is not supported in Qt yet";
119 event.call<
void>(
"stopPropagation");
124 m_compositionRange = emscripten::val::null();
127void QWasmInputContext::compositionEndCallback(
emscripten::val event)
129 const auto inputStr = QString::fromEcmaString(event[
"data"]);
131 if (preeditString().isEmpty())
134 if (inputStr != preeditString()) {
135 qCWarning(qLcQpaWasmInputContext) << Q_FUNC_INFO
136 <<
"Composition string" << inputStr
137 <<
"is differ from" << preeditString();
139 commitPreeditAndClear();
142void QWasmInputContext::compositionStartCallback(
emscripten::val event)
149void QWasmInputContext::compositionUpdateCallback(
emscripten::val event)
151 const auto compositionStr = QString::fromEcmaString(event[
"data"]);
152 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << compositionStr;
154 setPreeditString(compositionStr);
157void QWasmInputContext::beforeInputCallback(
emscripten::val event)
159 m_compositionRange = event.call<emscripten::val>(
"getTargetRanges");
161 if (
const CompositionRange range = getFirstRange(); range.isValid) {
162 qCDebug(qLcQpaWasmInputContext) <<
"startOffset" << range.start;
163 qCDebug(qLcQpaWasmInputContext) <<
"endOffset" << range.end;
169QWasmInputContext::CompositionRange QWasmInputContext::getFirstRange()
const
171 CompositionRange range;
172 if (!m_compositionRange.isNull() && !m_compositionRange.isUndefined()
173 && m_compositionRange[
"length"].as<
int>() > 0) {
174 emscripten::val first = m_compositionRange[0];
175 range.start = first[
"startOffset"].as<
int>();
176 range.end = first[
"endOffset"].as<
int>();
177 range.isValid =
true;
185QWasmInputContext::ResolvedRange QWasmInputContext::resolveRange(
const CompositionRange &range)
const
187 QInputMethodQueryEvent queryEvent(Qt::ImQueryAll);
188 QCoreApplication::sendEvent(m_focusObject, &queryEvent);
189 const int cursorPosition = queryEvent.value(Qt::ImCursorPosition).toInt();
190 return { range.start - cursorPosition, range.end - range.start };
193QWasmInputContext::QWasmInputContext()
195 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
198QWasmInputContext::~QWasmInputContext()
202void QWasmInputContext::update(Qt::InputMethodQueries queries)
204 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << queries;
206 if ((queries & Qt::ImEnabled) && (inputMethodAccepted() != m_inputMethodAccepted)) {
207 if (m_focusObject && !m_preeditString.isEmpty())
208 commitPreeditAndClear();
209 updateInputElement();
211 QPlatformInputContext::update(queries);
214void QWasmInputContext::showInputPanel()
216 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
221 updateInputElement();
224void QWasmInputContext::updateGeometry()
226 if (QWasmAccessibility::isEnabled())
229 if (m_inputElement.isNull())
232 const QWindow *focusWindow = QGuiApplication::focusWindow();
233 if (!m_focusObject || !focusWindow || !m_inputMethodAccepted) {
234 m_inputElement[
"style"].set(
"left",
"0px");
235 m_inputElement[
"style"].set(
"top",
"0px");
237 Q_ASSERT(focusWindow);
238 Q_ASSERT(m_focusObject);
239 Q_ASSERT(m_inputMethodAccepted);
241 const QRect inputItemRectangle = QPlatformInputContext::inputItemRectangle().toRect();
242 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO <<
"propagating inputItemRectangle:" << inputItemRectangle;
243 m_inputElement[
"style"].set(
"left", std::to_string(inputItemRectangle.x()) +
"px");
244 m_inputElement[
"style"].set(
"top", std::to_string(inputItemRectangle.y()) +
"px");
245 m_inputElement[
"style"].set(
"width",
"1px");
246 m_inputElement[
"style"].set(
"height",
"1px");
250void QWasmInputContext::updateInputElement()
252 m_inputMethodAccepted = inputMethodAccepted();
254 if (QWasmAccessibility::isEnabled())
263 if (!m_focusObject || !focusWindow || !m_inputMethodAccepted) {
264 if (!m_inputElement.isNull()) {
265 m_inputElement.set(
"value",
"");
266 m_inputElement.set(
"inputMode", std::string(
"none"));
272 if (!m_inputElement.isNull())
273 m_inputElement.call<
void>(
"blur");
276 m_inputElement = emscripten::val::null();
280 Q_ASSERT(focusWindow);
281 Q_ASSERT(m_focusObject);
282 Q_ASSERT(m_inputMethodAccepted);
284 m_inputElement = focusWindow->inputElement();
286 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << QRectF::fromDOMRect(m_inputElement.call<emscripten::val>(
"getBoundingClientRect"));
289 QInputMethodQueryEvent queryEvent(Qt::ImQueryAll);
290 QCoreApplication::sendEvent(m_focusObject, &queryEvent);
291 qCDebug(qLcQpaWasmInputContext) <<
"Qt surrounding text: " << queryEvent.value(Qt::ImSurroundingText).toString();
292 qCDebug(qLcQpaWasmInputContext) <<
"Qt current selection: " << queryEvent.value(Qt::ImCurrentSelection).toString();
293 qCDebug(qLcQpaWasmInputContext) <<
"Qt text before cursor: " << queryEvent.value(Qt::ImTextBeforeCursor).toString();
294 qCDebug(qLcQpaWasmInputContext) <<
"Qt text after cursor: " << queryEvent.value(Qt::ImTextAfterCursor).toString();
295 qCDebug(qLcQpaWasmInputContext) <<
"Qt cursor position: " << queryEvent.value(Qt::ImCursorPosition).toInt();
296 qCDebug(qLcQpaWasmInputContext) <<
"Qt anchor position: " << queryEvent.value(Qt::ImAnchorPosition).toInt();
298 m_inputElement.set(
"value", queryEvent.value(Qt::ImSurroundingText).toString().toStdString());
300 m_inputElement.set(
"selectionStart", queryEvent.value(Qt::ImAnchorPosition).toUInt());
301 m_inputElement.set(
"selectionEnd", queryEvent.value(Qt::ImCursorPosition).toUInt());
303 QInputMethodQueryEvent query((Qt::InputMethodQueries(Qt::ImHints)));
304 QCoreApplication::sendEvent(m_focusObject, &query);
305 if (Qt::InputMethodHints(query.value(Qt::ImHints).toInt()).testFlag(Qt::ImhHiddenText))
306 m_inputElement.set(
"type",
"password");
308 m_inputElement.set(
"type",
"text");
312 Qt::InputMethodHints imHints =
static_cast<Qt::InputMethodHints>(query.value(Qt::ImHints).toInt());
313 std::string inMode =
"text";
315 if (imHints & Qt::ImhDigitsOnly)
317 if (imHints & Qt::ImhFormattedNumbersOnly)
319 if (imHints & Qt::ImhDialableCharactersOnly)
321 if (imHints & Qt::ImhEmailCharactersOnly)
323 if (imHints & Qt::ImhUrlCharactersOnly)
326 m_inputElement.set(
"inputMode",inMode);
328 m_inputElement.call<
void>(
"focus");
331void QWasmInputContext::setFocusObject(QObject *object)
333 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << object << inputMethodAccepted();
336 if (m_focusObject && !m_preeditString.isEmpty())
337 commitPreeditAndClear();
339 m_focusObject = object;
341 updateInputElement();
342 QPlatformInputContext::setFocusObject(object);
345void QWasmInputContext::hideInputPanel()
347 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
351 updateInputElement();
354void QWasmInputContext::setPreeditString(QString preeditStr)
356 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << preeditStr;
357 m_preeditString = preeditStr;
360void QWasmInputContext::insertPreedit(
int replaceLength)
362 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << m_preeditString;
363 if (replaceLength == 0)
364 replaceLength = m_preeditString.length();
366 QList<QInputMethodEvent::Attribute> attributes;
368 QInputMethodEvent::Attribute attr_cursor(QInputMethodEvent::Cursor,
371 attributes.append(attr_cursor);
373 QTextCharFormat format;
374 format.setFontUnderline(
true);
375 format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
376 QInputMethodEvent::Attribute attr_format(QInputMethodEvent::TextFormat,
378 replaceLength, format);
379 attributes.append(attr_format);
382 QInputMethodEvent e(m_preeditString, attributes);
383 QCoreApplication::sendEvent(m_focusObject, &e);
386void QWasmInputContext::commitPreeditAndClear()
388 if (m_preeditString.isEmpty())
391 e.setCommitString(m_preeditString);
392 m_preeditString.clear();
393 QCoreApplication::sendEvent(m_focusObject, &e);
398void QWasmInputContext::commitText(
const QString &text,
int replaceFrom,
int replaceLength)
400 qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << text << replaceFrom << replaceLength;
402 e.setCommitString(text, replaceFrom, replaceLength);
403 QCoreApplication::sendEvent(m_focusObject, &e);
404 m_preeditString.clear();
static QWasmWindow * fromWindow(const QWindow *window)
friend class QWasmCompositor
Combined button and popup list for selecting options.