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