1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qwindowsinputcontext.h"
41 #include "qwindowscontext.h"
42 #include "qwindowswindow.h"
43 #include "qwindowsintegration.h"
44 #include "qwindowsmousehandler.h"
45 
46 #include <QtCore/qdebug.h>
47 #include <QtCore/qobject.h>
48 #include <QtCore/qrect.h>
49 #include <QtCore/qtextboundaryfinder.h>
50 #include <QtCore/qoperatingsystemversion.h>
51 
52 #include <QtGui/qevent.h>
53 #include <QtGui/qtextformat.h>
54 #include <QtGui/qpalette.h>
55 #include <QtGui/qguiapplication.h>
56 
57 #include <private/qhighdpiscaling_p.h>
58 
59 #include <algorithm>
60 
61 QT_BEGIN_NAMESPACE
62 
debugComposition(int lParam)63 static inline QByteArray debugComposition(int lParam)
64 {
65     QByteArray str;
66     if (lParam & GCS_RESULTSTR)
67         str += "RESULTSTR ";
68     if (lParam & GCS_COMPSTR)
69         str += "COMPSTR ";
70     if (lParam & GCS_COMPATTR)
71         str += "COMPATTR ";
72     if (lParam & GCS_CURSORPOS)
73         str += "CURSORPOS ";
74     if (lParam & GCS_COMPCLAUSE)
75         str += "COMPCLAUSE ";
76     if (lParam & CS_INSERTCHAR)
77         str += "INSERTCHAR ";
78     if (lParam & CS_NOMOVECARET)
79         str += "NOMOVECARET ";
80     return str;
81 }
82 
83 // Cancel current IME composition.
imeNotifyCancelComposition(HWND hwnd)84 static inline void imeNotifyCancelComposition(HWND hwnd)
85 {
86     if (!hwnd) {
87         qWarning() << __FUNCTION__ << "called with" << hwnd;
88         return;
89     }
90     const HIMC himc = ImmGetContext(hwnd);
91     ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
92     ImmReleaseContext(hwnd, himc);
93 }
94 
languageIdFromLocaleId(LCID localeId)95 static inline LCID languageIdFromLocaleId(LCID localeId)
96 {
97     return localeId & 0xFFFF;
98 }
99 
currentInputLanguageId()100 static inline LCID currentInputLanguageId()
101 {
102     return languageIdFromLocaleId(reinterpret_cast<quintptr>(GetKeyboardLayout(0)));
103 }
104 
105 Q_CORE_EXPORT QLocale qt_localeFromLCID(LCID id); // from qlocale_win.cpp
106 
107 /*!
108     \class QWindowsInputContext
109     \brief Windows Input context implementation
110 
111     Handles input of foreign characters (particularly East Asian)
112     languages.
113 
114     \section1 Testing
115 
116     \list
117     \li Install the East Asian language support and choose Japanese (say).
118     \li Compile the \a mainwindows/mdi example and open a text window.
119     \li In the language bar, switch to Japanese and choose the
120        Input method 'Hiragana'.
121     \li In a text editor control, type the syllable \a 'la'.
122        Underlined characters show up, indicating that there is completion
123        available. Press the Space key two times. A completion popup occurs
124        which shows the options.
125     \endlist
126 
127     Reconversion: Input texts can be 'converted' into different
128     input modes or more completion suggestions can be made based on
129     context to correct errors. This is bound to the 'Conversion key'
130     (F13-key in Japanese, which can be changed in the
131     configuration). After writing text, pressing the key selects text
132     and triggers a conversion popup, which shows the alternatives for
133     the word.
134 
135     \section1 Interaction
136 
137     When the user activates input methods,  Windows sends
138     WM_IME_STARTCOMPOSITION, WM_IME_COMPOSITION,
139     WM_IME_ENDCOMPOSITION messages that trigger startComposition(),
140     composition(), endComposition(), respectively. No key events are sent.
141 
142     composition() determines the markup of the pre-edit or selected
143     text and/or the final text and sends that to the focus object.
144 
145     In between startComposition(), endComposition(), multiple
146     compositions may happen (isComposing).
147 
148     update() is called to synchronize the position of the candidate
149     window with the microfocus rectangle of the focus object.
150     Also, a hidden caret is moved along with that position,
151     which is important for some Chinese input methods.
152 
153     reset() is called to cancel a composition if the mouse is
154     moved outside or for example some Undo/Redo operation is
155     invoked.
156 
157     \note Mouse interaction of popups with
158     QtWindows::InputMethodOpenCandidateWindowEvent and
159     QtWindows::InputMethodCloseCandidateWindowEvent
160     needs to be checked (mouse grab might interfere with candidate window).
161 
162     \internal
163 */
164 
165 
QWindowsInputContext()166 QWindowsInputContext::QWindowsInputContext() :
167     m_WM_MSIME_MOUSE(RegisterWindowMessage(L"MSIMEMouseOperation")),
168     m_languageId(currentInputLanguageId()),
169     m_locale(qt_localeFromLCID(m_languageId))
170 {
171     const quint32 bmpData = 0;
172     m_transparentBitmap = CreateBitmap(2, 2, 1, 1, &bmpData);
173 
174     connect(QGuiApplication::inputMethod(), &QInputMethod::cursorRectangleChanged,
175             this, &QWindowsInputContext::cursorRectChanged);
176 }
177 
~QWindowsInputContext()178 QWindowsInputContext::~QWindowsInputContext()
179 {
180     if (m_transparentBitmap)
181         DeleteObject(m_transparentBitmap);
182 }
183 
hasCapability(Capability capability) const184 bool QWindowsInputContext::hasCapability(Capability capability) const
185 {
186     switch (capability) {
187     case QPlatformInputContext::HiddenTextCapability:
188         return false; // QTBUG-40691, do not show IME on desktop for password entry fields.
189     default:
190         break;
191     }
192     return true;
193 }
194 
195 /*!
196     \brief Cancels a composition.
197 */
198 
reset()199 void QWindowsInputContext::reset()
200 {
201     QPlatformInputContext::reset();
202     if (!m_compositionContext.hwnd)
203         return;
204     qCDebug(lcQpaInputMethods) << __FUNCTION__;
205     if (m_compositionContext.isComposing && !m_compositionContext.focusObject.isNull()) {
206         QInputMethodEvent event;
207         if (!m_compositionContext.composition.isEmpty())
208             event.setCommitString(m_compositionContext.composition);
209         QCoreApplication::sendEvent(m_compositionContext.focusObject, &event);
210         endContextComposition();
211     }
212     imeNotifyCancelComposition(m_compositionContext.hwnd);
213     doneContext();
214 }
215 
setFocusObject(QObject *)216 void QWindowsInputContext::setFocusObject(QObject *)
217 {
218     // ### fixme: On Windows 8.1, it has been observed that the Input context
219     // remains active when this happens resulting in a lock-up. Consecutive
220     // key events still have VK_PROCESSKEY set and are thus ignored.
221     if (m_compositionContext.isComposing)
222         reset();
223     updateEnabled();
224 }
225 
getVirtualKeyboardWindowHandle() const226 HWND QWindowsInputContext::getVirtualKeyboardWindowHandle() const
227 {
228     return ::FindWindowA("IPTip_Main_Window", nullptr);
229 }
230 
keyboardRect() const231 QRectF QWindowsInputContext::keyboardRect() const
232 {
233     if (HWND hwnd = getVirtualKeyboardWindowHandle()) {
234         RECT rect;
235         if (::GetWindowRect(hwnd, &rect)) {
236             return QRectF(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
237         }
238     }
239     return QRectF();
240 }
241 
isInputPanelVisible() const242 bool QWindowsInputContext::isInputPanelVisible() const
243 {
244     HWND hwnd = getVirtualKeyboardWindowHandle();
245     if (hwnd && ::IsWindowEnabled(hwnd) && ::IsWindowVisible(hwnd))
246         return true;
247     // check if the Input Method Editor is open
248     if (inputMethodAccepted()) {
249         if (QWindow *window = QGuiApplication::focusWindow()) {
250             if (QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(window)) {
251                 if (HIMC himc = ImmGetContext(platformWindow->handle()))
252                     return ImmGetOpenStatus(himc);
253             }
254         }
255     }
256     return false;
257 }
258 
showInputPanel()259 void QWindowsInputContext::showInputPanel()
260 {
261     if (!inputMethodAccepted())
262         return;
263 
264     QWindow *window = QGuiApplication::focusWindow();
265     if (!window)
266         return;
267 
268     QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(window);
269     if (!platformWindow)
270         return;
271 
272     // Create an invisible 2x2 caret, which will be kept at the microfocus position.
273     // It is important for triggering the on-screen keyboard in touch-screen devices,
274     // for some Chinese input methods, and for Magnifier's "follow keyboard" feature.
275     if (!m_caretCreated && m_transparentBitmap)
276         m_caretCreated = CreateCaret(platformWindow->handle(), m_transparentBitmap, 0, 0);
277 
278     // For some reason, the on-screen keyboard is only triggered on the Surface
279     // with Windows 10 if the Windows IME is (re)enabled _after_ the caret is shown.
280     if (m_caretCreated) {
281         cursorRectChanged();
282         // We only call ShowCaret() on Windows 10 after 1703 as in earlier versions
283         // the caret would actually be visible (QTBUG-74492) and the workaround for
284         // the Surface seems unnecessary there anyway. But leave it hidden for IME.
285         // Only trigger the native OSK if the Qt OSK is not in use.
286         static bool imModuleEmpty = qEnvironmentVariableIsEmpty("QT_IM_MODULE");
287         bool nativeVKDisabled = QCoreApplication::testAttribute(Qt::AA_DisableNativeVirtualKeyboard);
288         if ((imModuleEmpty && !nativeVKDisabled)
289                 && QOperatingSystemVersion::current()
290                     >= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 16299)) {
291             ShowCaret(platformWindow->handle());
292         } else {
293             HideCaret(platformWindow->handle());
294         }
295         setWindowsImeEnabled(platformWindow, false);
296         setWindowsImeEnabled(platformWindow, true);
297     }
298 }
299 
hideInputPanel()300 void QWindowsInputContext::hideInputPanel()
301 {
302     if (m_caretCreated) {
303         DestroyCaret();
304         m_caretCreated = false;
305     }
306 }
307 
updateEnabled()308 void QWindowsInputContext::updateEnabled()
309 {
310     if (!QGuiApplication::focusObject())
311         return;
312     if (QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(QGuiApplication::focusWindow())) {
313         const bool accepted = inputMethodAccepted();
314         if (QWindowsContext::verbose > 1)
315             qCDebug(lcQpaInputMethods) << __FUNCTION__ << platformWindow->window() << "accepted=" << accepted;
316         QWindowsInputContext::setWindowsImeEnabled(platformWindow, accepted);
317     }
318 }
319 
setWindowsImeEnabled(QWindowsWindow * platformWindow,bool enabled)320 void QWindowsInputContext::setWindowsImeEnabled(QWindowsWindow *platformWindow, bool enabled)
321 {
322     if (!platformWindow)
323         return;
324     if (enabled) {
325         // Re-enable Windows IME by associating default context.
326         ImmAssociateContextEx(platformWindow->handle(), nullptr, IACE_DEFAULT);
327     } else {
328         // Disable Windows IME by associating 0 context.
329         ImmAssociateContext(platformWindow->handle(), nullptr);
330     }
331 }
332 
333 /*!
334     \brief Moves the candidate window along with microfocus of the focus object.
335 */
336 
update(Qt::InputMethodQueries queries)337 void QWindowsInputContext::update(Qt::InputMethodQueries queries)
338 {
339     if (queries & Qt::ImEnabled)
340         updateEnabled();
341     QPlatformInputContext::update(queries);
342 }
343 
cursorRectChanged()344 void QWindowsInputContext::cursorRectChanged()
345 {
346     QWindow *window = QGuiApplication::focusWindow();
347     if (!window)
348         return;
349 
350     qreal factor = QHighDpiScaling::factor(window);
351 
352     const QInputMethod *inputMethod = QGuiApplication::inputMethod();
353     const QRectF cursorRectangleF = inputMethod->cursorRectangle();
354     if (!cursorRectangleF.isValid())
355         return;
356 
357     const QRect cursorRectangle =
358         QRectF(cursorRectangleF.topLeft() * factor, cursorRectangleF.size() * factor).toRect();
359 
360     if (m_caretCreated)
361         SetCaretPos(cursorRectangle.x(), cursorRectangle.y());
362 
363     if (!m_compositionContext.hwnd)
364         return;
365 
366     qCDebug(lcQpaInputMethods) << __FUNCTION__<< cursorRectangle;
367 
368     const HIMC himc = ImmGetContext(m_compositionContext.hwnd);
369     if (!himc)
370         return;
371     // Move candidate list window to the microfocus position.
372     COMPOSITIONFORM cf;
373     // ### need X-like inputStyle config settings
374     cf.dwStyle = CFS_FORCE_POSITION;
375     cf.ptCurrentPos.x = cursorRectangle.x();
376     cf.ptCurrentPos.y = cursorRectangle.y();
377 
378     CANDIDATEFORM candf;
379     candf.dwIndex = 0;
380     candf.dwStyle = CFS_EXCLUDE;
381     candf.ptCurrentPos.x = cursorRectangle.x();
382     candf.ptCurrentPos.y = cursorRectangle.y() + cursorRectangle.height();
383     candf.rcArea.left = cursorRectangle.x();
384     candf.rcArea.top = cursorRectangle.y();
385     candf.rcArea.right = cursorRectangle.x() + cursorRectangle.width();
386     candf.rcArea.bottom = cursorRectangle.y() + cursorRectangle.height();
387 
388     ImmSetCompositionWindow(himc, &cf);
389     ImmSetCandidateWindow(himc, &candf);
390     ImmReleaseContext(m_compositionContext.hwnd, himc);
391 }
392 
invokeAction(QInputMethod::Action action,int cursorPosition)393 void QWindowsInputContext::invokeAction(QInputMethod::Action action, int cursorPosition)
394 {
395     if (action != QInputMethod::Click || !m_compositionContext.hwnd) {
396         QPlatformInputContext::invokeAction(action, cursorPosition);
397         return;
398     }
399 
400     qCDebug(lcQpaInputMethods) << __FUNCTION__ << cursorPosition << action;
401     if (cursorPosition < 0 || cursorPosition > m_compositionContext.composition.size())
402         reset();
403 
404     // Magic code that notifies Japanese IME about the cursor
405     // position.
406     const HIMC himc = ImmGetContext(m_compositionContext.hwnd);
407     const HWND imeWindow = ImmGetDefaultIMEWnd(m_compositionContext.hwnd);
408     const WPARAM mouseOperationCode =
409         MAKELONG(MAKEWORD(MK_LBUTTON, cursorPosition == 0 ? 2 : 1), cursorPosition);
410     SendMessage(imeWindow, m_WM_MSIME_MOUSE, mouseOperationCode, LPARAM(himc));
411     ImmReleaseContext(m_compositionContext.hwnd, himc);
412 }
413 
getCompositionString(HIMC himc,DWORD dwIndex)414 static inline QString getCompositionString(HIMC himc, DWORD dwIndex)
415 {
416     enum { bufferSize = 256 };
417     wchar_t buffer[bufferSize];
418     const int length = ImmGetCompositionString(himc, dwIndex, buffer, bufferSize * sizeof(wchar_t));
419     return QString::fromWCharArray(buffer,  size_t(length) / sizeof(wchar_t));
420 }
421 
422 // Determine the converted string range as pair of start/length to be selected.
getCompositionStringConvertedRange(HIMC himc,int * selStart,int * selLength)423 static inline void getCompositionStringConvertedRange(HIMC himc, int *selStart, int *selLength)
424 {
425     enum { bufferSize = 256 };
426     // Find the range of bytes with ATTR_TARGET_CONVERTED set.
427     char attrBuffer[bufferSize];
428     *selStart = *selLength = 0;
429     if (const int attrLength = ImmGetCompositionString(himc, GCS_COMPATTR, attrBuffer, bufferSize)) {
430         int start = 0;
431         while (start < attrLength && !(attrBuffer[start] & ATTR_TARGET_CONVERTED))
432             start++;
433         if (start < attrLength) {
434             int end = start + 1;
435             while (end < attrLength && (attrBuffer[end] & ATTR_TARGET_CONVERTED))
436                 end++;
437             *selStart = start;
438             *selLength = end - start;
439         }
440     }
441 }
442 
443 enum StandardFormat {
444     PreeditFormat,
445     SelectionFormat
446 };
447 
standardFormat(StandardFormat format)448 static inline QTextFormat standardFormat(StandardFormat format)
449 {
450     QTextCharFormat result;
451     switch (format) {
452     case PreeditFormat:
453         result.setUnderlineStyle(QTextCharFormat::DashUnderline);
454         break;
455     case SelectionFormat: {
456         // TODO: Should be that of the widget?
457         const QPalette palette = QGuiApplication::palette();
458         const QColor background = palette.text().color();
459         result.setBackground(QBrush(background));
460         result.setForeground(palette.window());
461         break;
462     }
463     }
464     return result;
465 }
466 
startComposition(HWND hwnd)467 bool QWindowsInputContext::startComposition(HWND hwnd)
468 {
469     QObject *fo = QGuiApplication::focusObject();
470     if (!fo)
471         return false;
472     // This should always match the object.
473     QWindow *window = QGuiApplication::focusWindow();
474     if (!window)
475         return false;
476     qCDebug(lcQpaInputMethods) << __FUNCTION__ << fo << window << "language=" << m_languageId;
477     if (!fo || QWindowsWindow::handleOf(window) != hwnd)
478         return false;
479     initContext(hwnd, fo);
480     startContextComposition();
481     return true;
482 }
483 
startContextComposition()484 void QWindowsInputContext::startContextComposition()
485 {
486     if (m_compositionContext.isComposing) {
487         qWarning("%s: Called out of sequence.", __FUNCTION__);
488         return;
489     }
490     m_compositionContext.isComposing = true;
491     m_compositionContext.composition.clear();
492     m_compositionContext.position = 0;
493     cursorRectChanged(); // position cursor initially.
494     update(Qt::ImQueryAll);
495 }
496 
endContextComposition()497 void QWindowsInputContext::endContextComposition()
498 {
499     if (!m_compositionContext.isComposing) {
500         qWarning("%s: Called out of sequence.", __FUNCTION__);
501         return;
502     }
503     m_compositionContext.composition.clear();
504     m_compositionContext.position = 0;
505     m_compositionContext.isComposing = false;
506 }
507 
508 // Create a list of markup attributes for QInputMethodEvent
509 // to display the selected part of the intermediate composition
510 // result differently.
511 static inline QList<QInputMethodEvent::Attribute>
intermediateMarkup(int position,int compositionLength,int selStart,int selLength)512     intermediateMarkup(int position, int compositionLength,
513                        int selStart, int selLength)
514 {
515     QList<QInputMethodEvent::Attribute> attributes;
516     if (selStart > 0)
517         attributes << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, selStart,
518                                                    standardFormat(PreeditFormat));
519     if (selLength)
520         attributes << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, selStart, selLength,
521                                                    standardFormat(SelectionFormat));
522     if (selStart + selLength < compositionLength)
523         attributes << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, selStart + selLength,
524                                                    compositionLength - selStart - selLength,
525                                                    standardFormat(PreeditFormat));
526     if (position >= 0)
527         attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, position, selLength ? 0 : 1, QVariant());
528     return attributes;
529 }
530 
531 /*!
532     \brief Notify focus object about markup or final text.
533 */
534 
composition(HWND hwnd,LPARAM lParamIn)535 bool QWindowsInputContext::composition(HWND hwnd, LPARAM lParamIn)
536 {
537     const int lParam = int(lParamIn);
538     qCDebug(lcQpaInputMethods) << '>' << __FUNCTION__ << m_compositionContext.focusObject
539         << debugComposition(lParam) << " composing=" << m_compositionContext.isComposing;
540     if (m_compositionContext.focusObject.isNull() || m_compositionContext.hwnd != hwnd || !lParam)
541         return false;
542     const HIMC himc = ImmGetContext(m_compositionContext.hwnd);
543     if (!himc)
544         return false;
545 
546     QScopedPointer<QInputMethodEvent> event;
547     if (lParam & (GCS_COMPSTR | GCS_COMPATTR | GCS_CURSORPOS)) {
548         if (!m_compositionContext.isComposing)
549             startContextComposition();
550         // Some intermediate composition result. Parametrize event with
551         // attribute sequence specifying the formatting of the converted part.
552         int selStart, selLength;
553         m_compositionContext.composition = getCompositionString(himc, GCS_COMPSTR);
554         m_compositionContext.position = ImmGetCompositionString(himc, GCS_CURSORPOS, nullptr, 0);
555         getCompositionStringConvertedRange(himc, &selStart, &selLength);
556         if ((lParam & CS_INSERTCHAR) && (lParam & CS_NOMOVECARET)) {
557             // make Korean work correctly. Hope this is correct for all IMEs
558             selStart = 0;
559             selLength = m_compositionContext.composition.size();
560         }
561         if (!selLength)
562             selStart = 0;
563 
564         event.reset(new QInputMethodEvent(m_compositionContext.composition,
565                                           intermediateMarkup(m_compositionContext.position,
566                                                              m_compositionContext.composition.size(),
567                                                              selStart, selLength)));
568     }
569     if (event.isNull())
570         event.reset(new QInputMethodEvent);
571 
572     if (lParam & GCS_RESULTSTR) {
573         // A fixed result, return the converted string
574         event->setCommitString(getCompositionString(himc, GCS_RESULTSTR));
575         if (!(lParam & GCS_DELTASTART))
576             endContextComposition();
577     }
578     const bool result = QCoreApplication::sendEvent(m_compositionContext.focusObject, event.data());
579     qCDebug(lcQpaInputMethods) << '<' << __FUNCTION__ << "sending markup="
580         << event->attributes().size() << " commit=" << event->commitString()
581         << " to " << m_compositionContext.focusObject << " returns " << result;
582     update(Qt::ImQueryAll);
583     ImmReleaseContext(m_compositionContext.hwnd, himc);
584     return result;
585 }
586 
endComposition(HWND hwnd)587 bool QWindowsInputContext::endComposition(HWND hwnd)
588 {
589     qCDebug(lcQpaInputMethods) << __FUNCTION__ << m_endCompositionRecursionGuard << hwnd;
590     // Googles Pinyin Input Method likes to call endComposition again
591     // when we call notifyIME with CPS_CANCEL, so protect ourselves
592     // against that.
593     if (m_endCompositionRecursionGuard || m_compositionContext.hwnd != hwnd)
594         return false;
595     if (m_compositionContext.focusObject.isNull())
596         return false;
597 
598     // QTBUG-58300: Ignore WM_IME_ENDCOMPOSITION when CTRL is pressed to prevent
599     // for example the text being cleared when pressing CTRL+A
600     if (m_locale.language() == QLocale::Korean
601         && QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier)) {
602         reset();
603         return true;
604     }
605 
606     m_endCompositionRecursionGuard = true;
607 
608     imeNotifyCancelComposition(m_compositionContext.hwnd);
609     if (m_compositionContext.isComposing) {
610         QInputMethodEvent event;
611         QCoreApplication::sendEvent(m_compositionContext.focusObject, &event);
612     }
613     doneContext();
614 
615     m_endCompositionRecursionGuard = false;
616     return true;
617 }
618 
initContext(HWND hwnd,QObject * focusObject)619 void QWindowsInputContext::initContext(HWND hwnd, QObject *focusObject)
620 {
621     if (m_compositionContext.hwnd)
622         doneContext();
623     m_compositionContext.hwnd = hwnd;
624     m_compositionContext.focusObject = focusObject;
625 
626     update(Qt::ImQueryAll);
627     m_compositionContext.isComposing = false;
628     m_compositionContext.position = 0;
629 }
630 
doneContext()631 void QWindowsInputContext::doneContext()
632 {
633     if (!m_compositionContext.hwnd)
634         return;
635     m_compositionContext.hwnd = nullptr;
636     m_compositionContext.composition.clear();
637     m_compositionContext.position = 0;
638     m_compositionContext.isComposing = false;
639     m_compositionContext.focusObject = nullptr;
640 }
641 
handleIME_Request(WPARAM wParam,LPARAM lParam,LRESULT * result)642 bool QWindowsInputContext::handleIME_Request(WPARAM wParam,
643                                              LPARAM lParam,
644                                              LRESULT *result)
645 {
646     switch (int(wParam)) {
647     case IMR_RECONVERTSTRING: {
648         const int size = reconvertString(reinterpret_cast<RECONVERTSTRING *>(lParam));
649         if (size < 0)
650             return false;
651         *result = size;
652     }
653         return true;
654     case IMR_CONFIRMRECONVERTSTRING:
655         return true;
656     default:
657         break;
658     }
659     return false;
660 }
661 
handleInputLanguageChanged(WPARAM wparam,LPARAM lparam)662 void QWindowsInputContext::handleInputLanguageChanged(WPARAM wparam, LPARAM lparam)
663 {
664     const LCID newLanguageId = languageIdFromLocaleId(WORD(lparam));
665     if (newLanguageId == m_languageId)
666         return;
667     const LCID oldLanguageId = m_languageId;
668     m_languageId = newLanguageId;
669     m_locale = qt_localeFromLCID(m_languageId);
670     emitLocaleChanged();
671 
672     qCDebug(lcQpaInputMethods) << __FUNCTION__ << Qt::hex << Qt::showbase
673         << oldLanguageId  << "->" << newLanguageId << "Character set:"
674         << DWORD(wparam) << Qt::dec << Qt::noshowbase << m_locale;
675 }
676 
677 /*!
678     \brief Determines the string for reconversion with selection.
679 
680     This is triggered twice by WM_IME_REQUEST, first with reconv=0
681     to determine the length and later with a reconv struct to obtain
682     the string with the position of the selection to be reconverted.
683 
684     Obtains the text from the focus object and marks the word
685     for selection (might not be entirely correct for Japanese).
686 */
687 
reconvertString(RECONVERTSTRING * reconv)688 int QWindowsInputContext::reconvertString(RECONVERTSTRING *reconv)
689 {
690     QObject *fo = QGuiApplication::focusObject();
691     if (!fo)
692         return false;
693 
694     const QVariant surroundingTextV = QInputMethod::queryFocusObject(Qt::ImSurroundingText, QVariant());
695     if (!surroundingTextV.isValid())
696         return -1;
697     const QString surroundingText = surroundingTextV.toString();
698     const int memSize = int(sizeof(RECONVERTSTRING))
699         + (surroundingText.length() + 1) * int(sizeof(ushort));
700     qCDebug(lcQpaInputMethods) << __FUNCTION__ << " reconv=" << reconv
701         << " surroundingText=" << surroundingText << " size=" << memSize;
702     // If memory is not allocated, return the required size.
703     if (!reconv)
704         return surroundingText.isEmpty() ? -1 : memSize;
705 
706     const QVariant posV = QInputMethod::queryFocusObject(Qt::ImCursorPosition, QVariant());
707     const int pos = posV.isValid() ? posV.toInt() : 0;
708     // Find the word in the surrounding text.
709     QTextBoundaryFinder bounds(QTextBoundaryFinder::Word, surroundingText);
710     bounds.setPosition(pos);
711     if (bounds.position() > 0 && !(bounds.boundaryReasons() & QTextBoundaryFinder::StartOfItem))
712         bounds.toPreviousBoundary();
713     const int startPos = bounds.position();
714     bounds.toNextBoundary();
715     const int endPos = bounds.position();
716     qCDebug(lcQpaInputMethods) << __FUNCTION__ << " boundary=" << startPos << endPos;
717     // Select the text, this will be overwritten by following IME events.
718     QList<QInputMethodEvent::Attribute> attributes;
719     attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, startPos, endPos-startPos, QVariant());
720     QInputMethodEvent selectEvent(QString(), attributes);
721     QCoreApplication::sendEvent(fo, &selectEvent);
722 
723     reconv->dwSize = DWORD(memSize);
724     reconv->dwVersion = 0;
725 
726     reconv->dwStrLen = DWORD(surroundingText.size());
727     reconv->dwStrOffset = sizeof(RECONVERTSTRING);
728     reconv->dwCompStrLen = DWORD(endPos - startPos); // TCHAR count.
729     reconv->dwCompStrOffset = DWORD(startPos) * sizeof(ushort); // byte count.
730     reconv->dwTargetStrLen = reconv->dwCompStrLen;
731     reconv->dwTargetStrOffset = reconv->dwCompStrOffset;
732     auto *pastReconv = reinterpret_cast<ushort *>(reconv + 1);
733     std::copy(surroundingText.utf16(), surroundingText.utf16() + surroundingText.size(),
734               QT_MAKE_UNCHECKED_ARRAY_ITERATOR(pastReconv));
735     return memSize;
736 }
737 
738 QT_END_NAMESPACE
739