diff options
author | Tasuku Suzuki <tasuku.suzuki@nokia.com> | 2012-02-29 13:32:20 +0900 |
---|---|---|
committer | Qt by Nokia <qt-info@nokia.com> | 2012-03-12 21:38:51 +0100 |
commit | 412dbdf410c765e75c60d1f48143dd6c02a69493 (patch) | |
tree | 565cfebe0ad28bc378479093ae84e54e464a3764 | |
parent | 01e8e9136dfa5d3260de0d318da6aece7214f85a (diff) |
Input method on Mac
Restore input method implimentation in Qt4
Task-number: QTBUG-23867
Change-Id: I5d405ccc8b0a73c399d992f6474a0cc38d191157
Reviewed-by: Morten Johan Sørvig <morten.sorvig@nokia.com>
-rw-r--r-- | src/plugins/platforms/cocoa/cocoa.pro | 2 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoahelpers.h | 2 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoahelpers.mm | 19 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoainputcontext.h | 70 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoainputcontext.mm | 122 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoaintegration.h | 2 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoaintegration.mm | 7 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoanativeinterface.h | 1 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qnsview.h | 4 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qnsview.mm | 233 |
10 files changed, 457 insertions, 5 deletions
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 <QtGui/QPlatformInputContext> +#include <QtCore/QPointer> + +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<QWindow> 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 <QtCore/QRect> +#include <QtGui/QGuiApplication> +#include <QtGui/QWindow> + +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<QCocoaNativeInterface *>(QGuiApplication::platformNativeInterface()); + if (!nativeInterface) return; + + QNSView *view = static_cast<QNSView *>(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<QPlatformFontDatabase> mFontDb; QAbstractEventDispatcher *mEventDispatcher; + QScopedPointer<QPlatformInputContext> mInputContext; QScopedPointer<QPlatformAccessibility> mAccessibility; QScopedPointer<QPlatformTheme> mPlatformTheme; QList<QCocoaScreen *> 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 <QtGui/qplatformaccessibility_qpa.h> @@ -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 <NSTextInput> { 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 <QtGui/QWindowSystemInterface> +#include <QtGui/QTextFormat> #include <QtCore/QDebug> #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<Qt::InputMethodHints>(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<CFStringRef>([aString string])); + } else { + commitString = QCFString::toQString(reinterpret_cast<CFStringRef>(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<QInputMethodEvent::Attribute> attrs; + attrs<<QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, selRange.location + selRange.length, 1, QVariant()); + + if ([aString isKindOfClass:[NSAttributedString class]]) { + // Preedit string has attribution + preeditString = QCFString::toQString(reinterpret_cast<CFStringRef>([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<<QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, + effectiveRange.location, + effectiveRange.length, + format); + } + index = effectiveRange.location + effectiveRange.length; + } + } else { + // No attributes specified, take only the preedit text. + preeditString = QCFString::toQString(reinterpret_cast<CFStringRef>(aString)); + } + + if (attrs.isEmpty()) { + QTextCharFormat format; + format.setFontUnderline(true); + attrs<<QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, + 0, preeditString.length(), format); + } + + m_composingText = preeditString; + + QObject *fo = QGuiApplication::focusObject(); + if (fo) { + QInputMethodEvent e(preeditString, attrs); + QCoreApplication::sendEvent(fo, &e); + m_keyEventsAccepted = true; + } +} + +- (void) unmarkText +{ + if (!m_composingText.isEmpty()) { + QObject *fo = QGuiApplication::focusObject(); + if (fo) { + QInputMethodEvent e; + e.setCommitString(m_composingText); + QCoreApplication::sendEvent(fo, &e); + } + } + m_composingText.clear(); +} + +- (BOOL) hasMarkedText +{ + return (m_composingText.isEmpty() ? NO: YES); +} + +- (NSInteger) conversationIdentifier +{ + return (NSInteger)self; +} + +- (NSAttributedString *) attributedSubstringFromRange:(NSRange)theRange +{ + QObject *fo = QGuiApplication::focusObject(); + if (!fo) + return nil; + QInputMethodQueryEvent queryEvent(Qt::ImCurrentSelection); + if (!QCoreApplication::sendEvent(fo, &queryEvent)) + return nil; + + QString selectedText = queryEvent.value(Qt::ImCurrentSelection).toString(); + if (selectedText.isEmpty()) + return nil; + + QCFString string(selectedText.mid(theRange.location, theRange.length)); + const NSString *tmpString = reinterpret_cast<const NSString *>((CFStringRef)string); + return [[[NSAttributedString alloc] initWithString:const_cast<NSString *>(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 |