10#include <QtCore/qdebug.h>
11#include <QtCore/qobject.h>
12#include <QtCore/qrect.h>
13#include <QtCore/qtextboundaryfinder.h>
15#include <QtGui/qevent.h>
16#include <QtGui/qtextformat.h>
17#include <QtGui/qpalette.h>
18#include <QtGui/qguiapplication.h>
20#include <private/qhighdpiscaling_p.h>
26static inline QByteArray debugComposition(
int lParam)
29 if (lParam & GCS_RESULTSTR)
31 if (lParam & GCS_COMPSTR)
33 if (lParam & GCS_COMPATTR)
35 if (lParam & GCS_CURSORPOS)
37 if (lParam & GCS_COMPCLAUSE)
39 if (lParam & CS_INSERTCHAR)
41 if (lParam & CS_NOMOVECARET)
42 str +=
"NOMOVECARET ";
50 qWarning() <<
__FUNCTION__ <<
"called with" << hwnd;
53 const HIMC himc = ImmGetContext(hwnd);
54 ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
55 ImmReleaseContext(hwnd, himc);
60 return localeId & 0xFFFF;
65 return languageIdFromLocaleId(
reinterpret_cast<quintptr>(GetKeyboardLayout(0)));
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
126
134 const quint32 bmpData = 0;
135 m_transparentBitmap = CreateBitmap(2, 2, 1, 1, &bmpData);
137 connect(QGuiApplication::inputMethod(), &QInputMethod::cursorRectangleChanged,
138 this, &QWindowsInputContext::cursorRectChanged);
143 if (m_transparentBitmap)
144 DeleteObject(m_transparentBitmap);
149 switch (capability) {
150 case QPlatformInputContext::HiddenTextCapability:
159
160
164 if (!m_compositionContext.hwnd)
166 qCDebug(lcQpaInputMethods) <<
__FUNCTION__;
167 if (m_compositionContext.isComposing && !m_compositionContext.focusObject.isNull()) {
168 QInputMethodEvent event;
169 if (!m_compositionContext.composition.isEmpty())
170 event.setCommitString(m_compositionContext.composition);
171 QCoreApplication::sendEvent(m_compositionContext.focusObject, &event);
172 endContextComposition();
174 imeNotifyCancelComposition(m_compositionContext.hwnd);
183 if (m_compositionContext.isComposing)
190 return ::FindWindowA(
"IPTip_Main_Window",
nullptr);
195 if (HWND hwnd = getVirtualKeyboardWindowHandle()) {
197 if (::GetWindowRect(hwnd, &rect)) {
198 return QRectF(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
206 HWND hwnd = getVirtualKeyboardWindowHandle();
207 if (hwnd && ::IsWindowEnabled(hwnd) && ::IsWindowVisible(hwnd))
210 if (inputMethodAccepted()) {
211 if (QWindow *window = QGuiApplication::focusWindow()) {
212 if (
QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(window)) {
213 if (HIMC himc = ImmGetContext(platformWindow->handle()))
214 return ImmGetOpenStatus(himc);
223 if (!inputMethodAccepted())
226 QWindow *window = QGuiApplication::focusWindow();
230 QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(window);
237 if (!m_caretCreated && m_transparentBitmap)
238 m_caretCreated = CreateCaret(platformWindow->handle(), m_transparentBitmap, 0, 0);
240 if (m_caretCreated) {
242 ShowCaret(platformWindow->handle());
248 if (m_caretCreated) {
250 m_caretCreated =
false;
256 if (!QGuiApplication::focusObject())
258 if (
QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(QGuiApplication::focusWindow())) {
259 const bool accepted = inputMethodAccepted();
261 qCDebug(lcQpaInputMethods) <<
__FUNCTION__ << platformWindow->window() <<
"accepted=" << accepted;
272 ImmAssociateContextEx(platformWindow->handle(),
nullptr, IACE_DEFAULT);
275 ImmAssociateContext(platformWindow->handle(),
nullptr);
280
281
285 if (queries & Qt::ImEnabled)
291 QWindow *window = QGuiApplication::focusWindow();
295 qreal factor = QHighDpiScaling::factor(window);
297 const QInputMethod *inputMethod = QGuiApplication::inputMethod();
298 const QRectF cursorRectangleF = inputMethod->cursorRectangle();
299 if (!cursorRectangleF.isValid())
302 const QRect cursorRectangle =
303 QRectF(cursorRectangleF.topLeft() * factor, cursorRectangleF.size() * factor).toRect();
306 SetCaretPos(cursorRectangle.x(), cursorRectangle.y());
308 if (!m_compositionContext.hwnd)
311 qCDebug(lcQpaInputMethods) <<
__FUNCTION__<< cursorRectangle;
313 const HIMC himc = ImmGetContext(m_compositionContext.hwnd);
319 cf.dwStyle = CFS_FORCE_POSITION;
320 cf.ptCurrentPos.x = cursorRectangle.x();
321 cf.ptCurrentPos.y = cursorRectangle.y();
325 candf.dwStyle = CFS_EXCLUDE;
326 candf.ptCurrentPos.x = cursorRectangle.x();
327 candf.ptCurrentPos.y = cursorRectangle.y() + cursorRectangle.height();
328 candf.rcArea.left = cursorRectangle.x();
329 candf.rcArea.top = cursorRectangle.y();
330 candf.rcArea.right = cursorRectangle.x() + cursorRectangle.width();
331 candf.rcArea.bottom = cursorRectangle.y() + cursorRectangle.height();
333 ImmSetCompositionWindow(himc, &cf);
334 ImmSetCandidateWindow(himc, &candf);
335 ImmReleaseContext(m_compositionContext.hwnd, himc);
340 if (action != QInputMethod::Click || !m_compositionContext.hwnd) {
341 QPlatformInputContext::invokeAction(action, cursorPosition);
345 qCDebug(lcQpaInputMethods) <<
__FUNCTION__ << cursorPosition << action;
346 if (cursorPosition < 0 || cursorPosition > m_compositionContext.composition.size())
351 const HIMC himc = ImmGetContext(m_compositionContext.hwnd);
352 const HWND imeWindow = ImmGetDefaultIMEWnd(m_compositionContext.hwnd);
353 const WPARAM mouseOperationCode =
354 MAKELONG(MAKEWORD(MK_LBUTTON, cursorPosition == 0 ? 2 : 1), cursorPosition);
355 SendMessage(imeWindow, m_WM_MSIME_MOUSE, mouseOperationCode, LPARAM(himc));
356 ImmReleaseContext(m_compositionContext.hwnd, himc);
361 enum { bufferSize = 256 };
362 wchar_t buffer[bufferSize];
363 const int length = ImmGetCompositionString(himc, dwIndex, buffer, bufferSize *
sizeof(
wchar_t));
364 return QString::fromWCharArray(buffer, size_t(length) /
sizeof(
wchar_t));
370 enum { bufferSize = 256 };
372 char attrBuffer[bufferSize];
373 *selStart = *selLength = 0;
374 if (
const int attrLength = ImmGetCompositionString(himc, GCS_COMPATTR, attrBuffer, bufferSize)) {
376 while (start < attrLength && !(attrBuffer[start] & ATTR_TARGET_CONVERTED))
378 if (start < attrLength) {
380 while (end < attrLength && (attrBuffer[end] & ATTR_TARGET_CONVERTED))
383 *selLength = end - start;
395 QTextCharFormat result;
398 result.setUnderlineStyle(QTextCharFormat::DashUnderline);
402 const QPalette palette = QGuiApplication::palette();
403 const QColor background = palette.text().color();
404 result.setBackground(QBrush(background));
405 result.setForeground(palette.window());
414 QObject *fo = QGuiApplication::focusObject();
418 QWindow *window = QGuiApplication::focusWindow();
421 qCDebug(lcQpaInputMethods) <<
__FUNCTION__ << fo << window <<
"language=" << m_languageId;
422 if (!fo || QWindowsWindow::handleOf(window) != hwnd)
424 initContext(hwnd, fo);
425 startContextComposition();
431 if (m_compositionContext.isComposing) {
432 qWarning(
"%s: Called out of sequence.",
__FUNCTION__);
435 m_compositionContext.isComposing =
true;
436 m_compositionContext.composition.clear();
437 m_compositionContext.position = 0;
439 update(Qt::ImQueryAll);
444 if (!m_compositionContext.isComposing) {
445 qWarning(
"%s: Called out of sequence.",
__FUNCTION__);
448 m_compositionContext.composition.clear();
449 m_compositionContext.position = 0;
450 m_compositionContext.isComposing =
false;
458 int selStart,
int selLength)
460 QList<QInputMethodEvent::Attribute> attributes;
462 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, selStart,
463 standardFormat(PreeditFormat));
465 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, selStart, selLength,
466 standardFormat(SelectionFormat));
467 if (selStart + selLength < compositionLength)
468 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, selStart + selLength,
469 compositionLength - selStart - selLength,
470 standardFormat(PreeditFormat));
472 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, position, selLength ? 0 : 1, QVariant());
477
478
482 const int lParam =
int(lParamIn);
483 qCDebug(lcQpaInputMethods) <<
'>' <<
__FUNCTION__ << m_compositionContext.focusObject
484 << debugComposition(lParam) <<
" composing=" << m_compositionContext.isComposing;
485 if (m_compositionContext.focusObject.isNull() || m_compositionContext.hwnd != hwnd || !lParam)
487 const HIMC himc = ImmGetContext(m_compositionContext.hwnd);
491 QScopedPointer<QInputMethodEvent> event;
492 if (lParam & (GCS_COMPSTR | GCS_COMPATTR | GCS_CURSORPOS)) {
493 if (!m_compositionContext.isComposing)
494 startContextComposition();
497 int selStart, selLength;
498 m_compositionContext.composition = getCompositionString(himc, GCS_COMPSTR);
499 m_compositionContext.position = ImmGetCompositionString(himc, GCS_CURSORPOS,
nullptr, 0);
500 getCompositionStringConvertedRange(himc, &selStart, &selLength);
501 if ((lParam & CS_INSERTCHAR) && (lParam & CS_NOMOVECARET)) {
504 selLength = m_compositionContext.composition.size();
509 event.reset(
new QInputMethodEvent(m_compositionContext.composition,
510 intermediateMarkup(m_compositionContext.position,
511 m_compositionContext.composition.size(),
512 selStart, selLength)));
515 event.reset(
new QInputMethodEvent);
517 if (lParam & GCS_RESULTSTR) {
519 event->setCommitString(getCompositionString(himc, GCS_RESULTSTR));
520 if (!(lParam & GCS_DELTASTART))
521 endContextComposition();
523 const bool result = QCoreApplication::sendEvent(m_compositionContext.focusObject, event.data());
524 qCDebug(lcQpaInputMethods) <<
'<' <<
__FUNCTION__ <<
"sending markup="
525 << event->attributes().size() <<
" commit=" << event->commitString()
526 <<
" to " << m_compositionContext.focusObject <<
" returns " << result;
527 update(Qt::ImQueryAll);
528 ImmReleaseContext(m_compositionContext.hwnd, himc);
534 qCDebug(lcQpaInputMethods) <<
__FUNCTION__ << m_endCompositionRecursionGuard << hwnd;
538 if (m_endCompositionRecursionGuard || m_compositionContext.hwnd != hwnd)
540 if (m_compositionContext.focusObject.isNull())
545 if (m_locale.language() == QLocale::Korean
546 && QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier)) {
551 m_endCompositionRecursionGuard =
true;
553 imeNotifyCancelComposition(m_compositionContext.hwnd);
554 if (m_compositionContext.isComposing) {
555 QInputMethodEvent event;
556 QCoreApplication::sendEvent(m_compositionContext.focusObject, &event);
560 m_endCompositionRecursionGuard =
false;
566 if (m_compositionContext.hwnd)
568 m_compositionContext.hwnd = hwnd;
569 m_compositionContext.focusObject = focusObject;
571 update(Qt::ImQueryAll);
572 m_compositionContext.isComposing =
false;
573 m_compositionContext.position = 0;
578 if (!m_compositionContext.hwnd)
580 m_compositionContext.hwnd =
nullptr;
581 m_compositionContext.composition.clear();
582 m_compositionContext.position = 0;
583 m_compositionContext.isComposing =
false;
584 m_compositionContext.focusObject =
nullptr;
591 switch (
int(wParam)) {
592 case IMR_RECONVERTSTRING: {
593 const int size = reconvertString(
reinterpret_cast<RECONVERTSTRING *>(lParam));
599 case IMR_CONFIRMRECONVERTSTRING:
609 const LCID newLanguageId = languageIdFromLocaleId(WORD(lparam));
610 if (newLanguageId == m_languageId)
612 const LCID oldLanguageId = m_languageId;
613 m_languageId = newLanguageId;
614 m_locale = qt_localeFromLCID(m_languageId);
617 qCDebug(lcQpaInputMethods) <<
__FUNCTION__ << Qt::hex << Qt::showbase
618 << oldLanguageId <<
"->" << newLanguageId <<
"Character set:"
619 << DWORD(wparam) << Qt::dec << Qt::noshowbase << m_locale;
623
624
625
626
627
628
629
630
631
635 QObject *fo = QGuiApplication::focusObject();
639 const QVariant surroundingTextV = QInputMethod::queryFocusObject(Qt::ImSurroundingText, QVariant());
640 if (!surroundingTextV.isValid())
642 const QString surroundingText = surroundingTextV.toString();
643 const int memSize =
int(
sizeof(RECONVERTSTRING))
644 + (surroundingText.length() + 1) *
int(
sizeof(ushort));
645 qCDebug(lcQpaInputMethods) <<
__FUNCTION__ <<
" reconv=" << reconv
646 <<
" surroundingText=" << surroundingText <<
" size=" << memSize;
649 return surroundingText.isEmpty() ? -1 : memSize;
651 const QVariant posV = QInputMethod::queryFocusObject(Qt::ImCursorPosition, QVariant());
652 const int pos = posV.isValid() ? posV.toInt() : 0;
654 QTextBoundaryFinder bounds(QTextBoundaryFinder::Word, surroundingText);
655 bounds.setPosition(pos);
656 if (bounds.position() > 0 && !(bounds.boundaryReasons() & QTextBoundaryFinder::StartOfItem))
657 bounds.toPreviousBoundary();
658 const int startPos = bounds.position();
659 bounds.toNextBoundary();
660 const int endPos = bounds.position();
661 qCDebug(lcQpaInputMethods) <<
__FUNCTION__ <<
" boundary=" << startPos << endPos;
663 QList<QInputMethodEvent::Attribute> attributes;
664 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, startPos, endPos-startPos, QVariant());
665 QInputMethodEvent selectEvent(QString(), attributes);
666 QCoreApplication::sendEvent(fo, &selectEvent);
668 reconv->dwSize = DWORD(memSize);
669 reconv->dwVersion = 0;
671 reconv->dwStrLen = DWORD(surroundingText.size());
672 reconv->dwStrOffset =
sizeof(RECONVERTSTRING);
673 reconv->dwCompStrLen = DWORD(endPos - startPos);
674 reconv->dwCompStrOffset = DWORD(startPos) *
sizeof(ushort);
675 reconv->dwTargetStrLen = reconv->dwCompStrLen;
676 reconv->dwTargetStrOffset = reconv->dwCompStrOffset;
677 auto *pastReconv =
reinterpret_cast<ushort *>(reconv + 1);
678 std::copy(surroundingText.utf16(), surroundingText.utf16() + surroundingText.size(),
Singleton container for all relevant information.
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)