Add Input context for Windows.
Change-Id: I20b97e863bf1198b9ad810bb5a25652327f626c9 Reviewed-on: http://codereview.qt.nokia.com/3463 Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com> Reviewed-by: Oliver Wolff <oliver.wolff@nokia.com>
This commit is contained in:
parent
f577774212
commit
1b2dae36d3
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -46,6 +46,7 @@
|
||||
#include "qwindowsmousehandler.h"
|
||||
#include "qtwindowsglobal.h"
|
||||
#include "qwindowsmime.h"
|
||||
#include "qwindowsinputcontext.h"
|
||||
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtGui/QWindowSystemInterface>
|
||||
@ -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:
|
||||
|
@ -106,6 +106,7 @@ public:
|
||||
static int verboseFonts;
|
||||
static int verboseGL;
|
||||
static int verboseOLE;
|
||||
static int verboseInputMethods;
|
||||
|
||||
explicit QWindowsContext(bool isOpenGL);
|
||||
~QWindowsContext();
|
||||
|
@ -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)
|
||||
|
598
src/plugins/platforms/windows/qwindowsinputcontext.cpp
Normal file
598
src/plugins/platforms/windows/qwindowsinputcontext.cpp
Normal file
@ -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 <QtCore/QDebug>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QRect>
|
||||
#include <QtCore/QTextBoundaryFinder>
|
||||
|
||||
#include <QtGui/QInputMethodEvent>
|
||||
#include <QtGui/QTextCharFormat>
|
||||
#include <QtGui/QPalette>
|
||||
#include <QtGui/QGuiApplication>
|
||||
|
||||
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 <class T>
|
||||
bool inputMethodQuery(QObject *fo, Qt::InputMethodQuery query, T *result)
|
||||
{
|
||||
QInputMethodQueryEvent queryEvent(query);
|
||||
if (!QCoreApplication::sendEvent(fo, &queryEvent))
|
||||
return false;
|
||||
*result = qvariant_cast<T>(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<QWindowsInputContext *>(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<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)
|
||||
{
|
||||
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<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, 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<RECONVERTSTRING *>(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<QInputMethodEvent::Attribute> 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<ushort *>(reconv + 1);
|
||||
qCopy(surroundingText.utf16(), surroundingText.utf16() + surroundingText.size(),
|
||||
pastReconv);
|
||||
return memSize;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
98
src/plugins/platforms/windows/qwindowsinputcontext.h
Normal file
98
src/plugins/platforms/windows/qwindowsinputcontext.h
Normal file
@ -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 <QtGui/QPlatformInputContext>
|
||||
|
||||
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
|
@ -49,6 +49,7 @@
|
||||
#include "qwindowsguieventdispatcher.h"
|
||||
#include "qwindowsclipboard.h"
|
||||
#include "qwindowsdrag.h"
|
||||
#include "qwindowsinputcontext.h"
|
||||
|
||||
#include <QtGui/QPlatformNativeInterface>
|
||||
#include <QtGui/QWindowSystemInterface>
|
||||
@ -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()
|
||||
|
@ -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 */)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user