From 1b2dae36d32bc4d37fa8a12653becd810101f82e Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Wed, 24 Aug 2011 09:30:29 +0200 Subject: Add Input context for Windows. Change-Id: I20b97e863bf1198b9ad810bb5a25652327f626c9 Reviewed-on: http://codereview.qt.nokia.com/3463 Reviewed-by: Qt Sanity Bot Reviewed-by: Oliver Wolff --- .../platforms/windows/qtwindows_additional.h | 3 + src/plugins/platforms/windows/qtwindowsglobal.h | 23 + src/plugins/platforms/windows/qwindowscontext.cpp | 16 + src/plugins/platforms/windows/qwindowscontext.h | 1 + .../windows/qwindowsguieventdispatcher.cpp | 8 +- .../platforms/windows/qwindowsinputcontext.cpp | 598 +++++++++++++++++++++ .../platforms/windows/qwindowsinputcontext.h | 98 ++++ .../platforms/windows/qwindowsintegration.cpp | 5 +- .../platforms/windows/qwindowskeymapper.cpp | 3 +- .../platforms/windows/qwindowsmousehandler.h | 17 + src/plugins/platforms/windows/windows.pro | 8 +- 11 files changed, 772 insertions(+), 8 deletions(-) create mode 100644 src/plugins/platforms/windows/qwindowsinputcontext.cpp create mode 100644 src/plugins/platforms/windows/qwindowsinputcontext.h (limited to 'src/plugins/platforms/windows') diff --git a/src/plugins/platforms/windows/qtwindows_additional.h b/src/plugins/platforms/windows/qtwindows_additional.h index 707d28559a..e262159b7b 100644 --- a/src/plugins/platforms/windows/qtwindows_additional.h +++ b/src/plugins/platforms/windows/qtwindows_additional.h @@ -79,6 +79,9 @@ typedef struct tagUPDATELAYEREDWINDOWINFO { #define PFD_DIRECT3D_ACCELERATED 0x00004000 #define PFD_SUPPORT_COMPOSITION 0x00008000 +// IME. +#define IMR_CONFIRMRECONVERTSTRING 0x0005 + #endif // if defined(Q_CC_MINGW) /* Touch is supported from Windows 7 onwards and data structures diff --git a/src/plugins/platforms/windows/qtwindowsglobal.h b/src/plugins/platforms/windows/qtwindowsglobal.h index 792792a136..692489dfc6 100644 --- a/src/plugins/platforms/windows/qtwindowsglobal.h +++ b/src/plugins/platforms/windows/qtwindowsglobal.h @@ -88,6 +88,12 @@ enum WindowsEventType // Simplify event types ClipboardEvent = ClipboardEventFlag + 1, ActivateApplicationEvent = ApplicationEventFlag + 1, DeactivateApplicationEvent = ApplicationEventFlag + 2, + InputMethodStartCompositionEvent = InputMethodEventFlag + 1, + InputMethodCompositionEvent = InputMethodEventFlag + 2, + InputMethodEndCompositionEvent = InputMethodEventFlag + 3, + InputMethodOpenCandidateWindowEvent = InputMethodEventFlag + 4, + InputMethodCloseCandidateWindowEvent = InputMethodEventFlag + 5, + InputMethodRequest = InputMethodEventFlag + 6, UnknownEvent = 542 }; @@ -143,6 +149,23 @@ inline QtWindows::WindowsEventType windowsEventType(UINT message, WPARAM wParamI case WM_RENDERALLFORMATS: case WM_DESTROYCLIPBOARD: return QtWindows::ClipboardEvent; + case WM_IME_STARTCOMPOSITION: + return QtWindows::InputMethodStartCompositionEvent; + case WM_IME_ENDCOMPOSITION: + return QtWindows::InputMethodEndCompositionEvent; + case WM_IME_COMPOSITION: + return QtWindows::InputMethodCompositionEvent; + case WM_IME_REQUEST: + return QtWindows::InputMethodRequest; + case WM_IME_NOTIFY: + switch (int(wParamIn)) { + case IMN_OPENCANDIDATE: + return QtWindows::InputMethodOpenCandidateWindowEvent; + case IMN_CLOSECANDIDATE: + return QtWindows::InputMethodCloseCandidateWindowEvent; + default: + break; + } default: break; } diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index d62cbfb4c6..c77a111490 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -46,6 +46,7 @@ #include "qwindowsmousehandler.h" #include "qtwindowsglobal.h" #include "qwindowsmime.h" +#include "qwindowsinputcontext.h" #include #include @@ -72,6 +73,7 @@ int QWindowsContext::verboseBackingStore = 0; int QWindowsContext::verboseFonts = 0; int QWindowsContext::verboseGL = 0; int QWindowsContext::verboseOLE = 0; +int QWindowsContext::verboseInputMethods = 0; // Get verbosity of components from "foo:2,bar:3" static inline int componentVerbose(const char *v, const char *keyWord) @@ -240,6 +242,7 @@ QWindowsContext::QWindowsContext(bool isOpenGL) : QWindowsContext::verboseFonts = componentVerbose(v, "fonts"); QWindowsContext::verboseGL = componentVerbose(v, "gl"); QWindowsContext::verboseOLE = componentVerbose(v, "ole"); + QWindowsContext::verboseInputMethods = componentVerbose(v, "im"); } } @@ -603,8 +606,21 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, case QtWindows::DeactivateWindowEvent: QWindowSystemInterface::handleWindowActivated(0); return true; + case QtWindows::InputMethodStartCompositionEvent: + return QWindowsInputContext::instance()->startComposition(hwnd); + case QtWindows::InputMethodCompositionEvent: + return QWindowsInputContext::instance()->composition(hwnd, lParam); + case QtWindows::InputMethodEndCompositionEvent: + return QWindowsInputContext::instance()->endComposition(hwnd); + case QtWindows::InputMethodRequest: + return QWindowsInputContext::instance()->handleIME_Request(wParam, lParam, result); + case QtWindows::InputMethodOpenCandidateWindowEvent: + case QtWindows::InputMethodCloseCandidateWindowEvent: + // TODO: Release/regrab mouse if a popup has mouse grab. + return false; case QtWindows::ClipboardEvent: case QtWindows::DestroyEvent: + case QtWindows::UnknownEvent: return false; default: diff --git a/src/plugins/platforms/windows/qwindowscontext.h b/src/plugins/platforms/windows/qwindowscontext.h index 8985c7f0f9..93662384c0 100644 --- a/src/plugins/platforms/windows/qwindowscontext.h +++ b/src/plugins/platforms/windows/qwindowscontext.h @@ -106,6 +106,7 @@ public: static int verboseFonts; static int verboseGL; static int verboseOLE; + static int verboseInputMethods; explicit QWindowsContext(bool isOpenGL); ~QWindowsContext(); diff --git a/src/plugins/platforms/windows/qwindowsguieventdispatcher.cpp b/src/plugins/platforms/windows/qwindowsguieventdispatcher.cpp index fcfdd4fbdd..fe03a7ffe8 100644 --- a/src/plugins/platforms/windows/qwindowsguieventdispatcher.cpp +++ b/src/plugins/platforms/windows/qwindowsguieventdispatcher.cpp @@ -176,6 +176,7 @@ messageDebugEntries[] = { {WM_MOUSELEAVE, "WM_MOUSELEAVE", true}, {WM_NCHITTEST, "WM_NCHITTEST", false}, {WM_IME_SETCONTEXT, "WM_IME_SETCONTEXT", true}, + {WM_INPUTLANGCHANGE, "WM_INPUTLANGCHANGE", true}, {WM_IME_NOTIFY, "WM_IME_NOTIFY", true}, #if defined(WM_DWMNCRENDERINGCHANGED) {WM_DWMNCRENDERINGCHANGED, "WM_DWMNCRENDERINGCHANGED", true}, @@ -188,7 +189,12 @@ messageDebugEntries[] = { {WM_RENDERFORMAT, "WM_RENDERFORMAT", true}, {WM_RENDERALLFORMATS, "WM_RENDERALLFORMATS", true}, {WM_DESTROYCLIPBOARD, "WM_DESTROYCLIPBOARD", true}, - {WM_CAPTURECHANGED, "WM_CAPTURECHANGED", true} + {WM_CAPTURECHANGED, "WM_CAPTURECHANGED", true}, + {WM_IME_STARTCOMPOSITION, "WM_IME_STARTCOMPOSITION"}, + {WM_IME_COMPOSITION, "WM_IME_COMPOSITION"}, + {WM_IME_ENDCOMPOSITION, "WM_IME_ENDCOMPOSITION"}, + {WM_IME_NOTIFY, "WM_IME_NOTIFY"}, + {WM_IME_REQUEST, "WM_IME_REQUEST"} }; static inline const MessageDebugEntry *messageDebugEntry(UINT msg) diff --git a/src/plugins/platforms/windows/qwindowsinputcontext.cpp b/src/plugins/platforms/windows/qwindowsinputcontext.cpp new file mode 100644 index 0000000000..ec456b1f9c --- /dev/null +++ b/src/plugins/platforms/windows/qwindowsinputcontext.cpp @@ -0,0 +1,598 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwindowsinputcontext.h" +#include "qwindowscontext.h" +#include "qwindowswindow.h" +#include "qwindowsintegration.h" +#include "qwindowsmousehandler.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +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) +{ + const HIMC himc = ImmGetContext(hwnd); + ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0); + ImmReleaseContext(hwnd, himc); +} + +// Query a QObject for an InputMethod-related value +// by sending a QInputMethodQueryEvent. +template + bool inputMethodQuery(QObject *fo, Qt::InputMethodQuery query, T *result) +{ + QInputMethodQueryEvent queryEvent(query); + if (!QCoreApplication::sendEvent(fo, &queryEvent)) + return false; + *result = qvariant_cast(queryEvent.value()); + return true; +} + +/*! + \class QWindowsInputContext + \brief Windows Input context implementation + + Handles input of foreign characters (particularly East Asian) + languages. + + \section1 Testing + + \list + \o Install the East Asian language support and choose Japanese (say). + \o Compile the \a mainwindows/mdi example and open a text window. + \o In the language bar, switch to Japanese and choose the + Input method 'Hiragana'. + \o 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). + + \ingroup qt-lighthouse-win +*/ + +QWindowsInputContext::CompositionContext::CompositionContext() : + hwnd(0), haveCaret(false), position(0), isComposing(false) +{ +} + +QWindowsInputContext::QWindowsInputContext() : + m_WM_MSIME_MOUSE(RegisterWindowMessage(L"MSIMEMouseOperation")), + m_endCompositionRecursionGuard(false) +{ +} + +QWindowsInputContext::~QWindowsInputContext() +{ +} + +/*! + \brief Cancels a composition. +*/ + +void QWindowsInputContext::reset() +{ + QPlatformInputContext::reset(); + if (!m_compositionContext.hwnd) + return; + QObject *fo = focusObject(); + if (QWindowsContext::verboseInputMethods) + qDebug() << __FUNCTION__<< fo; + if (!fo) + return; + if (m_compositionContext.isComposing) { + QInputMethodEvent event; + if (!m_compositionContext.composition.isEmpty()) + event.setCommitString(m_compositionContext.composition); + QCoreApplication::sendEvent(fo, &event); + endContextComposition(); + } + imeNotifyCancelComposition(m_compositionContext.hwnd); + doneContext(); +} + +/*! + \brief Moves the candidate window along with microfocus of the focus object. +*/ + +void QWindowsInputContext::update() +{ + QPlatformInputContext::update(); + if (!m_compositionContext.hwnd) + return; + QObject *fo = focusObject(); + if (!fo) + return; + const HIMC himc = ImmGetContext(m_compositionContext.hwnd); + if (!himc) + return; + // Move candidate list window to the microfocus position. + QRect globalMicroFocusRect; + if (!inputMethodQuery(fo, Qt::ImMicroFocus, &globalMicroFocusRect) || !globalMicroFocusRect.isValid()) + return; + if (QWindowsContext::verboseInputMethods) + qDebug() << __FUNCTION__ << himc << globalMicroFocusRect; + + if (globalMicroFocusRect.isValid()) { + const QRect microFocusRect(QWindowsGeometryHint::mapFromGlobal(m_compositionContext.hwnd, + globalMicroFocusRect.topLeft()), + globalMicroFocusRect.size()); + COMPOSITIONFORM cf; + // ### need X-like inputStyle config settings + cf.dwStyle = CFS_FORCE_POSITION; + cf.ptCurrentPos.x = microFocusRect.x(); + cf.ptCurrentPos.y = microFocusRect.y(); + + CANDIDATEFORM candf; + candf.dwIndex = 0; + candf.dwStyle = CFS_EXCLUDE; + candf.ptCurrentPos.x = microFocusRect.x(); + candf.ptCurrentPos.y = microFocusRect.y() + microFocusRect.height(); + candf.rcArea.left = microFocusRect.x(); + candf.rcArea.top = microFocusRect.y(); + candf.rcArea.right = microFocusRect.x() + microFocusRect.width(); + candf.rcArea.bottom = microFocusRect.y() + microFocusRect.height(); + + if (m_compositionContext.haveCaret) + SetCaretPos(microFocusRect.x(), microFocusRect.y()); + + ImmSetCompositionWindow(himc, &cf); + ImmSetCandidateWindow(himc, &candf); + } + ImmReleaseContext(m_compositionContext.hwnd, himc); +} + +void QWindowsInputContext::mouseHandler(int pos, QMouseEvent *event) +{ + if (event->type() != QEvent::MouseButtonPress || !m_compositionContext.hwnd) + return; + if (QWindowsContext::verboseInputMethods) + qDebug() << __FUNCTION__ << pos << event; + + if (pos < 0 || pos > m_compositionContext.composition.size()) + reset(); + + // Magic code that notifies Japanese IME about the cursor + // position. + const DWORD button = QWindowsMouseHandler::mouseButtonsToKeyState(event->buttons()); + const HIMC himc = ImmGetContext(m_compositionContext.hwnd); + const HWND imeWindow = ImmGetDefaultIMEWnd(m_compositionContext.hwnd); + SendMessage(imeWindow, m_WM_MSIME_MOUSE, MAKELONG(MAKEWORD(button, pos == 0 ? 2 : 1), pos), (LPARAM)himc); + ImmReleaseContext(m_compositionContext.hwnd, himc); +} + +void QWindowsInputContext::setFocusObject(QObject *object) +{ + if (QWindowsContext::verboseInputMethods) + qDebug() << __FUNCTION__ << object; + + QPlatformInputContext::setFocusObject(object); +} + +QWindowsInputContext *QWindowsInputContext::instance() +{ + return static_cast(QWindowsIntegration::instance()->inputContext()); +} + +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, 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.background()); + break; + } + } + return result; +} + +bool QWindowsInputContext::startComposition(HWND hwnd) +{ + const QObject *fo = focusObject(); + if (!fo) + return false; + // This should always match the object. + QWindow *window = QGuiApplication::activeWindow(); + if (!window) + return false; + if (QWindowsContext::verboseInputMethods) + qDebug() << __FUNCTION__ << fo << window; + if (!fo || QWindowsWindow::handleOf(window) != hwnd) + return false; + initContext(hwnd); + 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; + update(); +} + +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 + intermediateMarkup(int position, int compositionLength, + int selStart, int selLength) +{ + QList 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) +{ + QObject *fo = focusObject(); + const int lParam = int(lParamIn); + if (QWindowsContext::verboseInputMethods) + qDebug() << '>' << __FUNCTION__ << fo << debugComposition(lParam) + << " composing=" << m_compositionContext.isComposing; + if (!fo || m_compositionContext.hwnd != hwnd || !lParam) + return false; + const HIMC himc = ImmGetContext(m_compositionContext.hwnd); + if (!himc) + return false; + + QScopedPointer 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, 0, 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)); + endContextComposition(); + } + const bool result = QCoreApplication::sendEvent(fo, event.data()); + if (QWindowsContext::verboseInputMethods) + qDebug() << '<' << __FUNCTION__ << "sending markup=" + << event->attributes().size() + << " commit=" << event->commitString() + << " to " << fo << " returns " << result; + update(); + ImmReleaseContext(m_compositionContext.hwnd, himc); + return result; +} + +bool QWindowsInputContext::endComposition(HWND hwnd) +{ + if (QWindowsContext::verboseInputMethods) + qDebug() << __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; + QObject *fo = focusObject(); + if (!fo) + return false; + + m_endCompositionRecursionGuard = true; + + imeNotifyCancelComposition(m_compositionContext.hwnd); + if (m_compositionContext.isComposing) { + QInputMethodEvent event; + QCoreApplication::sendEvent(fo, &event); + } + doneContext(); + + m_endCompositionRecursionGuard = false; + return true; +} + +void QWindowsInputContext::initContext(HWND hwnd) +{ + if (m_compositionContext.hwnd) + doneContext(); + m_compositionContext.hwnd = hwnd; + // Create a hidden caret which is kept at the microfocus + // position in update(). This is important for some + // Chinese input methods. + m_compositionContext.haveCaret = CreateCaret(hwnd, 0, 1, 1); + HideCaret(hwnd); + update(); + m_compositionContext.isComposing = false; + m_compositionContext.position = 0; +} + +void QWindowsInputContext::doneContext() +{ + if (!m_compositionContext.hwnd) + return; + if (m_compositionContext.haveCaret) + DestroyCaret(); + m_compositionContext.hwnd = 0; + m_compositionContext.composition.clear(); + m_compositionContext.position = 0; + m_compositionContext.isComposing = m_compositionContext.haveCaret = false; +} + +bool QWindowsInputContext::handleIME_Request(WPARAM wParam, + LPARAM lParam, + LRESULT *result) +{ + switch (int(wParam)) { + case IMR_RECONVERTSTRING: { + const int size = reconvertString(reinterpret_cast(lParam)); + if (size < 0) + return false; + *result = size; + return true; + } + break; + case IMR_CONFIRMRECONVERTSTRING: + return true; + default: + break; + } + return false; +} + +/*! + \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 = focusObject(); + if (!fo) + return false; + + QString surroundingText; + if (!inputMethodQuery(fo, Qt::ImSurroundingText, &surroundingText)) + return -1; + const DWORD memSize = sizeof(RECONVERTSTRING) + + (surroundingText.length() + 1) * sizeof(ushort); + if (QWindowsContext::verboseInputMethods) + qDebug() << __FUNCTION__ << " reconv=" << reconv + << " surroundingText=" << surroundingText + << " size=" << memSize; + // If memory is not allocated, return the required size. + if (!reconv) + return surroundingText.isEmpty() ? -1 : int(memSize); + + int pos = 0; + inputMethodQuery(fo, Qt::ImCursorPosition, &pos); + // Find the word in the surrounding text. + QTextBoundaryFinder bounds(QTextBoundaryFinder::Word, surroundingText); + bounds.setPosition(pos); + if (bounds.isAtBoundary()) { + if (QTextBoundaryFinder::EndWord == bounds.boundaryReasons()) + bounds.toPreviousBoundary(); + } else { + bounds.toPreviousBoundary(); + } + const int startPos = bounds.position(); + bounds.toNextBoundary(); + const int endPos = bounds.position(); + if (QWindowsContext::verboseInputMethods) + qDebug() << __FUNCTION__ << " boundary=" << startPos << endPos; + // Select the text, this will be overwritten by following IME events. + QList attributes; + attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, startPos, endPos-startPos, QVariant()); + QInputMethodEvent selectEvent(QString(), attributes); + QCoreApplication::sendEvent(fo, &selectEvent); + + reconv->dwSize = memSize; + reconv->dwVersion = 0; + + reconv->dwStrLen = surroundingText.size(); + reconv->dwStrOffset = sizeof(RECONVERTSTRING); + reconv->dwCompStrLen = endPos - startPos; // TCHAR count. + reconv->dwCompStrOffset = startPos * sizeof(ushort); // byte count. + reconv->dwTargetStrLen = reconv->dwCompStrLen; + reconv->dwTargetStrOffset = reconv->dwCompStrOffset; + ushort *pastReconv = reinterpret_cast(reconv + 1); + qCopy(surroundingText.utf16(), surroundingText.utf16() + surroundingText.size(), + pastReconv); + return memSize; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowsinputcontext.h b/src/plugins/platforms/windows/qwindowsinputcontext.h new file mode 100644 index 0000000000..8ea66774d4 --- /dev/null +++ b/src/plugins/platforms/windows/qwindowsinputcontext.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSINPUTCONTEXT_H +#define QWINDOWSINPUTCONTEXT_H + +#include "qtwindows_additional.h" + +#include + +QT_BEGIN_NAMESPACE + +class QInputMethodEvent; + +class QWindowsInputContext : public QPlatformInputContext +{ + struct CompositionContext + { + CompositionContext(); + + HWND hwnd; + bool haveCaret; + QString composition; + int position; + bool isComposing; + }; +public: + explicit QWindowsInputContext(); + ~QWindowsInputContext(); + + virtual void reset(); + virtual void update(); + + virtual void mouseHandler(int x, QMouseEvent *event); + virtual void setFocusObject(QObject *o); + + static QWindowsInputContext *instance(); + + bool startComposition(HWND hwnd); + bool composition(HWND hwnd, LPARAM lParam); + bool endComposition(HWND hwnd); + + int reconvertString(RECONVERTSTRING *reconv); + + bool handleIME_Request(WPARAM wparam, LPARAM lparam, LRESULT *result); + +private: + void initContext(HWND hwnd); + void doneContext(); + void startContextComposition(); + void endContextComposition(); + + const DWORD m_WM_MSIME_MOUSE; + CompositionContext m_compositionContext; + bool m_endCompositionRecursionGuard; +}; + +QT_END_NAMESPACE + +#endif // QWINDOWSINPUTCONTEXT_H diff --git a/src/plugins/platforms/windows/qwindowsintegration.cpp b/src/plugins/platforms/windows/qwindowsintegration.cpp index e0ba36919e..70c6c0b4db 100644 --- a/src/plugins/platforms/windows/qwindowsintegration.cpp +++ b/src/plugins/platforms/windows/qwindowsintegration.cpp @@ -49,6 +49,7 @@ #include "qwindowsguieventdispatcher.h" #include "qwindowsclipboard.h" #include "qwindowsdrag.h" +#include "qwindowsinputcontext.h" #include #include @@ -136,6 +137,7 @@ struct QWindowsIntegrationPrivate QWindowsDrag m_drag; QWindowsGuiEventDispatcher *m_eventDispatcher; QOpenGLStaticContextPtr m_staticOpenGLContext; + QWindowsInputContext m_inputContext; }; QWindowsIntegrationPrivate::QWindowsIntegrationPrivate(bool openGL) @@ -249,8 +251,7 @@ QPlatformDrag *QWindowsIntegration::drag() const QPlatformInputContext * QWindowsIntegration::inputContext() const { - Q_UNIMPLEMENTED(); - return QPlatformIntegration::inputContext(); + return &d->m_inputContext; } QWindowsIntegration *QWindowsIntegration::instance() diff --git a/src/plugins/platforms/windows/qwindowskeymapper.cpp b/src/plugins/platforms/windows/qwindowskeymapper.cpp index 3ec32b2325..40613ea1f1 100644 --- a/src/plugins/platforms/windows/qwindowskeymapper.cpp +++ b/src/plugins/platforms/windows/qwindowskeymapper.cpp @@ -732,8 +732,7 @@ bool QWindowsKeyMapper::translateKeyEvent(QWindow *widget, HWND hwnd, return true; if (msg.message == WM_KEYDOWN || msg.message == WM_SYSKEYDOWN) updateKeyMap(msg); - translateKeyEventInternal(widget, msg, false); - return true; + return translateKeyEventInternal(widget, msg, false); } bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, const MSG &msg, bool /* grab */) diff --git a/src/plugins/platforms/windows/qwindowsmousehandler.h b/src/plugins/platforms/windows/qwindowsmousehandler.h index 227a66babf..953649102a 100644 --- a/src/plugins/platforms/windows/qwindowsmousehandler.h +++ b/src/plugins/platforms/windows/qwindowsmousehandler.h @@ -66,6 +66,7 @@ public: LRESULT *result); static inline Qt::MouseButtons keyStateToMouseButtons(int); + static inline int mouseButtonsToKeyState(Qt::MouseButtons); QWindow *windowUnderMouse() const { return m_windowUnderMouse.data(); } @@ -93,6 +94,22 @@ Qt::MouseButtons QWindowsMouseHandler::keyStateToMouseButtons(int wParam) return mb; } +int QWindowsMouseHandler::mouseButtonsToKeyState(Qt::MouseButtons mb) +{ + int result = 0; + if (mb & Qt::LeftButton) + result |= MK_LBUTTON; + if (mb & Qt::MiddleButton) + result |= MK_MBUTTON; + if (mb & Qt::RightButton) + result |= MK_RBUTTON; + if (mb & Qt::XButton1) + result |= MK_XBUTTON1; + if (mb & Qt::XButton2) + result |= MK_XBUTTON2; + return result; +} + QT_END_NAMESPACE #endif // QWINDOWSMOUSEHANDLER_H diff --git a/src/plugins/platforms/windows/windows.pro b/src/plugins/platforms/windows/windows.pro index 7e652f7418..e5627ae574 100644 --- a/src/plugins/platforms/windows/windows.pro +++ b/src/plugins/platforms/windows/windows.pro @@ -8,7 +8,7 @@ INCLUDEPATH += ../../../3rdparty/harfbuzz/src QTDIR_build:DESTDIR = $$QT_BUILD_TREE/plugins/platforms # Note: OpenGL32 must precede Gdi32 as it overwrites some functions. -LIBS *= -lOpenGL32 -lGdi32 -lUser32 -lOle32 -lWinspool +LIBS *= -lOpenGL32 -lGdi32 -lUser32 -lOle32 -lWinspool -lImm32 win32-g++: LIBS *= -luuid contains(QT_CONFIG, directwrite) { @@ -38,7 +38,8 @@ SOURCES += \ qwindowsmime.cpp \ qwindowsdrag.cpp \ qwindowscursor.cpp \ - pixmaputils.cpp + pixmaputils.cpp \ + qwindowsinputcontext.cpp HEADERS += \ qwindowsnativeimage.h \ @@ -62,7 +63,8 @@ HEADERS += \ qwindowsinternalmimedata.h \ qwindowscursor.h \ pixmaputils.h \ - array.h + array.h \ + qwindowsinputcontext.h target.path += $$[QT_INSTALL_PLUGINS]/platforms INSTALLS += target -- cgit v1.2.3