2009-10-01 02:52:50 +00:00
|
|
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set ts=2 sw=2 et tw=80: */
|
|
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
|
|
*
|
|
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
|
|
* the License. You may obtain a copy of the License at
|
|
|
|
* http://www.mozilla.org/MPL/
|
|
|
|
*
|
|
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
|
|
* for the specific language governing rights and limitations under the
|
|
|
|
* License.
|
|
|
|
*
|
|
|
|
* The Original Code is mozilla.org code.
|
|
|
|
*
|
|
|
|
* The Initial Developer of the Original Code is
|
|
|
|
* Mozilla Japan.
|
|
|
|
* Portions created by the Initial Developer are Copyright (C) 2009
|
|
|
|
* the Initial Developer. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Contributor(s):
|
|
|
|
* Masayuki Nakano <masayuki@d-toybox.com>
|
|
|
|
*
|
|
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
|
|
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
|
|
|
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
|
|
* the provisions above, a recipient may use your version of this file under
|
|
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
|
|
*
|
|
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
|
2011-05-08 10:19:15 +00:00
|
|
|
#include "TextInputHandler.h"
|
2009-10-01 02:52:50 +00:00
|
|
|
|
|
|
|
#include "nsChildView.h"
|
|
|
|
#include "nsObjCExceptions.h"
|
2009-10-16 09:12:09 +00:00
|
|
|
#include "nsBidiUtils.h"
|
2009-10-01 02:52:50 +00:00
|
|
|
|
|
|
|
#ifdef MOZ_LOGGING
|
|
|
|
#define FORCE_PR_LOG
|
|
|
|
#endif
|
|
|
|
#include "prlog.h"
|
|
|
|
|
2011-05-08 10:19:15 +00:00
|
|
|
using namespace mozilla::widget;
|
|
|
|
|
2009-10-01 02:52:50 +00:00
|
|
|
#undef DEBUG_IME_HANDLER
|
|
|
|
#undef DEBUG_TEXT_INPUT_HANDLER
|
|
|
|
//#define DEBUG_IME_HANDLER 1
|
|
|
|
//#define DEBUG_TEXT_INPUT_HANDLER 1
|
|
|
|
|
2011-02-19 04:55:49 +00:00
|
|
|
// TODO: static methods should be moved to nsCocoaUtils
|
|
|
|
|
2009-10-01 02:52:50 +00:00
|
|
|
static void
|
|
|
|
GetStringForNSString(const NSString *aSrc, nsAString& aDist)
|
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
|
|
if (!aSrc) {
|
|
|
|
aDist.Truncate();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
aDist.SetLength([aSrc length]);
|
|
|
|
[aSrc getCharacters: aDist.BeginWriting()];
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
|
|
}
|
|
|
|
|
2011-02-19 04:55:49 +00:00
|
|
|
static NSString* ToNSString(const nsAString& aString)
|
|
|
|
{
|
|
|
|
return [NSString stringWithCharacters:aString.BeginReading()
|
|
|
|
length:aString.Length()];
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
GeckoRectToNSRect(const nsIntRect& inGeckoRect, NSRect& outCocoaRect)
|
|
|
|
{
|
|
|
|
outCocoaRect.origin.x = inGeckoRect.x;
|
|
|
|
outCocoaRect.origin.y = inGeckoRect.y;
|
|
|
|
outCocoaRect.size.width = inGeckoRect.width;
|
|
|
|
outCocoaRect.size.height = inGeckoRect.height;
|
|
|
|
}
|
|
|
|
|
2009-10-01 02:52:50 +00:00
|
|
|
#ifdef DEBUG_IME_HANDLER
|
|
|
|
|
|
|
|
static const char*
|
|
|
|
TrueOrFalse(PRBool aBool)
|
|
|
|
{
|
|
|
|
return aBool ? "TRUE" : "FALSE";
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2011-05-08 10:19:15 +00:00
|
|
|
DebugPrintPointer(IMEInputHandler* aHandler)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
2011-05-08 10:19:15 +00:00
|
|
|
static IMEInputHandler* sLastHandler = nsnull;
|
2009-10-01 02:52:50 +00:00
|
|
|
if (aHandler == sLastHandler)
|
|
|
|
return;
|
|
|
|
sLastHandler = aHandler;
|
|
|
|
NSLog(@"%8p ************************************************", aHandler);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char*
|
|
|
|
GetWindowLevelName(NSInteger aWindowLevel)
|
|
|
|
{
|
|
|
|
switch (aWindowLevel) {
|
|
|
|
case kCGBaseWindowLevelKey:
|
|
|
|
return "kCGBaseWindowLevelKey (NSNormalWindowLevel)";
|
|
|
|
case kCGMinimumWindowLevelKey:
|
|
|
|
return "kCGMinimumWindowLevelKey";
|
|
|
|
case kCGDesktopWindowLevelKey:
|
|
|
|
return "kCGDesktopWindowLevelKey";
|
|
|
|
case kCGBackstopMenuLevelKey:
|
|
|
|
return "kCGBackstopMenuLevelKey";
|
|
|
|
case kCGNormalWindowLevelKey:
|
|
|
|
return "kCGNormalWindowLevelKey";
|
|
|
|
case kCGFloatingWindowLevelKey:
|
|
|
|
return "kCGFloatingWindowLevelKey (NSFloatingWindowLevel)";
|
|
|
|
case kCGTornOffMenuWindowLevelKey:
|
|
|
|
return "kCGTornOffMenuWindowLevelKey (NSSubmenuWindowLevel, NSTornOffMenuWindowLevel)";
|
|
|
|
case kCGDockWindowLevelKey:
|
|
|
|
return "kCGDockWindowLevelKey (NSDockWindowLevel)";
|
|
|
|
case kCGMainMenuWindowLevelKey:
|
|
|
|
return "kCGMainMenuWindowLevelKey (NSMainMenuWindowLevel)";
|
|
|
|
case kCGStatusWindowLevelKey:
|
|
|
|
return "kCGStatusWindowLevelKey (NSStatusWindowLevel)";
|
|
|
|
case kCGModalPanelWindowLevelKey:
|
|
|
|
return "kCGModalPanelWindowLevelKey (NSModalPanelWindowLevel)";
|
|
|
|
case kCGPopUpMenuWindowLevelKey:
|
|
|
|
return "kCGPopUpMenuWindowLevelKey (NSPopUpMenuWindowLevel)";
|
|
|
|
case kCGDraggingWindowLevelKey:
|
|
|
|
return "kCGDraggingWindowLevelKey";
|
|
|
|
case kCGScreenSaverWindowLevelKey:
|
|
|
|
return "kCGScreenSaverWindowLevelKey (NSScreenSaverWindowLevel)";
|
|
|
|
case kCGMaximumWindowLevelKey:
|
|
|
|
return "kCGMaximumWindowLevelKey";
|
|
|
|
case kCGOverlayWindowLevelKey:
|
|
|
|
return "kCGOverlayWindowLevelKey";
|
|
|
|
case kCGHelpWindowLevelKey:
|
|
|
|
return "kCGHelpWindowLevelKey";
|
|
|
|
case kCGUtilityWindowLevelKey:
|
|
|
|
return "kCGUtilityWindowLevelKey";
|
|
|
|
case kCGDesktopIconWindowLevelKey:
|
|
|
|
return "kCGDesktopIconWindowLevelKey";
|
|
|
|
case kCGCursorWindowLevelKey:
|
|
|
|
return "kCGCursorWindowLevelKey";
|
|
|
|
case kCGNumberOfWindowLevelKeys:
|
|
|
|
return "kCGNumberOfWindowLevelKeys";
|
|
|
|
default:
|
|
|
|
return "unknown window level";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // DEBUG_IME_HANDLER
|
|
|
|
|
2009-10-16 09:12:09 +00:00
|
|
|
static PRUint32 gHandlerInstanceCount = 0;
|
2011-05-08 10:19:15 +00:00
|
|
|
static TISInputSourceWrapper gCurrentKeyboardLayout;
|
2009-10-16 09:12:09 +00:00
|
|
|
|
|
|
|
static void
|
|
|
|
InitCurrentKeyboardLayout()
|
|
|
|
{
|
|
|
|
if (gHandlerInstanceCount > 0 &&
|
|
|
|
!gCurrentKeyboardLayout.IsInitializedByCurrentKeyboardLayout()) {
|
|
|
|
gCurrentKeyboardLayout.InitByCurrentKeyboardLayout();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
FinalizeCurrentKeyboardLayout()
|
|
|
|
{
|
|
|
|
gCurrentKeyboardLayout.Clear();
|
|
|
|
}
|
|
|
|
|
2009-10-01 02:52:50 +00:00
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
*
|
2011-05-08 10:19:15 +00:00
|
|
|
* TISInputSourceWrapper implementation
|
2009-10-01 02:52:50 +00:00
|
|
|
*
|
|
|
|
******************************************************************************/
|
|
|
|
|
2009-10-16 09:12:09 +00:00
|
|
|
// static
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper&
|
|
|
|
TISInputSourceWrapper::CurrentKeyboardLayout()
|
2009-10-16 09:12:09 +00:00
|
|
|
{
|
|
|
|
InitCurrentKeyboardLayout();
|
|
|
|
return gCurrentKeyboardLayout;
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
PRBool
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::UCKeyTranslateToString(
|
|
|
|
const UCKeyboardLayout* aHandle,
|
|
|
|
UInt32 aKeyCode, UInt32 aModifiers,
|
|
|
|
UInt32 aKbType, nsAString &aStr)
|
2009-10-16 09:12:09 +00:00
|
|
|
{
|
|
|
|
#ifdef DEBUG_TEXT_INPUT_HANDLER
|
2011-05-08 10:19:15 +00:00
|
|
|
NSLog(@"**** TISInputSourceWrapper::UCKeyTranslateToString: aHandle: %p, aKeyCode: %X, aModifiers: %X, aKbType: %X",
|
2009-10-16 09:12:09 +00:00
|
|
|
aHandle, aKeyCode, aModifiers, aKbType);
|
|
|
|
PRBool isShift = aModifiers & shiftKey;
|
|
|
|
PRBool isCtrl = aModifiers & controlKey;
|
|
|
|
PRBool isOpt = aModifiers & optionKey;
|
|
|
|
PRBool isCmd = aModifiers & cmdKey;
|
|
|
|
PRBool isCL = aModifiers & alphaLock;
|
|
|
|
PRBool isNL = aModifiers & kEventKeyModifierNumLockMask;
|
|
|
|
NSLog(@" Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
|
|
|
|
isShift ? "ON" : "off", isCtrl ? "ON" : "off", isOpt ? "ON" : "off",
|
|
|
|
isCmd ? "ON" : "off", isCL ? "ON" : "off", isNL ? "ON" : "off");
|
|
|
|
#endif
|
|
|
|
NS_PRECONDITION(aStr.IsEmpty(), "aStr isn't empty");
|
|
|
|
UInt32 deadKeyState = 0;
|
|
|
|
UniCharCount len;
|
|
|
|
UniChar chars[5];
|
|
|
|
OSStatus err = ::UCKeyTranslate(aHandle, aKeyCode,
|
|
|
|
kUCKeyActionDown, aModifiers >> 8,
|
|
|
|
aKbType, kUCKeyTranslateNoDeadKeysMask,
|
|
|
|
&deadKeyState, 5, &len, chars);
|
|
|
|
if (err != noErr) {
|
|
|
|
return PR_FALSE;
|
|
|
|
}
|
|
|
|
if (len == 0) {
|
|
|
|
return PR_TRUE;
|
|
|
|
}
|
|
|
|
if (!EnsureStringLength(aStr, len)) {
|
|
|
|
return PR_FALSE;
|
|
|
|
}
|
|
|
|
NS_ASSERTION(sizeof(PRUnichar) == sizeof(UniChar),
|
|
|
|
"size of PRUnichar and size of UniChar are different");
|
|
|
|
memcpy(aStr.BeginWriting(), chars, len * sizeof(PRUnichar));
|
|
|
|
#ifdef DEBUG_TEXT_INPUT_HANDLER
|
|
|
|
for (PRUint32 i = 0; i < PRUint32(len); i++) {
|
|
|
|
NSLog(@" result: %X(%C)", chars[i], chars[i] > ' ' ? chars[i] : ' ');
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return PR_TRUE;
|
|
|
|
}
|
|
|
|
|
2009-10-01 02:52:50 +00:00
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::InitByInputSourceID(const char* aID)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
Clear();
|
|
|
|
if (!aID)
|
|
|
|
return;
|
|
|
|
|
|
|
|
CFStringRef idstr = ::CFStringCreateWithCString(kCFAllocatorDefault, aID,
|
|
|
|
kCFStringEncodingASCII);
|
|
|
|
InitByInputSourceID(idstr);
|
|
|
|
::CFRelease(idstr);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::InitByInputSourceID(const nsAFlatString &aID)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
Clear();
|
|
|
|
if (aID.IsEmpty())
|
|
|
|
return;
|
|
|
|
CFStringRef idstr = ::CFStringCreateWithCharacters(kCFAllocatorDefault,
|
|
|
|
aID.get(), aID.Length());
|
|
|
|
InitByInputSourceID(idstr);
|
|
|
|
::CFRelease(idstr);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::InitByInputSourceID(const CFStringRef aID)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
Clear();
|
|
|
|
if (!aID)
|
|
|
|
return;
|
|
|
|
const void* keys[] = { kTISPropertyInputSourceID };
|
|
|
|
const void* values[] = { aID };
|
|
|
|
CFDictionaryRef filter =
|
|
|
|
::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
|
|
|
|
NS_ASSERTION(filter, "failed to create the filter");
|
|
|
|
mInputSourceList = ::TISCreateInputSourceList(filter, true);
|
|
|
|
::CFRelease(filter);
|
|
|
|
if (::CFArrayGetCount(mInputSourceList) > 0) {
|
|
|
|
mInputSource = static_cast<TISInputSourceRef>(
|
|
|
|
const_cast<void *>(::CFArrayGetValueAtIndex(mInputSourceList, 0)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::InitByLayoutID(SInt32 aLayoutID)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
Clear();
|
|
|
|
// XXX On TIS, the LayoutID is abolished completely. Therefore, the numbers
|
|
|
|
// have no meaning. If you need to add other keyboard layouts for
|
|
|
|
// nsIWidget::SynthesizeNativeKeyEvent, you don't need to check the old
|
|
|
|
// resource, you can use any numbers.
|
|
|
|
switch (aLayoutID) {
|
|
|
|
case 0:
|
|
|
|
InitByInputSourceID("com.apple.keylayout.US");
|
|
|
|
break;
|
|
|
|
case -18944:
|
|
|
|
InitByInputSourceID("com.apple.keylayout.Greek");
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
InitByInputSourceID("com.apple.keylayout.German");
|
|
|
|
break;
|
|
|
|
case 224:
|
|
|
|
InitByInputSourceID("com.apple.keylayout.Swedish-Pro");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::InitByCurrentInputSource()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
Clear();
|
|
|
|
mInputSource = ::TISCopyCurrentKeyboardInputSource();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::InitByCurrentKeyboardLayout()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
Clear();
|
|
|
|
mInputSource = ::TISCopyCurrentKeyboardLayoutInputSource();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::InitByCurrentASCIICapableInputSource()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
Clear();
|
|
|
|
mInputSource = ::TISCopyCurrentASCIICapableKeyboardInputSource();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::InitByCurrentASCIICapableKeyboardLayout()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
Clear();
|
|
|
|
mInputSource = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::InitByTISInputSourceRef(TISInputSourceRef aInputSource)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
Clear();
|
|
|
|
mInputSource = aInputSource;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::InitByLanguage(CFStringRef aLanguage)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
Clear();
|
|
|
|
mInputSource = ::TISCopyInputSourceForLanguage(aLanguage);
|
|
|
|
}
|
|
|
|
|
|
|
|
const UCKeyboardLayout*
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::GetUCKeyboardLayout()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_ENSURE_TRUE(mInputSource, nsnull);
|
2009-10-16 09:12:09 +00:00
|
|
|
if (mUCKeyboardLayout) {
|
|
|
|
return mUCKeyboardLayout;
|
|
|
|
}
|
2009-10-01 02:52:50 +00:00
|
|
|
CFDataRef uchr = static_cast<CFDataRef>(
|
|
|
|
::TISGetInputSourceProperty(mInputSource,
|
|
|
|
kTISPropertyUnicodeKeyLayoutData));
|
|
|
|
|
|
|
|
// We should be always able to get the layout here.
|
|
|
|
NS_ENSURE_TRUE(uchr, nsnull);
|
2009-10-16 09:12:09 +00:00
|
|
|
mUCKeyboardLayout =
|
|
|
|
reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(uchr));
|
|
|
|
return mUCKeyboardLayout;
|
2009-10-01 02:52:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::GetBoolProperty(const CFStringRef aKey)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
CFBooleanRef ret = static_cast<CFBooleanRef>(
|
|
|
|
::TISGetInputSourceProperty(mInputSource, aKey));
|
|
|
|
return ::CFBooleanGetValue(ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
|
|
|
|
CFStringRef &aStr)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
aStr = static_cast<CFStringRef>(
|
|
|
|
::TISGetInputSourceProperty(mInputSource, aKey));
|
|
|
|
return aStr != nsnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
|
|
|
|
nsAString &aStr)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
CFStringRef str;
|
|
|
|
GetStringProperty(aKey, str);
|
|
|
|
GetStringForNSString((const NSString*)str, aStr);
|
|
|
|
return !aStr.IsEmpty();
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::IsOpenedIMEMode()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_ENSURE_TRUE(mInputSource, PR_FALSE);
|
|
|
|
if (!IsIMEMode())
|
|
|
|
return PR_FALSE;
|
|
|
|
return !IsASCIICapable();
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::IsIMEMode()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_ENSURE_TRUE(mInputSource, PR_FALSE);
|
|
|
|
CFStringRef str;
|
|
|
|
GetInputSourceType(str);
|
|
|
|
NS_ENSURE_TRUE(str, PR_FALSE);
|
|
|
|
return ::CFStringCompare(kTISTypeKeyboardInputMode,
|
|
|
|
str, 0) == kCFCompareEqualTo;
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::GetLanguageList(CFArrayRef &aLanguageList)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_ENSURE_TRUE(mInputSource, PR_FALSE);
|
|
|
|
aLanguageList = static_cast<CFArrayRef>(
|
|
|
|
::TISGetInputSourceProperty(mInputSource,
|
|
|
|
kTISPropertyInputSourceLanguages));
|
|
|
|
return aLanguageList != nsnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::GetPrimaryLanguage(CFStringRef &aPrimaryLanguage)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_ENSURE_TRUE(mInputSource, PR_FALSE);
|
|
|
|
CFArrayRef langList;
|
|
|
|
NS_ENSURE_TRUE(GetLanguageList(langList), PR_FALSE);
|
|
|
|
if (::CFArrayGetCount(langList) == 0)
|
|
|
|
return PR_FALSE;
|
|
|
|
aPrimaryLanguage =
|
|
|
|
static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, 0));
|
|
|
|
return aPrimaryLanguage != nsnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::GetPrimaryLanguage(nsAString &aPrimaryLanguage)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_ENSURE_TRUE(mInputSource, PR_FALSE);
|
|
|
|
CFStringRef primaryLanguage;
|
|
|
|
NS_ENSURE_TRUE(GetPrimaryLanguage(primaryLanguage), PR_FALSE);
|
|
|
|
GetStringForNSString((const NSString*)primaryLanguage, aPrimaryLanguage);
|
|
|
|
return !aPrimaryLanguage.IsEmpty();
|
|
|
|
}
|
|
|
|
|
2009-10-16 09:12:09 +00:00
|
|
|
PRBool
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::IsForRTLLanguage()
|
2009-10-16 09:12:09 +00:00
|
|
|
{
|
|
|
|
if (mIsRTL < 0) {
|
|
|
|
// Get the input character of the 'A' key of ANSI keyboard layout.
|
|
|
|
nsAutoString str;
|
|
|
|
PRBool ret = TranslateToString(kVK_ANSI_A, 0, eKbdType_ANSI, str);
|
|
|
|
NS_ENSURE_TRUE(ret, ret);
|
|
|
|
PRUnichar ch = str.IsEmpty() ? PRUnichar(0) : str.CharAt(0);
|
|
|
|
mIsRTL = UCS2_CHAR_IS_BIDI(ch) || ch == 0xD802 || ch == 0xD803;
|
|
|
|
}
|
|
|
|
return mIsRTL != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::IsInitializedByCurrentKeyboardLayout()
|
2009-10-16 09:12:09 +00:00
|
|
|
{
|
|
|
|
return mInputSource == ::TISCopyCurrentKeyboardLayoutInputSource();
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::TranslateToString(UInt32 aKeyCode, UInt32 aModifiers,
|
|
|
|
UInt32 aKbdType, nsAString &aStr)
|
2009-10-16 09:12:09 +00:00
|
|
|
{
|
|
|
|
aStr.Truncate();
|
|
|
|
const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();
|
|
|
|
NS_ENSURE_TRUE(UCKey, PR_FALSE);
|
|
|
|
return UCKeyTranslateToString(UCKey, aKeyCode, aModifiers, aKbdType, aStr);
|
|
|
|
}
|
|
|
|
|
2009-10-01 02:52:50 +00:00
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::Select()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
if (!mInputSource)
|
|
|
|
return;
|
|
|
|
::TISSelectInputSource(mInputSource);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper::Clear()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
if (mInputSourceList) {
|
|
|
|
::CFRelease(mInputSourceList);
|
|
|
|
}
|
|
|
|
mInputSourceList = nsnull;
|
|
|
|
mInputSource = nsnull;
|
2009-10-16 09:12:09 +00:00
|
|
|
mIsRTL = -1;
|
|
|
|
mUCKeyboardLayout = nsnull;
|
2009-10-01 02:52:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
*
|
2011-05-08 10:19:15 +00:00
|
|
|
* TextInputHandler implementation (static methods)
|
2009-10-01 02:52:50 +00:00
|
|
|
*
|
|
|
|
******************************************************************************/
|
|
|
|
|
|
|
|
// static
|
|
|
|
CFArrayRef
|
2011-05-08 10:19:15 +00:00
|
|
|
TextInputHandler::CreateAllKeyboardLayoutList()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
const void* keys[] = { kTISPropertyInputSourceType };
|
|
|
|
const void* values[] = { kTISTypeKeyboardLayout };
|
|
|
|
CFDictionaryRef filter =
|
|
|
|
::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
|
|
|
|
NS_ASSERTION(filter, "failed to create the filter");
|
|
|
|
CFArrayRef list = ::TISCreateInputSourceList(filter, true);
|
|
|
|
::CFRelease(filter);
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
TextInputHandler::DebugPrintAllKeyboardLayouts(PRLogModuleInfo* aLogModuleInfo)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
CFArrayRef list = CreateAllKeyboardLayoutList();
|
|
|
|
PR_LOG(aLogModuleInfo, PR_LOG_ALWAYS, ("Keyboard layout configuration:"));
|
|
|
|
CFIndex idx = ::CFArrayGetCount(list);
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper tis;
|
2009-10-01 02:52:50 +00:00
|
|
|
for (CFIndex i = 0; i < idx; ++i) {
|
|
|
|
TISInputSourceRef inputSource = static_cast<TISInputSourceRef>(
|
|
|
|
const_cast<void *>(::CFArrayGetValueAtIndex(list, i)));
|
|
|
|
tis.InitByTISInputSourceRef(inputSource);
|
|
|
|
nsAutoString name, isid;
|
|
|
|
tis.GetLocalizedName(name);
|
|
|
|
tis.GetInputSourceID(isid);
|
|
|
|
PR_LOG(aLogModuleInfo, PR_LOG_ALWAYS,
|
|
|
|
(" %s\t<%s>%s%s\n",
|
|
|
|
NS_ConvertUTF16toUTF8(name).get(),
|
|
|
|
NS_ConvertUTF16toUTF8(isid).get(),
|
|
|
|
tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
|
|
|
|
tis.GetUCKeyboardLayout() ? "" : "\t(uchr is NOT AVAILABLE)"));
|
|
|
|
}
|
|
|
|
::CFRelease(list);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
*
|
2011-05-08 10:19:15 +00:00
|
|
|
* TextInputHandler implementation
|
2009-10-01 02:52:50 +00:00
|
|
|
*
|
|
|
|
******************************************************************************/
|
|
|
|
|
2011-05-08 10:19:15 +00:00
|
|
|
TextInputHandler::TextInputHandler() :
|
|
|
|
IMEInputHandler()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2011-05-08 10:19:15 +00:00
|
|
|
TextInputHandler::~TextInputHandler()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
*
|
2011-05-08 10:19:15 +00:00
|
|
|
* IMEInputHandler implementation (static methods)
|
2009-10-01 02:52:50 +00:00
|
|
|
*
|
|
|
|
******************************************************************************/
|
|
|
|
|
2011-05-08 10:19:15 +00:00
|
|
|
PRBool IMEInputHandler::sStaticMembersInitialized = PR_FALSE;
|
|
|
|
CFStringRef IMEInputHandler::sLatestIMEOpenedModeInputSourceID = nsnull;
|
|
|
|
IMEInputHandler* IMEInputHandler::sFocusedIMEHandler = nsnull;
|
2009-10-01 02:52:50 +00:00
|
|
|
|
|
|
|
// static
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::InitStaticMembers()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
if (sStaticMembersInitialized)
|
|
|
|
return;
|
|
|
|
sStaticMembersInitialized = PR_TRUE;
|
|
|
|
// We need to check the keyboard layout changes on all applications.
|
|
|
|
CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
|
|
|
|
// XXX Don't we need to remove the observer at shut down?
|
|
|
|
// Mac Dev Center's document doesn't say how to remove the observer if
|
|
|
|
// the second parameter is NULL.
|
|
|
|
::CFNotificationCenterAddObserver(center, NULL,
|
|
|
|
OnCurrentTextInputSourceChange,
|
|
|
|
kTISNotifySelectedKeyboardInputSourceChanged, NULL,
|
|
|
|
CFNotificationSuspensionBehaviorDeliverImmediately);
|
|
|
|
// Initiailize with the current keyboard layout
|
|
|
|
OnCurrentTextInputSourceChange(NULL, NULL,
|
|
|
|
kTISNotifySelectedKeyboardInputSourceChanged,
|
|
|
|
NULL, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter,
|
|
|
|
void* aObserver,
|
|
|
|
CFStringRef aName,
|
|
|
|
const void* aObject,
|
|
|
|
CFDictionaryRef aUserInfo)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
// Cache the latest IME opened mode to sLatestIMEOpenedModeInputSourceID.
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper tis;
|
2009-10-01 02:52:50 +00:00
|
|
|
tis.InitByCurrentInputSource();
|
|
|
|
if (tis.IsOpenedIMEMode()) {
|
|
|
|
tis.GetInputSourceID(sLatestIMEOpenedModeInputSourceID);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef DEBUG_IME_HANDLER
|
|
|
|
static CFStringRef sLastTIS = nsnull;
|
|
|
|
CFStringRef newTIS;
|
|
|
|
tis.GetInputSourceID(newTIS);
|
|
|
|
if (!sLastTIS ||
|
|
|
|
::CFStringCompare(sLastTIS, newTIS, 0) != kCFCompareEqualTo) {
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper tis1, tis2, tis3;
|
2009-10-01 02:52:50 +00:00
|
|
|
tis1.InitByCurrentKeyboardLayout();
|
|
|
|
tis2.InitByCurrentASCIICapableInputSource();
|
|
|
|
tis3.InitByCurrentASCIICapableKeyboardLayout();
|
|
|
|
CFStringRef is0, is1, is2, is3, type0, lang0, bundleID0;
|
|
|
|
tis.GetInputSourceID(is0);
|
|
|
|
tis1.GetInputSourceID(is1);
|
|
|
|
tis2.GetInputSourceID(is2);
|
|
|
|
tis3.GetInputSourceID(is3);
|
|
|
|
tis.GetInputSourceType(type0);
|
|
|
|
tis.GetPrimaryLanguage(lang0);
|
|
|
|
tis.GetBundleID(bundleID0);
|
|
|
|
NSLog(@"********************************************");
|
|
|
|
NSLog(@"Current Input Source is changed to:");
|
|
|
|
NSLog(@" current InputManager: %p", [NSInputManager currentInputManager]);
|
|
|
|
NSLog(@" %@", is0);
|
|
|
|
NSLog(@" type: %@ %s", type0,
|
|
|
|
tis.IsASCIICapable() ? "- ASCII capable " : "");
|
|
|
|
NSLog(@" primary language: %@", lang0);
|
|
|
|
NSLog(@" bundle ID: %@", bundleID0);
|
|
|
|
NSLog(@" current ASCII capable Input Source: %@", is2);
|
|
|
|
NSLog(@" current Keyboard Layout: %@", is1);
|
|
|
|
NSLog(@" current ASCII capable Keyboard Layout: %@", is3);
|
|
|
|
NSLog(@"********************************************");
|
|
|
|
}
|
|
|
|
sLastTIS = newTIS;
|
|
|
|
#endif // DEBUG_IME_HANDLER
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::FlushPendingMethods(nsITimer* aTimer, void* aClosure)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_ASSERTION(aClosure, "aClosure is null");
|
2011-05-08 10:19:15 +00:00
|
|
|
static_cast<IMEInputHandler*>(aClosure)->ExecutePendingMethods();
|
2009-10-01 02:52:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
CFArrayRef
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::CreateAllIMEModeList()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
const void* keys[] = { kTISPropertyInputSourceType };
|
|
|
|
const void* values[] = { kTISTypeKeyboardInputMode };
|
|
|
|
CFDictionaryRef filter =
|
|
|
|
::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
|
|
|
|
NS_ASSERTION(filter, "failed to create the filter");
|
|
|
|
CFArrayRef list = ::TISCreateInputSourceList(filter, true);
|
|
|
|
::CFRelease(filter);
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::DebugPrintAllIMEModes(PRLogModuleInfo* aLogModuleInfo)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
CFArrayRef list = CreateAllIMEModeList();
|
|
|
|
PR_LOG(aLogModuleInfo, PR_LOG_ALWAYS, ("IME mode configuration:"));
|
|
|
|
CFIndex idx = ::CFArrayGetCount(list);
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper tis;
|
2009-10-01 02:52:50 +00:00
|
|
|
for (CFIndex i = 0; i < idx; ++i) {
|
|
|
|
TISInputSourceRef inputSource = static_cast<TISInputSourceRef>(
|
|
|
|
const_cast<void *>(::CFArrayGetValueAtIndex(list, i)));
|
|
|
|
tis.InitByTISInputSourceRef(inputSource);
|
|
|
|
nsAutoString name, isid;
|
|
|
|
tis.GetLocalizedName(name);
|
|
|
|
tis.GetInputSourceID(isid);
|
|
|
|
PR_LOG(aLogModuleInfo, PR_LOG_ALWAYS,
|
|
|
|
(" %s\t<%s>%s%s\n",
|
|
|
|
NS_ConvertUTF16toUTF8(name).get(),
|
|
|
|
NS_ConvertUTF16toUTF8(isid).get(),
|
|
|
|
tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
|
|
|
|
tis.IsEnabled() ? "" : "\t(Isn't Enabled)"));
|
|
|
|
}
|
|
|
|
::CFRelease(list);
|
|
|
|
}
|
|
|
|
|
2010-08-16 08:20:27 +00:00
|
|
|
//static
|
|
|
|
TSMDocumentID
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::GetCurrentTSMDocumentID()
|
2010-08-16 08:20:27 +00:00
|
|
|
{
|
|
|
|
// On OS X 10.6.x at least, ::TSMGetActiveDocument() has a bug that prevents
|
|
|
|
// it from returning accurate results unless
|
|
|
|
// [NSInputManager currentInputManager] is called first.
|
|
|
|
// So, we need to call [NSInputManager currentInputManager] first here.
|
|
|
|
[NSInputManager currentInputManager];
|
|
|
|
return ::TSMGetActiveDocument();
|
|
|
|
}
|
2009-10-01 02:52:50 +00:00
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
*
|
2011-05-08 10:19:15 +00:00
|
|
|
* IMEInputHandler implementation #1
|
2009-10-01 02:52:50 +00:00
|
|
|
* The methods are releated to the pending methods. Some jobs should be
|
|
|
|
* run after the stack is finished, e.g, some methods cannot run the jobs
|
|
|
|
* during processing the focus event. And also some other jobs should be
|
|
|
|
* run at the next focus event is processed.
|
|
|
|
* The pending methods are recorded in mPendingMethods. They are executed
|
|
|
|
* by ExecutePendingMethods via FlushPendingMethods.
|
|
|
|
*
|
|
|
|
******************************************************************************/
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::ResetIMEWindowLevel()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
|
|
#ifdef DEBUG_IME_HANDLER
|
|
|
|
DebugPrintPointer(this);
|
2011-05-08 10:19:15 +00:00
|
|
|
NSLog(@"IMEInputHandler::ResetIMEWindowLevel");
|
2010-08-16 08:20:27 +00:00
|
|
|
NSLog(@" IsFocused:%s GetCurrentTSMDocumentID():%p",
|
|
|
|
TrueOrFalse(IsFocused()), GetCurrentTSMDocumentID());
|
2009-10-01 02:52:50 +00:00
|
|
|
#endif // DEBUG_IME_HANDLER
|
|
|
|
|
|
|
|
if (!mView)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!IsFocused()) {
|
|
|
|
// retry at next focus event
|
|
|
|
mPendingMethods |= kResetIMEWindowLevel;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-08-16 08:20:27 +00:00
|
|
|
TSMDocumentID doc = GetCurrentTSMDocumentID();
|
2009-10-01 02:52:50 +00:00
|
|
|
if (!doc) {
|
|
|
|
// retry
|
|
|
|
mPendingMethods |= kResetIMEWindowLevel;
|
|
|
|
NS_WARNING("Application is active but there is no active document");
|
|
|
|
ResetTimer();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// When the focus of Gecko is on a text box of a popup panel, the actual
|
|
|
|
// focused view is the panel's parent view (mView). But the editor is
|
|
|
|
// displayed on the popuped widget's view (editorView). So, their window
|
|
|
|
// level may be different.
|
|
|
|
NSView<mozView>* editorView = mOwnerWidget->GetEditorView();
|
|
|
|
if (!editorView) {
|
|
|
|
NS_ERROR("editorView is null");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We need to set the focused window level to TSMDocument. Then, the popup
|
|
|
|
// windows of IME (E.g., a candidate list window) will be over the focused
|
|
|
|
// view. See http://developer.apple.com/technotes/tn2005/tn2128.html#TNTAG1
|
|
|
|
NSInteger windowLevel = [[editorView window] level];
|
|
|
|
#ifdef DEBUG_IME_HANDLER
|
|
|
|
NSLog(@" windowLevel:%s (%x)",
|
|
|
|
GetWindowLevelName(windowLevel), windowLevel);
|
|
|
|
#endif // DEBUG_IME_HANDLER
|
|
|
|
|
|
|
|
// Chinese IMEs on 10.5 don't work fine if the level is NSNormalWindowLevel,
|
|
|
|
// then, we need to increment the value.
|
|
|
|
if (windowLevel == NSNormalWindowLevel)
|
|
|
|
windowLevel++;
|
|
|
|
|
2010-08-16 08:20:27 +00:00
|
|
|
::TSMSetDocumentProperty(GetCurrentTSMDocumentID(),
|
2009-10-01 02:52:50 +00:00
|
|
|
kTSMDocumentWindowLevelPropertyTag,
|
|
|
|
sizeof(windowLevel), &windowLevel);
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::DiscardIMEComposition()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
|
|
#ifdef DEBUG_IME_HANDLER
|
|
|
|
DebugPrintPointer(this);
|
2011-05-08 10:19:15 +00:00
|
|
|
NSLog(@"IMEInputHandler::DiscardIMEComposition");
|
2009-10-01 02:52:50 +00:00
|
|
|
NSLog(@" currentInputManager:%p", [NSInputManager currentInputManager]);
|
|
|
|
#endif // DEBUG_IME_HANDLER
|
|
|
|
|
|
|
|
if (!mView)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!IsFocused()) {
|
|
|
|
// retry at next focus event
|
|
|
|
mPendingMethods |= kDiscardIMEComposition;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSInputManager* im = [NSInputManager currentInputManager];
|
|
|
|
if (!im) {
|
|
|
|
// retry
|
|
|
|
mPendingMethods |= kDiscardIMEComposition;
|
|
|
|
NS_WARNING("Application is active but there is no currentInputManager");
|
|
|
|
ResetTimer();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mIgnoreIMECommit = PR_TRUE;
|
|
|
|
[im markedTextAbandoned: mView];
|
|
|
|
mIgnoreIMECommit = PR_FALSE;
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::SyncASCIICapableOnly()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
|
|
#ifdef DEBUG_IME_HANDLER
|
|
|
|
DebugPrintPointer(this);
|
2011-05-08 10:19:15 +00:00
|
|
|
NSLog(@"IMEInputHandler::SyncASCIICapableOnly");
|
2010-08-16 08:20:27 +00:00
|
|
|
NSLog(@" IsFocused:%s GetCurrentTSMDocumentID():%p",
|
|
|
|
TrueOrFalse(IsFocused()), GetCurrentTSMDocumentID());
|
2009-10-01 02:52:50 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
if (!mView)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!IsFocused()) {
|
|
|
|
// retry at next focus event
|
|
|
|
mPendingMethods |= kSyncASCIICapableOnly;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-08-16 08:20:27 +00:00
|
|
|
TSMDocumentID doc = GetCurrentTSMDocumentID();
|
2009-10-01 02:52:50 +00:00
|
|
|
if (!doc) {
|
|
|
|
// retry
|
|
|
|
mPendingMethods |= kSyncASCIICapableOnly;
|
|
|
|
NS_WARNING("Application is active but there is no active document");
|
|
|
|
ResetTimer();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mIsASCIICapableOnly) {
|
|
|
|
CFArrayRef ASCIICapableTISList = ::TISCreateASCIICapableInputSourceList();
|
|
|
|
::TSMSetDocumentProperty(doc,
|
|
|
|
kTSMDocumentEnabledInputSourcesPropertyTag,
|
|
|
|
sizeof(CFArrayRef),
|
|
|
|
&ASCIICapableTISList);
|
|
|
|
::CFRelease(ASCIICapableTISList);
|
|
|
|
} else {
|
|
|
|
::TSMRemoveDocumentProperty(doc,
|
|
|
|
kTSMDocumentEnabledInputSourcesPropertyTag);
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::ResetTimer()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_ASSERTION(mPendingMethods != 0,
|
|
|
|
"There are not pending methods, why this is called?");
|
|
|
|
if (mTimer) {
|
|
|
|
mTimer->Cancel();
|
2010-08-16 08:20:27 +00:00
|
|
|
} else {
|
|
|
|
mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
|
|
|
|
NS_ENSURE_TRUE(mTimer, );
|
2009-10-01 02:52:50 +00:00
|
|
|
}
|
|
|
|
mTimer->InitWithFuncCallback(FlushPendingMethods, this, 0,
|
|
|
|
nsITimer::TYPE_ONE_SHOT);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::ExecutePendingMethods()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
2010-08-16 08:20:27 +00:00
|
|
|
if (mTimer) {
|
|
|
|
mTimer->Cancel();
|
|
|
|
mTimer = nsnull;
|
|
|
|
}
|
|
|
|
|
2009-10-01 02:52:50 +00:00
|
|
|
if (![[NSApplication sharedApplication] isActive]) {
|
|
|
|
mIsInFocusProcessing = PR_FALSE;
|
|
|
|
// If we're not active, we should retry at focus event
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
PRUint32 pendingMethods = mPendingMethods;
|
|
|
|
// First, reset the pending method flags because if each methods cannot
|
|
|
|
// run now, they can reentry to the pending flags by theirselves.
|
|
|
|
mPendingMethods = 0;
|
|
|
|
|
|
|
|
if (pendingMethods & kDiscardIMEComposition)
|
|
|
|
DiscardIMEComposition();
|
|
|
|
if (pendingMethods & kSyncASCIICapableOnly)
|
|
|
|
SyncASCIICapableOnly();
|
|
|
|
if (pendingMethods & kResetIMEWindowLevel)
|
|
|
|
ResetIMEWindowLevel();
|
|
|
|
|
|
|
|
mIsInFocusProcessing = PR_FALSE;
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
|
|
}
|
|
|
|
|
2011-02-17 07:41:36 +00:00
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
*
|
2011-05-08 10:19:15 +00:00
|
|
|
* IMEInputHandler implementation (native event handlers)
|
2011-02-17 07:41:36 +00:00
|
|
|
*
|
|
|
|
******************************************************************************/
|
|
|
|
|
|
|
|
PRUint32
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::ConvertToTextRangeType(PRUint32 aUnderlineStyle,
|
|
|
|
NSRange& aSelectedRange)
|
2011-02-17 07:41:36 +00:00
|
|
|
{
|
|
|
|
#ifdef DEBUG_IME_HANDLER
|
|
|
|
NSLog(@"****in ConvertToTextRangeType = %d", aUnderlineStyle);
|
|
|
|
#endif
|
|
|
|
// We assume that aUnderlineStyle is NSUnderlineStyleSingle or
|
|
|
|
// NSUnderlineStyleThick. NSUnderlineStyleThick should indicate a selected
|
|
|
|
// clause. Otherwise, should indicate non-selected clause.
|
|
|
|
|
|
|
|
if (aSelectedRange.length == 0) {
|
|
|
|
switch (aUnderlineStyle) {
|
|
|
|
case NSUnderlineStyleSingle:
|
|
|
|
return NS_TEXTRANGE_RAWINPUT;
|
|
|
|
case NSUnderlineStyleThick:
|
|
|
|
return NS_TEXTRANGE_SELECTEDRAWTEXT;
|
|
|
|
default:
|
|
|
|
NS_WARNING("Unexpected line style");
|
|
|
|
return NS_TEXTRANGE_SELECTEDRAWTEXT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (aUnderlineStyle) {
|
|
|
|
case NSUnderlineStyleSingle:
|
|
|
|
return NS_TEXTRANGE_CONVERTEDTEXT;
|
|
|
|
case NSUnderlineStyleThick:
|
|
|
|
return NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
|
|
|
|
default:
|
|
|
|
NS_WARNING("Unexpected line style");
|
|
|
|
return NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
PRUint32
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::GetRangeCount(NSAttributedString *aAttrString)
|
2011-02-17 07:41:36 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
|
|
|
|
|
|
|
|
// Iterate through aAttrString for the NSUnderlineStyleAttributeName and
|
|
|
|
// count the different segments adjusting limitRange as we go.
|
|
|
|
PRUint32 count = 0;
|
|
|
|
NSRange effectiveRange;
|
|
|
|
NSRange limitRange = NSMakeRange(0, [aAttrString length]);
|
|
|
|
while (limitRange.length > 0) {
|
|
|
|
[aAttrString attribute:NSUnderlineStyleAttributeName
|
|
|
|
atIndex:limitRange.location
|
|
|
|
longestEffectiveRange:&effectiveRange
|
|
|
|
inRange:limitRange];
|
|
|
|
limitRange =
|
|
|
|
NSMakeRange(NSMaxRange(effectiveRange),
|
|
|
|
NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::SetTextRangeList(nsTArray<nsTextRange>& aTextRangeList,
|
|
|
|
NSAttributedString *aAttrString,
|
|
|
|
NSRange& aSelectedRange)
|
2011-02-17 07:41:36 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
|
|
// Convert the Cocoa range into the nsTextRange Array used in Gecko.
|
|
|
|
// Iterate through the attributed string and map the underline attribute to
|
|
|
|
// Gecko IME textrange attributes. We may need to change the code here if
|
|
|
|
// we change the implementation of validAttributesForMarkedText.
|
|
|
|
NSRange limitRange = NSMakeRange(0, [aAttrString length]);
|
|
|
|
PRUint32 rangeCount = GetRangeCount(aAttrString);
|
|
|
|
for (PRUint32 i = 0; i < rangeCount && limitRange.length > 0; i++) {
|
|
|
|
NSRange effectiveRange;
|
|
|
|
id attributeValue = [aAttrString attribute:NSUnderlineStyleAttributeName
|
|
|
|
atIndex:limitRange.location
|
|
|
|
longestEffectiveRange:&effectiveRange
|
|
|
|
inRange:limitRange];
|
|
|
|
|
|
|
|
nsTextRange range;
|
|
|
|
range.mStartOffset = effectiveRange.location;
|
|
|
|
range.mEndOffset = NSMaxRange(effectiveRange);
|
|
|
|
range.mRangeType =
|
|
|
|
ConvertToTextRangeType([attributeValue intValue], aSelectedRange);
|
|
|
|
aTextRangeList.AppendElement(range);
|
|
|
|
|
|
|
|
limitRange =
|
|
|
|
NSMakeRange(NSMaxRange(effectiveRange),
|
|
|
|
NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get current caret position.
|
|
|
|
nsTextRange range;
|
|
|
|
range.mStartOffset = aSelectedRange.location + aSelectedRange.length;
|
|
|
|
range.mEndOffset = range.mStartOffset;
|
|
|
|
range.mRangeType = NS_TEXTRANGE_CARETPOSITION;
|
|
|
|
aTextRangeList.AppendElement(range);
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::DispatchTextEvent(const nsString& aText,
|
|
|
|
NSAttributedString* aAttrString,
|
|
|
|
NSRange& aSelectedRange,
|
|
|
|
PRBool aDoCommit)
|
2011-02-17 07:41:36 +00:00
|
|
|
{
|
|
|
|
#ifdef DEBUG_IME_HANDLER
|
|
|
|
NSLog(@"****in DispatchTextEvent; string = '%@'", aAttrString);
|
|
|
|
NSLog(@" aSelectedRange = %d, %d",
|
|
|
|
aSelectedRange.location, aSelectedRange.length);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
nsTextEvent textEvent(PR_TRUE, NS_TEXT_TEXT, mOwnerWidget);
|
|
|
|
textEvent.time = PR_IntervalNow();
|
|
|
|
textEvent.theText = aText;
|
|
|
|
nsAutoTArray<nsTextRange, 4> textRanges;
|
|
|
|
if (!aDoCommit) {
|
|
|
|
SetTextRangeList(textRanges, aAttrString, aSelectedRange);
|
|
|
|
}
|
|
|
|
textEvent.rangeArray = textRanges.Elements();
|
|
|
|
textEvent.rangeCount = textRanges.Length();
|
|
|
|
|
|
|
|
return mOwnerWidget->DispatchWindowEvent(textEvent);
|
|
|
|
}
|
|
|
|
|
2011-02-19 04:55:34 +00:00
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::InitCompositionEvent(nsCompositionEvent& aCompositionEvent)
|
2011-02-19 04:55:34 +00:00
|
|
|
{
|
|
|
|
aCompositionEvent.time = PR_IntervalNow();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::InsertTextAsCommittingComposition(
|
|
|
|
NSAttributedString* aAttrString)
|
2011-02-19 04:55:34 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
|
|
#if DEBUG_IME
|
|
|
|
NSLog(@"****in InsertTextAsCommittingComposition: '%@'", aAttrString);
|
|
|
|
NSLog(@" mMarkedRange = %d, %d", mMarkedRange.location, mMarkedRange.length);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
nsRefPtr<nsChildView> kungFuDeathGrip(mOwnerWidget);
|
|
|
|
|
|
|
|
nsString str;
|
|
|
|
GetStringForNSString([aAttrString string], str);
|
|
|
|
|
|
|
|
if (!IsIMEComposing()) {
|
|
|
|
// XXXmnakano Probably, we shouldn't emulate composition in this case.
|
|
|
|
// I think that we should just fire DOM3 textInput event if we implement it.
|
|
|
|
nsCompositionEvent compStart(PR_TRUE, NS_COMPOSITION_START, mOwnerWidget);
|
|
|
|
InitCompositionEvent(compStart);
|
|
|
|
|
|
|
|
mOwnerWidget->DispatchWindowEvent(compStart);
|
|
|
|
if (!mView) {
|
|
|
|
return; // we're destroyed
|
|
|
|
}
|
|
|
|
|
|
|
|
OnStartIMEComposition();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IgnoreIMECommit()) {
|
|
|
|
str.Truncate();
|
|
|
|
}
|
|
|
|
|
|
|
|
NSRange range = NSMakeRange(0, str.Length());
|
|
|
|
DispatchTextEvent(str, aAttrString, range, PR_TRUE);
|
|
|
|
if (!mView) {
|
|
|
|
return; // we're destroyed
|
|
|
|
}
|
|
|
|
|
|
|
|
OnUpdateIMEComposition([aAttrString string]);
|
|
|
|
|
|
|
|
nsCompositionEvent compEnd(PR_TRUE, NS_COMPOSITION_END, mOwnerWidget);
|
|
|
|
InitCompositionEvent(compEnd);
|
|
|
|
mOwnerWidget->DispatchWindowEvent(compEnd);
|
|
|
|
if (!mView) {
|
|
|
|
return; // we're destroyed
|
|
|
|
}
|
|
|
|
|
|
|
|
OnEndIMEComposition();
|
|
|
|
|
|
|
|
mMarkedRange = NSMakeRange(NSNotFound, 0);
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::SetMarkedText(NSAttributedString* aAttrString,
|
|
|
|
NSRange& aSelectedRange)
|
2011-02-19 04:55:34 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
|
|
#if DEBUG_IME_HANDLER
|
|
|
|
NSLog(@"****in SetMarkedText location: %d, length: %d",
|
|
|
|
aSelectedRange.location, aSelectedRange.length);
|
|
|
|
NSLog(@" mMarkedRange = %d, %d", mMarkedRange.location, mMarkedRange.length);
|
|
|
|
NSLog(@" aAttrString = '%@'", aAttrString);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (IgnoreIMEComposition()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsRefPtr<nsChildView> kungFuDeathGrip(mOwnerWidget);
|
|
|
|
|
|
|
|
nsString str;
|
|
|
|
GetStringForNSString([aAttrString string], str);
|
|
|
|
|
|
|
|
mMarkedRange.length = str.Length();
|
|
|
|
|
|
|
|
if (!IsIMEComposing() && !str.IsEmpty()) {
|
|
|
|
nsQueryContentEvent selection(PR_TRUE, NS_QUERY_SELECTED_TEXT,
|
|
|
|
mOwnerWidget);
|
|
|
|
mOwnerWidget->DispatchWindowEvent(selection);
|
|
|
|
mMarkedRange.location = selection.mSucceeded ? selection.mReply.mOffset : 0;
|
|
|
|
|
|
|
|
nsCompositionEvent compStart(PR_TRUE, NS_COMPOSITION_START, mOwnerWidget);
|
|
|
|
InitCompositionEvent(compStart);
|
|
|
|
|
|
|
|
mOwnerWidget->DispatchWindowEvent(compStart);
|
|
|
|
if (!mView) {
|
|
|
|
return; // we're destroyed
|
|
|
|
}
|
|
|
|
|
|
|
|
OnStartIMEComposition();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IsIMEComposing()) {
|
|
|
|
OnUpdateIMEComposition([aAttrString string]);
|
|
|
|
|
|
|
|
PRBool doCommit = str.IsEmpty();
|
|
|
|
DispatchTextEvent(str, aAttrString, aSelectedRange, doCommit);
|
|
|
|
if (!mView) {
|
|
|
|
return; // we're destroyed
|
|
|
|
}
|
|
|
|
|
|
|
|
if (doCommit) {
|
|
|
|
nsCompositionEvent compEnd(PR_TRUE, NS_COMPOSITION_END, mOwnerWidget);
|
|
|
|
InitCompositionEvent(compEnd);
|
|
|
|
mOwnerWidget->DispatchWindowEvent(compEnd);
|
|
|
|
if (!mView) {
|
|
|
|
return; // we're destroyed
|
|
|
|
}
|
|
|
|
OnEndIMEComposition();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
|
|
}
|
|
|
|
|
2011-02-19 04:55:49 +00:00
|
|
|
NSInteger
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::ConversationIdentifier()
|
2011-02-19 04:55:49 +00:00
|
|
|
{
|
|
|
|
// NOTE: The size of NSInteger is same as pointer size.
|
|
|
|
nsQueryContentEvent textContent(PR_TRUE, NS_QUERY_TEXT_CONTENT, mOwnerWidget);
|
|
|
|
textContent.InitForQueryTextContent(0, 0);
|
|
|
|
mOwnerWidget->DispatchWindowEvent(textContent);
|
|
|
|
if (!textContent.mSucceeded) {
|
|
|
|
return reinterpret_cast<NSInteger>(mView);
|
|
|
|
}
|
|
|
|
// XXX This might return same ID as a previously existing editor if the
|
|
|
|
// deleted editor was created at the same address. Is there a better way?
|
|
|
|
return reinterpret_cast<NSInteger>(textContent.mReply.mContentsRoot);
|
|
|
|
}
|
|
|
|
|
|
|
|
NSAttributedString*
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange)
|
2011-02-19 04:55:49 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
|
|
|
|
|
|
|
#if DEBUG_IME_HANDLER
|
|
|
|
NSLog(@"****in GetAttributedSubstringFromRange");
|
|
|
|
NSLog(@" aRange = %d, %d", aRange.location, aRange.length);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (aRange.location == NSNotFound || aRange.length == 0) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsAutoString str;
|
|
|
|
nsQueryContentEvent textContent(PR_TRUE, NS_QUERY_TEXT_CONTENT, mOwnerWidget);
|
|
|
|
textContent.InitForQueryTextContent(aRange.location, aRange.length);
|
|
|
|
mOwnerWidget->DispatchWindowEvent(textContent);
|
|
|
|
|
|
|
|
if (!textContent.mSucceeded || textContent.mReply.mString.IsEmpty()) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSString* nsstr = ToNSString(textContent.mReply.mString);
|
|
|
|
NSAttributedString* result =
|
|
|
|
[[[NSAttributedString alloc] initWithString:nsstr
|
|
|
|
attributes:nil] autorelease];
|
|
|
|
return result;
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSRange
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::SelectedRange()
|
2011-02-19 04:55:49 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
|
|
|
|
|
|
|
|
#if DEBUG_IME_HANDLER
|
|
|
|
NSLog(@"****in SelectedRange");
|
|
|
|
#endif
|
|
|
|
nsQueryContentEvent selection(PR_TRUE, NS_QUERY_SELECTED_TEXT, mOwnerWidget);
|
|
|
|
mOwnerWidget->DispatchWindowEvent(selection);
|
|
|
|
if (!selection.mSucceeded) {
|
|
|
|
return NSMakeRange(NSNotFound, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#if DEBUG_IME_HANDLER
|
|
|
|
NSLog(@" result of SelectedRange = %d, %d",
|
|
|
|
selection.mReply.mOffset, selection.mReply.mString.Length());
|
|
|
|
#endif
|
|
|
|
return NSMakeRange(selection.mReply.mOffset,
|
|
|
|
selection.mReply.mString.Length());
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRange(0, 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
NSRect
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange)
|
2011-02-19 04:55:49 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
|
|
|
|
|
|
|
|
#if DEBUG_IME_HANDLER
|
|
|
|
NSLog(@"****in FirstRectForCharacterRange");
|
|
|
|
NSLog(@" aRange = %d, %d", aRange.location, aRange.length);
|
|
|
|
#endif
|
|
|
|
// XXX this returns first character rect or caret rect, it is limitation of
|
|
|
|
// now. We need more work for returns first line rect. But current
|
|
|
|
// implementation is enough for IMEs.
|
|
|
|
|
|
|
|
NSRect rect;
|
|
|
|
if (aRange.location == NSNotFound) {
|
|
|
|
return rect;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsIntRect r;
|
|
|
|
PRBool useCaretRect = (aRange.length == 0);
|
|
|
|
if (!useCaretRect) {
|
|
|
|
nsQueryContentEvent charRect(PR_TRUE, NS_QUERY_TEXT_RECT, mOwnerWidget);
|
|
|
|
charRect.InitForQueryTextRect(aRange.location, 1);
|
|
|
|
mOwnerWidget->DispatchWindowEvent(charRect);
|
|
|
|
if (charRect.mSucceeded) {
|
|
|
|
r = charRect.mReply.mRect;
|
|
|
|
} else {
|
|
|
|
useCaretRect = PR_TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (useCaretRect) {
|
|
|
|
nsQueryContentEvent caretRect(PR_TRUE, NS_QUERY_CARET_RECT, mOwnerWidget);
|
|
|
|
caretRect.InitForQueryCaretRect(aRange.location);
|
|
|
|
mOwnerWidget->DispatchWindowEvent(caretRect);
|
|
|
|
if (!caretRect.mSucceeded) {
|
|
|
|
return rect;
|
|
|
|
}
|
|
|
|
r = caretRect.mReply.mRect;
|
|
|
|
r.width = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsIWidget* rootWidget = mOwnerWidget->GetTopLevelWidget();
|
|
|
|
NSWindow* rootWindow =
|
|
|
|
static_cast<NSWindow*>(rootWidget->GetNativeData(NS_NATIVE_WINDOW));
|
|
|
|
NSView* rootView =
|
|
|
|
static_cast<NSView*>(rootWidget->GetNativeData(NS_NATIVE_WIDGET));
|
|
|
|
if (!rootWindow || !rootView) {
|
|
|
|
return rect;
|
|
|
|
}
|
|
|
|
GeckoRectToNSRect(r, rect);
|
|
|
|
rect = [rootView convertRect:rect toView:nil];
|
|
|
|
rect.origin = [rootWindow convertBaseToScreen:rect.origin];
|
|
|
|
#if DEBUG_IME_HANDLER
|
|
|
|
NSLog(@" result rect (x,y,w,h) = %f, %f, %f, %f",
|
|
|
|
rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
|
|
|
|
#endif
|
|
|
|
return rect;
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRect(0.0, 0.0, 0.0, 0.0));
|
|
|
|
}
|
|
|
|
|
|
|
|
NSUInteger
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::CharacterIndexForPoint(NSPoint& aPoint)
|
2011-02-19 04:55:49 +00:00
|
|
|
{
|
|
|
|
#if DEBUG_IME
|
|
|
|
NSLog(@"****in CharacterIndexForPoint");
|
|
|
|
#endif
|
|
|
|
// To implement this, we'd have to grovel in text frames looking at text
|
|
|
|
// offsets.
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSArray*
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::GetValidAttributesForMarkedText()
|
2011-02-19 04:55:49 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
|
|
|
|
|
|
|
#if DEBUG_IME
|
|
|
|
NSLog(@"****in GetValidAttributesForMarkedText");
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName,
|
|
|
|
// NSMarkedClauseSegmentAttributeName,
|
|
|
|
// NSTextInputReplacementRangeAttributeName,
|
|
|
|
// nil];
|
|
|
|
// empty array; we don't support any attributes right now
|
|
|
|
return [NSArray array];
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
|
|
|
|
}
|
|
|
|
|
2009-10-01 02:52:50 +00:00
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
*
|
2011-05-08 10:19:15 +00:00
|
|
|
* IMEInputHandler implementation #2
|
2009-10-01 02:52:50 +00:00
|
|
|
*
|
|
|
|
******************************************************************************/
|
|
|
|
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::IMEInputHandler() :
|
|
|
|
PluginTextInputHandler(),
|
2009-10-01 02:52:50 +00:00
|
|
|
mPendingMethods(0), mIMECompositionString(nsnull),
|
|
|
|
mIsIMEComposing(PR_FALSE), mIsIMEEnabled(PR_TRUE),
|
|
|
|
mIsASCIICapableOnly(PR_FALSE), mIgnoreIMECommit(PR_FALSE),
|
|
|
|
mIsInFocusProcessing(PR_FALSE)
|
|
|
|
{
|
|
|
|
InitStaticMembers();
|
2011-02-19 04:55:34 +00:00
|
|
|
|
|
|
|
mMarkedRange.location = NSNotFound;
|
|
|
|
mMarkedRange.length = 0;
|
2009-10-01 02:52:50 +00:00
|
|
|
}
|
|
|
|
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::~IMEInputHandler()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
if (mTimer) {
|
|
|
|
mTimer->Cancel();
|
|
|
|
mTimer = nsnull;
|
|
|
|
}
|
|
|
|
if (sFocusedIMEHandler == this) {
|
|
|
|
sFocusedIMEHandler = nsnull;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::OnFocusChangeInGecko(PRBool aFocus)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
#ifdef DEBUG_IME_HANDLER
|
|
|
|
DebugPrintPointer(this);
|
2011-05-08 10:19:15 +00:00
|
|
|
NSLog(@"IMEInputHandler::OnFocusChangeInGecko");
|
2009-10-01 02:52:50 +00:00
|
|
|
NSLog(@" aFocus:%s sFocusedIMEHandler:%p",
|
|
|
|
TrueOrFalse(aFocus), sFocusedIMEHandler);
|
|
|
|
#endif // DEBUG_IME_HANDLER
|
|
|
|
// This is called when the native focus is changed and when the native focus
|
|
|
|
// isn't changed but the focus is changed in Gecko.
|
|
|
|
// XXX currently, we're not called this method with PR_FALSE, we need to
|
|
|
|
// improve the nsIMEStateManager implementation.
|
|
|
|
if (!aFocus) {
|
|
|
|
if (sFocusedIMEHandler == this)
|
|
|
|
sFocusedIMEHandler = nsnull;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
sFocusedIMEHandler = this;
|
|
|
|
mIsInFocusProcessing = PR_TRUE;
|
|
|
|
|
|
|
|
// We need to reset the IME's window level by the current focused view of
|
|
|
|
// Gecko. It may be different from mView. However, we cannot get the
|
|
|
|
// new focused view here because the focus change process in Gecko hasn't
|
|
|
|
// been finished yet. So, we should post the job to the todo list.
|
|
|
|
mPendingMethods |= kResetIMEWindowLevel;
|
|
|
|
ResetTimer();
|
|
|
|
}
|
|
|
|
|
2011-05-08 10:19:15 +00:00
|
|
|
PRBool
|
|
|
|
IMEInputHandler::OnDestroyView(NSView<mozView> *aDestroyingView)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
// If we're not focused, the destroying view might be composing with it in
|
|
|
|
// another instance.
|
|
|
|
if (sFocusedIMEHandler && sFocusedIMEHandler != this) {
|
|
|
|
sFocusedIMEHandler->OnDestroyView(aDestroyingView);
|
|
|
|
}
|
|
|
|
|
2011-05-08 10:19:15 +00:00
|
|
|
if (!PluginTextInputHandler::OnDestroyView(aDestroyingView)) {
|
|
|
|
return PR_FALSE;
|
2009-10-01 02:52:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (IsIMEComposing()) {
|
|
|
|
// If our view is in the composition, we should clean up it.
|
|
|
|
// XXX Might CancelIMEComposition() fail because mView is being destroyed?
|
|
|
|
CancelIMEComposition();
|
|
|
|
OnEndIMEComposition();
|
|
|
|
}
|
|
|
|
|
2011-05-08 10:19:15 +00:00
|
|
|
return PR_TRUE;
|
2009-10-01 02:52:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::OnStartIMEComposition()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
|
|
#ifdef DEBUG_IME_HANDLER
|
|
|
|
DebugPrintPointer(this);
|
2011-05-08 10:19:15 +00:00
|
|
|
NSLog(@"IMEInputHandler::OnStartIMEComposition");
|
2011-02-19 04:55:34 +00:00
|
|
|
NSLog(@" mView=%p, currentInputManager:%p",
|
|
|
|
mView, [NSInputManager currentInputManager]);
|
2009-10-01 02:52:50 +00:00
|
|
|
#endif // DEBUG_IME_HANDLER
|
|
|
|
|
|
|
|
NS_ASSERTION(!mIsIMEComposing, "There is a composition already");
|
|
|
|
mIsIMEComposing = PR_TRUE;
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::OnUpdateIMEComposition(NSString* aIMECompositionString)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
|
|
#ifdef DEBUG_IME_HANDLER
|
|
|
|
DebugPrintPointer(this);
|
2011-05-08 10:19:15 +00:00
|
|
|
NSLog(@"IMEInputHandler::OnUpdateIMEComposition");
|
2009-10-01 02:52:50 +00:00
|
|
|
NSLog(@" aIMECompositionString:%@ currentInputManager:%p",
|
|
|
|
aIMECompositionString, [NSInputManager currentInputManager]);
|
|
|
|
#endif // DEBUG_IME_HANDLER
|
|
|
|
|
|
|
|
NS_ASSERTION(mIsIMEComposing, "We're not in composition");
|
|
|
|
|
|
|
|
if (mIMECompositionString)
|
|
|
|
[mIMECompositionString release];
|
|
|
|
mIMECompositionString = [aIMECompositionString retain];
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::OnEndIMEComposition()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
|
|
#ifdef DEBUG_IME_HANDLER
|
|
|
|
DebugPrintPointer(this);
|
2011-05-08 10:19:15 +00:00
|
|
|
NSLog(@"IMEInputHandler::OnEndIMEComposition");
|
2009-10-01 02:52:50 +00:00
|
|
|
NSLog(@" mIMECompositionString:%@ currentInputManager:%p",
|
|
|
|
mIMECompositionString, [NSInputManager currentInputManager]);
|
|
|
|
#endif // DEBUG_IME_HANDLER
|
|
|
|
|
|
|
|
NS_ASSERTION(mIsIMEComposing, "We're not in composition");
|
|
|
|
|
|
|
|
mIsIMEComposing = PR_FALSE;
|
|
|
|
|
|
|
|
if (mIMECompositionString) {
|
|
|
|
[mIMECompositionString release];
|
|
|
|
mIMECompositionString = nsnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::SendCommittedText(NSString *aString)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
|
|
NS_ASSERTION(mView, "mView is null");
|
|
|
|
|
|
|
|
NSAttributedString* attrStr =
|
|
|
|
[[NSAttributedString alloc] initWithString:aString];
|
|
|
|
[mView insertText:attrStr];
|
|
|
|
[attrStr release];
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::KillIMEComposition()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
|
|
#ifdef DEBUG_IME_HANDLER
|
|
|
|
DebugPrintPointer(this);
|
2011-05-08 10:19:15 +00:00
|
|
|
NSLog(@"IMEInputHandler::KillIMEComposition");
|
2009-10-01 02:52:50 +00:00
|
|
|
NSLog(@" currentInputManager:%p", [NSInputManager currentInputManager]);
|
|
|
|
#endif // DEBUG_IME_HANDLER
|
|
|
|
|
|
|
|
if (!mView)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (IsFocused()) {
|
|
|
|
[[NSInputManager currentInputManager] markedTextAbandoned: mView];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef DEBUG_IME_HANDLER
|
|
|
|
DebugPrintPointer(this);
|
2011-05-08 10:19:15 +00:00
|
|
|
NSLog(@"Pending IMEInputHandler::KillIMEComposition...");
|
2009-10-01 02:52:50 +00:00
|
|
|
#endif // DEBUG_IME_HANDLER
|
|
|
|
|
|
|
|
// Commit the composition internally.
|
|
|
|
SendCommittedText(mIMECompositionString);
|
|
|
|
NS_ASSERTION(!mIsIMEComposing, "We're still in a composition");
|
|
|
|
// The pending method will be fired by the next focus event.
|
|
|
|
mPendingMethods |= kDiscardIMEComposition;
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::CommitIMEComposition()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
|
|
if (!IsIMEComposing())
|
|
|
|
return;
|
|
|
|
|
|
|
|
#ifdef DEBUG_IME_HANDLER
|
|
|
|
DebugPrintPointer(this);
|
2011-05-08 10:19:15 +00:00
|
|
|
NSLog(@"IMEInputHandler::CommitIMEComposition");
|
2009-10-01 02:52:50 +00:00
|
|
|
NSLog(@" mIMECompositionString:%@ currentInputManager:%p",
|
|
|
|
mIMECompositionString, [NSInputManager currentInputManager]);
|
|
|
|
#endif // DEBUG_IME_HANDLER
|
|
|
|
|
|
|
|
KillIMEComposition();
|
|
|
|
|
|
|
|
if (!IsIMEComposing())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// If the composition is still there, KillIMEComposition only kills the
|
|
|
|
// composition in TSM. We also need to finish the our composition too.
|
|
|
|
SendCommittedText(mIMECompositionString);
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::CancelIMEComposition()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
|
|
if (!IsIMEComposing())
|
|
|
|
return;
|
|
|
|
|
|
|
|
#ifdef DEBUG_IME_HANDLER
|
|
|
|
DebugPrintPointer(this);
|
2011-05-08 10:19:15 +00:00
|
|
|
NSLog(@"IMEInputHandler::CancelIMEComposition");
|
2009-10-01 02:52:50 +00:00
|
|
|
NSLog(@" mIMECompositionString:%@ currentInputManager:%p",
|
|
|
|
mIMECompositionString, [NSInputManager currentInputManager]);
|
|
|
|
#endif // DEBUG_IME_HANDLER
|
|
|
|
|
|
|
|
// For canceling the current composing, we need to ignore the param of
|
|
|
|
// insertText. But this code is ugly...
|
|
|
|
mIgnoreIMECommit = PR_TRUE;
|
|
|
|
KillIMEComposition();
|
|
|
|
mIgnoreIMECommit = PR_FALSE;
|
|
|
|
|
|
|
|
if (!IsIMEComposing())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// If the composition is still there, KillIMEComposition only kills the
|
|
|
|
// composition in TSM. We also need to kill the our composition too.
|
|
|
|
SendCommittedText(@"");
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::IsFocused()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
|
|
|
|
NS_ENSURE_TRUE(mView, PR_FALSE);
|
|
|
|
NSWindow* window = [mView window];
|
|
|
|
NS_ENSURE_TRUE(window, PR_FALSE);
|
2010-08-16 08:20:27 +00:00
|
|
|
return [window firstResponder] == mView &&
|
|
|
|
([window isMainWindow] || [window isSheet]) &&
|
2009-10-01 02:52:50 +00:00
|
|
|
[[NSApplication sharedApplication] isActive];
|
|
|
|
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(PR_FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::IsIMEOpened()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper tis;
|
2009-10-01 02:52:50 +00:00
|
|
|
tis.InitByCurrentInputSource();
|
|
|
|
return tis.IsOpenedIMEMode();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::SetASCIICapableOnly(PRBool aASCIICapableOnly)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
if (aASCIICapableOnly == mIsASCIICapableOnly)
|
|
|
|
return;
|
|
|
|
|
|
|
|
CommitIMEComposition();
|
|
|
|
mIsASCIICapableOnly = aASCIICapableOnly;
|
|
|
|
SyncASCIICapableOnly();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::EnableIME(PRBool aEnableIME)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
if (aEnableIME == mIsIMEEnabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
CommitIMEComposition();
|
|
|
|
mIsIMEEnabled = aEnableIME;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::SetIMEOpenState(PRBool aOpenIME)
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
if (!IsFocused() || IsIMEOpened() == aOpenIME)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!aOpenIME) {
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper tis;
|
2009-10-01 02:52:50 +00:00
|
|
|
tis.InitByCurrentASCIICapableInputSource();
|
|
|
|
tis.Select();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we know the latest IME opened mode, we should select it.
|
|
|
|
if (sLatestIMEOpenedModeInputSourceID) {
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper tis;
|
2009-10-01 02:52:50 +00:00
|
|
|
tis.InitByInputSourceID(sLatestIMEOpenedModeInputSourceID);
|
|
|
|
tis.Select();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// XXX If the current input source is a mode of IME, we should turn on it,
|
|
|
|
// but we haven't found such way...
|
|
|
|
|
|
|
|
// Finally, we should refer the system locale but this is a little expensive,
|
|
|
|
// we shouldn't retry this (if it was succeeded, we already set
|
|
|
|
// sLatestIMEOpenedModeInputSourceID at that time).
|
|
|
|
static PRBool sIsPrefferredIMESearched = PR_FALSE;
|
|
|
|
if (sIsPrefferredIMESearched)
|
|
|
|
return;
|
|
|
|
sIsPrefferredIMESearched = PR_TRUE;
|
|
|
|
OpenSystemPreferredLanguageIME();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2011-05-08 10:19:15 +00:00
|
|
|
IMEInputHandler::OpenSystemPreferredLanguageIME()
|
2009-10-01 02:52:50 +00:00
|
|
|
{
|
|
|
|
CFArrayRef langList = ::CFLocaleCopyPreferredLanguages();
|
|
|
|
if (!langList) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
CFIndex count = ::CFArrayGetCount(langList);
|
|
|
|
for (CFIndex i = 0; i < count; i++) {
|
|
|
|
CFLocaleRef locale =
|
|
|
|
::CFLocaleCreate(kCFAllocatorDefault,
|
|
|
|
static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, i)));
|
|
|
|
if (!locale) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool changed = PR_FALSE;
|
|
|
|
CFStringRef lang = static_cast<CFStringRef>(
|
|
|
|
::CFLocaleGetValue(locale, kCFLocaleLanguageCode));
|
|
|
|
NS_ASSERTION(lang, "lang is null");
|
|
|
|
if (lang) {
|
2011-05-08 10:19:15 +00:00
|
|
|
TISInputSourceWrapper tis;
|
2009-10-01 02:52:50 +00:00
|
|
|
tis.InitByLanguage(lang);
|
|
|
|
if (tis.IsOpenedIMEMode()) {
|
|
|
|
#ifdef DEBUG_IME_HANDLER
|
|
|
|
CFStringRef foundTIS;
|
|
|
|
tis.GetInputSourceID(foundTIS);
|
2011-05-08 10:19:15 +00:00
|
|
|
NSLog(@"IMEInputHandler::OpenSystemPreferredLanguageIME");
|
2009-10-01 02:52:50 +00:00
|
|
|
NSLog(@" found Input Source: %@ by %@",
|
|
|
|
(const NSString*)foundTIS, lang);
|
|
|
|
#endif // DEBUG_IME_HANDLER
|
|
|
|
tis.Select();
|
|
|
|
changed = PR_TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
::CFRelease(locale);
|
|
|
|
if (changed) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
::CFRelease(langList);
|
|
|
|
}
|
2011-05-08 10:19:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
*
|
|
|
|
* PluginTextInputHandler implementation
|
|
|
|
*
|
|
|
|
******************************************************************************/
|
|
|
|
|
|
|
|
PluginTextInputHandler::PluginTextInputHandler() :
|
|
|
|
TextInputHandlerBase()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PluginTextInputHandler::~PluginTextInputHandler()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
*
|
|
|
|
* TextInputHandlerBase implementation
|
|
|
|
*
|
|
|
|
******************************************************************************/
|
|
|
|
|
|
|
|
TextInputHandlerBase::TextInputHandlerBase() :
|
|
|
|
mOwnerWidget(nsnull), mView(nsnull)
|
|
|
|
{
|
|
|
|
gHandlerInstanceCount++;
|
|
|
|
}
|
|
|
|
|
|
|
|
TextInputHandlerBase::~TextInputHandlerBase()
|
|
|
|
{
|
|
|
|
if (--gHandlerInstanceCount == 0) {
|
|
|
|
FinalizeCurrentKeyboardLayout();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
TextInputHandlerBase::Init(nsChildView* aOwner)
|
|
|
|
{
|
|
|
|
mOwnerWidget = aOwner;
|
|
|
|
mView =
|
|
|
|
static_cast<NSView<mozView>*>(aOwner->GetNativeData(NS_NATIVE_WIDGET));
|
|
|
|
}
|
|
|
|
|
|
|
|
PRBool
|
|
|
|
TextInputHandlerBase::OnDestroyView(NSView<mozView> *aDestroyingView)
|
|
|
|
{
|
|
|
|
if (aDestroyingView != mView) {
|
|
|
|
return PR_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
mView = nsnull;
|
|
|
|
|
|
|
|
return PR_TRUE;
|
|
|
|
}
|