684 lines
24 KiB
C++
684 lines
24 KiB
C++
// Copyright (C) 2016 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
|
|
|
#include "qwindowsinputcontext.h"
|
|
#include "qwindowscontext.h"
|
|
#include "qwindowswindow.h"
|
|
#include "qwindowsintegration.h"
|
|
#include "qwindowsmousehandler.h"
|
|
|
|
#include <QtCore/qdebug.h>
|
|
#include <QtCore/qobject.h>
|
|
#include <QtCore/qrect.h>
|
|
#include <QtCore/qtextboundaryfinder.h>
|
|
|
|
#include <QtGui/qevent.h>
|
|
#include <QtGui/qtextformat.h>
|
|
#include <QtGui/qpalette.h>
|
|
#include <QtGui/qguiapplication.h>
|
|
|
|
#include <private/qhighdpiscaling_p.h>
|
|
|
|
#include <algorithm>
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
static inline QByteArray debugComposition(int lParam)
|
|
{
|
|
QByteArray str;
|
|
if (lParam & GCS_RESULTSTR)
|
|
str += "RESULTSTR ";
|
|
if (lParam & GCS_COMPSTR)
|
|
str += "COMPSTR ";
|
|
if (lParam & GCS_COMPATTR)
|
|
str += "COMPATTR ";
|
|
if (lParam & GCS_CURSORPOS)
|
|
str += "CURSORPOS ";
|
|
if (lParam & GCS_COMPCLAUSE)
|
|
str += "COMPCLAUSE ";
|
|
if (lParam & CS_INSERTCHAR)
|
|
str += "INSERTCHAR ";
|
|
if (lParam & CS_NOMOVECARET)
|
|
str += "NOMOVECARET ";
|
|
return str;
|
|
}
|
|
|
|
// Cancel current IME composition.
|
|
static inline void imeNotifyCancelComposition(HWND hwnd)
|
|
{
|
|
if (!hwnd) {
|
|
qWarning() << __FUNCTION__ << "called with" << hwnd;
|
|
return;
|
|
}
|
|
const HIMC himc = ImmGetContext(hwnd);
|
|
ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
|
|
ImmReleaseContext(hwnd, himc);
|
|
}
|
|
|
|
static inline LCID languageIdFromLocaleId(LCID localeId)
|
|
{
|
|
return localeId & 0xFFFF;
|
|
}
|
|
|
|
static inline LCID currentInputLanguageId()
|
|
{
|
|
return languageIdFromLocaleId(reinterpret_cast<quintptr>(GetKeyboardLayout(0)));
|
|
}
|
|
|
|
Q_CORE_EXPORT QLocale qt_localeFromLCID(LCID id); // from qlocale_win.cpp
|
|
|
|
/*!
|
|
\class QWindowsInputContext
|
|
\brief Windows Input context implementation
|
|
|
|
Handles input of foreign characters (particularly East Asian)
|
|
languages.
|
|
|
|
\section1 Testing
|
|
|
|
\list
|
|
\li Install the East Asian language support and choose Japanese (say).
|
|
\li Compile the \a mainwindows/mdi example and open a text window.
|
|
\li In the language bar, switch to Japanese and choose the
|
|
Input method 'Hiragana'.
|
|
\li In a text editor control, type the syllable \a 'la'.
|
|
Underlined characters show up, indicating that there is completion
|
|
available. Press the Space key two times. A completion popup occurs
|
|
which shows the options.
|
|
\endlist
|
|
|
|
Reconversion: Input texts can be 'converted' into different
|
|
input modes or more completion suggestions can be made based on
|
|
context to correct errors. This is bound to the 'Conversion key'
|
|
(F13-key in Japanese, which can be changed in the
|
|
configuration). After writing text, pressing the key selects text
|
|
and triggers a conversion popup, which shows the alternatives for
|
|
the word.
|
|
|
|
\section1 Interaction
|
|
|
|
When the user activates input methods, Windows sends
|
|
WM_IME_STARTCOMPOSITION, WM_IME_COMPOSITION,
|
|
WM_IME_ENDCOMPOSITION messages that trigger startComposition(),
|
|
composition(), endComposition(), respectively. No key events are sent.
|
|
|
|
composition() determines the markup of the pre-edit or selected
|
|
text and/or the final text and sends that to the focus object.
|
|
|
|
In between startComposition(), endComposition(), multiple
|
|
compositions may happen (isComposing).
|
|
|
|
update() is called to synchronize the position of the candidate
|
|
window with the microfocus rectangle of the focus object.
|
|
Also, a hidden caret is moved along with that position,
|
|
which is important for some Chinese input methods.
|
|
|
|
reset() is called to cancel a composition if the mouse is
|
|
moved outside or for example some Undo/Redo operation is
|
|
invoked.
|
|
|
|
\note Mouse interaction of popups with
|
|
QtWindows::InputMethodOpenCandidateWindowEvent and
|
|
QtWindows::InputMethodCloseCandidateWindowEvent
|
|
needs to be checked (mouse grab might interfere with candidate window).
|
|
|
|
\internal
|
|
*/
|
|
|
|
|
|
QWindowsInputContext::QWindowsInputContext() :
|
|
m_WM_MSIME_MOUSE(RegisterWindowMessage(L"MSIMEMouseOperation")),
|
|
m_languageId(currentInputLanguageId()),
|
|
m_locale(qt_localeFromLCID(m_languageId))
|
|
{
|
|
const quint32 bmpData = 0;
|
|
m_transparentBitmap = CreateBitmap(2, 2, 1, 1, &bmpData);
|
|
|
|
connect(QGuiApplication::inputMethod(), &QInputMethod::cursorRectangleChanged,
|
|
this, &QWindowsInputContext::cursorRectChanged);
|
|
}
|
|
|
|
QWindowsInputContext::~QWindowsInputContext()
|
|
{
|
|
if (m_transparentBitmap)
|
|
DeleteObject(m_transparentBitmap);
|
|
}
|
|
|
|
bool QWindowsInputContext::hasCapability(Capability capability) const
|
|
{
|
|
switch (capability) {
|
|
case QPlatformInputContext::HiddenTextCapability:
|
|
return false; // QTBUG-40691, do not show IME on desktop for password entry fields.
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*!
|
|
\brief Cancels a composition.
|
|
*/
|
|
|
|
void QWindowsInputContext::reset()
|
|
{
|
|
if (!m_compositionContext.hwnd)
|
|
return;
|
|
qCDebug(lcQpaInputMethods) << __FUNCTION__;
|
|
if (m_compositionContext.isComposing && !m_compositionContext.focusObject.isNull()) {
|
|
QInputMethodEvent event;
|
|
if (!m_compositionContext.composition.isEmpty())
|
|
event.setCommitString(m_compositionContext.composition);
|
|
QCoreApplication::sendEvent(m_compositionContext.focusObject, &event);
|
|
endContextComposition();
|
|
}
|
|
imeNotifyCancelComposition(m_compositionContext.hwnd);
|
|
doneContext();
|
|
}
|
|
|
|
void QWindowsInputContext::setFocusObject(QObject *)
|
|
{
|
|
// ### fixme: On Windows 8.1, it has been observed that the Input context
|
|
// remains active when this happens resulting in a lock-up. Consecutive
|
|
// key events still have VK_PROCESSKEY set and are thus ignored.
|
|
if (m_compositionContext.isComposing)
|
|
reset();
|
|
updateEnabled();
|
|
}
|
|
|
|
HWND QWindowsInputContext::getVirtualKeyboardWindowHandle() const
|
|
{
|
|
return ::FindWindowA("IPTip_Main_Window", nullptr);
|
|
}
|
|
|
|
QRectF QWindowsInputContext::keyboardRect() const
|
|
{
|
|
if (HWND hwnd = getVirtualKeyboardWindowHandle()) {
|
|
RECT rect;
|
|
if (::GetWindowRect(hwnd, &rect)) {
|
|
return QRectF(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
|
|
}
|
|
}
|
|
return QRectF();
|
|
}
|
|
|
|
bool QWindowsInputContext::isInputPanelVisible() const
|
|
{
|
|
HWND hwnd = getVirtualKeyboardWindowHandle();
|
|
if (hwnd && ::IsWindowEnabled(hwnd) && ::IsWindowVisible(hwnd))
|
|
return true;
|
|
// check if the Input Method Editor is open
|
|
if (inputMethodAccepted()) {
|
|
if (QWindow *window = QGuiApplication::focusWindow()) {
|
|
if (QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(window)) {
|
|
if (HIMC himc = ImmGetContext(platformWindow->handle()))
|
|
return ImmGetOpenStatus(himc);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void QWindowsInputContext::showInputPanel()
|
|
{
|
|
if (!inputMethodAccepted())
|
|
return;
|
|
|
|
QWindow *window = QGuiApplication::focusWindow();
|
|
if (!window)
|
|
return;
|
|
|
|
QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(window);
|
|
if (!platformWindow)
|
|
return;
|
|
|
|
// Create an invisible 2x2 caret, which will be kept at the microfocus position.
|
|
// It is important for triggering the on-screen keyboard in touch-screen devices,
|
|
// for some Chinese input methods, and for Magnifier's "follow keyboard" feature.
|
|
if (!m_caretCreated && m_transparentBitmap)
|
|
m_caretCreated = CreateCaret(platformWindow->handle(), m_transparentBitmap, 0, 0);
|
|
|
|
if (m_caretCreated) {
|
|
cursorRectChanged();
|
|
ShowCaret(platformWindow->handle());
|
|
}
|
|
}
|
|
|
|
void QWindowsInputContext::hideInputPanel()
|
|
{
|
|
if (m_caretCreated) {
|
|
DestroyCaret();
|
|
m_caretCreated = false;
|
|
}
|
|
}
|
|
|
|
void QWindowsInputContext::updateEnabled()
|
|
{
|
|
if (!QGuiApplication::focusObject())
|
|
return;
|
|
if (QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(QGuiApplication::focusWindow())) {
|
|
const bool accepted = inputMethodAccepted();
|
|
if (QWindowsContext::verbose > 1)
|
|
qCDebug(lcQpaInputMethods) << __FUNCTION__ << platformWindow->window() << "accepted=" << accepted;
|
|
QWindowsInputContext::setWindowsImeEnabled(platformWindow, accepted);
|
|
}
|
|
}
|
|
|
|
void QWindowsInputContext::setWindowsImeEnabled(QWindowsWindow *platformWindow, bool enabled)
|
|
{
|
|
if (!platformWindow)
|
|
return;
|
|
if (enabled) {
|
|
// Re-enable Windows IME by associating default context.
|
|
ImmAssociateContextEx(platformWindow->handle(), nullptr, IACE_DEFAULT);
|
|
} else {
|
|
// Disable Windows IME by associating 0 context.
|
|
ImmAssociateContext(platformWindow->handle(), nullptr);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
\brief Moves the candidate window along with microfocus of the focus object.
|
|
*/
|
|
|
|
void QWindowsInputContext::update(Qt::InputMethodQueries queries)
|
|
{
|
|
if (queries & Qt::ImEnabled)
|
|
updateEnabled();
|
|
}
|
|
|
|
void QWindowsInputContext::cursorRectChanged()
|
|
{
|
|
QWindow *window = QGuiApplication::focusWindow();
|
|
if (!window)
|
|
return;
|
|
|
|
qreal factor = QHighDpiScaling::factor(window);
|
|
|
|
const QInputMethod *inputMethod = QGuiApplication::inputMethod();
|
|
const QRectF cursorRectangleF = inputMethod->cursorRectangle();
|
|
if (!cursorRectangleF.isValid())
|
|
return;
|
|
|
|
const QRect cursorRectangle =
|
|
QRectF(cursorRectangleF.topLeft() * factor, cursorRectangleF.size() * factor).toRect();
|
|
|
|
if (m_caretCreated)
|
|
SetCaretPos(cursorRectangle.x(), cursorRectangle.y());
|
|
|
|
if (!m_compositionContext.hwnd)
|
|
return;
|
|
|
|
qCDebug(lcQpaInputMethods) << __FUNCTION__<< cursorRectangle;
|
|
|
|
const HIMC himc = ImmGetContext(m_compositionContext.hwnd);
|
|
if (!himc)
|
|
return;
|
|
// Move candidate list window to the microfocus position.
|
|
COMPOSITIONFORM cf;
|
|
// ### need X-like inputStyle config settings
|
|
cf.dwStyle = CFS_FORCE_POSITION;
|
|
cf.ptCurrentPos.x = cursorRectangle.x();
|
|
cf.ptCurrentPos.y = cursorRectangle.y();
|
|
|
|
CANDIDATEFORM candf;
|
|
candf.dwIndex = 0;
|
|
candf.dwStyle = CFS_EXCLUDE;
|
|
candf.ptCurrentPos.x = cursorRectangle.x();
|
|
candf.ptCurrentPos.y = cursorRectangle.y() + cursorRectangle.height();
|
|
candf.rcArea.left = cursorRectangle.x();
|
|
candf.rcArea.top = cursorRectangle.y();
|
|
candf.rcArea.right = cursorRectangle.x() + cursorRectangle.width();
|
|
candf.rcArea.bottom = cursorRectangle.y() + cursorRectangle.height();
|
|
|
|
ImmSetCompositionWindow(himc, &cf);
|
|
ImmSetCandidateWindow(himc, &candf);
|
|
ImmReleaseContext(m_compositionContext.hwnd, himc);
|
|
}
|
|
|
|
void QWindowsInputContext::invokeAction(QInputMethod::Action action, int cursorPosition)
|
|
{
|
|
if (action != QInputMethod::Click || !m_compositionContext.hwnd) {
|
|
QPlatformInputContext::invokeAction(action, cursorPosition);
|
|
return;
|
|
}
|
|
|
|
qCDebug(lcQpaInputMethods) << __FUNCTION__ << cursorPosition << action;
|
|
if (cursorPosition < 0 || cursorPosition > m_compositionContext.composition.size())
|
|
reset();
|
|
|
|
// Magic code that notifies Japanese IME about the cursor
|
|
// position.
|
|
const HIMC himc = ImmGetContext(m_compositionContext.hwnd);
|
|
const HWND imeWindow = ImmGetDefaultIMEWnd(m_compositionContext.hwnd);
|
|
const WPARAM mouseOperationCode =
|
|
MAKELONG(MAKEWORD(MK_LBUTTON, cursorPosition == 0 ? 2 : 1), cursorPosition);
|
|
SendMessage(imeWindow, m_WM_MSIME_MOUSE, mouseOperationCode, LPARAM(himc));
|
|
ImmReleaseContext(m_compositionContext.hwnd, himc);
|
|
}
|
|
|
|
static inline QString getCompositionString(HIMC himc, DWORD dwIndex)
|
|
{
|
|
enum { bufferSize = 256 };
|
|
wchar_t buffer[bufferSize];
|
|
const int length = ImmGetCompositionString(himc, dwIndex, buffer, bufferSize * sizeof(wchar_t));
|
|
return QString::fromWCharArray(buffer, size_t(length) / sizeof(wchar_t));
|
|
}
|
|
|
|
// Determine the converted string range as pair of start/length to be selected.
|
|
static inline void getCompositionStringConvertedRange(HIMC himc, int *selStart, int *selLength)
|
|
{
|
|
enum { bufferSize = 256 };
|
|
// Find the range of bytes with ATTR_TARGET_CONVERTED set.
|
|
char attrBuffer[bufferSize];
|
|
*selStart = *selLength = 0;
|
|
if (const int attrLength = ImmGetCompositionString(himc, GCS_COMPATTR, attrBuffer, bufferSize)) {
|
|
int start = 0;
|
|
while (start < attrLength && !(attrBuffer[start] & ATTR_TARGET_CONVERTED))
|
|
start++;
|
|
if (start < attrLength) {
|
|
int end = start + 1;
|
|
while (end < attrLength && (attrBuffer[end] & ATTR_TARGET_CONVERTED))
|
|
end++;
|
|
*selStart = start;
|
|
*selLength = end - start;
|
|
}
|
|
}
|
|
}
|
|
|
|
enum StandardFormat {
|
|
PreeditFormat,
|
|
SelectionFormat
|
|
};
|
|
|
|
static inline QTextFormat standardFormat(StandardFormat format)
|
|
{
|
|
QTextCharFormat result;
|
|
switch (format) {
|
|
case PreeditFormat:
|
|
result.setUnderlineStyle(QTextCharFormat::DashUnderline);
|
|
break;
|
|
case SelectionFormat: {
|
|
// TODO: Should be that of the widget?
|
|
const QPalette palette = QGuiApplication::palette();
|
|
const QColor background = palette.text().color();
|
|
result.setBackground(QBrush(background));
|
|
result.setForeground(palette.window());
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool QWindowsInputContext::startComposition(HWND hwnd)
|
|
{
|
|
QObject *fo = QGuiApplication::focusObject();
|
|
if (!fo)
|
|
return false;
|
|
// This should always match the object.
|
|
QWindow *window = QGuiApplication::focusWindow();
|
|
if (!window)
|
|
return false;
|
|
qCDebug(lcQpaInputMethods) << __FUNCTION__ << fo << window << "language=" << m_languageId;
|
|
if (!fo || QWindowsWindow::handleOf(window) != hwnd)
|
|
return false;
|
|
initContext(hwnd, fo);
|
|
startContextComposition();
|
|
return true;
|
|
}
|
|
|
|
void QWindowsInputContext::startContextComposition()
|
|
{
|
|
if (m_compositionContext.isComposing) {
|
|
qWarning("%s: Called out of sequence.", __FUNCTION__);
|
|
return;
|
|
}
|
|
m_compositionContext.isComposing = true;
|
|
m_compositionContext.composition.clear();
|
|
m_compositionContext.position = 0;
|
|
cursorRectChanged(); // position cursor initially.
|
|
update(Qt::ImQueryAll);
|
|
}
|
|
|
|
void QWindowsInputContext::endContextComposition()
|
|
{
|
|
if (!m_compositionContext.isComposing) {
|
|
qWarning("%s: Called out of sequence.", __FUNCTION__);
|
|
return;
|
|
}
|
|
m_compositionContext.composition.clear();
|
|
m_compositionContext.position = 0;
|
|
m_compositionContext.isComposing = false;
|
|
}
|
|
|
|
// Create a list of markup attributes for QInputMethodEvent
|
|
// to display the selected part of the intermediate composition
|
|
// result differently.
|
|
static inline QList<QInputMethodEvent::Attribute>
|
|
intermediateMarkup(int position, int compositionLength,
|
|
int selStart, int selLength)
|
|
{
|
|
QList<QInputMethodEvent::Attribute> attributes;
|
|
if (selStart > 0)
|
|
attributes << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, selStart,
|
|
standardFormat(PreeditFormat));
|
|
if (selLength)
|
|
attributes << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, selStart, selLength,
|
|
standardFormat(SelectionFormat));
|
|
if (selStart + selLength < compositionLength)
|
|
attributes << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, selStart + selLength,
|
|
compositionLength - selStart - selLength,
|
|
standardFormat(PreeditFormat));
|
|
if (position >= 0)
|
|
attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, position, selLength ? 0 : 1, QVariant());
|
|
return attributes;
|
|
}
|
|
|
|
/*!
|
|
\brief Notify focus object about markup or final text.
|
|
*/
|
|
|
|
bool QWindowsInputContext::composition(HWND hwnd, LPARAM lParamIn)
|
|
{
|
|
const int lParam = int(lParamIn);
|
|
qCDebug(lcQpaInputMethods) << '>' << __FUNCTION__ << m_compositionContext.focusObject
|
|
<< debugComposition(lParam) << " composing=" << m_compositionContext.isComposing;
|
|
if (m_compositionContext.focusObject.isNull() || m_compositionContext.hwnd != hwnd || !lParam)
|
|
return false;
|
|
const HIMC himc = ImmGetContext(m_compositionContext.hwnd);
|
|
if (!himc)
|
|
return false;
|
|
|
|
QScopedPointer<QInputMethodEvent> event;
|
|
if (lParam & (GCS_COMPSTR | GCS_COMPATTR | GCS_CURSORPOS)) {
|
|
if (!m_compositionContext.isComposing)
|
|
startContextComposition();
|
|
// Some intermediate composition result. Parametrize event with
|
|
// attribute sequence specifying the formatting of the converted part.
|
|
int selStart, selLength;
|
|
m_compositionContext.composition = getCompositionString(himc, GCS_COMPSTR);
|
|
m_compositionContext.position = ImmGetCompositionString(himc, GCS_CURSORPOS, nullptr, 0);
|
|
getCompositionStringConvertedRange(himc, &selStart, &selLength);
|
|
if ((lParam & CS_INSERTCHAR) && (lParam & CS_NOMOVECARET)) {
|
|
// make Korean work correctly. Hope this is correct for all IMEs
|
|
selStart = 0;
|
|
selLength = m_compositionContext.composition.size();
|
|
}
|
|
if (!selLength)
|
|
selStart = 0;
|
|
|
|
event.reset(new QInputMethodEvent(m_compositionContext.composition,
|
|
intermediateMarkup(m_compositionContext.position,
|
|
m_compositionContext.composition.size(),
|
|
selStart, selLength)));
|
|
}
|
|
if (event.isNull())
|
|
event.reset(new QInputMethodEvent);
|
|
|
|
if (lParam & GCS_RESULTSTR) {
|
|
// A fixed result, return the converted string
|
|
event->setCommitString(getCompositionString(himc, GCS_RESULTSTR));
|
|
if (!(lParam & GCS_DELTASTART))
|
|
endContextComposition();
|
|
}
|
|
const bool result = QCoreApplication::sendEvent(m_compositionContext.focusObject, event.data());
|
|
qCDebug(lcQpaInputMethods) << '<' << __FUNCTION__ << "sending markup="
|
|
<< event->attributes().size() << " commit=" << event->commitString()
|
|
<< " to " << m_compositionContext.focusObject << " returns " << result;
|
|
update(Qt::ImQueryAll);
|
|
ImmReleaseContext(m_compositionContext.hwnd, himc);
|
|
return result;
|
|
}
|
|
|
|
bool QWindowsInputContext::endComposition(HWND hwnd)
|
|
{
|
|
qCDebug(lcQpaInputMethods) << __FUNCTION__ << m_endCompositionRecursionGuard << hwnd;
|
|
// Googles Pinyin Input Method likes to call endComposition again
|
|
// when we call notifyIME with CPS_CANCEL, so protect ourselves
|
|
// against that.
|
|
if (m_endCompositionRecursionGuard || m_compositionContext.hwnd != hwnd)
|
|
return false;
|
|
if (m_compositionContext.focusObject.isNull())
|
|
return false;
|
|
|
|
// QTBUG-58300: Ignore WM_IME_ENDCOMPOSITION when CTRL is pressed to prevent
|
|
// for example the text being cleared when pressing CTRL+A
|
|
if (m_locale.language() == QLocale::Korean
|
|
&& QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier)) {
|
|
reset();
|
|
return true;
|
|
}
|
|
|
|
m_endCompositionRecursionGuard = true;
|
|
|
|
imeNotifyCancelComposition(m_compositionContext.hwnd);
|
|
if (m_compositionContext.isComposing) {
|
|
QInputMethodEvent event;
|
|
QCoreApplication::sendEvent(m_compositionContext.focusObject, &event);
|
|
}
|
|
doneContext();
|
|
|
|
m_endCompositionRecursionGuard = false;
|
|
return true;
|
|
}
|
|
|
|
void QWindowsInputContext::initContext(HWND hwnd, QObject *focusObject)
|
|
{
|
|
if (m_compositionContext.hwnd)
|
|
doneContext();
|
|
m_compositionContext.hwnd = hwnd;
|
|
m_compositionContext.focusObject = focusObject;
|
|
|
|
update(Qt::ImQueryAll);
|
|
m_compositionContext.isComposing = false;
|
|
m_compositionContext.position = 0;
|
|
}
|
|
|
|
void QWindowsInputContext::doneContext()
|
|
{
|
|
if (!m_compositionContext.hwnd)
|
|
return;
|
|
m_compositionContext.hwnd = nullptr;
|
|
m_compositionContext.composition.clear();
|
|
m_compositionContext.position = 0;
|
|
m_compositionContext.isComposing = false;
|
|
m_compositionContext.focusObject = nullptr;
|
|
}
|
|
|
|
bool QWindowsInputContext::handleIME_Request(WPARAM wParam,
|
|
LPARAM lParam,
|
|
LRESULT *result)
|
|
{
|
|
switch (int(wParam)) {
|
|
case IMR_RECONVERTSTRING: {
|
|
const int size = reconvertString(reinterpret_cast<RECONVERTSTRING *>(lParam));
|
|
if (size < 0)
|
|
return false;
|
|
*result = size;
|
|
}
|
|
return true;
|
|
case IMR_CONFIRMRECONVERTSTRING:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void QWindowsInputContext::handleInputLanguageChanged(WPARAM wparam, LPARAM lparam)
|
|
{
|
|
const LCID newLanguageId = languageIdFromLocaleId(WORD(lparam));
|
|
if (newLanguageId == m_languageId)
|
|
return;
|
|
const LCID oldLanguageId = m_languageId;
|
|
m_languageId = newLanguageId;
|
|
m_locale = qt_localeFromLCID(m_languageId);
|
|
emitLocaleChanged();
|
|
|
|
qCDebug(lcQpaInputMethods) << __FUNCTION__ << Qt::hex << Qt::showbase
|
|
<< oldLanguageId << "->" << newLanguageId << "Character set:"
|
|
<< DWORD(wparam) << Qt::dec << Qt::noshowbase << m_locale;
|
|
}
|
|
|
|
/*!
|
|
\brief Determines the string for reconversion with selection.
|
|
|
|
This is triggered twice by WM_IME_REQUEST, first with reconv=0
|
|
to determine the length and later with a reconv struct to obtain
|
|
the string with the position of the selection to be reconverted.
|
|
|
|
Obtains the text from the focus object and marks the word
|
|
for selection (might not be entirely correct for Japanese).
|
|
*/
|
|
|
|
int QWindowsInputContext::reconvertString(RECONVERTSTRING *reconv)
|
|
{
|
|
QObject *fo = QGuiApplication::focusObject();
|
|
if (!fo)
|
|
return false;
|
|
|
|
const QVariant surroundingTextV = QInputMethod::queryFocusObject(Qt::ImSurroundingText, QVariant());
|
|
if (!surroundingTextV.isValid())
|
|
return -1;
|
|
const QString surroundingText = surroundingTextV.toString();
|
|
const int memSize = int(sizeof(RECONVERTSTRING))
|
|
+ (surroundingText.length() + 1) * int(sizeof(ushort));
|
|
qCDebug(lcQpaInputMethods) << __FUNCTION__ << " reconv=" << reconv
|
|
<< " surroundingText=" << surroundingText << " size=" << memSize;
|
|
// If memory is not allocated, return the required size.
|
|
if (!reconv)
|
|
return surroundingText.isEmpty() ? -1 : memSize;
|
|
|
|
const QVariant posV = QInputMethod::queryFocusObject(Qt::ImCursorPosition, QVariant());
|
|
const int pos = posV.isValid() ? posV.toInt() : 0;
|
|
// Find the word in the surrounding text.
|
|
QTextBoundaryFinder bounds(QTextBoundaryFinder::Word, surroundingText);
|
|
bounds.setPosition(pos);
|
|
if (bounds.position() > 0 && !(bounds.boundaryReasons() & QTextBoundaryFinder::StartOfItem))
|
|
bounds.toPreviousBoundary();
|
|
const int startPos = bounds.position();
|
|
bounds.toNextBoundary();
|
|
const int endPos = bounds.position();
|
|
qCDebug(lcQpaInputMethods) << __FUNCTION__ << " boundary=" << startPos << endPos;
|
|
// Select the text, this will be overwritten by following IME events.
|
|
QList<QInputMethodEvent::Attribute> attributes;
|
|
attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, startPos, endPos-startPos, QVariant());
|
|
QInputMethodEvent selectEvent(QString(), attributes);
|
|
QCoreApplication::sendEvent(fo, &selectEvent);
|
|
|
|
reconv->dwSize = DWORD(memSize);
|
|
reconv->dwVersion = 0;
|
|
|
|
reconv->dwStrLen = DWORD(surroundingText.size());
|
|
reconv->dwStrOffset = sizeof(RECONVERTSTRING);
|
|
reconv->dwCompStrLen = DWORD(endPos - startPos); // TCHAR count.
|
|
reconv->dwCompStrOffset = DWORD(startPos) * sizeof(ushort); // byte count.
|
|
reconv->dwTargetStrLen = reconv->dwCompStrLen;
|
|
reconv->dwTargetStrOffset = reconv->dwCompStrOffset;
|
|
auto *pastReconv = reinterpret_cast<ushort *>(reconv + 1);
|
|
std::copy(surroundingText.utf16(), surroundingText.utf16() + surroundingText.size(),
|
|
QT_MAKE_UNCHECKED_ARRAY_ITERATOR(pastReconv));
|
|
return memSize;
|
|
}
|
|
|
|
QT_END_NAMESPACE
|