From 412dbdf410c765e75c60d1f48143dd6c02a69493 Mon Sep 17 00:00:00 2001 From: Tasuku Suzuki Date: Wed, 29 Feb 2012 13:32:20 +0900 Subject: Input method on Mac MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restore input method implimentation in Qt4 Task-number: QTBUG-23867 Change-Id: I5d405ccc8b0a73c399d992f6474a0cc38d191157 Reviewed-by: Morten Johan Sørvig --- src/plugins/platforms/cocoa/cocoa.pro | 2 + src/plugins/platforms/cocoa/qcocoahelpers.h | 2 + src/plugins/platforms/cocoa/qcocoahelpers.mm | 19 ++ src/plugins/platforms/cocoa/qcocoainputcontext.h | 70 +++++++ src/plugins/platforms/cocoa/qcocoainputcontext.mm | 122 +++++++++++ src/plugins/platforms/cocoa/qcocoaintegration.h | 2 + src/plugins/platforms/cocoa/qcocoaintegration.mm | 7 + .../platforms/cocoa/qcocoanativeinterface.h | 1 + src/plugins/platforms/cocoa/qnsview.h | 4 +- src/plugins/platforms/cocoa/qnsview.mm | 233 ++++++++++++++++++++- 10 files changed, 457 insertions(+), 5 deletions(-) create mode 100644 src/plugins/platforms/cocoa/qcocoainputcontext.h create mode 100644 src/plugins/platforms/cocoa/qcocoainputcontext.mm (limited to 'src/plugins/platforms/cocoa') diff --git a/src/plugins/platforms/cocoa/cocoa.pro b/src/plugins/platforms/cocoa/cocoa.pro index b953210720..bd70a415d0 100644 --- a/src/plugins/platforms/cocoa/cocoa.pro +++ b/src/plugins/platforms/cocoa/cocoa.pro @@ -31,6 +31,7 @@ OBJECTIVE_SOURCES += main.mm \ qmacclipboard.mm \ qmacmime.mm \ qcocoasystemsettings.mm \ + qcocoainputcontext.mm \ HEADERS += qcocoaintegration.h \ qcocoatheme.h \ @@ -59,6 +60,7 @@ HEADERS += qcocoaintegration.h \ qmacclipboard.h \ qmacmime.h \ qcocoasystemsettings.h \ + qcocoainputcontext.h \ FORMS += $$PWD/../../../widgets/dialogs/qfiledialog.ui RESOURCES += qcocoaresources.qrc diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.h b/src/plugins/platforms/cocoa/qcocoahelpers.h index 3e3e8fa507..e5fe664731 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.h +++ b/src/plugins/platforms/cocoa/qcocoahelpers.h @@ -74,6 +74,8 @@ NSImage *qt_mac_create_nsimage(const QPixmap &pm); NSSize qt_mac_toNSSize(const QSize &qtSize); +QColor qt_mac_toQColor(const NSColor *color); + QChar qt_mac_qtKey2CocoaKey(Qt::Key key); Qt::Key qt_mac_cocoaKey2QtKey(QChar keyCode); diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm index ec4399b66c..e41ddf4a9f 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.mm +++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm @@ -137,6 +137,25 @@ NSSize qt_mac_toNSSize(const QSize &qtSize) return NSMakeSize(qtSize.width(), qtSize.height()); } +QColor qt_mac_toQColor(const NSColor *color) +{ + QColor qtColor; + NSString *colorSpace = [color colorSpaceName]; + if (colorSpace == NSDeviceCMYKColorSpace) { + CGFloat cyan, magenta, yellow, black, alpha; + [color getCyan:&cyan magenta:&magenta yellow:&yellow black:&black alpha:&alpha]; + qtColor.setCmykF(cyan, magenta, yellow, black, alpha); + } else { + NSColor *tmpColor; + tmpColor = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + CGFloat red, green, blue, alpha; + [tmpColor getRed:&red green:&green blue:&blue alpha:&alpha]; + qtColor.setRgbF(red, green, blue, alpha); + } + return qtColor; +} + + // Use this method to keep all the information in the TextSegment. As long as it is ordered // we are in OK shape, and we can influence that ourselves. struct KeyPair diff --git a/src/plugins/platforms/cocoa/qcocoainputcontext.h b/src/plugins/platforms/cocoa/qcocoainputcontext.h new file mode 100644 index 0000000000..172c87e2dc --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoainputcontext.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOCOAINPUTCONTEXT_H +#define QCOCOAINPUTCONTEXT_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QCocoaInputContext : public QPlatformInputContext +{ + Q_OBJECT +public: + explicit QCocoaInputContext(); + ~QCocoaInputContext(); + + virtual bool isValid() const { return true; } + + virtual void reset(); + +private Q_SLOTS: + void inputItemChanged(); + +private: + QPointer mWindow; +}; + +QT_END_NAMESPACE + +#endif // QCOCOAINPUTCONTEXT_H diff --git a/src/plugins/platforms/cocoa/qcocoainputcontext.mm b/src/plugins/platforms/cocoa/qcocoainputcontext.mm new file mode 100644 index 0000000000..db3488a0f5 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoainputcontext.mm @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnsview.h" +#include "qcocoainputcontext.h" +#include "qcocoanativeinterface.h" +#include "qcocoaautoreleasepool.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QCocoaInputContext + \brief Cocoa Input context implementation + + Handles input of foreign characters (particularly East Asian) + languages. + + \section1 Testing + + \list + \o Select input sources like 'Kotoeri' in Language & Text Preferences + \o Compile the \a mainwindows/mdi example and open a text window. + \o In the language bar, switch to 'Hiragana'. + \o In a text editor control, type the syllable \a 'la'. + Underlined characters show up, indicating that there is completion + available. Press the Space key two times. A completion popup occurs + which shows the options. + \endlist + + \section1 Interaction + + Input method support in Cocoa uses NSTextInput protorol. Therefore + almost all functionality is implemented in QNSView. + + \ingroup qt-lighthouse-cocoa +*/ + + + +QCocoaInputContext::QCocoaInputContext() + : QPlatformInputContext() + , mWindow(QGuiApplication::focusWindow()) +{ + connect(qApp->inputMethod(), SIGNAL(inputItemChanged()), this, SLOT(inputItemChanged())); +} + +QCocoaInputContext::~QCocoaInputContext() +{ +} + +/*! + \brief Cancels a composition. +*/ + +void QCocoaInputContext::reset() +{ + QPlatformInputContext::reset(); + + if (!mWindow) return; + + QCocoaNativeInterface *nativeInterface = qobject_cast(QGuiApplication::platformNativeInterface()); + if (!nativeInterface) return; + + QNSView *view = static_cast(nativeInterface->nativeResourceForWindow("nsview", mWindow)); + if (!view) return; + + QCocoaAutoReleasePool pool; + NSInputManager *currentIManager = [NSInputManager currentInputManager]; + if (currentIManager) { + [currentIManager markedTextAbandoned:view]; + [view unmarkText]; + } +} + +void QCocoaInputContext::inputItemChanged() +{ + mWindow = QGuiApplication::focusWindow(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.h b/src/plugins/platforms/cocoa/qcocoaintegration.h index bf54915365..2389fc2a55 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.h +++ b/src/plugins/platforms/cocoa/qcocoaintegration.h @@ -89,6 +89,7 @@ public: QPlatformFontDatabase *fontDatabase() const; QPlatformNativeInterface *nativeInterface() const; + QPlatformInputContext *inputContext() const; QPlatformAccessibility *accessibility() const; QPlatformDrag *drag() const; @@ -98,6 +99,7 @@ private: QScopedPointer mFontDb; QAbstractEventDispatcher *mEventDispatcher; + QScopedPointer mInputContext; QScopedPointer mAccessibility; QScopedPointer mPlatformTheme; QList mScreens; diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm index 8411a795c1..d490495be4 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.mm +++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm @@ -52,6 +52,7 @@ #include "qmenu_mac.h" #include "qcocoafiledialoghelper.h" #include "qcocoatheme.h" +#include "qcocoainputcontext.h" #include "qmacmime.h" #include @@ -90,6 +91,7 @@ QCocoaScreen::~QCocoaScreen() QCocoaIntegration::QCocoaIntegration() : mFontDb(new QCoreTextFontDatabase()) , mEventDispatcher(new QCocoaEventDispatcher()) + , mInputContext(new QCocoaInputContext) , mAccessibility(new QPlatformAccessibility) , mPlatformTheme(new QCocoaTheme) , mCocoaDrag(new QCocoaDrag) @@ -195,6 +197,11 @@ QPlatformNativeInterface *QCocoaIntegration::nativeInterface() const return new QCocoaNativeInterface(); } +QPlatformInputContext *QCocoaIntegration::inputContext() const +{ + return mInputContext.data(); +} + QPlatformAccessibility *QCocoaIntegration::accessibility() const { return mAccessibility.data(); diff --git a/src/plugins/platforms/cocoa/qcocoanativeinterface.h b/src/plugins/platforms/cocoa/qcocoanativeinterface.h index 7c6fb38577..d277cb2964 100644 --- a/src/plugins/platforms/cocoa/qcocoanativeinterface.h +++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.h @@ -48,6 +48,7 @@ class QWidget; class QCocoaNativeInterface : public QPlatformNativeInterface { + Q_OBJECT public: void *nativeResourceForWindow(const QByteArray &resourceString, QWindow *window); }; diff --git a/src/plugins/platforms/cocoa/qnsview.h b/src/plugins/platforms/cocoa/qnsview.h index 1a1a1cd3b9..b21e9e342f 100644 --- a/src/plugins/platforms/cocoa/qnsview.h +++ b/src/plugins/platforms/cocoa/qnsview.h @@ -51,12 +51,14 @@ QT_BEGIN_NAMESPACE class QCocoaWindow; QT_END_NAMESPACE -@interface QNSView : NSView { +@interface QNSView : NSView { CGImageRef m_cgImage; QWindow *m_window; QCocoaWindow *m_platformWindow; Qt::MouseButtons m_buttons; QAccessibleInterface *m_accessibleRoot; + QString m_composingText; + bool m_keyEventsAccepted; QStringList *currentCustomDragTypes; } diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index 9ed3332ba5..6206d53423 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -51,6 +51,7 @@ #include "qcocoadrag.h" #include +#include #include #ifdef QT_COCOA_ENABLE_ACCESSIBILITY_INSPECTOR @@ -94,6 +95,7 @@ static QTouchDevice *touchDevice = 0; m_window = window; m_platformWindow = platformWindow; m_accessibleRoot = 0; + m_keyEventsAccepted = false; #ifdef QT_COCOA_ENABLE_ACCESSIBILITY_INSPECTOR // prevent rift in space-time continuum, disable @@ -272,8 +274,15 @@ static QTouchDevice *touchDevice = 0; - (void)mouseDown:(NSEvent *)theEvent { - m_buttons |= Qt::LeftButton; - [self handleMouseEvent:theEvent]; + if ([self hasMarkedText]) { + NSInputManager* inputManager = [NSInputManager currentInputManager]; + if ([inputManager wantsToHandleMouseEvents]) { + [inputManager handleMouseEvent:theEvent]; + } + } else { + m_buttons |= Qt::LeftButton; + [self handleMouseEvent:theEvent]; + } } - (void)mouseDragged:(NSEvent *)theEvent @@ -467,12 +476,228 @@ static QTouchDevice *touchDevice = 0; - (void)keyDown:(NSEvent *)theEvent { - [self handleKeyEvent : theEvent eventType :int(QEvent::KeyPress)]; + QObject *fo = QGuiApplication::focusObject(); + m_keyEventsAccepted = false; + if (fo) { + QInputMethodQueryEvent queryEvent(Qt::ImHints); + if (QCoreApplication::sendEvent(fo, &queryEvent)) { + Qt::InputMethodHints hints = static_cast(queryEvent.value(Qt::ImHints).toUInt()); + if (!(hints & Qt::ImhDigitsOnly || hints & Qt::ImhFormattedNumbersOnly || hints & Qt::ImhHiddenText)) { + [self interpretKeyEvents:[NSArray arrayWithObject: theEvent]]; + } + } + } + + if (!m_keyEventsAccepted && m_composingText.isEmpty()) { + [self handleKeyEvent : theEvent eventType :int(QEvent::KeyPress)]; + } } - (void)keyUp:(NSEvent *)theEvent { - [self handleKeyEvent : theEvent eventType :int(QEvent::KeyRelease)]; + if (!m_keyEventsAccepted && m_composingText.isEmpty()) { + [self handleKeyEvent : theEvent eventType :int(QEvent::KeyRelease)]; + } +} + +- (void) doCommandBySelector:(SEL)aSelector +{ + [self tryToPerform:aSelector with:self]; +} + +- (void) insertText:(id)aString +{ + QString commitString; + if ([aString length]) { + if ([aString isKindOfClass:[NSAttributedString class]]) { + commitString = QCFString::toQString(reinterpret_cast([aString string])); + } else { + commitString = QCFString::toQString(reinterpret_cast(aString)); + }; + } + QObject *fo = QGuiApplication::focusObject(); + if (fo) { + QInputMethodEvent e; + e.setCommitString(commitString); + QCoreApplication::sendEvent(fo, &e); + m_keyEventsAccepted = true; + } + + m_composingText.clear(); + } + +- (void) setMarkedText:(id)aString selectedRange:(NSRange)selRange +{ + QString preeditString; + + QList attrs; + attrs<([aString string])); + int composingLength = preeditString.length(); + int index = 0; + // Create attributes for individual sections of preedit text + while (index < composingLength) { + NSRange effectiveRange; + NSRange range = NSMakeRange(index, composingLength-index); + NSDictionary *attributes = [aString attributesAtIndex:index + longestEffectiveRange:&effectiveRange + inRange:range]; + NSNumber *underlineStyle = [attributes objectForKey:NSUnderlineStyleAttributeName]; + if (underlineStyle) { + QColor clr (Qt::black); + NSColor *color = [attributes objectForKey:NSUnderlineColorAttributeName]; + if (color) { + clr = qt_mac_toQColor(color); + } + QTextCharFormat format; + format.setFontUnderline(true); + format.setUnderlineColor(clr); + attrs<(aString)); + } + + if (attrs.isEmpty()) { + QTextCharFormat format; + format.setFontUnderline(true); + attrs<((CFStringRef)string); + return [[[NSAttributedString alloc] initWithString:const_cast(tmpString)] autorelease]; +} + +- (NSRange) markedRange +{ + NSRange range; + if (!m_composingText.isEmpty()) { + range.location = 0; + range.length = m_composingText.length(); + } else { + range.location = NSNotFound; + range.length = 0; + } + return range; +} + + +- (NSRange) selectedRange +{ + NSRange selRange = {NSNotFound, 0}; + selRange.location = NSNotFound; + selRange.length = 0; + + QObject *fo = QGuiApplication::focusObject(); + if (!fo) + return selRange; + QInputMethodQueryEvent queryEvent(Qt::ImCurrentSelection); + if (!QCoreApplication::sendEvent(fo, &queryEvent)) + return selRange; + QString selectedText = queryEvent.value(Qt::ImCurrentSelection).toString(); + + if (!selectedText.isEmpty()) { + selRange.location = 0; + selRange.length = selectedText.length(); + } + return selRange; +} + +- (NSRect) firstRectForCharacterRange:(NSRange)theRange +{ + Q_UNUSED(theRange); + QObject *fo = QGuiApplication::focusObject(); + if (!fo) + return NSZeroRect; + + if (!m_window) + return NSZeroRect; + + // The returned rect is always based on the internal cursor. + QRect mr = qApp->inputMethod()->cursorRectangle().toRect(); + QPoint mp = m_window->mapToGlobal(mr.bottomLeft()); + + NSRect rect; + rect.origin.x = mp.x(); + rect.origin.y = qt_mac_flipYCoordinate(mp.y()); + rect.size.width = mr.width(); + rect.size.height = mr.height(); + return rect; +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint +{ + // We dont support cursor movements using mouse while composing. + Q_UNUSED(thePoint); + return NSNotFound; +} + +- (NSArray*) validAttributesForMarkedText +{ + QObject *fo = QGuiApplication::focusObject(); + if (!fo) + return nil; + + // Support only underline color/style. + return [NSArray arrayWithObjects:NSUnderlineColorAttributeName, + NSUnderlineStyleAttributeName, nil]; } -(void)registerDragTypes -- cgit v1.2.3