diff options
author | Lars Knoll <lars.knoll@nokia.com> | 2011-06-21 16:00:36 +0200 |
---|---|---|
committer | Lars Knoll <lars.knoll@nokia.com> | 2011-06-22 10:46:12 +0200 |
commit | 4363d836f6a2b245e8e12d1e5eb08db791e95fce (patch) | |
tree | 6b14e2e3b03d42ecd7dad378ba87d85276f19c90 /src/widgets/platforms | |
parent | e1293b49e3adbcff8eade91dc64901d40aeb5099 (diff) |
remove the plugin support for QInputContext
This will be handled through the lighthouse plugin
instead.
Diffstat (limited to 'src/widgets/platforms')
-rw-r--r-- | src/widgets/platforms/mac/qmacinputcontext_mac.cpp | 378 | ||||
-rw-r--r-- | src/widgets/platforms/mac/qmacinputcontext_p.h | 97 | ||||
-rw-r--r-- | src/widgets/platforms/s60/qcoefepinputcontext_p.h | 176 | ||||
-rw-r--r-- | src/widgets/platforms/s60/qcoefepinputcontext_s60.cpp | 1200 | ||||
-rw-r--r-- | src/widgets/platforms/win/qwininputcontext_p.h | 111 | ||||
-rw-r--r-- | src/widgets/platforms/win/qwininputcontext_win.cpp | 847 | ||||
-rw-r--r-- | src/widgets/platforms/x11/qximinputcontext_p.h | 142 | ||||
-rw-r--r-- | src/widgets/platforms/x11/qximinputcontext_x11.cpp | 885 |
8 files changed, 3836 insertions, 0 deletions
diff --git a/src/widgets/platforms/mac/qmacinputcontext_mac.cpp b/src/widgets/platforms/mac/qmacinputcontext_mac.cpp new file mode 100644 index 0000000000..a98cee714b --- /dev/null +++ b/src/widgets/platforms/mac/qmacinputcontext_mac.cpp @@ -0,0 +1,378 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module 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 <qvarlengtharray.h> +#include <qwidget.h> +#include <private/qmacinputcontext_p.h> +#include "qtextformat.h" +#include <qdebug.h> +#include <private/qapplication_p.h> +#include <private/qkeymapper_p.h> + +QT_BEGIN_NAMESPACE + +extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); + +#if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5) +# define typeRefCon typeSInt32 +# define typeByteCount typeSInt32 +#endif + +QMacInputContext::QMacInputContext(QObject *parent) + : QInputContext(parent), composing(false), recursionGuard(false), textDocument(0), + keydownEvent(0) +{ +// createTextDocument(); +} + +QMacInputContext::~QMacInputContext() +{ +#ifndef QT_MAC_USE_COCOA + if(textDocument) + DeleteTSMDocument(textDocument); +#endif +} + +void +QMacInputContext::createTextDocument() +{ +#ifndef QT_MAC_USE_COCOA + if(!textDocument) { + InterfaceTypeList itl = { kUnicodeDocument }; + NewTSMDocument(1, itl, &textDocument, SRefCon(this)); + } +#endif +} + + +QString QMacInputContext::language() +{ + return QString(); +} + + +void QMacInputContext::mouseHandler(int pos, QMouseEvent *e) +{ +#ifndef QT_MAC_USE_COCOA + if(e->type() != QEvent::MouseButtonPress) + return; + + if (!composing) + return; + if (pos < 0 || pos > currentText.length()) + reset(); + // ##### handle mouse position +#else + Q_UNUSED(pos); + Q_UNUSED(e); +#endif +} + +#if !defined QT_MAC_USE_COCOA + +static QTextFormat qt_mac_compose_format() +{ + QTextCharFormat ret; + ret.setFontUnderline(true); + return ret; +} + +void QMacInputContext::reset() +{ + if (recursionGuard) + return; + if (!currentText.isEmpty()){ + QInputMethodEvent e; + e.setCommitString(currentText); + qt_sendSpontaneousEvent(focusWidget(), &e); + currentText = QString(); + } + recursionGuard = true; + createTextDocument(); + composing = false; + ActivateTSMDocument(textDocument); + FixTSMDocument(textDocument); + recursionGuard = false; +} + +bool QMacInputContext::isComposing() const +{ + return composing; +} +#endif + +void QMacInputContext::setFocusWidget(QWidget *w) +{ + createTextDocument(); +#ifndef QT_MAC_USE_COCOA + if(w) + ActivateTSMDocument(textDocument); + else + DeactivateTSMDocument(textDocument); +#endif + QInputContext::setFocusWidget(w); +} + + +#ifndef QT_MAC_USE_COCOA +static EventTypeSpec input_events[] = { + { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent }, + { kEventClassTextInput, kEventTextInputOffsetToPos }, + { kEventClassTextInput, kEventTextInputUpdateActiveInputArea } +}; +static EventHandlerUPP input_proc_handlerUPP = 0; +static EventHandlerRef input_proc_handler = 0; +#endif + +void +QMacInputContext::initialize() +{ +#ifndef QT_MAC_USE_COCOA + if(!input_proc_handler) { + input_proc_handlerUPP = NewEventHandlerUPP(QMacInputContext::globalEventProcessor); + InstallEventHandler(GetApplicationEventTarget(), input_proc_handlerUPP, + GetEventTypeCount(input_events), input_events, + 0, &input_proc_handler); + } +#endif +} + +void +QMacInputContext::cleanup() +{ +#ifndef QT_MAC_USE_COCOA + if(input_proc_handler) { + RemoveEventHandler(input_proc_handler); + input_proc_handler = 0; + } + if(input_proc_handlerUPP) { + DisposeEventHandlerUPP(input_proc_handlerUPP); + input_proc_handlerUPP = 0; + } +#endif +} + +void QMacInputContext::setLastKeydownEvent(EventRef event) +{ + EventRef tmpEvent = keydownEvent; + keydownEvent = event; + if (keydownEvent) + RetainEvent(keydownEvent); + if (tmpEvent) + ReleaseEvent(tmpEvent); +} + +OSStatus +QMacInputContext::globalEventProcessor(EventHandlerCallRef, EventRef event, void *) +{ +#ifndef QT_MAC_USE_COCOA + QScopedLoopLevelCounter loopLevelCounter(QApplicationPrivate::instance()->threadData); + + SRefCon refcon = 0; + GetEventParameter(event, kEventParamTextInputSendRefCon, typeRefCon, 0, + sizeof(refcon), 0, &refcon); + QMacInputContext *context = reinterpret_cast<QMacInputContext*>(refcon); + + bool handled_event=true; + UInt32 ekind = GetEventKind(event), eclass = GetEventClass(event); + switch(eclass) { + case kEventClassTextInput: { + handled_event = false; + QWidget *widget = QApplicationPrivate::focus_widget; + bool canCompose = widget && (!context || widget->inputContext() == context) + && !(widget->inputMethodHints() & Qt::ImhDigitsOnly + || widget->inputMethodHints() & Qt::ImhFormattedNumbersOnly + || widget->inputMethodHints() & Qt::ImhHiddenText); + if(!canCompose) { + handled_event = false; + } else if(ekind == kEventTextInputOffsetToPos) { + if(!widget->testAttribute(Qt::WA_InputMethodEnabled)) { + handled_event = false; + break; + } + + QRect mr(widget->inputMethodQuery(Qt::ImMicroFocus).toRect()); + QPoint mp(widget->mapToGlobal(QPoint(mr.topLeft()))); + Point pt; + pt.h = mp.x(); + pt.v = mp.y() + mr.height(); + SetEventParameter(event, kEventParamTextInputReplyPoint, typeQDPoint, + sizeof(pt), &pt); + handled_event = true; + } else if(ekind == kEventTextInputUpdateActiveInputArea) { + if(!widget->testAttribute(Qt::WA_InputMethodEnabled)) { + handled_event = false; + break; + } + + if (context->recursionGuard) + break; + + ByteCount unilen = 0; + GetEventParameter(event, kEventParamTextInputSendText, typeUnicodeText, + 0, 0, &unilen, 0); + UniChar *unicode = (UniChar*)NewPtr(unilen); + GetEventParameter(event, kEventParamTextInputSendText, typeUnicodeText, + 0, unilen, 0, unicode); + QString text((QChar*)unicode, unilen / sizeof(UniChar)); + DisposePtr((char*)unicode); + + ByteCount fixed_length = 0; + GetEventParameter(event, kEventParamTextInputSendFixLen, typeByteCount, 0, + sizeof(fixed_length), 0, &fixed_length); + if(fixed_length == ULONG_MAX || fixed_length == unilen) { + QInputMethodEvent e; + e.setCommitString(text); + context->currentText = QString(); + qt_sendSpontaneousEvent(context->focusWidget(), &e); + handled_event = true; + context->reset(); + } else { + ByteCount rngSize = 0; + OSStatus err = GetEventParameter(event, kEventParamTextInputSendHiliteRng, typeTextRangeArray, 0, + 0, &rngSize, 0); + QVarLengthArray<TextRangeArray> highlight(rngSize); + if (noErr == err) { + err = GetEventParameter(event, kEventParamTextInputSendHiliteRng, typeTextRangeArray, 0, + rngSize, &rngSize, highlight.data()); + } + context->composing = true; + if(fixed_length > 0) { + const int qFixedLength = fixed_length / sizeof(UniChar); + QList<QInputMethodEvent::Attribute> attrs; + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, + qFixedLength, text.length()-qFixedLength, + qt_mac_compose_format()); + QInputMethodEvent e(text, attrs); + context->currentText = text; + e.setCommitString(text.left(qFixedLength), 0, qFixedLength); + qt_sendSpontaneousEvent(widget, &e); + handled_event = true; + } else { + /* Apple's enums that they have removed from Tiger :( + enum { + kCaretPosition = 1, + kRawText = 2, + kSelectedRawText = 3, + kConvertedText = 4, + kSelectedConvertedText = 5, + kBlockFillText = 6, + kOutlineText = 7, + kSelectedText = 8 + }; + */ +#ifndef kConvertedText +#define kConvertedText 4 +#endif +#ifndef kCaretPosition +#define kCaretPosition 1 +#endif + QList<QInputMethodEvent::Attribute> attrs; + if (!highlight.isEmpty()) { + TextRangeArray *data = highlight.data(); + for (int i = 0; i < data->fNumOfRanges; ++i) { + int start = data->fRange[i].fStart / sizeof(UniChar); + int len = (data->fRange[i].fEnd - data->fRange[i].fStart) / sizeof(UniChar); + if (data->fRange[i].fHiliteStyle == kCaretPosition) { + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, start, 0, QVariant()); + continue; + } + QTextCharFormat format; + format.setFontUnderline(true); + if (data->fRange[i].fHiliteStyle == kConvertedText) + format.setUnderlineColor(Qt::gray); + else + format.setUnderlineColor(Qt::black); + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, start, len, format); + } + } else { + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, + 0, text.length(), qt_mac_compose_format()); + } + context->currentText = text; + QInputMethodEvent e(text, attrs); + qt_sendSpontaneousEvent(widget, &e); + handled_event = true; + } + } +#if 0 + if(!context->composing) + handled_event = false; +#endif + + extern bool qt_mac_eat_unicode_key; //qapplication_mac.cpp + qt_mac_eat_unicode_key = handled_event; + } else if(ekind == kEventTextInputUnicodeForKeyEvent) { + EventRef key_ev = 0; + GetEventParameter(event, kEventParamTextInputSendKeyboardEvent, typeEventRef, 0, + sizeof(key_ev), 0, &key_ev); + QString text; + ByteCount unilen = 0; + if(GetEventParameter(key_ev, kEventParamKeyUnicodes, typeUnicodeText, 0, 0, &unilen, 0) == noErr) { + UniChar *unicode = (UniChar*)NewPtr(unilen); + GetEventParameter(key_ev, kEventParamKeyUnicodes, typeUnicodeText, 0, unilen, 0, unicode); + text = QString((QChar*)unicode, unilen / sizeof(UniChar)); + DisposePtr((char*)unicode); + } + unsigned char chr = 0; + GetEventParameter(key_ev, kEventParamKeyMacCharCodes, typeChar, 0, sizeof(chr), 0, &chr); + if(!chr || chr >= 128 || (text.length() > 0 && (text.length() > 1 || text.at(0) != QLatin1Char(chr)))) + handled_event = !widget->testAttribute(Qt::WA_InputMethodEnabled); + QMacInputContext *context = qobject_cast<QMacInputContext*>(qApp->inputContext()); + if (context && context->lastKeydownEvent()) { + qt_keymapper_private()->translateKeyEvent(widget, 0, context->lastKeydownEvent(), + 0, false); + context->setLastKeydownEvent(0); + } + } + break; } + default: + break; + } + if(!handled_event) //let the event go through + return eventNotHandledErr; +#else + Q_UNUSED(event); +#endif + return noErr; //we eat the event +} + +QT_END_NAMESPACE diff --git a/src/widgets/platforms/mac/qmacinputcontext_p.h b/src/widgets/platforms/mac/qmacinputcontext_p.h new file mode 100644 index 0000000000..4fb3eb57b6 --- /dev/null +++ b/src/widgets/platforms/mac/qmacinputcontext_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module 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 QMACINPUTCONTEXT_P_H +#define QMACINPUTCONTEXT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtWidgets/qinputcontext.h" +#include "private/qt_mac_p.h" + +QT_BEGIN_NAMESPACE + +class Q_WIDGETS_EXPORT QMacInputContext : public QInputContext +{ + Q_OBJECT + //Q_DECLARE_PRIVATE(QMacInputContext) + void createTextDocument(); +public: + explicit QMacInputContext(QObject* parent = 0); + virtual ~QMacInputContext(); + + virtual void setFocusWidget(QWidget *w); + virtual QString identifierName() { return QLatin1String("mac"); } + virtual QString language(); + + virtual void reset(); + + virtual bool isComposing() const; + + static OSStatus globalEventProcessor(EventHandlerCallRef, EventRef, void *); + static void initialize(); + static void cleanup(); + + EventRef lastKeydownEvent() { return keydownEvent; } + void setLastKeydownEvent(EventRef); + +protected: + void mouseHandler(int pos, QMouseEvent *); +private: + bool composing; + bool recursionGuard; + TSMDocumentID textDocument; + QString currentText; + EventRef keydownEvent; +}; + +QT_END_NAMESPACE + +#endif // QMACINPUTCONTEXT_P_H diff --git a/src/widgets/platforms/s60/qcoefepinputcontext_p.h b/src/widgets/platforms/s60/qcoefepinputcontext_p.h new file mode 100644 index 0000000000..148f092ac5 --- /dev/null +++ b/src/widgets/platforms/s60/qcoefepinputcontext_p.h @@ -0,0 +1,176 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module 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 QCOEFEPINPUTCONTEXT_P_H +#define QCOEFEPINPUTCONTEXT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QT_NO_IM + +#include "qinputcontext.h" +#include <qhash.h> +#include <qtimer.h> +#include <private/qcore_symbian_p.h> +#include <private/qt_s60_p.h> + +#include <fepbase.h> +#include <aknedsts.h> + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QCoeFepInputContext : public QInputContext, + public MCoeFepAwareTextEditor, + public MCoeFepAwareTextEditor_Extension1, + public MObjectProvider +{ + Q_OBJECT + +public: + QCoeFepInputContext(QObject *parent = 0); + ~QCoeFepInputContext(); + + QString identifierName() { return QLatin1String("coefep"); } + QString language(); + + void reset(); + void update(); + + bool filterEvent(const QEvent *event); + bool symbianFilterEvent(QWidget *keyWidget, const QSymbianEvent *event); + void mouseHandler( int x, QMouseEvent *event); + bool isComposing() const { return !m_preeditString.isEmpty(); } + + void setFocusWidget(QWidget * w); + void widgetDestroyed(QWidget *w); + + TCoeInputCapabilities inputCapabilities(); + + void resetSplitViewWidget(bool keepInputWidget = false); + void ensureFocusWidgetVisible(QWidget *widget); + +protected: + void timerEvent(QTimerEvent *timerEvent); + +private: + void commitCurrentString(bool cancelFepTransaction); + void updateHints(bool mustUpdateInputCapabilities); + void applyHints(Qt::InputMethodHints hints); + void applyFormat(QList<QInputMethodEvent::Attribute> *attributes); + void queueInputCapabilitiesChanged(); + bool needsInputPanel(); + void commitTemporaryPreeditString(); + bool isWidgetVisible(QWidget *widget, int offset = 0); + +private Q_SLOTS: + void ensureInputCapabilitiesChanged(); + void translateInputWidget(); + + // From MCoeFepAwareTextEditor +public: + void StartFepInlineEditL(const TDesC& aInitialInlineText, TInt aPositionOfInsertionPointInInlineText, + TBool aCursorVisibility, const MFormCustomDraw* aCustomDraw, + MFepInlineTextFormatRetriever& aInlineTextFormatRetriever, + MFepPointerEventHandlerDuringInlineEdit& aPointerEventHandlerDuringInlineEdit); + void UpdateFepInlineTextL(const TDesC& aNewInlineText, TInt aPositionOfInsertionPointInInlineText); + void SetInlineEditingCursorVisibilityL(TBool aCursorVisibility); + void CancelFepInlineEdit(); + TInt DocumentLengthForFep() const; + TInt DocumentMaximumLengthForFep() const; + void SetCursorSelectionForFepL(const TCursorSelection& aCursorSelection); + void GetCursorSelectionForFep(TCursorSelection& aCursorSelection) const; + void GetEditorContentForFep(TDes& aEditorContent, TInt aDocumentPosition, TInt aLengthToRetrieve) const; + void GetFormatForFep(TCharFormat& aFormat, TInt aDocumentPosition) const; + void GetScreenCoordinatesForFepL(TPoint& aLeftSideOfBaseLine, TInt& aHeight, TInt& aAscent, + TInt aDocumentPosition) const; +private: + void DoCommitFepInlineEditL(); + MCoeFepAwareTextEditor_Extension1* Extension1(TBool& aSetToTrue); + void ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateEvent aEventType); + + // From MCoeFepAwareTextEditor_Extension1 +public: + void SetStateTransferingOwnershipL(MCoeFepAwareTextEditor_Extension1::CState* aState, TUid aTypeSafetyUid); + MCoeFepAwareTextEditor_Extension1::CState* State(TUid aTypeSafetyUid); + + // From MObjectProvider +public: + TTypeUid::Ptr MopSupplyObject(TTypeUid id); + MObjectProvider *MopNext(); + +private: + QSymbianControl *m_parent; + CAknEdwinState *m_fepState; + QString m_preeditString; + Qt::InputMethodHints m_lastImHints; + TUint m_textCapabilities; + bool m_inDestruction; + bool m_pendingInputCapabilitiesChanged; + int m_cursorVisibility; + int m_inlinePosition; + MFepInlineTextFormatRetriever *m_formatRetriever; + MFepPointerEventHandlerDuringInlineEdit *m_pointerHandler; + QBasicTimer m_tempPreeditStringTimeout; + bool m_hasTempPreeditString; + + int m_splitViewResizeBy; + Qt::WindowStates m_splitViewPreviousWindowStates; + QRectF m_transformation; + + friend class tst_QInputContext; +}; + +Q_WIDGETS_EXPORT void qt_s60_setPartialScreenInputMode(bool enable); + +QT_END_NAMESPACE + +#endif // QT_NO_IM + +#endif // QCOEFEPINPUTCONTEXT_P_H diff --git a/src/widgets/platforms/s60/qcoefepinputcontext_s60.cpp b/src/widgets/platforms/s60/qcoefepinputcontext_s60.cpp new file mode 100644 index 0000000000..8c215360d8 --- /dev/null +++ b/src/widgets/platforms/s60/qcoefepinputcontext_s60.cpp @@ -0,0 +1,1200 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module 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 QT_NO_IM + +#include "qcoefepinputcontext_p.h" +#include <qapplication.h> +#include <qtextformat.h> +#include <qgraphicsview.h> +#include <qgraphicsscene.h> +#include <qgraphicswidget.h> +#include <qsymbianevent.h> +#include <qlayout.h> +#include <qdesktopwidget.h> +#include <private/qcore_symbian_p.h> + +#include <fepitfr.h> +#include <hal.h> + +#include <limits.h> +// You only find these enumerations on SDK 5 onwards, so we need to provide our own +// to remain compatible with older releases. They won't be called by pre-5.0 SDKs. + +// MAknEdStateObserver::EAknCursorPositionChanged +#define QT_EAknCursorPositionChanged MAknEdStateObserver::EAknEdwinStateEvent(6) +// MAknEdStateObserver::EAknActivatePenInputRequest +#define QT_EAknActivatePenInputRequest MAknEdStateObserver::EAknEdwinStateEvent(7) + +// EAknEditorFlagSelectionVisible is only valid from 3.2 onwards. +// Sym^3 AVKON FEP manager expects that this flag is used for FEP-aware editors +// that support text selection. +#define QT_EAknEditorFlagSelectionVisible 0x100000 + +// EAknEditorFlagEnablePartialScreen is only valid from Sym^3 onwards. +#define QT_EAknEditorFlagEnablePartialScreen 0x200000 + +QT_BEGIN_NAMESPACE + +Q_WIDGETS_EXPORT void qt_s60_setPartialScreenInputMode(bool enable) +{ + S60->partial_keyboard = enable; + + QInputContext *ic = 0; + if (QApplication::focusWidget()) { + ic = QApplication::focusWidget()->inputContext(); + } else if (qApp && qApp->inputContext()) { + ic = qApp->inputContext(); + } + if (ic) + ic->update(); +} + +QCoeFepInputContext::QCoeFepInputContext(QObject *parent) + : QInputContext(parent), + m_fepState(q_check_ptr(new CAknEdwinState)), // CBase derived object needs check on new + m_lastImHints(Qt::ImhNone), + m_textCapabilities(TCoeInputCapabilities::EAllText), + m_inDestruction(false), + m_pendingInputCapabilitiesChanged(false), + m_cursorVisibility(1), + m_inlinePosition(0), + m_formatRetriever(0), + m_pointerHandler(0), + m_hasTempPreeditString(false), + m_splitViewResizeBy(0), + m_splitViewPreviousWindowStates(Qt::WindowNoState) +{ + m_fepState->SetObjectProvider(this); + int defaultFlags = EAknEditorFlagDefault; + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0) { + if (S60->partial_keyboard) { + defaultFlags |= QT_EAknEditorFlagEnablePartialScreen; + } + defaultFlags |= QT_EAknEditorFlagSelectionVisible; + } + m_fepState->SetFlags(defaultFlags); + m_fepState->SetDefaultInputMode( EAknEditorTextInputMode ); + m_fepState->SetPermittedInputModes( EAknEditorAllInputModes ); + m_fepState->SetDefaultCase( EAknEditorTextCase ); + m_fepState->SetPermittedCases( EAknEditorAllCaseModes ); + m_fepState->SetSpecialCharacterTableResourceId(R_AVKON_SPECIAL_CHARACTER_TABLE_DIALOG); + m_fepState->SetNumericKeymap(EAknEditorAlphanumericNumberModeKeymap); +} + +QCoeFepInputContext::~QCoeFepInputContext() +{ + m_inDestruction = true; + + // This is to make sure that the FEP manager "forgets" about us, + // otherwise we may get callbacks even after we're destroyed. + // The call below is essentially equivalent to InputCapabilitiesChanged(), + // but is synchronous, rather than asynchronous. + CCoeEnv::Static()->SyncNotifyFocusObserversOfChangeInFocus(); + + if (m_fepState) + delete m_fepState; +} + +void QCoeFepInputContext::reset() +{ + commitCurrentString(true); +} + +void QCoeFepInputContext::ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateEvent aEventType) +{ + QT_TRAP_THROWING(m_fepState->ReportAknEdStateEventL(aEventType)); +} + +void QCoeFepInputContext::update() +{ + updateHints(false); + + // For pre-5.0 SDKs, we don't do text updates on S60 side. + if (QSysInfo::s60Version() < QSysInfo::SV_S60_5_0) { + return; + } + + // Don't be fooled (as I was) by the name of this enumeration. + // What it really does is tell the virtual keyboard UI that the text has been + // updated and it should be reflected in the internal display of the VK. + ReportAknEdStateEvent(QT_EAknCursorPositionChanged); +} + +void QCoeFepInputContext::setFocusWidget(QWidget *w) +{ + commitCurrentString(true); + + QInputContext::setFocusWidget(w); + + updateHints(true); +} + +void QCoeFepInputContext::widgetDestroyed(QWidget *w) +{ + // Make sure that the input capabilities of whatever new widget got focused are queried. + CCoeControl *ctrl = w->effectiveWinId(); + if (ctrl->IsFocused()) { + queueInputCapabilitiesChanged(); + } +} + +QString QCoeFepInputContext::language() +{ + TLanguage lang = m_fepState->LocalLanguage(); + const QByteArray localeName = qt_symbianLocaleName(lang); + if (!localeName.isEmpty()) { + return QString::fromLatin1(localeName); + } else { + return QString::fromLatin1("C"); + } +} + +bool QCoeFepInputContext::needsInputPanel() +{ + switch (QSysInfo::s60Version()) { + case QSysInfo::SV_S60_3_1: + case QSysInfo::SV_S60_3_2: + // There are no touch phones for pre-5.0 SDKs. + return false; +#ifdef Q_CC_NOKIAX86 + default: + // For emulator we assume that we need an input panel, since we can't + // separate between phone types. + return true; +#else + case QSysInfo::SV_S60_5_0: { + // For SDK == 5.0, we need phone specific detection, since the HAL API + // is no good on most phones. However, all phones at the time of writing use the + // input panel, except N97 in landscape mode, but in this mode it refuses to bring + // up the panel anyway, so we don't have to care. + return true; + } + default: + // For unknown/newer types, we try to use the HAL API. + int keyboardEnabled; + int keyboardType; + int err[2]; + err[0] = HAL::Get(HAL::EKeyboard, keyboardType); + err[1] = HAL::Get(HAL::EKeyboardState, keyboardEnabled); + if (err[0] == KErrNone && err[1] == KErrNone + && keyboardType != 0 && keyboardEnabled) + // Means that we have some sort of keyboard. + return false; + + // Fall back to using the input panel. + return true; +#endif // !Q_CC_NOKIAX86 + } +} + +bool QCoeFepInputContext::filterEvent(const QEvent *event) +{ + // The CloseSoftwareInputPanel event is not handled here, because the VK will automatically + // close when it discovers that the underlying widget does not have input capabilities. + + if (!focusWidget()) + return false; + + switch (event->type()) { + case QEvent::MouseButtonPress: + // Alphanumeric keypad doesn't like it when we click and text is still getting displayed + // It ignores the mouse event, so we need to commit and send a selection event (which will get triggered + // after the commit) + if (!m_preeditString.isEmpty()) { + commitCurrentString(true); + + int pos = focusWidget()->inputMethodQuery(Qt::ImCursorPosition).toInt(); + + QList<QInputMethodEvent::Attribute> selectAttributes; + selectAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, pos, 0, QVariant()); + QInputMethodEvent selectEvent(QLatin1String(""), selectAttributes); + sendEvent(selectEvent); + } + break; + case QEvent::KeyPress: + commitTemporaryPreeditString(); + // fall through intended + case QEvent::KeyRelease: + const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event); + //If proxy exists, always use hints from proxy. + QWidget *proxy = focusWidget()->focusProxy(); + Qt::InputMethodHints currentHints = proxy ? proxy->inputMethodHints() : focusWidget()->inputMethodHints(); + + switch (keyEvent->key()) { + case Qt::Key_F20: + Q_ASSERT(m_lastImHints == currentHints); + if (m_lastImHints & Qt::ImhHiddenText) { + // Special case in Symbian. On editors with secret text, F20 is for some reason + // considered to be a backspace. + QKeyEvent modifiedEvent(keyEvent->type(), Qt::Key_Backspace, keyEvent->modifiers(), + keyEvent->text(), keyEvent->isAutoRepeat(), keyEvent->count()); + QApplication::sendEvent(focusWidget(), &modifiedEvent); + return true; + } + break; + case Qt::Key_Select: + if (!m_preeditString.isEmpty()) { + commitCurrentString(true); + return true; + } + break; + default: + break; + } + + QString widgetText = focusWidget()->inputMethodQuery(Qt::ImSurroundingText).toString(); + bool validLength; + int maxLength = focusWidget()->inputMethodQuery(Qt::ImMaximumTextLength).toInt(&validLength); + if (!keyEvent->text().isEmpty() && validLength + && widgetText.size() + m_preeditString.size() >= maxLength) { + // Don't send key events with string content if the widget is "full". + return true; + } + + if (keyEvent->type() == QEvent::KeyPress + && currentHints & Qt::ImhHiddenText + && !keyEvent->text().isEmpty()) { + // Send some temporary preedit text in order to make text visible for a moment. + m_preeditString = keyEvent->text(); + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent imEvent(m_preeditString, attributes); + sendEvent(imEvent); + m_tempPreeditStringTimeout.start(1000, this); + m_hasTempPreeditString = true; + update(); + return true; + } + break; + } + + if (!needsInputPanel()) + return false; + + if (event->type() == QEvent::RequestSoftwareInputPanel) { + // Notify S60 that we want the virtual keyboard to show up. + QSymbianControl *sControl; + sControl = focusWidget()->effectiveWinId()->MopGetObject(sControl); + Q_ASSERT(sControl); + + // The FEP UI temporarily steals focus when it shows up the first time, causing + // all sorts of weird effects on the focused widgets. Since it will immediately give + // back focus to us, we temporarily disable focus handling until the job's done. + if (sControl) { + sControl->setIgnoreFocusChanged(true); + } + + ensureInputCapabilitiesChanged(); + m_fepState->ReportAknEdStateEventL(MAknEdStateObserver::QT_EAknActivatePenInputRequest); + + if (sControl) { + sControl->setIgnoreFocusChanged(false); + } + return true; + } + + return false; +} + +bool QCoeFepInputContext::symbianFilterEvent(QWidget *keyWidget, const QSymbianEvent *event) +{ + Q_UNUSED(keyWidget); + if (event->type() == QSymbianEvent::CommandEvent) + // A command basically means the same as a button being pushed. With Qt buttons + // that would normally result in a reset of the input method due to the focus change. + // This should also happen for commands. + reset(); + + if (event->type() == QSymbianEvent::WindowServerEvent + && event->windowServerEvent() + && event->windowServerEvent()->Type() == EEventWindowVisibilityChanged + && S60->splitViewLastWidget) { + + QGraphicsView *gv = qobject_cast<QGraphicsView*>(S60->splitViewLastWidget); + const bool alwaysResize = (gv && gv->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff); + + if (alwaysResize) { + TUint visibleFlags = event->windowServerEvent()->VisibilityChanged()->iFlags; + if (visibleFlags & TWsVisibilityChangedEvent::EPartiallyVisible) + ensureFocusWidgetVisible(S60->splitViewLastWidget); + if (visibleFlags & TWsVisibilityChangedEvent::ENotVisible) + resetSplitViewWidget(true); + } + } + + return false; +} + +void QCoeFepInputContext::timerEvent(QTimerEvent *timerEvent) +{ + if (timerEvent->timerId() == m_tempPreeditStringTimeout.timerId()) + commitTemporaryPreeditString(); +} + +void QCoeFepInputContext::commitTemporaryPreeditString() +{ + if (m_tempPreeditStringTimeout.isActive()) + m_tempPreeditStringTimeout.stop(); + + if (!m_hasTempPreeditString) + return; + + commitCurrentString(false); +} + +void QCoeFepInputContext::mouseHandler( int x, QMouseEvent *event) +{ + Q_ASSERT(focusWidget()); + + if (event->type() == QEvent::MouseButtonPress && event->button() == Qt::LeftButton) { + commitCurrentString(true); + int pos = focusWidget()->inputMethodQuery(Qt::ImCursorPosition).toInt(); + + QList<QInputMethodEvent::Attribute> attributes; + attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, pos + x, 0, QVariant()); + QInputMethodEvent event(QLatin1String(""), attributes); + sendEvent(event); + } +} + +TCoeInputCapabilities QCoeFepInputContext::inputCapabilities() +{ + if (m_inDestruction || !focusWidget()) { + return TCoeInputCapabilities(TCoeInputCapabilities::ENone, 0, 0); + } + + return TCoeInputCapabilities(m_textCapabilities, this, 0); +} + +void QCoeFepInputContext::resetSplitViewWidget(bool keepInputWidget) +{ + QGraphicsView *gv = qobject_cast<QGraphicsView*>(S60->splitViewLastWidget); + + if (!gv) { + return; + } + + QSymbianControl *symControl = static_cast<QSymbianControl*>(gv->effectiveWinId()); + symControl->CancelLongTapTimer(); + + const bool alwaysResize = (gv->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff); + QWidget *windowToMove = gv->window(); + + bool userResize = gv->testAttribute(Qt::WA_Resized); + + windowToMove->setUpdatesEnabled(false); + + if (!alwaysResize) { + if (gv->scene()) { + if (gv->scene()->focusItem()) { + // Check if the widget contains cursorPositionChanged signal and disconnect from it. + QByteArray signal = QMetaObject::normalizedSignature(SIGNAL(cursorPositionChanged())); + int index = gv->scene()->focusItem()->toGraphicsObject()->metaObject()->indexOfSignal(signal.right(signal.length() - 1)); + if (index != -1) + disconnect(gv->scene()->focusItem()->toGraphicsObject(), SIGNAL(cursorPositionChanged()), this, SLOT(translateInputWidget())); + } + + QGraphicsItem *rootItem = 0; + foreach (QGraphicsItem *item, gv->scene()->items()) { + if (!item->parentItem()) { + rootItem = item; + break; + } + } + if (rootItem) + rootItem->resetTransform(); + } + } else { + if (m_splitViewResizeBy) + gv->resize(gv->rect().width(), m_splitViewResizeBy); + } + // Resizing might have led to widget losing its original windowstate. + // Restore previous window state. + + if (m_splitViewPreviousWindowStates != windowToMove->windowState()) + windowToMove->setWindowState(m_splitViewPreviousWindowStates); + + windowToMove->setUpdatesEnabled(true); + + gv->setAttribute(Qt::WA_Resized, userResize); //not a user resize + + m_splitViewResizeBy = 0; + if (!keepInputWidget) { + m_splitViewPreviousWindowStates = Qt::WindowNoState; + S60->splitViewLastWidget = 0; + } +} + +// Checks if a given widget is visible in the splitview rect. The offset +// parameter can be used to validate if moving widget upwards or downwards +// by the offset would make a difference for the visibility. + +bool QCoeFepInputContext::isWidgetVisible(QWidget *widget, int offset) +{ + bool visible = false; + if (widget) { + QRect splitViewRect = qt_TRect2QRect(static_cast<CEikAppUi*>(S60->appUi())->ClientRect()); + QWidget *window = QApplication::activeWindow(); + QGraphicsView *gv = qobject_cast<QGraphicsView*>(widget); + if (gv && window) { + if (QGraphicsScene *scene = gv->scene()) { + if (QGraphicsItem *focusItem = scene->focusItem()) { + QPoint cursorPos = window->mapToGlobal(focusItem->cursor().pos()); + cursorPos.setY(cursorPos.y() + offset); + if (splitViewRect.contains(cursorPos)) { + visible = true; + } + } + } + } + } + return visible; +} + +// Ensure that the input widget is visible in the splitview rect. + +void QCoeFepInputContext::ensureFocusWidgetVisible(QWidget *widget) +{ + // Native side opening and closing its virtual keyboard when it changes the keyboard layout, + // has an adverse impact on long tap timer. Cancel the timer when splitview opens to avoid this. + QSymbianControl *symControl = static_cast<QSymbianControl*>(widget->effectiveWinId()); + symControl->CancelLongTapTimer(); + + // Graphicsviews that have vertical scrollbars should always be resized to the splitview area. + // Graphicsviews without scrollbars should be translated. + + QGraphicsView *gv = qobject_cast<QGraphicsView*>(widget); + if (!gv) + return; + + const bool alwaysResize = (gv && gv->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff); + const bool moveWithinVisibleArea = (S60->splitViewLastWidget != 0); + + QWidget *windowToMove = gv ? gv : symControl->widget(); + if (!windowToMove->isWindow()) + windowToMove = windowToMove->window(); + if (!windowToMove) { + return; + } + + // When opening the keyboard (not moving within the splitview area), save the original + // window state. In some cases, ensuring input widget visibility might lead to window + // states getting changed. + + if (!moveWithinVisibleArea) { + // Check if the widget contains cursorPositionChanged signal and connect to it. + QByteArray signal = QMetaObject::normalizedSignature(SIGNAL(cursorPositionChanged())); + if (gv->scene() && gv->scene()->focusItem()) { + int index = gv->scene()->focusItem()->toGraphicsObject()->metaObject()->indexOfSignal(signal.right(signal.length() - 1)); + if (index != -1) + connect(gv->scene()->focusItem()->toGraphicsObject(), SIGNAL(cursorPositionChanged()), this, SLOT(translateInputWidget())); + } + S60->splitViewLastWidget = widget; + m_splitViewPreviousWindowStates = windowToMove->windowState(); + } + + int windowTop = widget->window()->pos().y(); + + const bool userResize = widget->testAttribute(Qt::WA_Resized); + + QRect splitViewRect = qt_TRect2QRect(static_cast<CEikAppUi*>(S60->appUi())->ClientRect()); + + + // When resizing a window widget, it will lose its maximized window state. + // Native applications hide statuspane in splitview state, so lets move to + // fullscreen mode. This makes available area slightly bigger, which helps usability + // and greatly reduces event passing in orientation switch cases, + // as the statuspane size is not changing. + + if (alwaysResize) + windowToMove->setUpdatesEnabled(false); + + if (!(windowToMove->windowState() & Qt::WindowFullScreen)) { + windowToMove->setWindowState( + (windowToMove->windowState() & ~(Qt::WindowMinimized | Qt::WindowFullScreen)) | Qt::WindowFullScreen); + } + + if (alwaysResize) { + if (!moveWithinVisibleArea) { + m_splitViewResizeBy = widget->height(); + windowTop = widget->geometry().top(); + widget->resize(widget->width(), splitViewRect.height() - windowTop); + } + + if (gv->scene()) { + const QRectF microFocusRect = gv->scene()->inputMethodQuery(Qt::ImMicroFocus).toRectF(); + gv->ensureVisible(microFocusRect); + } + } else { + translateInputWidget(); + } + + if (alwaysResize) + windowToMove->setUpdatesEnabled(true); + + widget->setAttribute(Qt::WA_Resized, userResize); //not a user resize +} + +static QTextCharFormat qt_TCharFormat2QTextCharFormat(const TCharFormat &cFormat, bool validStyleColor) +{ + QTextCharFormat qFormat; + + if (validStyleColor) { + QBrush foreground(QColor(cFormat.iFontPresentation.iTextColor.Internal())); + qFormat.setForeground(foreground); + } + + qFormat.setFontStrikeOut(cFormat.iFontPresentation.iStrikethrough == EStrikethroughOn); + qFormat.setFontUnderline(cFormat.iFontPresentation.iUnderline == EUnderlineOn); + + return qFormat; +} + +void QCoeFepInputContext::updateHints(bool mustUpdateInputCapabilities) +{ + QWidget *w = focusWidget(); + if (w) { + QWidget *proxy = w->focusProxy(); + Qt::InputMethodHints hints = proxy ? proxy->inputMethodHints() : w->inputMethodHints(); + + // Since splitview support works like an input method hint, yet it is private flag, + // we need to update its state separately. + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0) { + TInt currentFlags = m_fepState->Flags(); + if (S60->partial_keyboard) + currentFlags |= QT_EAknEditorFlagEnablePartialScreen; + else + currentFlags &= ~QT_EAknEditorFlagEnablePartialScreen; + if (currentFlags != m_fepState->Flags()) + m_fepState->SetFlags(currentFlags); + } + + if (hints != m_lastImHints) { + m_lastImHints = hints; + applyHints(hints); + } else if (!mustUpdateInputCapabilities) { + // Optimization. Return immediately if there was no change. + return; + } + } + queueInputCapabilitiesChanged(); +} + +void QCoeFepInputContext::applyHints(Qt::InputMethodHints hints) +{ + using namespace Qt; + + commitTemporaryPreeditString(); + + const bool anynumbermodes = hints & (ImhDigitsOnly | ImhFormattedNumbersOnly | ImhDialableCharactersOnly); + const bool anytextmodes = hints & (ImhUppercaseOnly | ImhLowercaseOnly | ImhEmailCharactersOnly | ImhUrlCharactersOnly); + const bool numbersOnly = anynumbermodes && !anytextmodes; + const bool noOnlys = !(hints & ImhExclusiveInputMask); + // if alphanumeric input, or if multiple incompatible number modes are selected; + // then make all symbols available in numeric mode too. + const bool needsCharMap= !numbersOnly || ((hints & ImhFormattedNumbersOnly) && (hints & ImhDialableCharactersOnly)); + TInt flags; + Qt::InputMethodHints oldHints = hints; + + // Some sanity checking. Make sure that only one preference is set. + InputMethodHints prefs = ImhPreferNumbers | ImhPreferUppercase | ImhPreferLowercase; + prefs &= hints; + if (prefs != ImhPreferNumbers && prefs != ImhPreferUppercase && prefs != ImhPreferLowercase) { + hints &= ~prefs; + } + if (!noOnlys) { + // Make sure that the preference is within the permitted set. + if (hints & ImhPreferNumbers && !anynumbermodes) { + hints &= ~ImhPreferNumbers; + } else if (hints & ImhPreferUppercase && !(hints & ImhUppercaseOnly)) { + hints &= ~ImhPreferUppercase; + } else if (hints & ImhPreferLowercase && !(hints & ImhLowercaseOnly)) { + hints &= ~ImhPreferLowercase; + } + // If there is no preference, set it to something within the permitted set. + if (!(hints & ImhPreferNumbers || hints & ImhPreferUppercase || hints & ImhPreferLowercase)) { + if (hints & ImhLowercaseOnly) { + hints |= ImhPreferLowercase; + } else if (hints & ImhUppercaseOnly) { + hints |= ImhPreferUppercase; + } else if (numbersOnly) { + hints |= ImhPreferNumbers; + } + } + } + + if (hints & ImhPreferNumbers) { + m_fepState->SetDefaultInputMode(EAknEditorNumericInputMode); + m_fepState->SetCurrentInputMode(EAknEditorNumericInputMode); + } else { + m_fepState->SetDefaultInputMode(EAknEditorTextInputMode); + m_fepState->SetCurrentInputMode(EAknEditorTextInputMode); + } + flags = 0; + if (noOnlys || (anynumbermodes && anytextmodes)) { + flags = EAknEditorAllInputModes; + } + else if (anynumbermodes) { + flags |= EAknEditorNumericInputMode; + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0 + && ((hints & ImhFormattedNumbersOnly) || (hints & ImhDialableCharactersOnly))) { + //workaround - the * key does not launch the symbols menu, making it impossible to use these modes unless text mode is enabled. + flags |= EAknEditorTextInputMode; + } + } + else if (anytextmodes) { + flags |= EAknEditorTextInputMode; + } + else { + flags = EAknEditorAllInputModes; + } + m_fepState->SetPermittedInputModes(flags); + ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateInputModeUpdate); + + if (hints & ImhPreferLowercase) { + m_fepState->SetDefaultCase(EAknEditorLowerCase); + m_fepState->SetCurrentCase(EAknEditorLowerCase); + } else if (hints & ImhPreferUppercase) { + m_fepState->SetDefaultCase(EAknEditorUpperCase); + m_fepState->SetCurrentCase(EAknEditorUpperCase); + } else if (hints & ImhNoAutoUppercase) { + m_fepState->SetDefaultCase(EAknEditorLowerCase); + m_fepState->SetCurrentCase(EAknEditorLowerCase); + } else { + m_fepState->SetDefaultCase(EAknEditorTextCase); + m_fepState->SetCurrentCase(EAknEditorTextCase); + } + flags = 0; + if (hints & ImhUppercaseOnly) { + flags |= EAknEditorUpperCase; + } + if (hints & ImhLowercaseOnly) { + flags |= EAknEditorLowerCase; + } + if (flags == 0) { + flags = EAknEditorAllCaseModes; + if (hints & ImhNoAutoUppercase) { + flags &= ~EAknEditorTextCase; + } + } + m_fepState->SetPermittedCases(flags); + ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateCaseModeUpdate); + + flags = 0; + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0) { + if (S60->partial_keyboard) + flags |= QT_EAknEditorFlagEnablePartialScreen; + flags |= QT_EAknEditorFlagSelectionVisible; + } + if (hints & ImhUppercaseOnly && !(hints & ImhLowercaseOnly) + || hints & ImhLowercaseOnly && !(hints & ImhUppercaseOnly)) { + flags |= EAknEditorFlagFixedCase; + } + // Using T9 and hidden text together may actually crash the FEP, so check for hidden text too. + if (hints & ImhNoPredictiveText || hints & ImhHiddenText) { + flags |= EAknEditorFlagNoT9; + } + if (needsCharMap) + flags |= EAknEditorFlagUseSCTNumericCharmap; + m_fepState->SetFlags(flags); + ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateFlagsUpdate); + + if (hints & ImhDialableCharactersOnly) { + // This is first, because if (ImhDialableCharactersOnly | ImhFormattedNumbersOnly) + // is specified, this one is more natural (# key enters a #) + flags = EAknEditorStandardNumberModeKeymap; + } else if (hints & ImhFormattedNumbersOnly) { + // # key enters decimal point + flags = EAknEditorCalculatorNumberModeKeymap; + } else if (hints & ImhDigitsOnly) { + // This is last, because it is most restrictive (# key is inactive) + flags = EAknEditorPlainNumberModeKeymap; + } else { + flags = EAknEditorStandardNumberModeKeymap; + } + m_fepState->SetNumericKeymap(static_cast<TAknEditorNumericKeymap>(flags)); + + if (hints & ImhUrlCharactersOnly) { + // URL characters is everything except space, so a superset of the other restrictions + m_fepState->SetSpecialCharacterTableResourceId(R_AVKON_URL_SPECIAL_CHARACTER_TABLE_DIALOG); + } else if (hints & ImhEmailCharactersOnly) { + m_fepState->SetSpecialCharacterTableResourceId(R_AVKON_EMAIL_ADDR_SPECIAL_CHARACTER_TABLE_DIALOG); + } else if (needsCharMap) { + m_fepState->SetSpecialCharacterTableResourceId(R_AVKON_SPECIAL_CHARACTER_TABLE_DIALOG); + } else if ((hints & ImhFormattedNumbersOnly) || (hints & ImhDialableCharactersOnly)) { + m_fepState->SetSpecialCharacterTableResourceId(R_AVKON_SPECIAL_CHARACTER_TABLE_DIALOG); + } else { + m_fepState->SetSpecialCharacterTableResourceId(0); + } + + if (hints & ImhHiddenText) { + m_textCapabilities = TCoeInputCapabilities::EAllText | TCoeInputCapabilities::ESecretText; + } else { + m_textCapabilities = TCoeInputCapabilities::EAllText; + } +} + +void QCoeFepInputContext::applyFormat(QList<QInputMethodEvent::Attribute> *attributes) +{ + TCharFormat cFormat; + QColor styleTextColor; + if (QWidget *focused = focusWidget()) { + QGraphicsView *gv = qobject_cast<QGraphicsView*>(focused); + if (!gv) // could be either the QGV or its viewport that has focus + gv = qobject_cast<QGraphicsView*>(focused->parentWidget()); + if (gv) { + if (QGraphicsScene *scene = gv->scene()) { + if (QGraphicsItem *focusItem = scene->focusItem()) { + if (focusItem->isWidget()) { + styleTextColor = static_cast<QGraphicsWidget*>(focusItem)->palette().text().color(); + } + } + } + } else { + styleTextColor = focused->palette().text().color(); + } + } else { + styleTextColor = QApplication::palette("QLineEdit").text().color(); + } + + if (styleTextColor.isValid()) { + const TLogicalRgb fontColor(TRgb(styleTextColor.red(), styleTextColor.green(), styleTextColor.blue(), styleTextColor.alpha())); + cFormat.iFontPresentation.iTextColor = fontColor; + } + + TInt numChars = 0; + TInt charPos = 0; + int oldSize = attributes->size(); + while (m_formatRetriever) { + m_formatRetriever->GetFormatOfFepInlineText(cFormat, numChars, charPos); + if (numChars <= 0) { + // This shouldn't happen according to S60 docs, but apparently does sometimes. + break; + } + attributes->append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, + charPos, + numChars, + QVariant(qt_TCharFormat2QTextCharFormat(cFormat, styleTextColor.isValid())))); + charPos += numChars; + if (charPos >= m_preeditString.size()) { + break; + } + } + + if (attributes->size() == oldSize) { + // S60 didn't provide any format, so let's give our own instead. + attributes->append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, + 0, + m_preeditString.size(), + standardFormat(PreeditFormat))); + } +} + +void QCoeFepInputContext::queueInputCapabilitiesChanged() +{ + if (m_pendingInputCapabilitiesChanged) + return; + + // Call ensureInputCapabilitiesChanged asynchronously. This is done to improve performance + // by not updating input capabilities too often. The reason we don't call the Symbian + // asynchronous version of InputCapabilitiesChanged is because we need to ensure that it + // is synchronous in some specific cases. Those will call ensureInputCapabilitesChanged. + QMetaObject::invokeMethod(this, "ensureInputCapabilitiesChanged", Qt::QueuedConnection); + m_pendingInputCapabilitiesChanged = true; +} + +void QCoeFepInputContext::ensureInputCapabilitiesChanged() +{ + if (!m_pendingInputCapabilitiesChanged) + return; + + // The call below is essentially equivalent to InputCapabilitiesChanged(), + // but is synchronous, rather than asynchronous. + CCoeEnv::Static()->SyncNotifyFocusObserversOfChangeInFocus(); + m_pendingInputCapabilitiesChanged = false; +} + +void QCoeFepInputContext::translateInputWidget() +{ + QGraphicsView *gv = qobject_cast<QGraphicsView *>(S60->splitViewLastWidget); + QRect splitViewRect = qt_TRect2QRect(static_cast<CEikAppUi*>(S60->appUi())->ClientRect()); + + QRectF cursor = gv->scene()->inputMethodQuery(Qt::ImMicroFocus).toRectF(); + QPolygon cursorP = gv->mapFromScene(cursor); + QRectF vkbRect = QRectF(splitViewRect.bottomLeft(), qApp->desktop()->rect().bottomRight()); + if (cursor.isEmpty() || vkbRect.isEmpty()) + return; + + // Fetch root item (i.e. graphicsitem with no parent) + QGraphicsItem *rootItem = 0; + foreach (QGraphicsItem *item, gv->scene()->items()) { + if (!item->parentItem()) { + rootItem = item; + break; + } + } + if (!rootItem) + return; + + m_transformation = (rootItem->transform().isTranslating()) ? QRectF(0,0, gv->width(), rootItem->transform().dy()) : QRectF(); + + // Do nothing if the cursor is visible in the splitview area. + if (splitViewRect.contains(cursorP.boundingRect())) + return; + + // New Y position should be ideally at the center of the splitview area. + // If that would expose unpainted canvas, limit the tranformation to the visible scene bottom. + + const qreal maxY = gv->sceneRect().bottom() - splitViewRect.bottom() + m_transformation.height(); + qreal dy = -(qMin(maxY, (cursor.bottom() - vkbRect.top() / 2))); + + // Do not allow transform above screen top. + if (m_transformation.height() + dy > 0) + return; + + rootItem->setTransform(QTransform::fromTranslate(0, dy), true); +} + +void QCoeFepInputContext::StartFepInlineEditL(const TDesC& aInitialInlineText, + TInt aPositionOfInsertionPointInInlineText, TBool aCursorVisibility, const MFormCustomDraw* /*aCustomDraw*/, + MFepInlineTextFormatRetriever& aInlineTextFormatRetriever, + MFepPointerEventHandlerDuringInlineEdit& aPointerEventHandlerDuringInlineEdit) +{ + QWidget *w = focusWidget(); + if (!w) + return; + + commitTemporaryPreeditString(); + + QList<QInputMethodEvent::Attribute> attributes; + + m_cursorVisibility = aCursorVisibility ? 1 : 0; + m_inlinePosition = aPositionOfInsertionPointInInlineText; + m_preeditString = qt_TDesC2QString(aInitialInlineText); + + m_formatRetriever = &aInlineTextFormatRetriever; + m_pointerHandler = &aPointerEventHandlerDuringInlineEdit; + + // With T9 aInitialInlineText is typically empty when StartFepInlineEditL is called, + // but FEP requires that selected text is always removed at StartFepInlineEditL. + // Let's remove the selected text if aInitialInlineText is empty and there is selected text + if (m_preeditString.isEmpty()) { + int anchor = w->inputMethodQuery(Qt::ImAnchorPosition).toInt(); + int cursorPos = w->inputMethodQuery(Qt::ImCursorPosition).toInt(); + int replacementLength = qAbs(cursorPos-anchor); + if (replacementLength > 0) { + int replacementStart = cursorPos < anchor ? 0 : -replacementLength; + QList<QInputMethodEvent::Attribute> clearSelectionAttributes; + QInputMethodEvent clearSelectionEvent(QLatin1String(""), clearSelectionAttributes); + clearSelectionEvent.setCommitString(QLatin1String(""), replacementStart, replacementLength); + sendEvent(clearSelectionEvent); + } + } + + applyFormat(&attributes); + + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, + m_inlinePosition, + m_cursorVisibility, + QVariant())); + QInputMethodEvent event(m_preeditString, attributes); + sendEvent(event); +} + +void QCoeFepInputContext::UpdateFepInlineTextL(const TDesC& aNewInlineText, + TInt aPositionOfInsertionPointInInlineText) +{ + QWidget *w = focusWidget(); + if (!w) + return; + + commitTemporaryPreeditString(); + + m_inlinePosition = aPositionOfInsertionPointInInlineText; + + QList<QInputMethodEvent::Attribute> attributes; + applyFormat(&attributes); + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, + m_inlinePosition, + m_cursorVisibility, + QVariant())); + QString newPreeditString = qt_TDesC2QString(aNewInlineText); + QInputMethodEvent event(newPreeditString, attributes); + if (newPreeditString.isEmpty() && m_preeditString.isEmpty()) { + // In Symbian world this means "erase last character". + event.setCommitString(QLatin1String(""), -1, 1); + } + m_preeditString = newPreeditString; + sendEvent(event); +} + +void QCoeFepInputContext::SetInlineEditingCursorVisibilityL(TBool aCursorVisibility) +{ + QWidget *w = focusWidget(); + if (!w) + return; + + m_cursorVisibility = aCursorVisibility ? 1 : 0; + + QList<QInputMethodEvent::Attribute> attributes; + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, + m_inlinePosition, + m_cursorVisibility, + QVariant())); + QInputMethodEvent event(m_preeditString, attributes); + sendEvent(event); +} + +void QCoeFepInputContext::CancelFepInlineEdit() +{ + // We are not supposed to ever have a tempPreeditString and a real preedit string + // from S60 at the same time, so it should be safe to rely on this test to determine + // whether we should honor S60's request to clear the text or not. + if (m_hasTempPreeditString) + return; + + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event(QLatin1String(""), attributes); + event.setCommitString(QLatin1String(""), 0, 0); + m_preeditString.clear(); + m_inlinePosition = 0; + sendEvent(event); +} + +TInt QCoeFepInputContext::DocumentLengthForFep() const +{ + QWidget *w = focusWidget(); + if (!w) + return 0; + + QVariant variant = w->inputMethodQuery(Qt::ImSurroundingText); + return variant.value<QString>().size() + m_preeditString.size(); +} + +TInt QCoeFepInputContext::DocumentMaximumLengthForFep() const +{ + QWidget *w = focusWidget(); + if (!w) + return 0; + + QVariant variant = w->inputMethodQuery(Qt::ImMaximumTextLength); + int size; + if (variant.isValid()) { + size = variant.toInt(); + } else { + size = INT_MAX; // Sensible default for S60. + } + return size; +} + +void QCoeFepInputContext::SetCursorSelectionForFepL(const TCursorSelection& aCursorSelection) +{ + QWidget *w = focusWidget(); + if (!w) + return; + + commitTemporaryPreeditString(); + + int pos = aCursorSelection.iAnchorPos; + int length = aCursorSelection.iCursorPos - pos; + + QList<QInputMethodEvent::Attribute> attributes; + attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, pos, length, QVariant()); + QInputMethodEvent event(m_preeditString, attributes); + sendEvent(event); +} + +void QCoeFepInputContext::GetCursorSelectionForFep(TCursorSelection& aCursorSelection) const +{ + QWidget *w = focusWidget(); + if (!w) { + aCursorSelection.SetSelection(0,0); + return; + } + + int cursor = w->inputMethodQuery(Qt::ImCursorPosition).toInt() + m_preeditString.size(); + int anchor = w->inputMethodQuery(Qt::ImAnchorPosition).toInt() + m_preeditString.size(); + QString text = w->inputMethodQuery(Qt::ImSurroundingText).value<QString>(); + int combinedSize = text.size() + m_preeditString.size(); + if (combinedSize < anchor || combinedSize < cursor) { + // ### TODO! FIXME! QTBUG-5050 + // This is a hack to prevent crashing in 4.6 with QLineEdits that use input masks. + // The root problem is that cursor position is relative to displayed text instead of the + // actual text we get. + // + // To properly fix this we would need to know the displayText of QLineEdits instead + // of just the text, which on itself should be a trivial change. The difficulties start + // when we need to commit the changes back to the QLineEdit, which would have to be somehow + // able to handle displayText, too. + // + // Until properly fixed, the cursor and anchor positions will not reflect correct positions + // for masked QLineEdits, unless all the masked positions are filled in order so that + // cursor position relative to the displayed text matches position relative to actual text. + aCursorSelection.iAnchorPos = combinedSize; + aCursorSelection.iCursorPos = combinedSize; + } else { + aCursorSelection.iAnchorPos = anchor; + aCursorSelection.iCursorPos = cursor; + } +} + +void QCoeFepInputContext::GetEditorContentForFep(TDes& aEditorContent, TInt aDocumentPosition, + TInt aLengthToRetrieve) const +{ + QWidget *w = focusWidget(); + if (!w) { + aEditorContent.FillZ(aLengthToRetrieve); + return; + } + + QString text = w->inputMethodQuery(Qt::ImSurroundingText).value<QString>(); + // FEP expects the preedit string to be part of the editor content, so let's mix it in. + int cursor = w->inputMethodQuery(Qt::ImCursorPosition).toInt(); + text.insert(cursor, m_preeditString); + aEditorContent.Copy(qt_QString2TPtrC(text.mid(aDocumentPosition, aLengthToRetrieve))); +} + +void QCoeFepInputContext::GetFormatForFep(TCharFormat& aFormat, TInt /* aDocumentPosition */) const +{ + QWidget *w = focusWidget(); + if (!w) { + aFormat = TCharFormat(); + return; + } + + QFont font = w->inputMethodQuery(Qt::ImFont).value<QFont>(); + QFontMetrics metrics(font); + //QString name = font.rawName(); + QString name = font.defaultFamily(); // TODO! FIXME! Should be the above. + QHBufC hBufC(name); + aFormat = TCharFormat(hBufC->Des(), metrics.height()); +} + +void QCoeFepInputContext::GetScreenCoordinatesForFepL(TPoint& aLeftSideOfBaseLine, TInt& aHeight, + TInt& aAscent, TInt /* aDocumentPosition */) const +{ + QWidget *w = focusWidget(); + if (!w) { + aLeftSideOfBaseLine = TPoint(0,0); + aHeight = 0; + aAscent = 0; + return; + } + + QRect rect = w->inputMethodQuery(Qt::ImMicroFocus).value<QRect>(); + aLeftSideOfBaseLine.iX = rect.left(); + aLeftSideOfBaseLine.iY = rect.bottom(); + + QFont font = w->inputMethodQuery(Qt::ImFont).value<QFont>(); + QFontMetrics metrics(font); + aHeight = metrics.height(); + aAscent = metrics.ascent(); +} + +void QCoeFepInputContext::DoCommitFepInlineEditL() +{ + commitCurrentString(false); + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0) + ReportAknEdStateEvent(QT_EAknCursorPositionChanged); + +} + +void QCoeFepInputContext::commitCurrentString(bool cancelFepTransaction) +{ + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event(QLatin1String(""), attributes); + event.setCommitString(m_preeditString, 0, 0); + m_preeditString.clear(); + m_inlinePosition = 0; + sendEvent(event); + + m_hasTempPreeditString = false; + + if (cancelFepTransaction) { + CCoeFep* fep = CCoeEnv::Static()->Fep(); + if (fep) + fep->CancelTransaction(); + } +} + +MCoeFepAwareTextEditor_Extension1* QCoeFepInputContext::Extension1(TBool& aSetToTrue) +{ + aSetToTrue = ETrue; + return this; +} + +void QCoeFepInputContext::SetStateTransferingOwnershipL(MCoeFepAwareTextEditor_Extension1::CState* aState, + TUid /*aTypeSafetyUid*/) +{ + // Note: The S60 docs are wrong! See the State() function. + if (m_fepState) + delete m_fepState; + m_fepState = static_cast<CAknEdwinState *>(aState); +} + +MCoeFepAwareTextEditor_Extension1::CState* QCoeFepInputContext::State(TUid /*aTypeSafetyUid*/) +{ + // Note: The S60 docs are horribly wrong when describing the + // SetStateTransferingOwnershipL function and this function. They say that the former + // sets a CState object identified by the TUid, and the latter retrieves it. + // In reality, the CState is expected to always be a CAknEdwinState (even if it was not + // previously set), and the TUid is ignored. All in all, there is a single CAknEdwinState + // per QCoeFepInputContext, which should be deleted if the SetStateTransferingOwnershipL + // function is used to set a new one. + return m_fepState; +} + +TTypeUid::Ptr QCoeFepInputContext::MopSupplyObject(TTypeUid /*id*/) +{ + return TTypeUid::Null(); +} + +MObjectProvider *QCoeFepInputContext::MopNext() +{ + QWidget *w = focusWidget(); + if (w) + return w->effectiveWinId(); + return 0; +} + +QT_END_NAMESPACE + +#endif // QT_NO_IM diff --git a/src/widgets/platforms/win/qwininputcontext_p.h b/src/widgets/platforms/win/qwininputcontext_p.h new file mode 100644 index 0000000000..c0a6ac6b6f --- /dev/null +++ b/src/widgets/platforms/win/qwininputcontext_p.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module 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 QWININPUTCONTEXT_P_H +#define QWININPUTCONTEXT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qinputcontext.cpp. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtWidgets/qinputcontext.h" +#include "QtCore/qt_windows.h" + +#if !defined(IMR_RECONVERTSTRING) +typedef struct tagRECONVERTSTRING { + DWORD dwSize; + DWORD dwVersion; + DWORD dwStrLen; + DWORD dwStrOffset; + DWORD dwCompStrLen; + DWORD dwCompStrOffset; + DWORD dwTargetStrLen; + DWORD dwTargetStrOffset; +} RECONVERTSTRING, *PRECONVERTSTRING; +#endif + +QT_BEGIN_NAMESPACE + +class QWinInputContext : public QInputContext +{ + Q_OBJECT +public: + explicit QWinInputContext(QObject* parent = 0); + virtual ~QWinInputContext(); + + virtual QString identifierName() { return QLatin1String("win"); } + virtual QString language(); + + virtual void reset(); + virtual void update(); + + virtual void mouseHandler(int x, QMouseEvent *event); + virtual bool isComposing() const; + + virtual void setFocusWidget(QWidget *w); + + bool startComposition(); + bool endComposition(); + bool composition(LPARAM lparam); + int reconvertString(RECONVERTSTRING *reconv); + + static void TranslateMessage(const MSG *msg); + static LRESULT DefWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + + static void updateImeStatus(QWidget *w, bool hasFocus); + static void enablePopupChild(QWidget *w, bool e); + static void enable(QWidget *w, bool e); + +private: + void init(); + bool recursionGuard; +}; + +QT_END_NAMESPACE + +#endif // QWININPUTCONTEXT_P_H diff --git a/src/widgets/platforms/win/qwininputcontext_win.cpp b/src/widgets/platforms/win/qwininputcontext_win.cpp new file mode 100644 index 0000000000..9ec9942af8 --- /dev/null +++ b/src/widgets/platforms/win/qwininputcontext_win.cpp @@ -0,0 +1,847 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module 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 "qwininputcontext_p.h" +#include "qinputcontext_p.h" + +#include "qfont.h" +#include "qwidget.h" +#include "qapplication.h" +#include "qevent.h" +#include "qtextformat.h" +#include "qtextboundaryfinder.h" + +//#define Q_IME_DEBUG + +#ifdef Q_IME_DEBUG +#include "qdebug.h" +#endif + +#if defined(Q_WS_WINCE) +extern void qt_wince_show_SIP(bool show); // defined in qguifunctions_wince.cpp +#endif + +QT_BEGIN_NAMESPACE + +extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); + + +DEFINE_GUID(IID_IActiveIMMApp, +0x08c0e040, 0x62d1, 0x11d1, 0x93, 0x26, 0x0, 0x60, 0xb0, 0x67, 0xb8, 0x6e); + + + +DEFINE_GUID(CLSID_CActiveIMM, +0x4955DD33, 0xB159, 0x11d0, 0x8F, 0xCF, 0x0, 0xAA, 0x00, 0x6B, 0xCC, 0x59); + + + +DEFINE_GUID(IID_IActiveIMMMessagePumpOwner, +0xb5cf2cfa, 0x8aeb, 0x11d1, 0x93, 0x64, 0x0, 0x60, 0xb0, 0x67, 0xb8, 0x6e); + + + +interface IEnumRegisterWordW; +interface IEnumInputContext; + + +bool qt_sendSpontaneousEvent(QObject*, QEvent*); + + +#define IFMETHOD HRESULT STDMETHODCALLTYPE + +interface IActiveIMMApp : public IUnknown +{ +public: + virtual IFMETHOD AssociateContext(HWND hWnd, HIMC hIME, HIMC __RPC_FAR *phPrev) = 0; + virtual IFMETHOD dummy_ConfigureIMEA() = 0; + virtual IFMETHOD ConfigureIMEW(HKL hKL, HWND hWnd, DWORD dwMode, REGISTERWORDW __RPC_FAR *pData) = 0; + virtual IFMETHOD CreateContext(HIMC __RPC_FAR *phIMC) = 0; + virtual IFMETHOD DestroyContext(HIMC hIME) = 0; + virtual IFMETHOD dummy_EnumRegisterWordA() = 0; + virtual IFMETHOD EnumRegisterWordW(HKL hKL, LPWSTR szReading, DWORD dwStyle, LPWSTR szRegister, LPVOID pData, + IEnumRegisterWordW __RPC_FAR *__RPC_FAR *pEnum) = 0; + virtual IFMETHOD dummy_EscapeA() = 0; + virtual IFMETHOD EscapeW(HKL hKL, HIMC hIMC, UINT uEscape, LPVOID pData, LRESULT __RPC_FAR *plResult) = 0; + virtual IFMETHOD dummy_GetCandidateListA() = 0; + virtual IFMETHOD GetCandidateListW(HIMC hIMC, DWORD dwIndex, UINT uBufLen, CANDIDATELIST __RPC_FAR *pCandList, + UINT __RPC_FAR *puCopied) = 0; + virtual IFMETHOD dummy_GetCandidateListCountA() = 0; + virtual IFMETHOD GetCandidateListCountW(HIMC hIMC, DWORD __RPC_FAR *pdwListSize, DWORD __RPC_FAR *pdwBufLen) = 0; + virtual IFMETHOD GetCandidateWindow(HIMC hIMC, DWORD dwIndex, CANDIDATEFORM __RPC_FAR *pCandidate) = 0; + virtual IFMETHOD dummy_GetCompositionFontA() = 0; + virtual IFMETHOD GetCompositionFontW(HIMC hIMC, LOGFONTW __RPC_FAR *plf) = 0; + virtual IFMETHOD dummy_GetCompositionStringA() = 0; + virtual IFMETHOD GetCompositionStringW(HIMC hIMC, DWORD dwIndex, DWORD dwBufLen, LONG __RPC_FAR *plCopied, LPVOID pBuf) = 0; + virtual IFMETHOD GetCompositionWindow(HIMC hIMC, COMPOSITIONFORM __RPC_FAR *pCompForm) = 0; + virtual IFMETHOD GetContext(HWND hWnd, HIMC __RPC_FAR *phIMC) = 0; + virtual IFMETHOD dummy_GetConversionListA() = 0; + virtual IFMETHOD GetConversionListW(HKL hKL, HIMC hIMC, LPWSTR pSrc, UINT uBufLen, UINT uFlag, + CANDIDATELIST __RPC_FAR *pDst, UINT __RPC_FAR *puCopied) = 0; + virtual IFMETHOD GetConversionStatus(HIMC hIMC, DWORD __RPC_FAR *pfdwConversion, DWORD __RPC_FAR *pfdwSentence) = 0; + virtual IFMETHOD GetDefaultIMEWnd(HWND hWnd, HWND __RPC_FAR *phDefWnd) = 0; + virtual IFMETHOD dummy_GetDescriptionA() = 0; + virtual IFMETHOD GetDescriptionW(HKL hKL, UINT uBufLen, LPWSTR szDescription, UINT __RPC_FAR *puCopied) = 0; + virtual IFMETHOD dummy_GetGuideLineA() = 0; + virtual IFMETHOD GetGuideLineW(HIMC hIMC, DWORD dwIndex, DWORD dwBufLen, LPWSTR pBuf, DWORD __RPC_FAR *pdwResult) = 0; + virtual IFMETHOD dummy_GetIMEFileNameA() = 0; + virtual IFMETHOD GetIMEFileNameW(HKL hKL, UINT uBufLen, LPWSTR szFileName, UINT __RPC_FAR *puCopied) = 0; + virtual IFMETHOD GetOpenStatus(HIMC hIMC) = 0; + virtual IFMETHOD GetProperty(HKL hKL, DWORD fdwIndex, DWORD __RPC_FAR *pdwProperty) = 0; + virtual IFMETHOD dummy_GetRegisterWordStyleA() = 0; + virtual IFMETHOD GetRegisterWordStyleW(HKL hKL, UINT nItem, STYLEBUFW __RPC_FAR *pStyleBuf, UINT __RPC_FAR *puCopied) = 0; + virtual IFMETHOD GetStatusWindowPos(HIMC hIMC, POINT __RPC_FAR *pptPos) = 0; + virtual IFMETHOD GetVirtualKey(HWND hWnd, UINT __RPC_FAR *puVirtualKey) = 0; + virtual IFMETHOD dummy_InstallIMEA() = 0; + virtual IFMETHOD InstallIMEW(LPWSTR szIMEFileName, LPWSTR szLayoutText, HKL __RPC_FAR *phKL) = 0; + virtual IFMETHOD IsIME(HKL hKL) = 0; + virtual IFMETHOD dummy_IsUIMessageA() = 0; + virtual IFMETHOD IsUIMessageW(HWND hWndIME, UINT msg, WPARAM wParam, LPARAM lParam) = 0; + virtual IFMETHOD NotifyIME(HIMC hIMC, DWORD dwAction, DWORD dwIndex, DWORD dwValue) = 0; + virtual IFMETHOD dummy_RegisterWordA() = 0; + virtual IFMETHOD RegisterWordW(HKL hKL, LPWSTR szReading, DWORD dwStyle, LPWSTR szRegister) = 0; + virtual IFMETHOD ReleaseContext(HWND hWnd, HIMC hIMC) = 0; + virtual IFMETHOD SetCandidateWindow(HIMC hIMC, CANDIDATEFORM __RPC_FAR *pCandidate) = 0; + virtual IFMETHOD SetCompositionFontA(HIMC hIMC, LOGFONTA __RPC_FAR *plf) = 0; + virtual IFMETHOD SetCompositionFontW(HIMC hIMC, LOGFONTW __RPC_FAR *plf) = 0; + virtual IFMETHOD dummy_SetCompositionStringA() = 0; + virtual IFMETHOD SetCompositionStringW(HIMC hIMC, DWORD dwIndex, LPVOID pComp, DWORD dwCompLen, + LPVOID pRead, DWORD dwReadLen) = 0; + virtual IFMETHOD SetCompositionWindow(HIMC hIMC, COMPOSITIONFORM __RPC_FAR *pCompForm) = 0; + virtual IFMETHOD SetConversionStatus(HIMC hIMC, DWORD fdwConversion, DWORD fdwSentence) = 0; + virtual IFMETHOD SetOpenStatus(HIMC hIMC, BOOL fOpen) = 0; + virtual IFMETHOD SetStatusWindowPos(HIMC hIMC, POINT __RPC_FAR *pptPos) = 0; + virtual IFMETHOD SimulateHotKey(HWND hWnd, DWORD dwHotKeyID) = 0; + virtual IFMETHOD dummy_UnregisterWordA() = 0; + virtual IFMETHOD UnregisterWordW(HKL hKL, LPWSTR szReading, DWORD dwStyle, LPWSTR szUnregister) = 0; + virtual IFMETHOD Activate(BOOL fRestoreLayout) = 0; + virtual IFMETHOD Deactivate(void) = 0; + virtual IFMETHOD OnDefWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam, LRESULT __RPC_FAR *plResult) = 0; + virtual IFMETHOD FilterClientWindows(ATOM __RPC_FAR *aaClassList, UINT uSize) = 0; + virtual IFMETHOD dummy_GetCodePageA() = 0; + virtual IFMETHOD GetLangId(HKL hKL, LANGID __RPC_FAR *plid) = 0; + virtual IFMETHOD AssociateContextEx(HWND hWnd, HIMC hIMC, DWORD dwFlags) = 0; + virtual IFMETHOD DisableIME(DWORD idThread) = 0; + virtual IFMETHOD dummy_GetImeMenuItemsA() = 0; + virtual IFMETHOD GetImeMenuItemsW(HIMC hIMC, DWORD dwFlags, DWORD dwType, /*IMEMENUITEMINFOW*/ void __RPC_FAR *pImeParentMenu, + /*IMEMENUITEMINFOW*/ void __RPC_FAR *pImeMenu, DWORD dwSize, DWORD __RPC_FAR *pdwResult) = 0; + virtual IFMETHOD EnumInputContext(DWORD idThread, IEnumInputContext __RPC_FAR *__RPC_FAR *ppEnum) = 0; +}; + +interface IActiveIMMMessagePumpOwner : public IUnknown +{ +public: + virtual IFMETHOD Start(void) = 0; + virtual IFMETHOD End(void) = 0; + virtual IFMETHOD OnTranslateMessage(const MSG __RPC_FAR *pMsg) = 0; + virtual IFMETHOD Pause(DWORD __RPC_FAR *pdwCookie) = 0; + virtual IFMETHOD Resume(DWORD dwCookie) = 0; +}; + + +static IActiveIMMApp *aimm = 0; +static IActiveIMMMessagePumpOwner *aimmpump = 0; +static QString *imeComposition = 0; +static int imePosition = -1; +bool qt_use_rtl_extensions = false; +static bool haveCaret = false; + +#ifndef LGRPID_INSTALLED +#define LGRPID_INSTALLED 0x00000001 // installed language group ids +#define LGRPID_SUPPORTED 0x00000002 // supported language group ids +#endif + +#ifndef LGRPID_ARABIC +#define LGRPID_WESTERN_EUROPE 0x0001 // Western Europe & U.S. +#define LGRPID_CENTRAL_EUROPE 0x0002 // Central Europe +#define LGRPID_BALTIC 0x0003 // Baltic +#define LGRPID_GREEK 0x0004 // Greek +#define LGRPID_CYRILLIC 0x0005 // Cyrillic +#define LGRPID_TURKISH 0x0006 // Turkish +#define LGRPID_JAPANESE 0x0007 // Japanese +#define LGRPID_KOREAN 0x0008 // Korean +#define LGRPID_TRADITIONAL_CHINESE 0x0009 // Traditional Chinese +#define LGRPID_SIMPLIFIED_CHINESE 0x000a // Simplified Chinese +#define LGRPID_THAI 0x000b // Thai +#define LGRPID_HEBREW 0x000c // Hebrew +#define LGRPID_ARABIC 0x000d // Arabic +#define LGRPID_VIETNAMESE 0x000e // Vietnamese +#define LGRPID_INDIC 0x000f // Indic +#define LGRPID_GEORGIAN 0x0010 // Georgian +#define LGRPID_ARMENIAN 0x0011 // Armenian +#endif + +static DWORD WM_MSIME_MOUSE = 0; + +QWinInputContext::QWinInputContext(QObject *parent) + : QInputContext(parent), recursionGuard(false) +{ +#ifndef Q_WS_WINCE + QSysInfo::WinVersion ver = QSysInfo::windowsVersion(); + if (ver & QSysInfo::WV_NT_based && ver >= QSysInfo::WV_VISTA) { + // Since the IsValidLanguageGroup/IsValidLocale functions always return true on + // Vista, check the Keyboard Layouts for enabling RTL. + UINT nLayouts = GetKeyboardLayoutList(0, 0); + if (nLayouts) { + HKL *lpList = new HKL[nLayouts]; + GetKeyboardLayoutList(nLayouts, lpList); + for (int i = 0; i<(int)nLayouts; i++) { + WORD plangid = PRIMARYLANGID((quintptr)lpList[i]); + if (plangid == LANG_ARABIC + || plangid == LANG_HEBREW + || plangid == LANG_FARSI +#ifdef LANG_SYRIAC + || plangid == LANG_SYRIAC +#endif + ) { + qt_use_rtl_extensions = true; + break; + } + } + delete []lpList; + } + } else { + // figure out whether a RTL language is installed + qt_use_rtl_extensions = IsValidLanguageGroup(LGRPID_ARABIC, LGRPID_INSTALLED) + || IsValidLanguageGroup(LGRPID_HEBREW, LGRPID_INSTALLED) + || IsValidLocale(MAKELCID(MAKELANGID(LANG_ARABIC, SUBLANG_DEFAULT), SORT_DEFAULT), LCID_INSTALLED) + || IsValidLocale(MAKELCID(MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT), SORT_DEFAULT), LCID_INSTALLED) +#ifdef LANG_SYRIAC + || IsValidLocale(MAKELCID(MAKELANGID(LANG_SYRIAC, SUBLANG_DEFAULT), SORT_DEFAULT), LCID_INSTALLED) +#endif + || IsValidLocale(MAKELCID(MAKELANGID(LANG_FARSI, SUBLANG_DEFAULT), SORT_DEFAULT), LCID_INSTALLED); + } +#else + qt_use_rtl_extensions = false; +#endif + + WM_MSIME_MOUSE = RegisterWindowMessage(L"MSIMEMouseOperation"); +} + +QWinInputContext::~QWinInputContext() +{ + // release active input method if we have one + if (aimm) { + aimmpump->End(); + aimmpump->Release(); + aimm->Deactivate(); + aimm->Release(); + aimm = 0; + aimmpump = 0; + } + delete imeComposition; + imeComposition = 0; +} + +static HWND getDefaultIMEWnd(HWND wnd) +{ + HWND ime_wnd; + if(aimm) + aimm->GetDefaultIMEWnd(wnd, &ime_wnd); + else + ime_wnd = ImmGetDefaultIMEWnd(wnd); + return ime_wnd; +} + +static HIMC getContext(HWND wnd) +{ + HIMC imc; + if (aimm) + aimm->GetContext(wnd, &imc); + else + imc = ImmGetContext(wnd); + + return imc; +} + +static void releaseContext(HWND wnd, HIMC imc) +{ + if (aimm) + aimm->ReleaseContext(wnd, imc); + else + ImmReleaseContext(wnd, imc); +} + +static void notifyIME(HIMC imc, DWORD dwAction, DWORD dwIndex, DWORD dwValue) +{ + if (!imc) + return; + if (aimm) + aimm->NotifyIME(imc, dwAction, dwIndex, dwValue); + else + ImmNotifyIME(imc, dwAction, dwIndex, dwValue); +} + +static LONG getCompositionString(HIMC himc, DWORD dwIndex, LPVOID lpbuf, DWORD dBufLen) +{ + LONG len = 0; + if (aimm) + aimm->GetCompositionStringW(himc, dwIndex, dBufLen, &len, lpbuf); + else + len = ImmGetCompositionString(himc, dwIndex, lpbuf, dBufLen); + return len; +} + +static int getCursorPosition(HIMC himc) +{ + return getCompositionString(himc, GCS_CURSORPOS, 0, 0); +} + +static QString getString(HIMC himc, DWORD dwindex, int *selStart = 0, int *selLength = 0) +{ + const int bufferSize = 256; + wchar_t buffer[bufferSize]; + int len = getCompositionString(himc, dwindex, buffer, bufferSize * sizeof(wchar_t)); + + if (selStart) { + char attrbuffer[bufferSize]; + int attrlen = getCompositionString(himc, GCS_COMPATTR, attrbuffer, bufferSize); + *selStart = attrlen+1; + *selLength = -1; + for (int i = 0; i < attrlen; i++) { + if (attrbuffer[i] & ATTR_TARGET_CONVERTED) { + *selStart = qMin(*selStart, i); + *selLength = qMax(*selLength, i); + } + } + *selLength = qMax(0, *selLength - *selStart + 1); + } + + if (len <= 0) + return QString(); + + return QString((QChar*)buffer, len / sizeof(QChar)); +} + +void QWinInputContext::TranslateMessage(const MSG *msg) +{ + if (!aimmpump || aimmpump->OnTranslateMessage(msg) != S_OK) + ::TranslateMessage(msg); +} + +LRESULT QWinInputContext::DefWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + LRESULT retval; + if (!aimm || aimm->OnDefWindowProc(hwnd, msg, wParam, lParam, &retval) != S_OK) + { + retval = ::DefWindowProc(hwnd, msg, wParam, lParam); + } + return retval; +} + + +void QWinInputContext::update() +{ + QWidget *w = focusWidget(); + if(!w) + return; + + Q_ASSERT(w->testAttribute(Qt::WA_WState_Created)); + HIMC imc = getContext(w->effectiveWinId()); + + if (!imc) + return; + + QFont f = qvariant_cast<QFont>(w->inputMethodQuery(Qt::ImFont)); + HFONT hf; + hf = f.handle(); + + LOGFONT lf; + if (GetObject(hf, sizeof(lf), &lf)) { + if (aimm) + aimm->SetCompositionFontW(imc, &lf); + else + ImmSetCompositionFont(imc, &lf); + } + + QRect r = w->inputMethodQuery(Qt::ImMicroFocus).toRect(); + + // The ime window positions are based on the WinId with active focus. + QWidget *imeWnd = QWidget::find(::GetFocus()); + if (imeWnd && !aimm) { + QPoint pt (r.topLeft()); + pt = w->mapToGlobal(pt); + pt = imeWnd->mapFromGlobal(pt); + r.moveTo(pt); + } + + COMPOSITIONFORM cf; + // ### need X-like inputStyle config settings + cf.dwStyle = CFS_FORCE_POSITION; + cf.ptCurrentPos.x = r.x(); + cf.ptCurrentPos.y = r.y(); + + CANDIDATEFORM candf; + candf.dwIndex = 0; + candf.dwStyle = CFS_EXCLUDE; + candf.ptCurrentPos.x = r.x(); + candf.ptCurrentPos.y = r.y() + r.height(); + candf.rcArea.left = r.x(); + candf.rcArea.top = r.y(); + candf.rcArea.right = r.x() + r.width(); + candf.rcArea.bottom = r.y() + r.height(); + + if(haveCaret) + SetCaretPos(r.x(), r.y()); + + if (aimm) { + aimm->SetCompositionWindow(imc, &cf); + aimm->SetCandidateWindow(imc, &candf); + } else { + ImmSetCompositionWindow(imc, &cf); + ImmSetCandidateWindow(imc, &candf); + } + + releaseContext(w->effectiveWinId(), imc); +} + + +bool QWinInputContext::endComposition() +{ + QWidget *fw = focusWidget(); +#ifdef Q_IME_DEBUG + qDebug("endComposition! fw = %s", fw ? fw->className() : "(null)"); +#endif + bool result = true; + if(imePosition == -1 || recursionGuard) + return result; + + // Googles Pinyin Input Method likes to call endComposition again + // when we call notifyIME with CPS_CANCEL, so protect ourselves + // against that. + recursionGuard = true; + + if (fw) { + Q_ASSERT(fw->testAttribute(Qt::WA_WState_Created)); + HIMC imc = getContext(fw->effectiveWinId()); + notifyIME(imc, NI_COMPOSITIONSTR, CPS_CANCEL, 0); + releaseContext(fw->effectiveWinId(), imc); + if(haveCaret) { + DestroyCaret(); + haveCaret = false; + } + } + + if (!fw) + fw = QApplication::focusWidget(); + + if (fw) { + QInputMethodEvent e; + result = qt_sendSpontaneousEvent(fw, &e); + } + + if (imeComposition) + imeComposition->clear(); + imePosition = -1; + + recursionGuard = false; + + return result; +} + +void QWinInputContext::reset() +{ + QWidget *fw = focusWidget(); + +#ifdef Q_IME_DEBUG + qDebug("sending accept to focus widget %s", fw ? fw->className() : "(null)"); +#endif + + if (fw && imePosition != -1) { + QInputMethodEvent e; + if (imeComposition) + e.setCommitString(*imeComposition); + imePosition = -1; + qt_sendSpontaneousEvent(fw, &e); + } + + if (imeComposition) + imeComposition->clear(); + imePosition = -1; + + if (fw) { + Q_ASSERT(fw->testAttribute(Qt::WA_WState_Created)); + HIMC imc = getContext(fw->effectiveWinId()); + notifyIME(imc, NI_COMPOSITIONSTR, CPS_CANCEL, 0); + releaseContext(fw->effectiveWinId(), imc); + } + +} + + +bool QWinInputContext::startComposition() +{ +#ifdef Q_IME_DEBUG + qDebug("startComposition"); +#endif + + if (!imeComposition) + imeComposition = new QString(); + + QWidget *fw = focusWidget(); + if (fw) { + Q_ASSERT(fw->testAttribute(Qt::WA_WState_Created)); + imePosition = 0; + haveCaret = CreateCaret(fw->effectiveWinId(), 0, 1, 1); + HideCaret(fw->effectiveWinId()); + update(); + } + return fw != 0; +} + +enum StandardFormat { + PreeditFormat, + SelectionFormat +}; + +bool QWinInputContext::composition(LPARAM lParam) +{ +#ifdef Q_IME_DEBUG + QString 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 "; + qDebug("composition, lParam=(%x) %s imePosition=%d", lParam, str.latin1(), imePosition); +#endif + + bool result = true; + + if(!lParam) + // bogus event + return true; + + QWidget *fw = QApplication::focusWidget(); + if (fw) { + Q_ASSERT(fw->testAttribute(Qt::WA_WState_Created)); + HIMC imc = getContext(fw->effectiveWinId()); + QInputMethodEvent e; + if (lParam & (GCS_COMPSTR | GCS_COMPATTR | GCS_CURSORPOS)) { + if (imePosition == -1) + // need to send a start event + startComposition(); + + // some intermediate composition result + int selStart, selLength; + *imeComposition = getString(imc, GCS_COMPSTR, &selStart, &selLength); + imePosition = getCursorPosition(imc); + if (lParam & CS_INSERTCHAR && lParam & CS_NOMOVECARET) { + // make korean work correctly. Hope this is correct for all IMEs + selStart = 0; + selLength = imeComposition->length(); + } + if(selLength == 0) + selStart = 0; + + QList<QInputMethodEvent::Attribute> attrs; + if (selStart > 0) + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, selStart, + standardFormat(PreeditFormat)); + if (selLength) + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, selStart, selLength, + standardFormat(SelectionFormat)); + if (selStart + selLength < imeComposition->length()) + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, selStart + selLength, + imeComposition->length() - selStart - selLength, + standardFormat(PreeditFormat)); + if(imePosition >= 0) + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, imePosition, selLength ? 0 : 1, QVariant()); + + e = QInputMethodEvent(*imeComposition, attrs); + } + if (lParam & GCS_RESULTSTR) { + if(imePosition == -1) + startComposition(); + // a fixed result, return the converted string + *imeComposition = getString(imc, GCS_RESULTSTR); + imePosition = -1; + e.setCommitString(*imeComposition); + imeComposition->clear(); + } + result = qt_sendSpontaneousEvent(fw, &e); + update(); + releaseContext(fw->effectiveWinId(), imc); + } +#ifdef Q_IME_DEBUG + qDebug("imecomposition: cursor pos at %d, str=%x", imePosition, str[0].unicode()); +#endif + return result; +} + +static HIMC defaultContext = 0; + +// checks whether widget is a popup +inline bool isPopup(QWidget *w) +{ + if (w && (w->windowFlags() & Qt::Popup) == Qt::Popup) + return true; + else + return false; +} +// checks whether widget is in a popup +inline bool isInPopup(QWidget *w) +{ + if (w && (isPopup(w) || isPopup(w->window()))) + return true; + else + return false; +} + +// find the parent widget, which is a non popup toplevel +// this is valid only if the widget is/in a popup +inline QWidget *findParentforPopup(QWidget *w) +{ + QWidget *e = QWidget::find(w->effectiveWinId()); + // check if this or its parent is a popup + while (isInPopup(e)) { + e = e->window()->parentWidget(); + if (!e) + break; + e = QWidget::find(e->effectiveWinId()); + } + if (e) + return e->window(); + else + return 0; +} + +// enables or disables the ime +inline void enableIme(QWidget *w, bool value) +{ + if (value) { + // enable ime + if (defaultContext) + ImmAssociateContext(w->effectiveWinId(), defaultContext); +#ifdef Q_WS_WINCE + if (qApp->autoSipEnabled()) + qt_wince_show_SIP(true); +#endif + } else { + // disable ime + HIMC oldimc = ImmAssociateContext(w->effectiveWinId(), 0); + if (!defaultContext) + defaultContext = oldimc; +#ifdef Q_WS_WINCE + if (qApp->autoSipEnabled()) + qt_wince_show_SIP(false); +#endif + } +} + + +void QWinInputContext::updateImeStatus(QWidget *w, bool hasFocus) +{ + if (!w) + return; + // It's always the proxy that carries the hints. + QWidget *focusProxyWidget = w->focusProxy(); + if (!focusProxyWidget) + focusProxyWidget = w; + bool e = w->testAttribute(Qt::WA_InputMethodEnabled) && w->isEnabled() + && !(focusProxyWidget->inputMethodHints() & (Qt::ImhExclusiveInputMask | Qt::ImhHiddenText)); + bool hasIme = e && hasFocus; +#ifdef Q_IME_DEBUG + qDebug("%s HasFocus = %d hasIme = %d e = %d ", w->className(), hasFocus, hasIme, e); +#endif + if (hasFocus || e) { + if (isInPopup(w)) + QWinInputContext::enablePopupChild(w, hasIme); + else + QWinInputContext::enable(w, hasIme); + } +} + +void QWinInputContext::enablePopupChild(QWidget *w, bool e) +{ + if (aimm) { + enable(w, e); + return; + } + + if (!w || !isInPopup(w)) + return; +#ifdef Q_IME_DEBUG + qDebug("enablePopupChild: w=%s, enable = %s", w ? w->className() : "(null)" , e ? "true" : "false"); +#endif + QWidget *parent = findParentforPopup(w); + if (parent) { + // update ime status of the normal toplevel parent of the popup + enableIme(parent, e); + } + QWidget *toplevel = w->window(); + if (toplevel) { + // update ime status of the toplevel popup + enableIme(toplevel, e); + } +} + +void QWinInputContext::enable(QWidget *w, bool e) +{ + if(w) { +#ifdef Q_IME_DEBUG + qDebug("enable: w=%s, enable = %s", w ? w->className() : "(null)" , e ? "true" : "false"); +#endif + if (!w->testAttribute(Qt::WA_WState_Created)) + return; + if(aimm) { + HIMC oldimc; + if (!e) { + aimm->AssociateContext(w->effectiveWinId(), 0, &oldimc); + if (!defaultContext) + defaultContext = oldimc; + } else if (defaultContext) { + aimm->AssociateContext(w->effectiveWinId(), defaultContext, &oldimc); + } + } else { + // update ime status on the widget + QWidget *p = QWidget::find(w->effectiveWinId()); + if (p) + enableIme(p, e); + } + } +} + +void QWinInputContext::setFocusWidget(QWidget *w) +{ + QWidget *oldFocus = focusWidget(); + if (oldFocus == w) + return; + if (w) { + QWinInputContext::updateImeStatus(w, true); + } else { + if (oldFocus) + QWinInputContext::updateImeStatus(oldFocus , false); + } + QInputContext::setFocusWidget(w); + update(); +} + +bool QWinInputContext::isComposing() const +{ + return imeComposition && !imeComposition->isEmpty(); +} + +void QWinInputContext::mouseHandler(int pos, QMouseEvent *e) +{ + if(e->type() != QEvent::MouseButtonPress) + return; + + if (pos < 0 || pos > imeComposition->length()) + reset(); + + // Probably should pass the correct button, but it seems to work fine like this. + DWORD button = MK_LBUTTON; + + QWidget *fw = focusWidget(); + if (fw) { + Q_ASSERT(fw->testAttribute(Qt::WA_WState_Created)); + HIMC himc = getContext(fw->effectiveWinId()); + HWND ime_wnd = getDefaultIMEWnd(fw->effectiveWinId()); + SendMessage(ime_wnd, WM_MSIME_MOUSE, MAKELONG(MAKEWORD(button, pos == 0 ? 2 : 1), pos), (LPARAM)himc); + releaseContext(fw->effectiveWinId(), himc); + } + //qDebug("mouseHandler: got value %d pos=%d", ret,pos); +} + +QString QWinInputContext::language() +{ + return QString(); +} + +int QWinInputContext::reconvertString(RECONVERTSTRING *reconv) +{ + QWidget *w = focusWidget(); + if(!w) + return -1; + + Q_ASSERT(w->testAttribute(Qt::WA_WState_Created)); + QString surroundingText = qvariant_cast<QString>(w->inputMethodQuery(Qt::ImSurroundingText)); + int memSize = sizeof(RECONVERTSTRING)+(surroundingText.length()+1)*sizeof(ushort); + // If memory is not allocated, return the required size. + if (!reconv) { + if (surroundingText.isEmpty()) + return -1; + else + return memSize; + } + int pos = qvariant_cast<int>(w->inputMethodQuery(Qt::ImCursorPosition)); + // 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(); + } + int startPos = bounds.position(); + bounds.toNextBoundary(); + int endPos = bounds.position(); + // select the text, this will be overwritten by following ime events. + QList<QInputMethodEvent::Attribute> attrs; + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, startPos, endPos-startPos, QVariant()); + QInputMethodEvent e(QString(), attrs); + qt_sendSpontaneousEvent(w, &e); + + reconv->dwSize = memSize; + reconv->dwVersion = 0; + + reconv->dwStrLen = surroundingText.length(); + reconv->dwStrOffset = sizeof(RECONVERTSTRING); + reconv->dwCompStrLen = endPos-startPos; + reconv->dwCompStrOffset = startPos*sizeof(ushort); + reconv->dwTargetStrLen = reconv->dwCompStrLen; + reconv->dwTargetStrOffset = reconv->dwCompStrOffset; + memcpy((char*)(reconv+1), surroundingText.utf16(), surroundingText.length()*sizeof(ushort)); + return memSize; +} + +QT_END_NAMESPACE diff --git a/src/widgets/platforms/x11/qximinputcontext_p.h b/src/widgets/platforms/x11/qximinputcontext_p.h new file mode 100644 index 0000000000..47c6f78ff9 --- /dev/null +++ b/src/widgets/platforms/x11/qximinputcontext_p.h @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module 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$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Definition of QXIMInputContext class +** +** Copyright (C) 2003-2004 immodule for Qt Project. All rights reserved. +** +** This file is written to contribute to Nokia Corporation and/or its subsidiary(-ies) under their own +** license. You may use this file under your Qt license. Following +** description is copied from their original file headers. Contact +** immodule-qt@freedesktop.org if any conditions of this licensing are +** not clear to you. +** +****************************************************************************/ + +#ifndef QXIMINPUTCONTEXT_P_H +#define QXIMINPUTCONTEXT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#if !defined(Q_NO_IM) + +#include "QtCore/qglobal.h" +#include "QtWidgets/qinputcontext.h" +#include "QtGui/qfont.h" +#include "QtCore/qhash.h" +#ifdef Q_WS_X11 +#include "QtCore/qlist.h" +#include "QtCore/qbitarray.h" +#include "QtGui/qwindowdefs.h" +#include "private/qt_x11_p.h" +#endif + +QT_BEGIN_NAMESPACE + +class QKeyEvent; +class QWidget; +class QFont; +class QString; + +class QXIMInputContext : public QInputContext +{ + Q_OBJECT +public: + struct ICData { + XIC ic; + XFontSet fontset; + QWidget *widget; + QString text; + QBitArray selectedChars; + bool composing; + bool preeditEmpty; + void clear(); + }; + + QXIMInputContext(); + ~QXIMInputContext(); + + QString identifierName(); + QString language(); + + void reset(); + + void mouseHandler( int x, QMouseEvent *event); + bool isComposing() const; + + void setFocusWidget( QWidget *w ); + void widgetDestroyed(QWidget *w); + + void create_xim(); + void close_xim(); + + void update(); + + ICData *icData() const; +protected: + bool x11FilterEvent( QWidget *keywidget, XEvent *event ); + +private: + static XIMStyle xim_style; + + QString _language; + XIM xim; + QHash<WId, ICData *> ximData; + + ICData *createICData(QWidget *w); +}; + +QT_END_NAMESPACE + +#endif // Q_NO_IM + +#endif // QXIMINPUTCONTEXT_P_H diff --git a/src/widgets/platforms/x11/qximinputcontext_x11.cpp b/src/widgets/platforms/x11/qximinputcontext_x11.cpp new file mode 100644 index 0000000000..de1212c556 --- /dev/null +++ b/src/widgets/platforms/x11/qximinputcontext_x11.cpp @@ -0,0 +1,885 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module 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$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Implementation of QXIMInputContext class +** +** Copyright (C) 2003-2004 immodule for Qt Project. All rights reserved. +** +** This file is written to contribute to Nokia Corporation and/or its subsidiary(-ies) under their own +** license. You may use this file under your Qt license. Following +** description is copied from their original file headers. Contact +** immodule-qt@freedesktop.org if any conditions of this licensing are +** not clear to you. +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qdebug.h" +#include "qximinputcontext_p.h" + +#if !defined(QT_NO_IM) + +QT_BEGIN_NAMESPACE + +#if !defined(QT_NO_XIM) + +QT_BEGIN_INCLUDE_NAMESPACE +#include "qplatformdefs.h" + +#include "qapplication.h" +#include "qwidget.h" +#include "qstring.h" +#include "qlist.h" +#include "qtextcodec.h" +#include "qevent.h" +#include "qtextformat.h" + +#include "qx11info_x11.h" + +#include <stdlib.h> +#include <limits.h> +QT_END_INCLUDE_NAMESPACE + +// #define QT_XIM_DEBUG +#ifdef QT_XIM_DEBUG +#define XIM_DEBUG qDebug +#else +#define XIM_DEBUG if (0) qDebug +#endif + +// from qapplication_x11.cpp +// #### move to X11 struct +extern XIMStyle qt_xim_preferred_style; +extern char *qt_ximServer; +extern int qt_ximComposingKeycode; +extern QTextCodec * qt_input_mapper; + +XIMStyle QXIMInputContext::xim_style = 0; +// moved from qapplication_x11.cpp +static const XIMStyle xim_default_style = XIMPreeditCallbacks | XIMStatusNothing; + + +extern "C" { +#ifdef USE_X11R6_XIM + static void xim_create_callback(XIM /*im*/, + XPointer client_data, + XPointer /*call_data*/) + { + QXIMInputContext *qic = reinterpret_cast<QXIMInputContext *>(client_data); + // qDebug("xim_create_callback"); + qic->create_xim(); + } + + static void xim_destroy_callback(XIM /*im*/, + XPointer client_data, + XPointer /*call_data*/) + { + QXIMInputContext *qic = reinterpret_cast<QXIMInputContext *>(client_data); + // qDebug("xim_destroy_callback"); + qic->close_xim(); + XRegisterIMInstantiateCallback(X11->display, 0, 0, 0, + (XIMProc) xim_create_callback, reinterpret_cast<char *>(qic)); + } +#endif // USE_X11R6_XIM + + static int xic_start_callback(XIC, XPointer client_data, XPointer) { + QXIMInputContext *qic = (QXIMInputContext *) client_data; + if (!qic) { + XIM_DEBUG("xic_start_callback: no qic"); + return 0; + } + QXIMInputContext::ICData *data = qic->icData(); + if (!data) { + XIM_DEBUG("xic_start_callback: no ic data"); + return 0; + } + XIM_DEBUG("xic_start_callback"); + + data->clear(); + data->composing = true; + + return 0; + } + + static int xic_draw_callback(XIC, XPointer client_data, XPointer call_data) { + QXIMInputContext *qic = (QXIMInputContext *) client_data; + if (!qic) { + XIM_DEBUG("xic_draw_callback: no qic"); + return 0; + } + QXIMInputContext::ICData *data = qic->icData(); + if (!data) { + XIM_DEBUG("xic_draw_callback: no ic data"); + return 0; + } + XIM_DEBUG("xic_draw_callback"); + + + if(!data->composing) { + data->clear(); + data->composing = true; + } + + XIMPreeditDrawCallbackStruct *drawstruct = (XIMPreeditDrawCallbackStruct *) call_data; + XIMText *text = (XIMText *) drawstruct->text; + int cursor = drawstruct->caret, sellen = 0, selstart = 0; + + if (!drawstruct->caret && !drawstruct->chg_first && !drawstruct->chg_length && !text) { + if(data->text.isEmpty()) { + XIM_DEBUG("compose emptied"); + // if the composition string has been emptied, we need + // to send an InputMethodEnd event + QInputMethodEvent e; + qic->sendEvent(e); + data->clear(); + + // if the commit string has coming after here, InputMethodStart + // will be sent dynamically + } + return 0; + } + + if (text) { + char *str = 0; + if (text->encoding_is_wchar) { + int l = wcstombs(NULL, text->string.wide_char, text->length); + if (l != -1) { + str = new char[l + 1]; + wcstombs(str, text->string.wide_char, l); + str[l] = 0; + } + } else + str = text->string.multi_byte; + + if (!str) + return 0; + + QString s = QString::fromLocal8Bit(str); + + if (text->encoding_is_wchar) + delete [] str; + + if (drawstruct->chg_length < 0) + data->text.replace(drawstruct->chg_first, INT_MAX, s); + else + data->text.replace(drawstruct->chg_first, drawstruct->chg_length, s); + + if (data->selectedChars.size() < data->text.length()) { + // expand the selectedChars array if the compose string is longer + int from = data->selectedChars.size(); + data->selectedChars.resize(data->text.length()); + for (int x = from; x < data->selectedChars.size(); ++x) + data->selectedChars.clearBit(x); + } + + // determine if the changed chars are selected based on text->feedback + for (int x = 0; x < text->length; ++x) + data->selectedChars.setBit(x + drawstruct->chg_first, + (text->feedback ? (text->feedback[x] & XIMReverse) : 0)); + + // figure out where the selection starts, and how long it is + bool started = false; + for (int x = 0; x < qMin(data->selectedChars.size(), data->text.length()); ++x) { + if (started) { + if (data->selectedChars.testBit(x)) ++sellen; + else break; + } else { + if (data->selectedChars.testBit(x)) { + selstart = x; + started = true; + sellen = 1; + } + } + } + } else { + if (drawstruct->chg_length == 0) + drawstruct->chg_length = -1; + + data->text.remove(drawstruct->chg_first, drawstruct->chg_length); + bool qt_compose_emptied = data->text.isEmpty(); + if (qt_compose_emptied) { + XIM_DEBUG("compose emptied 2 text=%s", data->text.toUtf8().constData()); + // if the composition string has been emptied, we need + // to send an InputMethodEnd event + QInputMethodEvent e; + qic->sendEvent(e); + data->clear(); + // if the commit string has coming after here, InputMethodStart + // will be sent dynamically + return 0; + } + } + + XIM_DEBUG("sending compose: '%s', cursor=%d, sellen=%d", + data->text.toUtf8().constData(), cursor, sellen); + QList<QInputMethodEvent::Attribute> attrs; + if (selstart > 0) + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, selstart, + qic->standardFormat(QInputContext::PreeditFormat)); + if (sellen) + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, selstart, sellen, + qic->standardFormat(QInputContext::SelectionFormat)); + if (selstart + sellen < data->text.length()) + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, + selstart + sellen, data->text.length() - selstart - sellen, + qic->standardFormat(QInputContext::PreeditFormat)); + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, cursor, sellen ? 0 : 1, QVariant()); + QInputMethodEvent e(data->text, attrs); + data->preeditEmpty = data->text.isEmpty(); + qic->sendEvent(e); + + return 0; + } + + static int xic_done_callback(XIC, XPointer client_data, XPointer) { + QXIMInputContext *qic = (QXIMInputContext *) client_data; + if (!qic) + return 0; + + XIM_DEBUG("xic_done_callback"); + // Don't send InputMethodEnd here. QXIMInputContext::x11FilterEvent() + // handles InputMethodEnd with commit string. + return 0; + } +} + +void QXIMInputContext::ICData::clear() +{ + text = QString(); + selectedChars.clear(); + composing = false; + preeditEmpty = true; +} + +QXIMInputContext::ICData *QXIMInputContext::icData() const +{ + if (QWidget *w = focusWidget()) + return ximData.value(w->effectiveWinId()); + return 0; +} +/* The cache here is needed, as X11 leaks a few kb for every + XFreeFontSet call, so we avoid creating and deletion of fontsets as + much as possible +*/ +static XFontSet fontsetCache[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +static int fontsetRefCount = 0; + +static const char * const fontsetnames[] = { + "-*-fixed-medium-r-*-*-16-*,-*-*-medium-r-*-*-16-*", + "-*-fixed-medium-i-*-*-16-*,-*-*-medium-i-*-*-16-*", + "-*-fixed-bold-r-*-*-16-*,-*-*-bold-r-*-*-16-*", + "-*-fixed-bold-i-*-*-16-*,-*-*-bold-i-*-*-16-*", + "-*-fixed-medium-r-*-*-24-*,-*-*-medium-r-*-*-24-*", + "-*-fixed-medium-i-*-*-24-*,-*-*-medium-i-*-*-24-*", + "-*-fixed-bold-r-*-*-24-*,-*-*-bold-r-*-*-24-*", + "-*-fixed-bold-i-*-*-24-*,-*-*-bold-i-*-*-24-*" +}; + +static XFontSet getFontSet(const QFont &f) +{ + int i = 0; + if (f.italic()) + i |= 1; + if (f.bold()) + i |= 2; + + if (f.pointSize() > 20) + i += 4; + + if (!fontsetCache[i]) { + Display* dpy = X11->display; + int missCount; + char** missList; + fontsetCache[i] = XCreateFontSet(dpy, fontsetnames[i], &missList, &missCount, 0); + if(missCount > 0) + XFreeStringList(missList); + if (!fontsetCache[i]) { + fontsetCache[i] = XCreateFontSet(dpy, "-*-fixed-*-*-*-*-16-*", &missList, &missCount, 0); + if(missCount > 0) + XFreeStringList(missList); + if (!fontsetCache[i]) + fontsetCache[i] = (XFontSet)-1; + } + } + return (fontsetCache[i] == (XFontSet)-1) ? 0 : fontsetCache[i]; +} + +extern bool qt_use_rtl_extensions; // from qapplication_x11.cpp +#ifndef QT_NO_XKB +extern QLocale q_getKeyboardLocale(const QByteArray &layoutName, const QByteArray &variantName); +#endif + +QXIMInputContext::QXIMInputContext() +{ + if (!qt_xim_preferred_style) // no configured input style, use the default + qt_xim_preferred_style = xim_default_style; + + xim = 0; + QByteArray ximServerName(qt_ximServer); + if (qt_ximServer) + ximServerName.prepend("@im="); + else + ximServerName = ""; + + if (!XSupportsLocale()) +#ifndef QT_NO_DEBUG + qWarning("Qt: Locale not supported on X server") +#endif + ; +#ifdef USE_X11R6_XIM + else if (XSetLocaleModifiers (ximServerName.constData()) == 0) + qWarning("Qt: Cannot set locale modifiers: %s", ximServerName.constData()); + else + XRegisterIMInstantiateCallback(X11->display, 0, 0, 0, + (XIMProc) xim_create_callback, reinterpret_cast<char *>(this)); +#else // !USE_X11R6_XIM + else if (XSetLocaleModifiers ("") == 0) + qWarning("Qt: Cannot set locale modifiers"); + else + QXIMInputContext::create_xim(); +#endif // USE_X11R6_XIM + +#ifndef QT_NO_XKB + if (X11->use_xkb) { + QByteArray layoutName; + QByteArray variantName; + + Atom type = XNone; + int format = 0; + ulong nitems = 0; + ulong bytesAfter = 0; + uchar *data = 0; + if (XGetWindowProperty(X11->display, RootWindow(X11->display, 0), ATOM(_XKB_RULES_NAMES), 0, 1024, + false, XA_STRING, &type, &format, &nitems, &bytesAfter, &data) == Success + && type == XA_STRING && format == 8 && nitems > 2) { + + char *names[5] = { 0, 0, 0, 0, 0 }; + char *p = reinterpret_cast<char *>(data), *end = p + nitems; + int i = 0; + do { + names[i++] = p; + p += qstrlen(p) + 1; + } while (p < end); + + QList<QByteArray> layoutNames = QByteArray::fromRawData(names[2], qstrlen(names[2])).split(','); + QList<QByteArray> variantNames = QByteArray::fromRawData(names[3], qstrlen(names[3])).split(','); + for (int i = 0; i < qMin(layoutNames.count(), variantNames.count()); ++i ) { + QByteArray variantName = variantNames.at(i); + const int dashPos = variantName.indexOf("-"); + if (dashPos >= 0) + variantName.truncate(dashPos); + QLocale keyboardInputLocale = q_getKeyboardLocale(layoutNames.at(i), variantName); + if (keyboardInputLocale.textDirection() == Qt::RightToLeft) + qt_use_rtl_extensions = true; + } + } + + if (data) + XFree(data); + } +#endif // QT_NO_XKB + +} + + +/*!\internal + Creates the application input method. +*/ +void QXIMInputContext::create_xim() +{ + ++fontsetRefCount; +#ifndef QT_NO_XIM + xim = XOpenIM(X11->display, 0, 0, 0); + if (xim) { + +#ifdef USE_X11R6_XIM + XIMCallback destroy; + destroy.callback = (XIMProc) xim_destroy_callback; + destroy.client_data = XPointer(this); + if (XSetIMValues(xim, XNDestroyCallback, &destroy, (char *) 0) != 0) + qWarning("Xlib doesn't support destroy callback"); +#endif // USE_X11R6_XIM + + XIMStyles *styles = 0; + XGetIMValues(xim, XNQueryInputStyle, &styles, (char *) 0, (char *) 0); + if (styles) { + int i; + for (i = 0; !xim_style && i < styles->count_styles; i++) { + if (styles->supported_styles[i] == qt_xim_preferred_style) { + xim_style = qt_xim_preferred_style; + break; + } + } + // if the preferred input style couldn't be found, look for + // Nothing + for (i = 0; !xim_style && i < styles->count_styles; i++) { + if (styles->supported_styles[i] == (XIMPreeditNothing | XIMStatusNothing)) { + xim_style = XIMPreeditNothing | XIMStatusNothing; + break; + } + } + // ... and failing that, None. + for (i = 0; !xim_style && i < styles->count_styles; i++) { + if (styles->supported_styles[i] == (XIMPreeditNone | + XIMStatusNone)) { + xim_style = XIMPreeditNone | XIMStatusNone; + break; + } + } + + // qDebug("QApplication: using im style %lx", xim_style); + XFree((char *)styles); + } + + if (xim_style) { + +#ifdef USE_X11R6_XIM + XUnregisterIMInstantiateCallback(X11->display, 0, 0, 0, + (XIMProc) xim_create_callback, reinterpret_cast<char *>(this)); +#endif // USE_X11R6_XIM + + if (QWidget *focusWidget = QApplication::focusWidget()) { + // reinitialize input context after the input method + // server (like SCIM) has been launched without + // requiring the user to manually switch focus. + if (focusWidget->testAttribute(Qt::WA_InputMethodEnabled) + && focusWidget->testAttribute(Qt::WA_WState_Created) + && focusWidget->isEnabled()) + setFocusWidget(focusWidget); + } + // following code fragment is not required for immodule + // version of XIM +#if 0 + QWidgetList list = qApp->topLevelWidgets(); + for (int i = 0; i < list.size(); ++i) { + QWidget *w = list.at(i); + w->d->createTLSysExtra(); + } +#endif + } else { + // Give up + qWarning("No supported input style found." + " See InputMethod documentation."); + close_xim(); + } + } +#endif // QT_NO_XIM +} + +/*!\internal + Closes the application input method. +*/ +void QXIMInputContext::close_xim() +{ + for(QHash<WId, ICData *>::const_iterator i = ximData.constBegin(), + e = ximData.constEnd(); i != e; ++i) { + ICData *data = i.value(); + if (data->ic) + XDestroyIC(data->ic); + delete data; + } + ximData.clear(); + + if ( --fontsetRefCount == 0 ) { + Display *dpy = X11->display; + for ( int i = 0; i < 8; i++ ) { + if ( fontsetCache[i] && fontsetCache[i] != (XFontSet)-1 ) { + XFreeFontSet(dpy, fontsetCache[i]); + fontsetCache[i] = 0; + } + } + } + + setFocusWidget(0); + xim = 0; +} + + + +QXIMInputContext::~QXIMInputContext() +{ + XIM old_xim = xim; // close_xim clears xim pointer. + close_xim(); + if (old_xim) + XCloseIM(old_xim); +} + + +QString QXIMInputContext::identifierName() +{ + // the name should be "xim" rather than "XIM" to be consistent + // with corresponding immodule of GTK+ + return QLatin1String("xim"); +} + + +QString QXIMInputContext::language() +{ + QString language; + if (xim) { + QByteArray locale(XLocaleOfIM(xim)); + + if (locale.startsWith("zh")) { + // Chinese language should be formed as "zh_CN", "zh_TW", "zh_HK" + language = QLatin1String(locale.left(5)); + } else { + // other languages should be two-letter ISO 639 language code + language = QLatin1String(locale.left(2)); + } + } + return language; +} + +void QXIMInputContext::reset() +{ + QWidget *w = focusWidget(); + if (!w) + return; + + ICData *data = ximData.value(w->effectiveWinId()); + if (!data) + return; + + if (data->ic) { + char *mb = XmbResetIC(data->ic); + QInputMethodEvent e; + if (mb) { + e.setCommitString(QString::fromLocal8Bit(mb)); + XFree(mb); + data->preeditEmpty = false; // force sending an event + } + if (!data->preeditEmpty) { + sendEvent(e); + update(); + } + } + data->clear(); +} + +void QXIMInputContext::widgetDestroyed(QWidget *w) +{ + QInputContext::widgetDestroyed(w); + ICData *data = ximData.take(w->effectiveWinId()); + if (!data) + return; + + data->clear(); + if (data->ic) + XDestroyIC(data->ic); + delete data; +} + +void QXIMInputContext::mouseHandler(int pos, QMouseEvent *e) +{ + if(e->type() != QEvent::MouseButtonPress) + return; + + XIM_DEBUG("QXIMInputContext::mouseHandler pos=%d", pos); + if (QWidget *w = focusWidget()) { + ICData *data = ximData.value(w->effectiveWinId()); + if (!data) + return; + if (pos < 0 || pos > data->text.length()) + reset(); + // ##### handle mouse position + } +} + +bool QXIMInputContext::isComposing() const +{ + QWidget *w = focusWidget(); + if (!w) + return false; + + ICData *data = ximData.value(w->effectiveWinId()); + if (!data) + return false; + return data->composing; +} + +void QXIMInputContext::setFocusWidget(QWidget *w) +{ + if (!xim) + return; + QWidget *oldFocus = focusWidget(); + if (oldFocus == w) + return; + + if (language() != QLatin1String("ja")) + reset(); + + if (oldFocus) { + ICData *data = ximData.value(oldFocus->effectiveWinId()); + if (data && data->ic) + XUnsetICFocus(data->ic); + } + + QInputContext::setFocusWidget(w); + + if (!w || w->inputMethodHints() & (Qt::ImhExclusiveInputMask | Qt::ImhHiddenText)) + return; + + ICData *data = ximData.value(w->effectiveWinId()); + if (!data) + data = createICData(w); + + if (data->ic) + XSetICFocus(data->ic); + + update(); +} + + +bool QXIMInputContext::x11FilterEvent(QWidget *keywidget, XEvent *event) +{ + int xkey_keycode = event->xkey.keycode; + if (!keywidget->testAttribute(Qt::WA_WState_Created)) + return false; + if (XFilterEvent(event, keywidget->effectiveWinId())) { + qt_ximComposingKeycode = xkey_keycode; // ### not documented in xlib + + update(); + + return true; + } + if (event->type != XKeyPress || event->xkey.keycode != 0) + return false; + + QWidget *w = focusWidget(); + if (keywidget != w) + return false; + ICData *data = ximData.value(w->effectiveWinId()); + if (!data) + return false; + + // input method has sent us a commit string + QByteArray string; + string.resize(513); + KeySym key; // unused + Status status; // unused + QString text; + int count = XmbLookupString(data->ic, &event->xkey, string.data(), string.size(), + &key, &status); + + if (status == XBufferOverflow) { + string.resize(count + 1); + count = XmbLookupString(data->ic, &event->xkey, string.data(), string.size(), + &key, &status); + } + if (count > 0) { + // XmbLookupString() gave us some text, convert it to unicode + text = qt_input_mapper->toUnicode(string.constData() , count); + if (text.isEmpty()) { + // codec couldn't convert to unicode? this can happen when running in the + // C locale (or with no LANG set). try converting from latin-1 + text = QString::fromLatin1(string.constData(), count); + } + } + +#if 0 + if (!(xim_style & XIMPreeditCallbacks) || !isComposing()) { + // ############### send a regular key event here! + ; + } +#endif + + QInputMethodEvent e; + e.setCommitString(text); + sendEvent(e); + data->clear(); + + update(); + + return true; +} + + +QXIMInputContext::ICData *QXIMInputContext::createICData(QWidget *w) +{ + ICData *data = new ICData; + data->widget = w; + data->preeditEmpty = true; + + XVaNestedList preedit_attr = 0; + XIMCallback startcallback, drawcallback, donecallback; + + QFont font = w->font(); + data->fontset = getFontSet(font); + + if (xim_style & XIMPreeditArea) { + XRectangle rect; + rect.x = 0; + rect.y = 0; + rect.width = w->width(); + rect.height = w->height(); + + preedit_attr = XVaCreateNestedList(0, + XNArea, &rect, + XNFontSet, data->fontset, + (char *) 0); + } else if (xim_style & XIMPreeditPosition) { + XPoint spot; + spot.x = 1; + spot.y = 1; + + preedit_attr = XVaCreateNestedList(0, + XNSpotLocation, &spot, + XNFontSet, data->fontset, + (char *) 0); + } else if (xim_style & XIMPreeditCallbacks) { + startcallback.client_data = (XPointer) this; + startcallback.callback = (XIMProc) xic_start_callback; + drawcallback.client_data = (XPointer) this; + drawcallback.callback = (XIMProc)xic_draw_callback; + donecallback.client_data = (XPointer) this; + donecallback.callback = (XIMProc) xic_done_callback; + + preedit_attr = XVaCreateNestedList(0, + XNPreeditStartCallback, &startcallback, + XNPreeditDrawCallback, &drawcallback, + XNPreeditDoneCallback, &donecallback, + (char *) 0); + } + + if (preedit_attr) { + data->ic = XCreateIC(xim, + XNInputStyle, xim_style, + XNClientWindow, w->effectiveWinId(), + XNPreeditAttributes, preedit_attr, + (char *) 0); + XFree(preedit_attr); + } else { + data->ic = XCreateIC(xim, + XNInputStyle, xim_style, + XNClientWindow, w->effectiveWinId(), + (char *) 0); + } + + if (data->ic) { + // when resetting the input context, preserve the input state + (void) XSetICValues(data->ic, XNResetState, XIMPreserveState, (char *) 0); + } else { + qWarning("Failed to create XIC"); + } + + ximData[w->effectiveWinId()] = data; + return data; +} + +void QXIMInputContext::update() +{ + QWidget *w = focusWidget(); + if (!w) + return; + + ICData *data = ximData.value(w->effectiveWinId()); + if (!data || !data->ic) + return; + + QRect r = w->inputMethodQuery(Qt::ImMicroFocus).toRect(); + QPoint p; + if (w->nativeParentWidget()) + p = w->mapTo(w->nativeParentWidget(), QPoint((r.left() + r.right() + 1)/2, r.bottom())); + else + p = QPoint((r.left() + r.right() + 1)/2, r.bottom()); + XPoint spot; + spot.x = p.x(); + spot.y = p.y(); + + r = w->rect(); + XRectangle area; + area.x = r.x(); + area.y = r.y(); + area.width = r.width(); + area.height = r.height(); + + XFontSet fontset = getFontSet(qvariant_cast<QFont>(w->inputMethodQuery(Qt::ImFont))); + if (data->fontset == fontset) + fontset = 0; + else + data->fontset = fontset; + + XVaNestedList preedit_attr; + if (fontset) + preedit_attr = XVaCreateNestedList(0, + XNSpotLocation, &spot, + XNArea, &area, + XNFontSet, fontset, + (char *) 0); + else + preedit_attr = XVaCreateNestedList(0, + XNSpotLocation, &spot, + XNArea, &area, + (char *) 0); + + XSetICValues(data->ic, XNPreeditAttributes, preedit_attr, (char *) 0); + XFree(preedit_attr); +} + + +#else +/* + When QT_NO_XIM is defined, we provide a dummy implementation for + this class. The reason for this is that the header file is moc'ed + regardless of QT_NO_XIM. The best would be to remove the file + completely from the pri file is QT_NO_XIM was defined, or for moc + to understand this preprocessor directive. Since the header does + not declare this class when QT_NO_XIM is defined, this is dead + code. +*/ +bool QXIMInputContext::isComposing() const { return false; } +QString QXIMInputContext::identifierName() { return QString(); } +void QXIMInputContext::mouseHandler(int, QMouseEvent *) {} +void QXIMInputContext::setFocusWidget(QWidget *) {} +void QXIMInputContext::reset() {} +void QXIMInputContext::update() {} +QXIMInputContext::~QXIMInputContext() {} +void QXIMInputContext::widgetDestroyed(QWidget *) {} +QString QXIMInputContext::language() { return QString(); } +bool QXIMInputContext::x11FilterEvent(QWidget *, XEvent *) { return true; } + +#endif //QT_NO_XIM + +QT_END_NAMESPACE + +#endif //QT_NO_IM |