9#include <QtCore/qdebug.h>
10#include <QtCore/qobject.h>
11#include <QtCore/qrect.h>
12#include <QtCore/qtextboundaryfinder.h>
14#include <QtGui/qevent.h>
15#include <QtGui/qtextformat.h>
16#include <QtGui/qpalette.h>
17#include <QtGui/qguiapplication.h>
19#include <private/qhighdpiscaling_p.h>
25static inline QByteArray debugComposition(
int lParam)
28 if (lParam & GCS_RESULTSTR)
30 if (lParam & GCS_COMPSTR)
32 if (lParam & GCS_COMPATTR)
34 if (lParam & GCS_CURSORPOS)
36 if (lParam & GCS_COMPCLAUSE)
38 if (lParam & CS_INSERTCHAR)
40 if (lParam & CS_NOMOVECARET)
41 str +=
"NOMOVECARET ";
49 qWarning() <<
__FUNCTION__ <<
"called with" << hwnd;
52 const HIMC himc = ImmGetContext(hwnd);
53 ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
54 ImmReleaseContext(hwnd, himc);
59 return localeId & 0xFFFF;
64 return languageIdFromLocaleId(
reinterpret_cast<quintptr>(GetKeyboardLayout(0)));
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
133 const quint32 bmpData = 0;
134 m_transparentBitmap = CreateBitmap(2, 2, 1, 1, &bmpData);
136 connect(QGuiApplication::inputMethod(), &QInputMethod::cursorRectangleChanged,
137 this, &QWindowsInputContext::cursorRectChanged);
142 if (m_transparentBitmap)
143 DeleteObject(m_transparentBitmap);
148 switch (capability) {
149 case QPlatformInputContext::HiddenTextCapability:
158
159
163 if (!m_compositionContext.hwnd)
165 qCDebug(lcQpaInputMethods) <<
__FUNCTION__;
166 if (m_compositionContext.isComposing && !m_compositionContext.focusObject.isNull()) {
167 QInputMethodEvent event;
168 if (!m_compositionContext.composition.isEmpty())
169 event.setCommitString(m_compositionContext.composition);
170 QCoreApplication::sendEvent(m_compositionContext.focusObject, &event);
171 endContextComposition();
173 imeNotifyCancelComposition(m_compositionContext.hwnd);
182 if (m_compositionContext.isComposing)
189 return ::FindWindowA(
"IPTip_Main_Window",
nullptr);
194 if (HWND hwnd = getVirtualKeyboardWindowHandle()) {
196 if (::GetWindowRect(hwnd, &rect)) {
197 return QRectF(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
205 HWND hwnd = getVirtualKeyboardWindowHandle();
206 if (hwnd && ::IsWindowEnabled(hwnd) && ::IsWindowVisible(hwnd))
209 if (inputMethodAccepted()) {
210 if (QWindow *window = QGuiApplication::focusWindow()) {
211 if (
QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(window)) {
212 if (HIMC himc = ImmGetContext(platformWindow->handle()))
213 return ImmGetOpenStatus(himc);
222 if (!inputMethodAccepted())
225 QWindow *window = QGuiApplication::focusWindow();
229 QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(window);
236 if (!m_caretCreated && m_transparentBitmap)
237 m_caretCreated = CreateCaret(platformWindow->handle(), m_transparentBitmap, 0, 0);
239 if (m_caretCreated) {
241 ShowCaret(platformWindow->handle());
247 if (m_caretCreated) {
249 m_caretCreated =
false;
255 if (!QGuiApplication::focusObject())
257 if (
QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(QGuiApplication::focusWindow())) {
258 const bool accepted = inputMethodAccepted();
259 if (QWindowsContext::verbose > 1)
260 qCDebug(lcQpaInputMethods) <<
__FUNCTION__ << platformWindow->window() <<
"accepted=" << accepted;
271 ImmAssociateContextEx(platformWindow->handle(),
nullptr, IACE_DEFAULT);
274 ImmAssociateContext(platformWindow->handle(),
nullptr);
279
280
284 if (queries & Qt::ImEnabled)
290 QWindow *window = QGuiApplication::focusWindow();
294 qreal factor = QHighDpiScaling::factor(window);
296 const QInputMethod *inputMethod = QGuiApplication::inputMethod();
297 const QRectF cursorRectangleF = inputMethod->cursorRectangle();
298 if (!cursorRectangleF.isValid())
301 const QRect cursorRectangle =
302 QRectF(cursorRectangleF.topLeft() * factor, cursorRectangleF.size() * factor).toRect();
305 SetCaretPos(cursorRectangle.x(), cursorRectangle.y());
307 if (!m_compositionContext.hwnd)
310 qCDebug(lcQpaInputMethods) <<
__FUNCTION__<< cursorRectangle;
312 const HIMC himc = ImmGetContext(m_compositionContext.hwnd);
318 cf.dwStyle = CFS_FORCE_POSITION;
319 cf.ptCurrentPos.x = cursorRectangle.x();
320 cf.ptCurrentPos.y = cursorRectangle.y();
324 candf.dwStyle = CFS_EXCLUDE;
325 candf.ptCurrentPos.x = cursorRectangle.x();
326 candf.ptCurrentPos.y = cursorRectangle.y() + cursorRectangle.height();
327 candf.rcArea.left = cursorRectangle.x();
328 candf.rcArea.top = cursorRectangle.y();
329 candf.rcArea.right = cursorRectangle.x() + cursorRectangle.width();
330 candf.rcArea.bottom = cursorRectangle.y() + cursorRectangle.height();
332 ImmSetCompositionWindow(himc, &cf);
333 ImmSetCandidateWindow(himc, &candf);
334 ImmReleaseContext(m_compositionContext.hwnd, himc);
339 if (action != QInputMethod::Click || !m_compositionContext.hwnd) {
340 QPlatformInputContext::invokeAction(action, cursorPosition);
344 qCDebug(lcQpaInputMethods) <<
__FUNCTION__ << cursorPosition << action;
345 if (cursorPosition < 0 || cursorPosition > m_compositionContext.composition.size())
350 const HIMC himc = ImmGetContext(m_compositionContext.hwnd);
351 const HWND imeWindow = ImmGetDefaultIMEWnd(m_compositionContext.hwnd);
352 const WPARAM mouseOperationCode =
353 MAKELONG(MAKEWORD(MK_LBUTTON, cursorPosition == 0 ? 2 : 1), cursorPosition);
354 SendMessage(imeWindow, m_WM_MSIME_MOUSE, mouseOperationCode, LPARAM(himc));
355 ImmReleaseContext(m_compositionContext.hwnd, himc);
360 enum { bufferSize = 256 };
361 wchar_t buffer[bufferSize];
362 const int length = ImmGetCompositionString(himc, dwIndex, buffer, bufferSize *
sizeof(
wchar_t));
363 return QString::fromWCharArray(buffer, size_t(length) /
sizeof(
wchar_t));
369 enum { bufferSize = 256 };
371 char attrBuffer[bufferSize];
372 *selStart = *selLength = 0;
373 if (
const int attrLength = ImmGetCompositionString(himc, GCS_COMPATTR, attrBuffer, bufferSize)) {
375 while (start < attrLength && !(attrBuffer[start] & ATTR_TARGET_CONVERTED))
377 if (start < attrLength) {
379 while (end < attrLength && (attrBuffer[end] & ATTR_TARGET_CONVERTED))
382 *selLength = end - start;
394 QTextCharFormat result;
397 result.setUnderlineStyle(QTextCharFormat::DashUnderline);
401 const QPalette palette = QGuiApplication::palette();
402 const QColor background = palette.text().color();
403 result.setBackground(QBrush(background));
404 result.setForeground(palette.window());
413 QObject *fo = QGuiApplication::focusObject();
417 QWindow *window = QGuiApplication::focusWindow();
420 qCDebug(lcQpaInputMethods) <<
__FUNCTION__ << fo << window <<
"language=" << m_languageId;
421 if (!fo || QWindowsWindow::handleOf(window) != hwnd)
423 initContext(hwnd, fo);
424 startContextComposition();
430 if (m_compositionContext.isComposing) {
431 qWarning(
"%s: Called out of sequence.",
__FUNCTION__);
434 m_compositionContext.isComposing =
true;
435 m_compositionContext.composition.clear();
436 m_compositionContext.position = 0;
438 update(Qt::ImQueryAll);
443 if (!m_compositionContext.isComposing) {
444 qWarning(
"%s: Called out of sequence.",
__FUNCTION__);
447 m_compositionContext.composition.clear();
448 m_compositionContext.position = 0;
449 m_compositionContext.isComposing =
false;
457 int selStart,
int selLength)
459 QList<QInputMethodEvent::Attribute> attributes;
461 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, selStart,
462 standardFormat(PreeditFormat));
464 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, selStart, selLength,
465 standardFormat(SelectionFormat));
466 if (selStart + selLength < compositionLength)
467 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, selStart + selLength,
468 compositionLength - selStart - selLength,
469 standardFormat(PreeditFormat));
471 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, position, selLength ? 0 : 1, QVariant());
476
477
481 const int lParam =
int(lParamIn);
482 qCDebug(lcQpaInputMethods) <<
'>' <<
__FUNCTION__ << m_compositionContext.focusObject
483 << debugComposition(lParam) <<
" composing=" << m_compositionContext.isComposing;
484 if (m_compositionContext.focusObject.isNull() || m_compositionContext.hwnd != hwnd || !lParam)
486 const HIMC himc = ImmGetContext(m_compositionContext.hwnd);
490 QScopedPointer<QInputMethodEvent> event;
491 if (lParam & (GCS_COMPSTR | GCS_COMPATTR | GCS_CURSORPOS)) {
492 if (!m_compositionContext.isComposing)
493 startContextComposition();
496 int selStart, selLength;
497 m_compositionContext.composition = getCompositionString(himc, GCS_COMPSTR);
498 m_compositionContext.position = ImmGetCompositionString(himc, GCS_CURSORPOS,
nullptr, 0);
499 getCompositionStringConvertedRange(himc, &selStart, &selLength);
500 if ((lParam & CS_INSERTCHAR) && (lParam & CS_NOMOVECARET)) {
503 selLength = m_compositionContext.composition.size();
508 event.reset(
new QInputMethodEvent(m_compositionContext.composition,
509 intermediateMarkup(m_compositionContext.position,
510 m_compositionContext.composition.size(),
511 selStart, selLength)));
514 event.reset(
new QInputMethodEvent);
516 if (lParam & GCS_RESULTSTR) {
518 event->setCommitString(getCompositionString(himc, GCS_RESULTSTR));
519 if (!(lParam & GCS_DELTASTART))
520 endContextComposition();
522 const bool result = QCoreApplication::sendEvent(m_compositionContext.focusObject, event.data());
523 qCDebug(lcQpaInputMethods) <<
'<' <<
__FUNCTION__ <<
"sending markup="
524 << event->attributes().size() <<
" commit=" << event->commitString()
525 <<
" to " << m_compositionContext.focusObject <<
" returns " << result;
526 update(Qt::ImQueryAll);
527 ImmReleaseContext(m_compositionContext.hwnd, himc);
533 qCDebug(lcQpaInputMethods) <<
__FUNCTION__ << m_endCompositionRecursionGuard << hwnd;
537 if (m_endCompositionRecursionGuard || m_compositionContext.hwnd != hwnd)
539 if (m_compositionContext.focusObject.isNull())
544 if (m_locale.language() == QLocale::Korean
545 && QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier)) {
550 m_endCompositionRecursionGuard =
true;
552 imeNotifyCancelComposition(m_compositionContext.hwnd);
553 if (m_compositionContext.isComposing) {
554 QInputMethodEvent event;
555 QCoreApplication::sendEvent(m_compositionContext.focusObject, &event);
559 m_endCompositionRecursionGuard =
false;
565 if (m_compositionContext.hwnd)
567 m_compositionContext.hwnd = hwnd;
568 m_compositionContext.focusObject = focusObject;
570 update(Qt::ImQueryAll);
571 m_compositionContext.isComposing =
false;
572 m_compositionContext.position = 0;
577 if (!m_compositionContext.hwnd)
579 m_compositionContext.hwnd =
nullptr;
580 m_compositionContext.composition.clear();
581 m_compositionContext.position = 0;
582 m_compositionContext.isComposing =
false;
583 m_compositionContext.focusObject =
nullptr;
590 switch (
int(wParam)) {
591 case IMR_RECONVERTSTRING: {
592 const int size = reconvertString(
reinterpret_cast<RECONVERTSTRING *>(lParam));
598 case IMR_CONFIRMRECONVERTSTRING:
608 const LCID newLanguageId = languageIdFromLocaleId(WORD(lparam));
609 if (newLanguageId == m_languageId)
611 const LCID oldLanguageId = m_languageId;
612 m_languageId = newLanguageId;
613 m_locale = qt_localeFromLCID(m_languageId);
616 qCDebug(lcQpaInputMethods) <<
__FUNCTION__ << Qt::hex << Qt::showbase
617 << oldLanguageId <<
"->" << newLanguageId <<
"Character set:"
618 << DWORD(wparam) << Qt::dec << Qt::noshowbase << m_locale;
622
623
624
625
626
627
628
629
630
634 QObject *fo = QGuiApplication::focusObject();
638 const QVariant surroundingTextV = QInputMethod::queryFocusObject(Qt::ImSurroundingText, QVariant());
639 if (!surroundingTextV.isValid())
641 const QString surroundingText = surroundingTextV.toString();
642 const int memSize =
int(
sizeof(RECONVERTSTRING))
643 + (surroundingText.length() + 1) *
int(
sizeof(ushort));
644 qCDebug(lcQpaInputMethods) <<
__FUNCTION__ <<
" reconv=" << reconv
645 <<
" surroundingText=" << surroundingText <<
" size=" << memSize;
648 return surroundingText.isEmpty() ? -1 : memSize;
650 const QVariant posV = QInputMethod::queryFocusObject(Qt::ImCursorPosition, QVariant());
651 const int pos = posV.isValid() ? posV.toInt() : 0;
653 QTextBoundaryFinder bounds(QTextBoundaryFinder::Word, surroundingText);
654 bounds.setPosition(pos);
655 if (bounds.position() > 0 && !(bounds.boundaryReasons() & QTextBoundaryFinder::StartOfItem))
656 bounds.toPreviousBoundary();
657 const int startPos = bounds.position();
658 bounds.toNextBoundary();
659 const int endPos = bounds.position();
660 qCDebug(lcQpaInputMethods) <<
__FUNCTION__ <<
" boundary=" << startPos << endPos;
662 QList<QInputMethodEvent::Attribute> attributes;
663 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, startPos, endPos-startPos, QVariant());
664 QInputMethodEvent selectEvent(QString(), attributes);
665 QCoreApplication::sendEvent(fo, &selectEvent);
667 reconv->dwSize = DWORD(memSize);
668 reconv->dwVersion = 0;
670 reconv->dwStrLen = DWORD(surroundingText.size());
671 reconv->dwStrOffset =
sizeof(RECONVERTSTRING);
672 reconv->dwCompStrLen = DWORD(endPos - startPos);
673 reconv->dwCompStrOffset = DWORD(startPos) *
sizeof(ushort);
674 reconv->dwTargetStrLen = reconv->dwCompStrLen;
675 reconv->dwTargetStrOffset = reconv->dwCompStrOffset;
676 auto *pastReconv =
reinterpret_cast<ushort *>(reconv + 1);
677 std::copy(surroundingText.utf16(), surroundingText.utf16() + surroundingText.size(),
Windows Input context implementation.
~QWindowsInputContext() override
QRectF keyboardRect() const override
This function can be reimplemented to return virtual keyboard rectangle in currently active window co...
void update(Qt::InputMethodQueries) override
Moves the candidate window along with microfocus of the focus object.
bool composition(HWND hwnd, LPARAM lParam)
Notify focus object about markup or final text.
bool hasCapability(Capability capability) const override
Returns whether the implementation supports capability.
bool startComposition(HWND hwnd)
void invokeAction(QInputMethod::Action, int cursorPosition) override
Called when the word currently being composed in the input item is tapped by the user.
void handleInputLanguageChanged(WPARAM wparam, LPARAM lparam)
void showInputPanel() override
Request to show input panel.
static void setWindowsImeEnabled(QWindowsWindow *platformWindow, bool enabled)
bool isInputPanelVisible() const override
Returns input panel visibility status.
void setFocusObject(QObject *object) override
This virtual method gets called to notify updated focus to object.
bool handleIME_Request(WPARAM wparam, LPARAM lparam, LRESULT *result)
int reconvertString(RECONVERTSTRING *reconv)
Determines the string for reconversion with selection.
bool endComposition(HWND hwnd)
void hideInputPanel() override
Request to hide input panel.
void reset() override
Cancels a composition.
static QList< QInputMethodEvent::Attribute > intermediateMarkup(int position, int compositionLength, int selStart, int selLength)
static void imeNotifyCancelComposition(HWND hwnd)
static void getCompositionStringConvertedRange(HIMC himc, int *selStart, int *selLength)
static QString getCompositionString(HIMC himc, DWORD dwIndex)
static LCID currentInputLanguageId()
static QTextFormat standardFormat(StandardFormat format)
static LCID languageIdFromLocaleId(LCID localeId)