5#include <QtCore/private/qnapi_p.h>
6#include <qohosjsenv_p.h>
7#include <QtCore/private/qohoslogger_p.h>
8#include <QtCore/qnamespace.h>
9#include <QtGui/private/qhighdpiscaling_p.h>
14#include <QInputDevice>
15#include <QGuiApplication>
16#include <QTextCharFormat>
18#include <qohosjsutils.h>
19#include <inputmethod/inputmethod_controller_capi.h>
27using InsertedTextId = QtOhos::TypedId<std::uint64_t,
struct InsertedTextIdTag>;
34 InsertedTextId
id()
const;
56 JsInputMethodInsertedTextComposer() =
default;
57 void initOrIncrementId();
90 if (!m_id.hasValue()) {
91 m_id = InsertedTextId(0);
93 auto oldValue = m_id.value().value();
94 m_id = InsertedTextId(++oldValue);
101 return QOhosInsertedText(text, m_id.value());
106 using TextInputType = ::InputMethod_TextInputType;
107 if (hints & Qt::ImhMultiLine) {
108 return TextInputType::IME_TEXT_INPUT_TYPE_MULTILINE;
109 }
else if (hints & Qt::ImhDigitsOnly) {
110 return (hints & Qt::ImhHiddenText)
111 ? TextInputType::IME_TEXT_INPUT_TYPE_NUMBER_PASSWORD
112 : TextInputType::IME_TEXT_INPUT_TYPE_NUMBER;
113 }
else if (hints & Qt::ImhDialableCharactersOnly) {
114 return TextInputType::IME_TEXT_INPUT_TYPE_PHONE;
115 }
else if (hints & (Qt::ImhDate | Qt::ImhTime)) {
116 return TextInputType::IME_TEXT_INPUT_TYPE_DATETIME;
117 }
else if (hints & Qt::ImhEmailCharactersOnly) {
118 return TextInputType::IME_TEXT_INPUT_TYPE_EMAIL_ADDRESS;
119 }
else if (hints & Qt::ImhUrlCharactersOnly) {
120 return TextInputType::IME_TEXT_INPUT_TYPE_URL;
121 }
else if (hints & Qt::ImhHiddenText) {
122 return TextInputType::IME_TEXT_INPUT_TYPE_VISIBLE_PASSWORD;
124 return TextInputType::IME_TEXT_INPUT_TYPE_TEXT;
131 case ::InputMethod_Direction::IME_DIRECTION_NONE:
133 case ::InputMethod_Direction::IME_DIRECTION_UP:
135 case ::InputMethod_Direction::IME_DIRECTION_DOWN:
137 case ::InputMethod_Direction::IME_DIRECTION_LEFT:
139 case ::InputMethod_Direction::IME_DIRECTION_RIGHT:
147 switch (qtEnterKeyType) {
148 case Qt::EnterKeyType::EnterKeyReturn:
149 return ::InputMethod_EnterKeyType::IME_ENTER_KEY_NEWLINE;
150 case Qt::EnterKeyType::EnterKeyDone:
151 return ::InputMethod_EnterKeyType::IME_ENTER_KEY_DONE;
152 case Qt::EnterKeyType::EnterKeyGo:
153 return ::InputMethod_EnterKeyType::IME_ENTER_KEY_GO;
154 case Qt::EnterKeyType::EnterKeySend:
155 return ::InputMethod_EnterKeyType::IME_ENTER_KEY_SEND;
156 case Qt::EnterKeyType::EnterKeySearch:
157 return ::InputMethod_EnterKeyType::IME_ENTER_KEY_SEARCH;
158 case Qt::EnterKeyType::EnterKeyNext:
159 return ::InputMethod_EnterKeyType::IME_ENTER_KEY_NEXT;
160 case Qt::EnterKeyType::EnterKeyPrevious:
161 return ::InputMethod_EnterKeyType::IME_ENTER_KEY_PREVIOUS;
162 case Qt::EnterKeyType::EnterKeyDefault:
163 return ::InputMethod_EnterKeyType::IME_ENTER_KEY_NONE;
166 "%s: Cannot map Qt::EnterKeyType to OHOS value. Returning ::InputMethod_EnterKeyType::IME_ENTER_KEY_UNSPECIFIED",
168 return ::InputMethod_EnterKeyType::IME_ENTER_KEY_UNSPECIFIED;
172 QOhosInputContext::RequestKeyboardReason qtRequestKeyboardReason)
174 switch (qtRequestKeyboardReason) {
175 case QOhosInputContext::RequestKeyboardReason::NONE:
176 return ::InputMethod_RequestKeyboardReason::IME_REQUEST_REASON_NONE;
177 case QOhosInputContext::RequestKeyboardReason::MOUSE:
178 return ::InputMethod_RequestKeyboardReason::IME_REQUEST_REASON_MOUSE;
179 case QOhosInputContext::RequestKeyboardReason::TOUCH:
180 return ::InputMethod_RequestKeyboardReason::IME_REQUEST_REASON_TOUCH;
181 case QOhosInputContext::RequestKeyboardReason::OTHER:
182 return ::InputMethod_RequestKeyboardReason::IME_REQUEST_REASON_OTHER;
185 "%s: Cannot map QOhosInputContext::RequestKeyboardReason to OHOS value. Returning ::InputMethod_RequestKeyboardReason::IME_REQUEST_REASON_NONE",
187 return ::InputMethod_RequestKeyboardReason::IME_REQUEST_REASON_NONE;
193 const auto value = query->value(property).toInt(&converted);
200 if (!lastInsertedTextId.hasValue()) {
201 qOhosPrintfError(
"%s: JsInputMethodInsertedTextComposer has no last inserted text ID", Q_FUNC_INFO);
205 auto currentInsertedTextId = insertedText.id();
206 if (currentInsertedTextId != lastInsertedTextId.value()) {
208 "%s: inserted text and one currently processed differ from each other, system won't be notified with changeSelection()", Q_FUNC_INFO);
214 auto startPosition = cursorPosition;
215 auto endPosition = cursorPosition + insertedText
.text().length();
216 jsState.eval<QNapi::Promise>(
217 "@ohos.inputMethod.getController().changeSelection(*)", {insertedText.text(), startPosition, endPosition})
218 .onCatch(QtOhos::makeErrorLoggingJsCallback(
"changeSelection()"))
219 .onFinally(std::move(continueFunc));
225 int x = qBound(rect.left(), p.x(), rect.right());
226 int y = qBound(rect.top(), p.y(), rect.bottom());
235 , m_qtImEnabled(
false)
242 auto __dbg = make_QCScopedDebug(
"QOhosInputContext::QOhosInputContext");
245 QGuiApplication::inputMethod(), &QInputMethod::cursorRectangleChanged,
246 this, &QOhosInputContext::onCursorRectangleChanged);
248 QGuiApplication::instance()->installEventFilter(
this);
255 if (m_imConnectionState == ImConnectionState::Attached) {
256 setImConnectionState(ImConnectionState::Detached);
257 setImConnectionState(ImConnectionState::Attached);
263 auto __dbg = make_QCScopedDebug(
"QOhosInputContext::commit");
265 auto preeditText =
std::exchange(m_pendingPreeditText, {});
267 QInputMethodEvent event;
268 event.setCommitString(preeditText);
269 sendFocusObjectInputMethodEvent(&event);
274 m_qtImEnabled = queryImEnabled();
275 const auto qtInputMethodHintsValue = queryInputMethodHints();
276 const auto qtEnterKeyTypeValue = queryEnterKeyType();
278 const bool imStateChanged =
279 m_qtInputMethodHints != qtInputMethodHintsValue
280 || m_qtEnterKeyType != qtEnterKeyTypeValue;
282 auto imAlreadyAttached = m_imConnectionState == ImConnectionState::Attached;
283 if (imAlreadyAttached && imStateChanged)
284 updateInputMethodControllerAttributes(qtInputMethodHintsValue, qtEnterKeyTypeValue);
286 m_qtInputMethodHints = qtInputMethodHintsValue;
287 m_qtEnterKeyType = qtEnterKeyTypeValue;
289 if (inputMethodAccepted() && m_qtImEnabled) {
290 auto updateCausedByWidgetTransform = queries == Qt::ImInputItemClipRectangle;
291 auto updateCausedByQueryAllImParameters = queries == Qt::ImQueryAll;
293 if (updateCausedByQueryAllImParameters)
296 if (!updateCausedByWidgetTransform || !imAlreadyAttached) {
306 auto __dbg = make_QCScopedDebug(
"QOhosInputContext::invokeAction");
311 auto __dbg = make_QCScopedDebug(
"QOhosInputContext::keyboardRect");
317 auto __dbg = make_QCScopedDebug(
"QOhosInputContext::isAnimating");
323 auto __dbg = make_QCScopedDebug(
"QOhosInputContext::showInputPanel");
324 setImConnectionState(ImConnectionState::Attached);
329 auto __dbg = make_QCScopedDebug(
"QOhosInputContext::hideInputPanel");
330 setImConnectionState(ImConnectionState::Detached);
335 auto __dbg = make_QCScopedDebug(
"QOhosInputContext::isInputPanelVisible");
336 return m_imConnectionRequestedState == ImConnectionState::Attached;
341 m_focusObject = object;
346 return m_focusObject;
351 if (requestedState != m_imConnectionRequestedState) {
352 setImConnectionStateImpl(requestedState);
354 if (m_imConnectionState == ImConnectionState::Attached && !m_softwareKeyboardVisible)
359void QOhosInputContext::setImConnectionStateImpl(ImConnectionState requestedState)
361 m_imConnectionRequestedState = m_qtImEnabled ? requestedState : ImConnectionState::Detached;
363 if (!m_imConnectionRequestActive) {
364 m_imConnectionRequestActive =
true;
366 dispatchRequestedImStateChange(requestedState);
370void QOhosInputContext::dispatchRequestedImStateChange(ImConnectionState requestedState)
373 if (requestedState == ImConnectionState::Attached)
374 result = attachToInputMethodController();
376 result = detachFromInputMethodController();
377 handleRequestedImConnectionState(requestedState, result);
382 class ImCallbacks :
public QOhosInputMethodProxy::ClientCallbacks
386 : m_inputContext(inputContext)
391 void onInsertText(
std::string text)
override
393 m_inputContext.sendInsertedTextToQt(
std::move(text));
396 void onInsertPreviewText(
std::string previewText)
override
398 m_inputContext.sendInsertedPreviewTextToQt(
std::move(previewText));
401 void onDeleteForward(
int length)
override
403 m_inputContext.sendFocusObjectFunctionalKeyEvent(Qt::Key_Delete, QChar::fromLatin1(
'\u007F'), length);
406 void onDeleteBackward(
int length)
override
408 m_inputContext.sendFocusObjectFunctionalKeyEvent(Qt::Key_Backspace, QChar::fromLatin1(
'\u0008'), length);
411 void onSendKeyboardStatus(::InputMethod_KeyboardStatus keyboardStatus)
override
413 bool ohosKeyboardShown = keyboardStatus == ::InputMethod_KeyboardStatus::IME_KEYBOARD_STATUS_SHOW;
417 void onSendEnterKey(::InputMethod_EnterKeyType)
override
419 m_inputContext.sendFocusObjectFunctionalKeyEvent(Qt::Key_Enter, QChar::fromLatin1(
'\u000D'));
422 void onMoveCursor(::InputMethod_Direction direction)
override
424 auto qtDirection = tryMapInputMethodDirectionToQt(direction);
425 if (!qtDirection.hasValue()) {
427 "got unsupported InputMethod_Direction value (%d), cursor won't be moved!",
428 static_cast<
int>(direction));
432 m_inputContext.sendCursorMoveToQt(qtDirection.value());
439 qOhosPrintfWarning(
"%s: proxy already exists!", Q_FUNC_INFO);
441 m_imProxy = std::make_shared<QOhosInputMethodProxy>(
442 std::make_shared<ImCallbacks>(*
this),
443 mapQtToOhosImeRequestReason(
444 m_lastInputTypeToTriggerSoftKeyboard.valueOr(RequestKeyboardReason::OTHER)));
446 if (m_imProxy->hasAttachedSuccessfully()) {
447 m_imProxy->notifyConfigurationChange(
448 mapQtToOhosImeEnterKeyType(m_qtEnterKeyType),
449 mapQtInputMethodHintsToOhosImeTextInputType(m_qtInputMethodHints));
458 qOhosPrintfWarning(
"%s: proxy doesn't exist!", Q_FUNC_INFO);
464void QOhosInputContext::handleRequestedImConnectionState(ImConnectionState requestedState,
bool success)
466 m_imConnectionRequestActive =
false;
469 qOhosWarning(QtForOhos)
471 << (requestedState == ImConnectionState::Attached ?
"attach to" :
"detach from")
476 m_imConnectionState = requestedState;
478 if (m_imConnectionRequestedState != m_imConnectionState)
479 setImConnectionStateImpl(m_imConnectionRequestedState);
481 onCursorRectangleChanged();
486 if (m_imProxy ==
nullptr) {
487 qOhosPrintfError(
"%s: proxy doesn't exist!", Q_FUNC_INFO);
491 m_imProxy->showTextInput(mapQtToOhosImeRequestReason(
492 m_lastInputTypeToTriggerSoftKeyboard.valueOr(RequestKeyboardReason::OTHER)));
497 m_softwareKeyboardVisible = visible;
502 m_lastInputTypeToTriggerSoftKeyboard = inputType;
507 if (m_imConnectionState == ImConnectionState::Detached) {
508 qOhosWarning(QtForOhos) <<
"Attempting to update cursor position when detached from controller";
509 m_updateCursorRectangleAfterAttaching =
true;
513 auto *focusedWindow = QGuiApplication::focusWindow();
514 if (focusedWindow ==
nullptr) {
515 qOhosCritical(QtForOhos)
516 <<
"Could not retrieve focused window. Updating cursor position isn't possible";
520 auto focusedWindowPosition = focusedWindow->position();
521 QRect cursorRectangle = QGuiApplication::inputMethod()->cursorRectangle().toRect();
522 if (!m_updateCursorRectangleAfterAttaching
523 && cursorRectangle == m_lastCursorRectangle
524 && m_lastFocusedWindowPosition == focusedWindowPosition) {
528 m_lastCursorRectangle = cursorRectangle;
529 m_lastFocusedWindowPosition = focusedWindowPosition;
530 auto screenSpaceInputItemRectangle =
531 QGuiApplication::inputMethod()->inputItemClipRectangle()
532 .translated(m_lastFocusedWindowPosition)
534 auto screenSpaceCursorRectangle = m_lastCursorRectangle.translated(m_lastFocusedWindowPosition);
535 auto inputItemClampedCursorPos = clampToRect(
536 {screenSpaceCursorRectangle.x(), screenSpaceCursorRectangle.y()},
537 screenSpaceInputItemRectangle.isValid()
538 ? screenSpaceInputItemRectangle
539 : focusedWindow->geometry());
540 auto nativeCursorPos = QHighDpiScaling::mapPositionToNative(
541 inputItemClampedCursorPos, focusedWindow->screen()->handle());
543 nativeCursorPos.x(), nativeCursorPos.y(),
544 screenSpaceCursorRectangle.width(), screenSpaceCursorRectangle.height()
546 m_updateCursorRectangleAfterAttaching =
false;
550 Qt::InputMethodHints qtInputMethodHints, Qt::EnterKeyType qtEnterKeyType)
552 if (m_imProxy ==
nullptr) {
553 qOhosPrintfError(
"%s: proxy doesn't exist!", Q_FUNC_INFO);
557 m_imProxy->notifyConfigurationChange(
558 mapQtToOhosImeEnterKeyType(qtEnterKeyType),
559 mapQtInputMethodHintsToOhosImeTextInputType(qtInputMethodHints));
564 if (m_imProxy ==
nullptr) {
565 qOhosPrintfError(
"%s: proxy doesn't exist!", Q_FUNC_INFO);
569 m_imProxy->notifyCursorUpdate(globalCursorRect);
574 if (obj->isWidgetType() && queryImEnabled()) {
575 auto focusReason = event->reason();
576 if (focusReason == Qt::OtherFocusReason || focusReason == Qt::ActiveWindowFocusReason)
577 setLastInputTypeToTriggerSoftKeyboard(RequestKeyboardReason::NONE);
583 if (event->type() == QEvent::FocusIn)
584 handleFocusInEvent(obj,
static_cast<QFocusEvent *>(event));
592 auto query = tryQueryFocusObjectInputMethod(Qt::ImEnabled);
596 if (!query->value(Qt::ImEnabled).toBool()) {
597 qOhosDebug(QtForOhos) <<
"onInsertText(): focus object is not able to handle InputMethod";
601 m_pendingPreeditText.clear();
603 QInputMethodEvent event;
604 event.setCommitString(QString::fromStdString(insertedText.text()));
605 sendFocusObjectInputMethodEvent(&event);
607 auto cursorPosition = tryQueryCursorPosition();
608 if (cursorPosition.hasValue())
609 notifyOhosInputMethodAboutPossibleAutocorrection(insertedText, cursorPosition.value());
611 qOhosWarning(QtForOhos) <<
"Couldn't obtain IM cursorPosition";
616 auto query = tryQueryFocusObjectInputMethod(Qt::ImEnabled);
620 if (!query->value(Qt::ImEnabled).toBool()) {
621 qOhosPrintfDebug(
"%s: focus object is not able to handle InputMethod", Q_FUNC_INFO);
625 const QString previewString = QString::fromStdString(previewText);
626 QList<QInputMethodEvent::Attribute> imEventAttributes;
627 if (!previewString.isEmpty()) {
628 QTextCharFormat format;
629 format.setFontUnderline(
true);
630 imEventAttributes.append(
631 QInputMethodEvent::Attribute(
632 QInputMethodEvent::TextFormat, 0, previewString.length(), format));
635 m_pendingPreeditText = previewString;
637 QInputMethodEvent preeditStringEvent(previewString, imEventAttributes);
638 sendFocusObjectInputMethodEvent(&preeditStringEvent);
643 auto *focusObject = focusObjectOrNull();
644 if (focusObject ==
nullptr) {
645 qOhosWarning(QtForOhos) <<
"sendFocusObjectInputMethodEvent(): no focus object to send event to!";
649 QCoreApplication::sendEvent(focusObject, event);
655 case QOhosInputContext::Direction::CURSOR_UP:
656 sendFocusObjectFunctionalKeyEvent(Qt::Key_Up, QChar(std::int32_t(Qt::Key_Up)));
658 case QOhosInputContext::Direction::CURSOR_DOWN:
659 sendFocusObjectFunctionalKeyEvent(Qt::Key_Down, QChar(std::int32_t(Qt::Key_Down)));
661 case QOhosInputContext::Direction::CURSOR_LEFT:
662 sendFocusObjectFunctionalKeyEvent(Qt::Key_Left, QChar(std::int32_t(Qt::Key_Left)));
664 case QOhosInputContext::Direction::CURSOR_RIGHT:
665 sendFocusObjectFunctionalKeyEvent(Qt::Key_Right, QChar(std::int32_t(Qt::Key_Right)));
670void QOhosInputContext::sendFocusObjectFunctionalKeyEvent(Qt::Key key,
const QChar &keyChar,
int repeatCount)
672 auto *focusObject = focusObjectOrNull();
673 if (focusObject ==
nullptr) {
674 qOhosWarning(QtForOhos) <<
"sendFocusObjectFunctionalKeyEvent(): no focus object to send event to!";
678 for (
int i = 0; i < repeatCount; ++i) {
679 QGuiApplication::postEvent(focusObject,
new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier, QString(keyChar)));
680 QGuiApplication::postEvent(focusObject,
new QKeyEvent(QEvent::KeyRelease, key, Qt::NoModifier, QString(keyChar)));
686 auto query = tryQueryFocusObjectInputMethod(Qt::ImEnabled);
687 return !query.isNull() && query->value(Qt::ImEnabled).toBool();
692 QOhosOptional<Qt::InputMethodHints> hints;
693 auto query = tryQueryFocusObjectInputMethod(Qt::ImHints);
694 if (!query.isNull()) {
695 auto imHintsInt = tryGetIntPropertyFromQuery(Qt::ImHints, query);
696 if (imHintsInt.hasValue())
697 hints =
static_cast<Qt::InputMethodHints>(imHintsInt.value());
700 return hints.valueOr(defaultInputMethodHints);
705 QOhosOptional<Qt::EnterKeyType> enterKeyType;
706 auto query = tryQueryFocusObjectInputMethod(Qt::ImEnterKeyType);
707 if (!query.isNull()) {
708 auto imEnterKeyTypeInt = tryGetIntPropertyFromQuery(Qt::ImEnterKeyType, query);
709 if (imEnterKeyTypeInt.hasValue())
710 enterKeyType =
static_cast<Qt::EnterKeyType>(imEnterKeyTypeInt.value());
713 return enterKeyType.valueOr(defaultEnterKeyType);
718 auto query = tryQueryFocusObjectInputMethod(Qt::ImCursorPosition);
719 return !query.isNull()
720 ? tryGetIntPropertyFromQuery(Qt::ImCursorPosition, query)
721 : makeEmptyQOhosOptional();
724QSharedPointer<QInputMethodQueryEvent>
QOhosInputContext::tryQueryFocusObjectInputMethod(Qt::InputMethodQueries queries)
const
726 auto *focusObject = focusObjectOrNull();
727 if (focusObject ==
nullptr) {
728 qOhosWarning(QtForOhos) <<
"tryQueryFocusObjectInputMethod(): no focus object to query information from!";
732 auto imqEvent = QSharedPointer<QInputMethodQueryEvent>::create(queries);
733 QCoreApplication::sendEvent(focusObject, imqEvent.data());
void update(Qt::InputMethodQueries queries) override
Notification on editor updates.
QRectF keyboardRect() const override
This function can be reimplemented to return virtual keyboard rectangle in currently active window co...
void setSoftwareKeyboardVisibilityStatus(bool visible)
void showInputPanel() override
Request to show input panel.
void invokeAction(QInputMethod::Action action, int cursorPosition) override
Called when the word currently being composed in the input item is tapped by the user.
void setLastInputTypeToTriggerSoftKeyboard(RequestKeyboardReason inputType)
void reset() override
Method to be called when input method needs to be reset.
void hideInputPanel() override
Request to hide input panel.
bool eventFilter(QObject *obj, QEvent *event) override
Filters events if this object has been installed as an event filter for the watched object.
bool isInputPanelVisible() const override
Returns input panel visibility status.
QObject * focusObjectOrNull() const
void setFocusObject(QObject *object) override
This virtual method gets called to notify updated focus to object.
bool isAnimating() const override
This function can be reimplemented to return true whenever input method is animating shown or hidden.
std::enable_if_t< qohosplugincore_h_detail::isQOhosOptional< QOhosInvokeResult< Func, T > >, QOhosInvokeResult< Func, T > > andThen(Func &&func) const
JsInputMethodInsertedTextComposer & operator=(const JsInputMethodInsertedTextComposer &)=delete
JsInputMethodInsertedTextComposer(JsInputMethodInsertedTextComposer &&)=delete
static JsInputMethodInsertedTextComposer & instance()
JsInputMethodInsertedTextComposer & operator=(JsInputMethodInsertedTextComposer &&)=delete
QOhosOptional< InsertedTextId > lastId() const
JsInputMethodInsertedTextComposer(const JsInputMethodInsertedTextComposer &)=delete
QOhosInsertedText makeInsertedText(const std::string &text)
InsertedTextId id() const
QOhosInsertedText(const std::string &text, const InsertedTextId &id)
Combined button and popup list for selecting options.
QOhosOptional< QOhosInputContext::Direction > tryMapInputMethodDirectionToQt(::InputMethod_Direction direction)
::InputMethod_TextInputType mapQtInputMethodHintsToOhosImeTextInputType(Qt::InputMethodHints hints)
::InputMethod_EnterKeyType mapQtToOhosImeEnterKeyType(Qt::EnterKeyType qtEnterKeyType)
const Qt::EnterKeyType defaultEnterKeyType
void notifyOhosInputMethodAboutPossibleAutocorrection(const QOhosInsertedText &insertedText, int cursorPosition)
const Qt::InputMethodHints defaultInputMethodHints
QPoint clampToRect(const QPoint &p, const QRect &rect)
::InputMethod_RequestKeyboardReason mapQtToOhosImeRequestReason(QOhosInputContext::RequestKeyboardReason qtRequestKeyboardReason)
QOhosOptional< int > tryGetIntPropertyFromQuery(Qt::InputMethodQuery property, QSharedPointer< QInputMethodQueryEvent > query)
void invokeInJsThreadAndWaitForContinue(std::function< void(JsState &, std::function< void()>)> &&task)
QOhosOptional< void > makeEmptyQOhosOptional()