зеркало из https://github.com/mozilla/pjs.git
3901 строка
123 KiB
Plaintext
3901 строка
123 KiB
Plaintext
/* -*- 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 ***** */
|
|
|
|
#include "mozilla/Util.h"
|
|
|
|
#include "TextInputHandler.h"
|
|
|
|
#ifdef MOZ_LOGGING
|
|
#define FORCE_PR_LOG /* Allow logging in the release build */
|
|
#endif // MOZ_LOGGING
|
|
#include "prlog.h"
|
|
|
|
#include "nsChildView.h"
|
|
#include "nsObjCExceptions.h"
|
|
#include "nsBidiUtils.h"
|
|
#include "nsToolkit.h"
|
|
#include "nsCocoaUtils.h"
|
|
#include "nsPrintfCString.h"
|
|
|
|
#ifdef __LP64__
|
|
#include "ComplexTextInputPanel.h"
|
|
#endif // __LP64__
|
|
|
|
#ifndef NP_NO_CARBON
|
|
#include <objc/runtime.h>
|
|
#endif // NP_NO_CARBON
|
|
|
|
#ifdef MOZ_LOGGING
|
|
#define FORCE_PR_LOG
|
|
#endif
|
|
#include "prlog.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::widget;
|
|
|
|
#ifdef PR_LOGGING
|
|
|
|
PRLogModuleInfo* gLog = nsnull;
|
|
|
|
static const char*
|
|
OnOrOff(bool aBool)
|
|
{
|
|
return aBool ? "ON" : "off";
|
|
}
|
|
|
|
static const char*
|
|
TrueOrFalse(bool aBool)
|
|
{
|
|
return aBool ? "TRUE" : "FALSE";
|
|
}
|
|
|
|
static const char*
|
|
GetKeyNameForNativeKeyCode(unsigned short aNativeKeyCode)
|
|
{
|
|
switch (aNativeKeyCode) {
|
|
case kEscapeKeyCode: return "Escape";
|
|
case kRCommandKeyCode: return "Right-Command";
|
|
case kCommandKeyCode: return "Command";
|
|
case kShiftKeyCode: return "Shift";
|
|
case kCapsLockKeyCode: return "CapsLock";
|
|
case kOptionkeyCode: return "Option";
|
|
case kControlKeyCode: return "Control";
|
|
case kRShiftKeyCode: return "Right-Shift";
|
|
case kROptionKeyCode: return "Right-Option";
|
|
case kRControlKeyCode: return "Right-Control";
|
|
case kClearKeyCode: return "Clear";
|
|
|
|
case kF1KeyCode: return "F1";
|
|
case kF2KeyCode: return "F2";
|
|
case kF3KeyCode: return "F3";
|
|
case kF4KeyCode: return "F4";
|
|
case kF5KeyCode: return "F5";
|
|
case kF6KeyCode: return "F6";
|
|
case kF7KeyCode: return "F7";
|
|
case kF8KeyCode: return "F8";
|
|
case kF9KeyCode: return "F9";
|
|
case kF10KeyCode: return "F10";
|
|
case kF11KeyCode: return "F11";
|
|
case kF12KeyCode: return "F12";
|
|
case kF13KeyCode: return "F13/PrintScreen";
|
|
case kF14KeyCode: return "F14/ScrollLock";
|
|
case kF15KeyCode: return "F15/Pause";
|
|
|
|
case kKeypad0KeyCode: return "NumPad-0";
|
|
case kKeypad1KeyCode: return "NumPad-1";
|
|
case kKeypad2KeyCode: return "NumPad-2";
|
|
case kKeypad3KeyCode: return "NumPad-3";
|
|
case kKeypad4KeyCode: return "NumPad-4";
|
|
case kKeypad5KeyCode: return "NumPad-5";
|
|
case kKeypad6KeyCode: return "NumPad-6";
|
|
case kKeypad7KeyCode: return "NumPad-7";
|
|
case kKeypad8KeyCode: return "NumPad-8";
|
|
case kKeypad9KeyCode: return "NumPad-9";
|
|
|
|
case kKeypadMultiplyKeyCode: return "NumPad-*";
|
|
case kKeypadAddKeyCode: return "NumPad-+";
|
|
case kKeypadSubtractKeyCode: return "NumPad--";
|
|
case kKeypadDecimalKeyCode: return "NumPad-.";
|
|
case kKeypadDivideKeyCode: return "NumPad-/";
|
|
case kKeypadEqualsKeyCode: return "NumPad-=";
|
|
case kEnterKeyCode: return "NumPad-Enter";
|
|
case kReturnKeyCode: return "NumPad-Return";
|
|
case kPowerbookEnterKeyCode: return "NumPad-EnterOnPowerBook";
|
|
|
|
case kInsertKeyCode: return "Insert/Help";
|
|
case kDeleteKeyCode: return "Delete";
|
|
case kTabKeyCode: return "Tab";
|
|
case kTildeKeyCode: return "Tilde";
|
|
case kBackspaceKeyCode: return "Backspace";
|
|
case kHomeKeyCode: return "Home";
|
|
case kEndKeyCode: return "End";
|
|
case kPageUpKeyCode: return "PageUp";
|
|
case kPageDownKeyCode: return "PageDown";
|
|
case kLeftArrowKeyCode: return "LeftArrow";
|
|
case kRightArrowKeyCode: return "RightArrow";
|
|
case kUpArrowKeyCode: return "UpArrow";
|
|
case kDownArrowKeyCode: return "DownArrow";
|
|
|
|
default: return "undefined";
|
|
}
|
|
}
|
|
|
|
static const char*
|
|
GetCharacters(const NSString* aString)
|
|
{
|
|
nsAutoString str;
|
|
nsCocoaUtils::GetStringForNSString(aString, str);
|
|
if (str.IsEmpty()) {
|
|
return "";
|
|
}
|
|
|
|
nsAutoString escapedStr;
|
|
for (PRUint32 i = 0; i < str.Length(); i++) {
|
|
PRUnichar ch = str[i];
|
|
if (ch < 0x20) {
|
|
nsPrintfCString utf8str("(U+%04X)", ch);
|
|
escapedStr += NS_ConvertUTF8toUTF16(utf8str);
|
|
} else if (ch <= 0x7E) {
|
|
escapedStr += ch;
|
|
} else {
|
|
nsPrintfCString utf8str("(U+%04X)", ch);
|
|
escapedStr += ch;
|
|
escapedStr += NS_ConvertUTF8toUTF16(utf8str);
|
|
}
|
|
}
|
|
|
|
// the result will be freed automatically by cocoa.
|
|
NSString* result = nsCocoaUtils::ToNSString(escapedStr);
|
|
return [result UTF8String];
|
|
}
|
|
|
|
static const char*
|
|
GetCharacters(const CFStringRef aString)
|
|
{
|
|
const NSString* str = reinterpret_cast<const NSString*>(aString);
|
|
return GetCharacters(str);
|
|
}
|
|
|
|
static const char*
|
|
GetNativeKeyEventType(NSEvent* aNativeEvent)
|
|
{
|
|
switch ([aNativeEvent type]) {
|
|
case NSKeyDown: return "NSKeyDown";
|
|
case NSKeyUp: return "NSKeyUp";
|
|
case NSFlagsChanged: return "NSFlagsChanged";
|
|
default: return "not key event";
|
|
}
|
|
}
|
|
|
|
static const char*
|
|
GetGeckoKeyEventType(const nsEvent &aEvent)
|
|
{
|
|
switch (aEvent.message) {
|
|
case NS_KEY_DOWN: return "NS_KEY_DOWN";
|
|
case NS_KEY_UP: return "NS_KEY_UP";
|
|
case NS_KEY_PRESS: return "NS_KEY_PRESS";
|
|
default: return "not key event";
|
|
}
|
|
}
|
|
|
|
static const char*
|
|
GetRangeTypeName(PRUint32 aRangeType)
|
|
{
|
|
switch (aRangeType) {
|
|
case NS_TEXTRANGE_RAWINPUT:
|
|
return "NS_TEXTRANGE_RAWINPUT";
|
|
case NS_TEXTRANGE_CONVERTEDTEXT:
|
|
return "NS_TEXTRANGE_CONVERTEDTEXT";
|
|
case NS_TEXTRANGE_SELECTEDRAWTEXT:
|
|
return "NS_TEXTRANGE_SELECTEDRAWTEXT";
|
|
case NS_TEXTRANGE_SELECTEDCONVERTEDTEXT:
|
|
return "NS_TEXTRANGE_SELECTEDCONVERTEDTEXT";
|
|
case NS_TEXTRANGE_CARETPOSITION:
|
|
return "NS_TEXTRANGE_CARETPOSITION";
|
|
default:
|
|
return "invalid range type";
|
|
}
|
|
}
|
|
|
|
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 // #ifdef PR_LOGGING
|
|
|
|
static PRUint32 gHandlerInstanceCount = 0;
|
|
static TISInputSourceWrapper gCurrentKeyboardLayout;
|
|
|
|
static void
|
|
InitLogModule()
|
|
{
|
|
#ifdef PR_LOGGING
|
|
// Clear() is always called when TISInputSourceWrappper is created.
|
|
if (!gLog) {
|
|
gLog = PR_NewLogModule("TextInputHandlerWidgets");
|
|
TextInputHandler::DebugPrintAllKeyboardLayouts();
|
|
IMEInputHandler::DebugPrintAllIMEModes();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
InitCurrentKeyboardLayout()
|
|
{
|
|
if (gHandlerInstanceCount > 0 &&
|
|
!gCurrentKeyboardLayout.IsInitializedByCurrentKeyboardLayout()) {
|
|
gCurrentKeyboardLayout.InitByCurrentKeyboardLayout();
|
|
}
|
|
}
|
|
|
|
static void
|
|
FinalizeCurrentKeyboardLayout()
|
|
{
|
|
gCurrentKeyboardLayout.Clear();
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
/******************************************************************************
|
|
*
|
|
* TISInputSourceWrapper implementation
|
|
*
|
|
******************************************************************************/
|
|
|
|
// static
|
|
TISInputSourceWrapper&
|
|
TISInputSourceWrapper::CurrentKeyboardLayout()
|
|
{
|
|
InitCurrentKeyboardLayout();
|
|
return gCurrentKeyboardLayout;
|
|
}
|
|
|
|
bool
|
|
TISInputSourceWrapper::TranslateToString(UInt32 aKeyCode, UInt32 aModifiers,
|
|
UInt32 aKbType, nsAString &aStr)
|
|
{
|
|
aStr.Truncate();
|
|
|
|
const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TISInputSourceWrapper::TranslateToString, aKeyCode=0x%X, "
|
|
"aModifiers=0x%X, aKbType=0x%X UCKey=%p\n "
|
|
"Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
|
|
this, aKeyCode, aModifiers, aKbType, UCKey,
|
|
OnOrOff(aModifiers & shiftKey), OnOrOff(aModifiers & controlKey),
|
|
OnOrOff(aModifiers & optionKey), OnOrOff(aModifiers & cmdKey),
|
|
OnOrOff(aModifiers & alphaLock),
|
|
OnOrOff(aModifiers & kEventKeyModifierNumLockMask)));
|
|
|
|
NS_ENSURE_TRUE(UCKey, false);
|
|
|
|
UInt32 deadKeyState = 0;
|
|
UniCharCount len;
|
|
UniChar chars[5];
|
|
OSStatus err = ::UCKeyTranslate(UCKey, aKeyCode,
|
|
kUCKeyActionDown, aModifiers >> 8,
|
|
aKbType, kUCKeyTranslateNoDeadKeysMask,
|
|
&deadKeyState, 5, &len, chars);
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TISInputSourceWrapper::TranslateToString, err=0x%X, len=%llu",
|
|
this, err, len));
|
|
|
|
NS_ENSURE_TRUE(err == noErr, false);
|
|
if (len == 0) {
|
|
return true;
|
|
}
|
|
NS_ENSURE_TRUE(EnsureStringLength(aStr, len), false);
|
|
NS_ASSERTION(sizeof(PRUnichar) == sizeof(UniChar),
|
|
"size of PRUnichar and size of UniChar are different");
|
|
memcpy(aStr.BeginWriting(), chars, len * sizeof(PRUnichar));
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TISInputSourceWrapper::TranslateToString, aStr=\"%s\"",
|
|
this, NS_ConvertUTF16toUTF8(aStr).get()));
|
|
|
|
return true;
|
|
}
|
|
|
|
PRUint32
|
|
TISInputSourceWrapper::TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers,
|
|
UInt32 aKbType)
|
|
{
|
|
nsAutoString str;
|
|
if (!TranslateToString(aKeyCode, aModifiers, aKbType, str) ||
|
|
str.Length() != 1) {
|
|
return 0;
|
|
}
|
|
return static_cast<PRUint32>(str.CharAt(0));
|
|
}
|
|
|
|
void
|
|
TISInputSourceWrapper::InitByInputSourceID(const char* aID)
|
|
{
|
|
Clear();
|
|
if (!aID)
|
|
return;
|
|
|
|
CFStringRef idstr = ::CFStringCreateWithCString(kCFAllocatorDefault, aID,
|
|
kCFStringEncodingASCII);
|
|
InitByInputSourceID(idstr);
|
|
::CFRelease(idstr);
|
|
}
|
|
|
|
void
|
|
TISInputSourceWrapper::InitByInputSourceID(const nsAFlatString &aID)
|
|
{
|
|
Clear();
|
|
if (aID.IsEmpty())
|
|
return;
|
|
CFStringRef idstr = ::CFStringCreateWithCharacters(kCFAllocatorDefault,
|
|
aID.get(), aID.Length());
|
|
InitByInputSourceID(idstr);
|
|
::CFRelease(idstr);
|
|
}
|
|
|
|
void
|
|
TISInputSourceWrapper::InitByInputSourceID(const CFStringRef aID)
|
|
{
|
|
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
|
|
TISInputSourceWrapper::InitByLayoutID(SInt32 aLayoutID,
|
|
bool aOverrideKeyboard)
|
|
{
|
|
// NOTE: Doument new layout IDs in TextInputHandler.h when you add ones.
|
|
switch (aLayoutID) {
|
|
case 0:
|
|
InitByInputSourceID("com.apple.keylayout.US");
|
|
break;
|
|
case 1:
|
|
InitByInputSourceID("com.apple.keylayout.Greek");
|
|
break;
|
|
case 2:
|
|
InitByInputSourceID("com.apple.keylayout.German");
|
|
break;
|
|
case 3:
|
|
InitByInputSourceID("com.apple.keylayout.Swedish-Pro");
|
|
break;
|
|
case 4:
|
|
InitByInputSourceID("com.apple.keylayout.DVORAK-QWERTYCMD");
|
|
break;
|
|
case 5:
|
|
InitByInputSourceID("com.apple.keylayout.Thai");
|
|
break;
|
|
default:
|
|
Clear();
|
|
break;
|
|
}
|
|
mOverrideKeyboard = aOverrideKeyboard;
|
|
}
|
|
|
|
void
|
|
TISInputSourceWrapper::InitByCurrentInputSource()
|
|
{
|
|
Clear();
|
|
mInputSource = ::TISCopyCurrentKeyboardInputSource();
|
|
}
|
|
|
|
void
|
|
TISInputSourceWrapper::InitByCurrentKeyboardLayout()
|
|
{
|
|
Clear();
|
|
mInputSource = ::TISCopyCurrentKeyboardLayoutInputSource();
|
|
}
|
|
|
|
void
|
|
TISInputSourceWrapper::InitByCurrentASCIICapableInputSource()
|
|
{
|
|
Clear();
|
|
mInputSource = ::TISCopyCurrentASCIICapableKeyboardInputSource();
|
|
}
|
|
|
|
void
|
|
TISInputSourceWrapper::InitByCurrentASCIICapableKeyboardLayout()
|
|
{
|
|
Clear();
|
|
mInputSource = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
|
|
}
|
|
|
|
void
|
|
TISInputSourceWrapper::InitByTISInputSourceRef(TISInputSourceRef aInputSource)
|
|
{
|
|
Clear();
|
|
mInputSource = aInputSource;
|
|
}
|
|
|
|
void
|
|
TISInputSourceWrapper::InitByLanguage(CFStringRef aLanguage)
|
|
{
|
|
Clear();
|
|
mInputSource = ::TISCopyInputSourceForLanguage(aLanguage);
|
|
}
|
|
|
|
const UCKeyboardLayout*
|
|
TISInputSourceWrapper::GetUCKeyboardLayout()
|
|
{
|
|
NS_ENSURE_TRUE(mInputSource, nsnull);
|
|
if (mUCKeyboardLayout) {
|
|
return mUCKeyboardLayout;
|
|
}
|
|
CFDataRef uchr = static_cast<CFDataRef>(
|
|
::TISGetInputSourceProperty(mInputSource,
|
|
kTISPropertyUnicodeKeyLayoutData));
|
|
|
|
// We should be always able to get the layout here.
|
|
NS_ENSURE_TRUE(uchr, nsnull);
|
|
mUCKeyboardLayout =
|
|
reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(uchr));
|
|
return mUCKeyboardLayout;
|
|
}
|
|
|
|
bool
|
|
TISInputSourceWrapper::GetBoolProperty(const CFStringRef aKey)
|
|
{
|
|
CFBooleanRef ret = static_cast<CFBooleanRef>(
|
|
::TISGetInputSourceProperty(mInputSource, aKey));
|
|
return ::CFBooleanGetValue(ret);
|
|
}
|
|
|
|
bool
|
|
TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
|
|
CFStringRef &aStr)
|
|
{
|
|
aStr = static_cast<CFStringRef>(
|
|
::TISGetInputSourceProperty(mInputSource, aKey));
|
|
return aStr != nsnull;
|
|
}
|
|
|
|
bool
|
|
TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
|
|
nsAString &aStr)
|
|
{
|
|
CFStringRef str;
|
|
GetStringProperty(aKey, str);
|
|
nsCocoaUtils::GetStringForNSString((const NSString*)str, aStr);
|
|
return !aStr.IsEmpty();
|
|
}
|
|
|
|
bool
|
|
TISInputSourceWrapper::IsOpenedIMEMode()
|
|
{
|
|
NS_ENSURE_TRUE(mInputSource, false);
|
|
if (!IsIMEMode())
|
|
return false;
|
|
return !IsASCIICapable();
|
|
}
|
|
|
|
bool
|
|
TISInputSourceWrapper::IsIMEMode()
|
|
{
|
|
NS_ENSURE_TRUE(mInputSource, false);
|
|
CFStringRef str;
|
|
GetInputSourceType(str);
|
|
NS_ENSURE_TRUE(str, false);
|
|
return ::CFStringCompare(kTISTypeKeyboardInputMode,
|
|
str, 0) == kCFCompareEqualTo;
|
|
}
|
|
|
|
bool
|
|
TISInputSourceWrapper::GetLanguageList(CFArrayRef &aLanguageList)
|
|
{
|
|
NS_ENSURE_TRUE(mInputSource, false);
|
|
aLanguageList = static_cast<CFArrayRef>(
|
|
::TISGetInputSourceProperty(mInputSource,
|
|
kTISPropertyInputSourceLanguages));
|
|
return aLanguageList != nsnull;
|
|
}
|
|
|
|
bool
|
|
TISInputSourceWrapper::GetPrimaryLanguage(CFStringRef &aPrimaryLanguage)
|
|
{
|
|
NS_ENSURE_TRUE(mInputSource, false);
|
|
CFArrayRef langList;
|
|
NS_ENSURE_TRUE(GetLanguageList(langList), false);
|
|
if (::CFArrayGetCount(langList) == 0)
|
|
return false;
|
|
aPrimaryLanguage =
|
|
static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, 0));
|
|
return aPrimaryLanguage != nsnull;
|
|
}
|
|
|
|
bool
|
|
TISInputSourceWrapper::GetPrimaryLanguage(nsAString &aPrimaryLanguage)
|
|
{
|
|
NS_ENSURE_TRUE(mInputSource, false);
|
|
CFStringRef primaryLanguage;
|
|
NS_ENSURE_TRUE(GetPrimaryLanguage(primaryLanguage), false);
|
|
nsCocoaUtils::GetStringForNSString((const NSString*)primaryLanguage,
|
|
aPrimaryLanguage);
|
|
return !aPrimaryLanguage.IsEmpty();
|
|
}
|
|
|
|
bool
|
|
TISInputSourceWrapper::IsForRTLLanguage()
|
|
{
|
|
if (mIsRTL < 0) {
|
|
// Get the input character of the 'A' key of ANSI keyboard layout.
|
|
nsAutoString str;
|
|
bool 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;
|
|
}
|
|
|
|
bool
|
|
TISInputSourceWrapper::IsInitializedByCurrentKeyboardLayout()
|
|
{
|
|
return mInputSource == ::TISCopyCurrentKeyboardLayoutInputSource();
|
|
}
|
|
|
|
void
|
|
TISInputSourceWrapper::Select()
|
|
{
|
|
if (!mInputSource)
|
|
return;
|
|
::TISSelectInputSource(mInputSource);
|
|
}
|
|
|
|
void
|
|
TISInputSourceWrapper::Clear()
|
|
{
|
|
// Clear() is always called when TISInputSourceWrappper is created.
|
|
InitLogModule();
|
|
|
|
if (mInputSourceList) {
|
|
::CFRelease(mInputSourceList);
|
|
}
|
|
mInputSourceList = nsnull;
|
|
mInputSource = nsnull;
|
|
mIsRTL = -1;
|
|
mUCKeyboardLayout = nsnull;
|
|
mOverrideKeyboard = false;
|
|
}
|
|
|
|
void
|
|
TISInputSourceWrapper::InitKeyEvent(NSEvent *aNativeKeyEvent,
|
|
nsKeyEvent& aKeyEvent)
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TISInputSourceWrapper::InitKeyEvent, aNativeKeyEvent=%p"
|
|
"aKeyEvent.message=%s",
|
|
this, aNativeKeyEvent, GetGeckoKeyEventType(aKeyEvent)));
|
|
|
|
NS_ENSURE_TRUE(aNativeKeyEvent, );
|
|
|
|
aKeyEvent.time = PR_IntervalNow();
|
|
|
|
nsCocoaUtils::InitInputEvent(aKeyEvent, aNativeKeyEvent);
|
|
|
|
aKeyEvent.refPoint = nsIntPoint(0, 0);
|
|
aKeyEvent.isChar = false; // XXX not used in XP level
|
|
|
|
NSString* str = nil;
|
|
if ([aNativeKeyEvent type] != NSFlagsChanged) {
|
|
str = [aNativeKeyEvent charactersIgnoringModifiers];
|
|
}
|
|
aKeyEvent.keyCode =
|
|
TextInputHandler::ComputeGeckoKeyCode([aNativeKeyEvent keyCode], str);
|
|
|
|
switch ([aNativeKeyEvent keyCode]) {
|
|
case kCommandKeyCode:
|
|
case kShiftKeyCode:
|
|
case kOptionkeyCode:
|
|
case kControlKeyCode:
|
|
aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
|
|
break;
|
|
|
|
case kRCommandKeyCode:
|
|
case kRShiftKeyCode:
|
|
case kROptionKeyCode:
|
|
case kRControlKeyCode:
|
|
aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT;
|
|
break;
|
|
|
|
case kKeypad0KeyCode:
|
|
case kKeypad1KeyCode:
|
|
case kKeypad2KeyCode:
|
|
case kKeypad3KeyCode:
|
|
case kKeypad4KeyCode:
|
|
case kKeypad5KeyCode:
|
|
case kKeypad6KeyCode:
|
|
case kKeypad7KeyCode:
|
|
case kKeypad8KeyCode:
|
|
case kKeypad9KeyCode:
|
|
case kKeypadMultiplyKeyCode:
|
|
case kKeypadAddKeyCode:
|
|
case kKeypadSubtractKeyCode:
|
|
case kKeypadDecimalKeyCode:
|
|
case kKeypadDivideKeyCode:
|
|
case kKeypadEqualsKeyCode:
|
|
case kEnterKeyCode:
|
|
case kPowerbookEnterKeyCode:
|
|
aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
|
|
break;
|
|
|
|
default:
|
|
aKeyEvent.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
|
|
break;
|
|
}
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TISInputSourceWrapper::InitKeyEvent, "
|
|
"shift=%s, ctrl=%s, alt=%s, meta=%s",
|
|
this, OnOrOff(aKeyEvent.IsShift()), OnOrOff(aKeyEvent.IsControl()),
|
|
OnOrOff(aKeyEvent.IsAlt()), OnOrOff(aKeyEvent.IsMeta())));
|
|
|
|
if (aKeyEvent.message == NS_KEY_PRESS &&
|
|
!TextInputHandler::IsSpecialGeckoKey([aNativeKeyEvent keyCode])) {
|
|
InitKeyPressEvent(aNativeKeyEvent, aKeyEvent);
|
|
return;
|
|
}
|
|
|
|
aKeyEvent.charCode = 0;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TISInputSourceWrapper::InitKeyEvent, keyCode=0x%X charCode=0x0",
|
|
this, aKeyEvent.keyCode));
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK
|
|
}
|
|
|
|
void
|
|
TISInputSourceWrapper::InitKeyPressEvent(NSEvent *aNativeKeyEvent,
|
|
nsKeyEvent& aKeyEvent)
|
|
{
|
|
NS_ASSERTION(aKeyEvent.message == NS_KEY_PRESS,
|
|
"aKeyEvent must be NS_KEY_PRESS event");
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TISInputSourceWrapper::InitKeyPressEvent, aNativeKeyEvent=%p"
|
|
"aKeyEvent.message=%s",
|
|
this, aNativeKeyEvent, GetGeckoKeyEventType(aKeyEvent)));
|
|
|
|
aKeyEvent.isChar = true; // this is not a special key XXX not used in XP
|
|
|
|
aKeyEvent.charCode = 0;
|
|
NSString* chars = [aNativeKeyEvent characters];
|
|
if ([chars length] > 0) {
|
|
aKeyEvent.charCode = [chars characterAtIndex:0];
|
|
}
|
|
|
|
// convert control-modified charCode to raw charCode (with appropriate case)
|
|
if (aKeyEvent.IsControl() && aKeyEvent.charCode <= 26) {
|
|
aKeyEvent.charCode += (aKeyEvent.IsShift()) ? ('A' - 1) : ('a' - 1);
|
|
}
|
|
|
|
if (aKeyEvent.charCode != 0) {
|
|
aKeyEvent.keyCode = 0;
|
|
}
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TISInputSourceWrapper::InitKeyPressEvent, "
|
|
"aKeyEvent.keyCode=0x%X, aKeyEvent.charCode=0x%X",
|
|
this, aKeyEvent.keyCode, aKeyEvent.charCode));
|
|
|
|
if (!aKeyEvent.IsControl() && !aKeyEvent.IsMeta() && !aKeyEvent.IsAlt()) {
|
|
return;
|
|
}
|
|
|
|
TISInputSourceWrapper USLayout("com.apple.keylayout.US");
|
|
bool isRomanKeyboardLayout = IsASCIICapable();
|
|
|
|
// If a keyboard layout override is set, we also need to force the
|
|
// keyboard type to something ANSI to avoid test failures on machines
|
|
// with JIS keyboards (since the pair of keyboard layout and physical
|
|
// keyboard type form the actual key layout). This assumes that the
|
|
// test setting the override was written assuming an ANSI keyboard.
|
|
UInt32 kbType = mOverrideKeyboard ? eKbdType_ANSI : ::LMGetKbdType();
|
|
|
|
UInt32 key = [aNativeKeyEvent keyCode];
|
|
|
|
// Caps lock and num lock modifier state:
|
|
UInt32 lockState = 0;
|
|
if ([aNativeKeyEvent modifierFlags] & NSAlphaShiftKeyMask) {
|
|
lockState |= alphaLock;
|
|
}
|
|
if ([aNativeKeyEvent modifierFlags] & NSNumericPadKeyMask) {
|
|
lockState |= kEventKeyModifierNumLockMask;
|
|
}
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TISInputSourceWrapper::InitKeyPressEvent, "
|
|
"isRomanKeyboardLayout=%s, kbType=0x%X, key=0x%X",
|
|
this, TrueOrFalse(isRomanKeyboardLayout), kbType, key));
|
|
|
|
nsString str;
|
|
|
|
// normal chars
|
|
PRUint32 unshiftedChar = TranslateToChar(key, lockState, kbType);
|
|
UInt32 shiftLockMod = shiftKey | lockState;
|
|
PRUint32 shiftedChar = TranslateToChar(key, shiftLockMod, kbType);
|
|
|
|
// characters generated with Cmd key
|
|
// XXX we should remove CapsLock state, which changes characters from
|
|
// Latin to Cyrillic with Russian layout on 10.4 only when Cmd key
|
|
// is pressed.
|
|
UInt32 numState = (lockState & ~alphaLock); // only num lock state
|
|
PRUint32 uncmdedChar = TranslateToChar(key, numState, kbType);
|
|
UInt32 shiftNumMod = numState | shiftKey;
|
|
PRUint32 uncmdedShiftChar = TranslateToChar(key, shiftNumMod, kbType);
|
|
PRUint32 uncmdedUSChar = USLayout.TranslateToChar(key, numState, kbType);
|
|
UInt32 cmdNumMod = cmdKey | numState;
|
|
PRUint32 cmdedChar = TranslateToChar(key, cmdNumMod, kbType);
|
|
UInt32 cmdShiftNumMod = shiftKey | cmdNumMod;
|
|
PRUint32 cmdedShiftChar = TranslateToChar(key, cmdShiftNumMod, kbType);
|
|
|
|
// Is the keyboard layout changed by Cmd key?
|
|
// E.g., Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
|
|
bool isCmdSwitchLayout = uncmdedChar != cmdedChar;
|
|
// Is the keyboard layout for Latin, but Cmd key switches the layout?
|
|
// I.e., Dvorak-QWERTY
|
|
bool isDvorakQWERTY = isCmdSwitchLayout && isRomanKeyboardLayout;
|
|
|
|
// If the current keyboard is not Dvorak-QWERTY or Cmd is not pressed,
|
|
// we should append unshiftedChar and shiftedChar for handling the
|
|
// normal characters. These are the characters that the user is most
|
|
// likely to associate with this key.
|
|
if ((unshiftedChar || shiftedChar) &&
|
|
(!aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
|
|
nsAlternativeCharCode altCharCodes(unshiftedChar, shiftedChar);
|
|
aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes);
|
|
}
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TISInputSourceWrapper::InitKeyPressEvent, "
|
|
"aKeyEvent.isMeta=%s, isDvorakQWERTY=%s, "
|
|
"unshiftedChar=U+%X, shiftedChar=U+%X",
|
|
this, OnOrOff(aKeyEvent.IsMeta()), TrueOrFalse(isDvorakQWERTY),
|
|
unshiftedChar, shiftedChar));
|
|
|
|
// Most keyboard layouts provide the same characters in the NSEvents
|
|
// with Command+Shift as with Command. However, with Command+Shift we
|
|
// want the character on the second level. e.g. With a US QWERTY
|
|
// layout, we want "?" when the "/","?" key is pressed with
|
|
// Command+Shift.
|
|
|
|
// On a German layout, the OS gives us '/' with Cmd+Shift+SS(eszett)
|
|
// even though Cmd+SS is 'SS' and Shift+'SS' is '?'. This '/' seems
|
|
// like a hack to make the Cmd+"?" event look the same as the Cmd+"?"
|
|
// event on a US keyboard. The user thinks they are typing Cmd+"?", so
|
|
// we'll prefer the "?" character, replacing charCode with shiftedChar
|
|
// when Shift is pressed. However, in case there is a layout where the
|
|
// character unique to Cmd+Shift is the character that the user expects,
|
|
// we'll send it as an alternative char.
|
|
bool hasCmdShiftOnlyChar =
|
|
cmdedChar != cmdedShiftChar && uncmdedShiftChar != cmdedShiftChar;
|
|
PRUint32 originalCmdedShiftChar = cmdedShiftChar;
|
|
|
|
// If we can make a good guess at the characters that the user would
|
|
// expect this key combination to produce (with and without Shift) then
|
|
// use those characters. This also corrects for CapsLock, which was
|
|
// ignored above.
|
|
if (!isCmdSwitchLayout) {
|
|
// The characters produced with Command seem similar to those without
|
|
// Command.
|
|
if (unshiftedChar) {
|
|
cmdedChar = unshiftedChar;
|
|
}
|
|
if (shiftedChar) {
|
|
cmdedShiftChar = shiftedChar;
|
|
}
|
|
} else if (uncmdedUSChar == cmdedChar) {
|
|
// It looks like characters from a US layout are provided when Command
|
|
// is down.
|
|
PRUint32 ch = USLayout.TranslateToChar(key, lockState, kbType);
|
|
if (ch) {
|
|
cmdedChar = ch;
|
|
}
|
|
ch = USLayout.TranslateToChar(key, shiftLockMod, kbType);
|
|
if (ch) {
|
|
cmdedShiftChar = ch;
|
|
}
|
|
}
|
|
|
|
// Only charCode (not alternativeCharCodes) is available to javascript,
|
|
// so attempt to set this to the most likely intended (or most useful)
|
|
// character. Note that cmdedChar and cmdedShiftChar are usually
|
|
// Latin/ASCII characters and that is what is wanted here as accel
|
|
// keys are expected to be Latin characters.
|
|
//
|
|
// XXX We should do something similar when Control is down (bug 429510).
|
|
if (aKeyEvent.IsMeta() && !(aKeyEvent.IsControl() || aKeyEvent.IsAlt())) {
|
|
// The character to use for charCode.
|
|
PRUint32 preferredCharCode = 0;
|
|
preferredCharCode = aKeyEvent.IsShift() ? cmdedShiftChar : cmdedChar;
|
|
|
|
if (preferredCharCode) {
|
|
aKeyEvent.charCode = preferredCharCode;
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TISInputSourceWrapper::InitKeyPressEvent, "
|
|
"aKeyEvent.charCode=U+%X",
|
|
this, aKeyEvent.charCode));
|
|
}
|
|
}
|
|
|
|
// If the current keyboard layout is switched by the Cmd key,
|
|
// we should append cmdedChar and shiftedCmdChar that are
|
|
// Latin char for the key.
|
|
// If the keyboard layout is Dvorak-QWERTY, we should append them only when
|
|
// command key is pressed because when command key isn't pressed, uncmded
|
|
// chars have been appended already.
|
|
if ((cmdedChar || cmdedShiftChar) && isCmdSwitchLayout &&
|
|
(aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
|
|
nsAlternativeCharCode altCharCodes(cmdedChar, cmdedShiftChar);
|
|
aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes);
|
|
}
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TISInputSourceWrapper::InitKeyPressEvent, "
|
|
"hasCmdShiftOnlyChar=%s, isCmdSwitchLayout=%s, isDvorakQWERTY=%s, "
|
|
"cmdedChar=U+%X, cmdedShiftChar=U+%X",
|
|
this, TrueOrFalse(hasCmdShiftOnlyChar), TrueOrFalse(isDvorakQWERTY),
|
|
TrueOrFalse(isDvorakQWERTY), cmdedChar, cmdedShiftChar));
|
|
// Special case for 'SS' key of German layout. See the comment of
|
|
// hasCmdShiftOnlyChar definition for the detail.
|
|
if (hasCmdShiftOnlyChar && originalCmdedShiftChar) {
|
|
nsAlternativeCharCode altCharCodes(0, originalCmdedShiftChar);
|
|
aKeyEvent.alternativeCharCodes.AppendElement(altCharCodes);
|
|
}
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TISInputSourceWrapper::InitKeyPressEvent, "
|
|
"hasCmdShiftOnlyChar=%s, originalCmdedShiftChar=U+%X",
|
|
this, TrueOrFalse(hasCmdShiftOnlyChar), originalCmdedShiftChar));
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
/******************************************************************************
|
|
*
|
|
* TextInputHandler implementation (static methods)
|
|
*
|
|
******************************************************************************/
|
|
|
|
NSUInteger TextInputHandler::sLastModifierState = 0;
|
|
|
|
// static
|
|
CFArrayRef
|
|
TextInputHandler::CreateAllKeyboardLayoutList()
|
|
{
|
|
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
|
|
TextInputHandler::DebugPrintAllKeyboardLayouts()
|
|
{
|
|
#ifdef PR_LOGGING
|
|
if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
|
|
CFArrayRef list = CreateAllKeyboardLayoutList();
|
|
PR_LOG(gLog, PR_LOG_ALWAYS, ("Keyboard layout configuration:"));
|
|
CFIndex idx = ::CFArrayGetCount(list);
|
|
TISInputSourceWrapper tis;
|
|
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(gLog, 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);
|
|
}
|
|
#endif // #ifdef PR_LOGGING
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
/******************************************************************************
|
|
*
|
|
* TextInputHandler implementation
|
|
*
|
|
******************************************************************************/
|
|
|
|
TextInputHandler::TextInputHandler(nsChildView* aWidget,
|
|
NSView<mozView> *aNativeView) :
|
|
IMEInputHandler(aWidget, aNativeView)
|
|
{
|
|
InitLogModule();
|
|
[mView installTextInputHandler:this];
|
|
}
|
|
|
|
TextInputHandler::~TextInputHandler()
|
|
{
|
|
[mView uninstallTextInputHandler];
|
|
}
|
|
|
|
bool
|
|
TextInputHandler::HandleKeyDownEvent(NSEvent* aNativeEvent)
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
|
|
|
|
if (Destroyed()) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::HandleKeyDownEvent, "
|
|
"widget has been already destroyed", this));
|
|
return false;
|
|
}
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::HandleKeyDownEvent, aNativeEvent=%p, "
|
|
"type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", "
|
|
"charactersIgnoringModifiers=\"%s\"",
|
|
this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
|
|
[aNativeEvent keyCode], [aNativeEvent keyCode],
|
|
[aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]),
|
|
GetCharacters([aNativeEvent charactersIgnoringModifiers])));
|
|
|
|
nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
|
|
|
|
KeyEventState* currentKeyEvent = PushKeyEvent(aNativeEvent);
|
|
AutoKeyEventStateCleaner remover(this);
|
|
|
|
BOOL nonDeadKeyPress = [[aNativeEvent characters] length] > 0;
|
|
if (nonDeadKeyPress && !IsIMEComposing()) {
|
|
NSResponder* firstResponder = [[mView window] firstResponder];
|
|
|
|
nsKeyEvent keydownEvent(true, NS_KEY_DOWN, mWidget);
|
|
InitKeyEvent(aNativeEvent, keydownEvent);
|
|
|
|
#ifndef NP_NO_CARBON
|
|
EventRecord carbonEvent;
|
|
if ([mView pluginEventModel] == NPEventModelCarbon) {
|
|
ConvertCocoaKeyEventToCarbonEvent(aNativeEvent, carbonEvent, true);
|
|
keydownEvent.pluginEvent = &carbonEvent;
|
|
}
|
|
#endif // #ifndef NP_NO_CARBON
|
|
|
|
currentKeyEvent->mKeyDownHandled = DispatchEvent(keydownEvent);
|
|
if (Destroyed()) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::HandleKeyDownEvent, "
|
|
"widget was destroyed by keydown event", this));
|
|
return currentKeyEvent->KeyDownOrPressHandled();
|
|
}
|
|
|
|
// The key down event may have shifted the focus, in which
|
|
// case we should not fire the key press.
|
|
// XXX This is a special code only on Cocoa widget, why is this needed?
|
|
if (firstResponder != [[mView window] firstResponder]) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::HandleKeyDownEvent, "
|
|
"view lost focus by keydown event", this));
|
|
return currentKeyEvent->KeyDownOrPressHandled();
|
|
}
|
|
|
|
// If this is the context menu key command, send a context menu key event.
|
|
NSUInteger modifierFlags =
|
|
[aNativeEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
|
|
if (modifierFlags == NSControlKeyMask &&
|
|
[[aNativeEvent charactersIgnoringModifiers] isEqualToString:@" "]) {
|
|
nsMouseEvent contextMenuEvent(true, NS_CONTEXTMENU,
|
|
[mView widget], nsMouseEvent::eReal,
|
|
nsMouseEvent::eContextMenuKey);
|
|
contextMenuEvent.modifiers = 0;
|
|
|
|
bool cmEventHandled = DispatchEvent(contextMenuEvent);
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::HandleKeyDownEvent, "
|
|
"context menu event dispatched, handled=%s%s",
|
|
this, TrueOrFalse(cmEventHandled),
|
|
Destroyed() ? " and widget was destroyed" : ""));
|
|
[mView maybeInitContextMenuTracking];
|
|
// Bail, there is nothing else to do here.
|
|
return (cmEventHandled || currentKeyEvent->KeyDownOrPressHandled());
|
|
}
|
|
}
|
|
|
|
// Let Cocoa interpret the key events, caching IsIMEComposing first.
|
|
bool wasComposing = IsIMEComposing();
|
|
bool interpretKeyEventsCalled = false;
|
|
if (IsIMEEnabled() || IsASCIICapableOnly()) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::HandleKeyDownEvent, calling interpretKeyEvents",
|
|
this));
|
|
[mView interpretKeyEvents:[NSArray arrayWithObject:aNativeEvent]];
|
|
interpretKeyEventsCalled = true;
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::HandleKeyDownEvent, called interpretKeyEvents",
|
|
this));
|
|
}
|
|
|
|
if (Destroyed()) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::HandleKeyDownEvent, widget was destroyed",
|
|
this));
|
|
return currentKeyEvent->KeyDownOrPressHandled();
|
|
}
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::HandleKeyDownEvent, wasComposing=%s, "
|
|
"IsIMEComposing()=%s",
|
|
this, TrueOrFalse(wasComposing), TrueOrFalse(IsIMEComposing())));
|
|
|
|
if (!currentKeyEvent->mKeyPressDispatched && nonDeadKeyPress &&
|
|
!wasComposing && !IsIMEComposing()) {
|
|
nsKeyEvent keypressEvent(true, NS_KEY_PRESS, mWidget);
|
|
InitKeyEvent(aNativeEvent, keypressEvent);
|
|
|
|
// If we called interpretKeyEvents and this isn't normal character input
|
|
// then IME probably ate the event for some reason. We do not want to
|
|
// send a key press event in that case.
|
|
// TODO:
|
|
// There are some other cases which IME eats the current event.
|
|
// 1. If key events were nested during calling interpretKeyEvents, it means
|
|
// that IME did something. Then, we should do nothing.
|
|
// 2. If one or more commands are called like "deleteBackward", we should
|
|
// dispatch keypress event at that time. Note that the command may have
|
|
// been a converted or generated action by IME. Then, we shouldn't do
|
|
// our default action for this key.
|
|
if (!(interpretKeyEventsCalled &&
|
|
IsNormalCharInputtingEvent(keypressEvent))) {
|
|
if (currentKeyEvent->mKeyDownHandled ||
|
|
currentKeyEvent->mCausedOtherKeyEvents) {
|
|
keypressEvent.flags |= NS_EVENT_FLAG_NO_DEFAULT;
|
|
}
|
|
currentKeyEvent->mKeyPressHandled = DispatchEvent(keypressEvent);
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::HandleKeyDownEvent, keypress event dispatched",
|
|
this));
|
|
}
|
|
}
|
|
|
|
// Note: mWidget might have become null here. Don't count on it from here on.
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::HandleKeyDownEvent, "
|
|
"keydown handled=%s, keypress handled=%s",
|
|
this, TrueOrFalse(currentKeyEvent->mKeyDownHandled),
|
|
TrueOrFalse(currentKeyEvent->mKeyPressHandled)));
|
|
return currentKeyEvent->KeyDownOrPressHandled();
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
|
|
}
|
|
|
|
void
|
|
TextInputHandler::HandleKeyUpEvent(NSEvent* aNativeEvent)
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::HandleKeyUpEvent, aNativeEvent=%p, "
|
|
"type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", "
|
|
"charactersIgnoringModifiers=\"%s\", "
|
|
"mIgnoreNextKeyUpEvent=%s, IsIMEComposing()=%s",
|
|
this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
|
|
[aNativeEvent keyCode], [aNativeEvent keyCode],
|
|
[aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]),
|
|
GetCharacters([aNativeEvent charactersIgnoringModifiers]),
|
|
TrueOrFalse(mIgnoreNextKeyUpEvent), TrueOrFalse(IsIMEComposing())));
|
|
|
|
if (mIgnoreNextKeyUpEvent) {
|
|
mIgnoreNextKeyUpEvent = false;
|
|
return;
|
|
}
|
|
|
|
if (Destroyed()) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::HandleKeyUpEvent, "
|
|
"widget has been already destroyed", this));
|
|
return;
|
|
}
|
|
|
|
// if we don't have any characters we can't generate a keyUp event
|
|
if ([[aNativeEvent characters] length] == 0 || IsIMEComposing()) {
|
|
return;
|
|
}
|
|
|
|
nsKeyEvent keyupEvent(true, NS_KEY_UP, mWidget);
|
|
InitKeyEvent(aNativeEvent, keyupEvent);
|
|
|
|
DispatchEvent(keyupEvent);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void
|
|
TextInputHandler::HandleFlagsChanged(NSEvent* aNativeEvent)
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
if (Destroyed()) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::HandleFlagsChanged, "
|
|
"widget has been already destroyed", this));
|
|
return;
|
|
}
|
|
|
|
nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::HandleFlagsChanged, aNativeEvent=%p, "
|
|
"type=%s, keyCode=%s (0x%X), sLastModifierState=0x%X, IsIMEComposing()=%s",
|
|
this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
|
|
GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
|
|
sLastModifierState, TrueOrFalse(IsIMEComposing())));
|
|
|
|
// CapsLock state and other modifier states are different:
|
|
// CapsLock state does not revert when the CapsLock key goes up, as the
|
|
// modifier state does for other modifier keys on key up.
|
|
if ([aNativeEvent keyCode] == kCapsLockKeyCode) {
|
|
// Fire key down event for caps lock.
|
|
DispatchKeyEventForFlagsChanged(aNativeEvent, true);
|
|
if (Destroyed()) {
|
|
return;
|
|
}
|
|
// XXX should we fire keyup event too? The keyup event for CapsLock key
|
|
// is never dispatched on Gecko.
|
|
} else if ([aNativeEvent type] == NSFlagsChanged) {
|
|
// Fire key up/down events for the modifier keys (shift, alt, ctrl, command)
|
|
NSUInteger modifiers =
|
|
[aNativeEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
|
|
const NSUInteger kModifierMaskTable[] =
|
|
{ NSShiftKeyMask, NSControlKeyMask,
|
|
NSAlternateKeyMask, NSCommandKeyMask };
|
|
const PRUint32 kModifierCount = ArrayLength(kModifierMaskTable);
|
|
|
|
for (PRUint32 i = 0; i < kModifierCount; i++) {
|
|
NSUInteger modifierBit = kModifierMaskTable[i];
|
|
if ((modifiers & modifierBit) != (sLastModifierState & modifierBit)) {
|
|
bool isKeyDown = ((modifiers & modifierBit) != 0);
|
|
DispatchKeyEventForFlagsChanged(aNativeEvent, isKeyDown);
|
|
if (Destroyed()) {
|
|
return;
|
|
}
|
|
|
|
// Stop if focus has changed.
|
|
// Check to see if mView is still the first responder.
|
|
if (![mView isFirstResponder]) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
sLastModifierState = modifiers;
|
|
}
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void
|
|
TextInputHandler::DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent,
|
|
bool aDispatchKeyDown)
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
if (Destroyed()) {
|
|
return;
|
|
}
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::DispatchKeyEventForFlagsChanged, aNativeEvent=%p, "
|
|
"type=%s, keyCode=%s (0x%X), aDispatchKeyDown=%s, IsIMEComposing()=%s",
|
|
this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
|
|
GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
|
|
TrueOrFalse(aDispatchKeyDown), TrueOrFalse(IsIMEComposing())));
|
|
|
|
if ([aNativeEvent type] != NSFlagsChanged || IsIMEComposing()) {
|
|
return;
|
|
}
|
|
|
|
PRUint32 message = aDispatchKeyDown ? NS_KEY_DOWN : NS_KEY_UP;
|
|
|
|
#ifndef NP_NO_CARBON
|
|
EventRecord carbonEvent;
|
|
#endif // ifndef NP_NO_CARBON
|
|
NPCocoaEvent cocoaEvent;
|
|
|
|
// Fire a key event.
|
|
nsKeyEvent keyEvent(true, message, mWidget);
|
|
InitKeyEvent(aNativeEvent, keyEvent);
|
|
|
|
// create event for use by plugins
|
|
if ([mView isPluginView]) {
|
|
#ifndef NP_NO_CARBON
|
|
if ([mView pluginEventModel] == NPEventModelCarbon) {
|
|
ConvertCocoaKeyEventToCarbonEvent(aNativeEvent, carbonEvent,
|
|
aDispatchKeyDown);
|
|
keyEvent.pluginEvent = &carbonEvent;
|
|
}
|
|
#endif // ifndef NP_NO_CARBON
|
|
if ([mView pluginEventModel] == NPEventModelCocoa) {
|
|
ConvertCocoaKeyEventToNPCocoaEvent(aNativeEvent, cocoaEvent);
|
|
keyEvent.pluginEvent = &cocoaEvent;
|
|
}
|
|
}
|
|
|
|
DispatchEvent(keyEvent);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void
|
|
TextInputHandler::InsertText(NSAttributedString *aAttrString)
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
if (Destroyed()) {
|
|
return;
|
|
}
|
|
|
|
KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::InsertText, aAttrString=\"%s\", "
|
|
"IsIMEComposing()=%s, IgnoreIMEComposition()=%s, "
|
|
"keyevent=%p, keypressDispatched=%s",
|
|
this, GetCharacters([aAttrString string]), TrueOrFalse(IsIMEComposing()),
|
|
TrueOrFalse(IgnoreIMEComposition()),
|
|
currentKeyEvent ? currentKeyEvent->mKeyEvent : nsnull,
|
|
currentKeyEvent ?
|
|
TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A"));
|
|
|
|
if (IgnoreIMEComposition()) {
|
|
return;
|
|
}
|
|
|
|
nsString str;
|
|
nsCocoaUtils::GetStringForNSString([aAttrString string], str);
|
|
if (!IsIMEComposing() && str.IsEmpty()) {
|
|
return; // nothing to do
|
|
}
|
|
|
|
if (str.Length() != 1 || IsIMEComposing()) {
|
|
InsertTextAsCommittingComposition(aAttrString);
|
|
return;
|
|
}
|
|
|
|
// Don't let the same event be fired twice when hitting
|
|
// enter/return! (Bug 420502)
|
|
if (currentKeyEvent && currentKeyEvent->mKeyPressDispatched) {
|
|
return;
|
|
}
|
|
|
|
nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
|
|
|
|
// Dispatch keypress event with char instead of textEvent
|
|
nsKeyEvent keypressEvent(true, NS_KEY_PRESS, mWidget);
|
|
keypressEvent.charCode = str.CharAt(0);
|
|
keypressEvent.keyCode = 0;
|
|
keypressEvent.isChar = true;
|
|
|
|
// Don't set other modifiers from the current event, because here in
|
|
// -insertText: they've already been taken into account in creating
|
|
// the input string.
|
|
|
|
// create event for use by plugins
|
|
#ifndef NP_NO_CARBON
|
|
EventRecord carbonEvent;
|
|
#endif // #ifndef NP_NO_CARBON
|
|
|
|
if (currentKeyEvent) {
|
|
NSEvent* keyEvent = currentKeyEvent->mKeyEvent;
|
|
nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
|
|
|
|
// XXX The ASCII characters inputting mode of egbridge (Japanese IME)
|
|
// might send the keyDown event with wrong keyboard layout if other
|
|
// keyboard layouts are already loaded. In that case, the native event
|
|
// doesn't match to this gecko event...
|
|
#ifndef NP_NO_CARBON
|
|
if ([mView pluginEventModel] == NPEventModelCarbon) {
|
|
ConvertCocoaKeyEventToCarbonEvent(keyEvent, carbonEvent, true);
|
|
keypressEvent.pluginEvent = &carbonEvent;
|
|
}
|
|
#endif // #ifndef NP_NO_CARBON
|
|
|
|
if (currentKeyEvent->mKeyDownHandled) {
|
|
keypressEvent.flags |= NS_EVENT_FLAG_NO_DEFAULT;
|
|
}
|
|
|
|
if (!IsPrintableChar(keypressEvent.charCode)) {
|
|
keypressEvent.keyCode =
|
|
ComputeGeckoKeyCode([keyEvent keyCode],
|
|
[keyEvent charactersIgnoringModifiers]);
|
|
keypressEvent.charCode = 0;
|
|
}
|
|
} else {
|
|
nsCocoaUtils::InitInputEvent(keypressEvent, static_cast<NSEvent*>(nsnull));
|
|
// Note that insertText is not called only at key pressing.
|
|
if (!IsPrintableChar(keypressEvent.charCode)) {
|
|
keypressEvent.keyCode =
|
|
ComputeGeckoKeyCodeFromChar(keypressEvent.charCode);
|
|
keypressEvent.charCode = 0;
|
|
}
|
|
}
|
|
|
|
// Remove basic modifiers from keypress event because if they are included,
|
|
// nsPlaintextEditor ignores the event.
|
|
keypressEvent.modifiers &= ~(widget::MODIFIER_CONTROL |
|
|
widget::MODIFIER_ALT |
|
|
widget::MODIFIER_META);
|
|
|
|
// TODO:
|
|
// If mCurrentKeyEvent.mKeyEvent is null and when we implement textInput
|
|
// event of DOM3 Events, we should dispatch it instead of keypress event.
|
|
bool keyPressHandled = DispatchEvent(keypressEvent);
|
|
|
|
// Note: mWidget might have become null here. Don't count on it from here on.
|
|
|
|
if (currentKeyEvent) {
|
|
currentKeyEvent->mKeyPressHandled = keyPressHandled;
|
|
currentKeyEvent->mKeyPressDispatched = true;
|
|
}
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
bool
|
|
TextInputHandler::DoCommandBySelector(const char* aSelector)
|
|
{
|
|
nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
|
|
|
|
KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::DoCommandBySelector, aSelector=\"%s\", "
|
|
"Destroyed()=%s, keypressHandled=%s, causedOtherKeyEvents=%s",
|
|
this, aSelector ? aSelector : "", TrueOrFalse(Destroyed()),
|
|
currentKeyEvent ?
|
|
TrueOrFalse(currentKeyEvent->mKeyPressHandled) : "N/A",
|
|
currentKeyEvent ?
|
|
TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A"));
|
|
|
|
if (currentKeyEvent && !currentKeyEvent->mKeyPressDispatched) {
|
|
nsKeyEvent keypressEvent(true, NS_KEY_PRESS, mWidget);
|
|
InitKeyEvent(currentKeyEvent->mKeyEvent, keypressEvent);
|
|
if (currentKeyEvent->mKeyDownHandled ||
|
|
currentKeyEvent->mCausedOtherKeyEvents) {
|
|
keypressEvent.flags |= NS_EVENT_FLAG_NO_DEFAULT;
|
|
}
|
|
currentKeyEvent->mKeyPressHandled = DispatchEvent(keypressEvent);
|
|
currentKeyEvent->mKeyPressDispatched = true;
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandler::DoCommandBySelector, keypress event "
|
|
"dispatched, Destroyed()=%s, keypressHandled=%s",
|
|
this, TrueOrFalse(Destroyed()),
|
|
TrueOrFalse(currentKeyEvent->mKeyPressHandled)));
|
|
}
|
|
|
|
return !Destroyed() && currentKeyEvent &&
|
|
(currentKeyEvent->mKeyPressHandled ||
|
|
currentKeyEvent->mCausedOtherKeyEvents);
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
/******************************************************************************
|
|
*
|
|
* IMEInputHandler implementation (static methods)
|
|
*
|
|
******************************************************************************/
|
|
|
|
bool IMEInputHandler::sStaticMembersInitialized = false;
|
|
CFStringRef IMEInputHandler::sLatestIMEOpenedModeInputSourceID = nsnull;
|
|
IMEInputHandler* IMEInputHandler::sFocusedIMEHandler = nsnull;
|
|
|
|
// static
|
|
void
|
|
IMEInputHandler::InitStaticMembers()
|
|
{
|
|
if (sStaticMembersInitialized)
|
|
return;
|
|
sStaticMembersInitialized = 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
|
|
IMEInputHandler::OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter,
|
|
void* aObserver,
|
|
CFStringRef aName,
|
|
const void* aObject,
|
|
CFDictionaryRef aUserInfo)
|
|
{
|
|
// Cache the latest IME opened mode to sLatestIMEOpenedModeInputSourceID.
|
|
TISInputSourceWrapper tis;
|
|
tis.InitByCurrentInputSource();
|
|
if (tis.IsOpenedIMEMode()) {
|
|
tis.GetInputSourceID(sLatestIMEOpenedModeInputSourceID);
|
|
}
|
|
|
|
#ifdef PR_LOGGING
|
|
if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
|
|
static CFStringRef sLastTIS = nsnull;
|
|
CFStringRef newTIS;
|
|
tis.GetInputSourceID(newTIS);
|
|
if (!sLastTIS ||
|
|
::CFStringCompare(sLastTIS, newTIS, 0) != kCFCompareEqualTo) {
|
|
TISInputSourceWrapper tis1, tis2, tis3;
|
|
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);
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("IMEInputHandler::OnCurrentTextInputSourceChange,\n"
|
|
" Current Input Source is changed to:\n"
|
|
" currentInputManager=%p\n"
|
|
" %s\n"
|
|
" type=%s %s\n"
|
|
" primary language=%s\n"
|
|
" bundle ID=%s\n"
|
|
" current ASCII capable Input Source=%s\n"
|
|
" current Keyboard Layout=%s\n"
|
|
" current ASCII capable Keyboard Layout=%s",
|
|
[NSInputManager currentInputManager], GetCharacters(is0),
|
|
GetCharacters(type0), tis.IsASCIICapable() ? "- ASCII capable " : "",
|
|
GetCharacters(lang0), GetCharacters(bundleID0),
|
|
GetCharacters(is2), GetCharacters(is1), GetCharacters(is3)));
|
|
}
|
|
sLastTIS = newTIS;
|
|
}
|
|
#endif // #ifdef PR_LOGGING
|
|
}
|
|
|
|
// static
|
|
void
|
|
IMEInputHandler::FlushPendingMethods(nsITimer* aTimer, void* aClosure)
|
|
{
|
|
NS_ASSERTION(aClosure, "aClosure is null");
|
|
static_cast<IMEInputHandler*>(aClosure)->ExecutePendingMethods();
|
|
}
|
|
|
|
// static
|
|
CFArrayRef
|
|
IMEInputHandler::CreateAllIMEModeList()
|
|
{
|
|
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
|
|
IMEInputHandler::DebugPrintAllIMEModes()
|
|
{
|
|
#ifdef PR_LOGGING
|
|
if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
|
|
CFArrayRef list = CreateAllIMEModeList();
|
|
PR_LOG(gLog, PR_LOG_ALWAYS, ("IME mode configuration:"));
|
|
CFIndex idx = ::CFArrayGetCount(list);
|
|
TISInputSourceWrapper tis;
|
|
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(gLog, 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);
|
|
}
|
|
#endif // #ifdef PR_LOGGING
|
|
}
|
|
|
|
//static
|
|
TSMDocumentID
|
|
IMEInputHandler::GetCurrentTSMDocumentID()
|
|
{
|
|
// 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();
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
/******************************************************************************
|
|
*
|
|
* IMEInputHandler implementation #1
|
|
* 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
|
|
IMEInputHandler::ResetIMEWindowLevel()
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::ResetIMEWindowLevel, "
|
|
"Destroyed()=%s, IsFocused()=%s, GetCurrentTSMDocumentID()=%p",
|
|
this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
|
|
GetCurrentTSMDocumentID()));
|
|
|
|
if (Destroyed()) {
|
|
return;
|
|
}
|
|
|
|
if (!IsFocused()) {
|
|
// retry at next focus event
|
|
mPendingMethods |= kResetIMEWindowLevel;
|
|
return;
|
|
}
|
|
|
|
TSMDocumentID doc = GetCurrentTSMDocumentID();
|
|
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 = mWidget->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];
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::ResetIMEWindowLevel, windowLevel=%s (%X)",
|
|
this, GetWindowLevelName(windowLevel), windowLevel));
|
|
|
|
// 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++;
|
|
|
|
::TSMSetDocumentProperty(GetCurrentTSMDocumentID(),
|
|
kTSMDocumentWindowLevelPropertyTag,
|
|
sizeof(windowLevel), &windowLevel);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void
|
|
IMEInputHandler::DiscardIMEComposition()
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::DiscardIMEComposition, "
|
|
"Destroyed()=%s, IsFocused()=%s, currentInputManager=%p",
|
|
this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
|
|
[NSInputManager currentInputManager]));
|
|
|
|
if (Destroyed()) {
|
|
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 = true;
|
|
[im markedTextAbandoned: mView];
|
|
mIgnoreIMECommit = false;
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK
|
|
}
|
|
|
|
void
|
|
IMEInputHandler::SyncASCIICapableOnly()
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::SyncASCIICapableOnly, "
|
|
"Destroyed()=%s, IsFocused()=%s, mIsASCIICapableOnly=%s, "
|
|
"GetCurrentTSMDocumentID()=%p",
|
|
this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
|
|
TrueOrFalse(mIsASCIICapableOnly), GetCurrentTSMDocumentID()));
|
|
|
|
if (Destroyed()) {
|
|
return;
|
|
}
|
|
|
|
if (!IsFocused()) {
|
|
// retry at next focus event
|
|
mPendingMethods |= kSyncASCIICapableOnly;
|
|
return;
|
|
}
|
|
|
|
TSMDocumentID doc = GetCurrentTSMDocumentID();
|
|
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
|
|
IMEInputHandler::ResetTimer()
|
|
{
|
|
NS_ASSERTION(mPendingMethods != 0,
|
|
"There are not pending methods, why this is called?");
|
|
if (mTimer) {
|
|
mTimer->Cancel();
|
|
} else {
|
|
mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
|
|
NS_ENSURE_TRUE(mTimer, );
|
|
}
|
|
mTimer->InitWithFuncCallback(FlushPendingMethods, this, 0,
|
|
nsITimer::TYPE_ONE_SHOT);
|
|
}
|
|
|
|
void
|
|
IMEInputHandler::ExecutePendingMethods()
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
if (mTimer) {
|
|
mTimer->Cancel();
|
|
mTimer = nsnull;
|
|
}
|
|
|
|
if (![[NSApplication sharedApplication] isActive]) {
|
|
mIsInFocusProcessing = 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 = false;
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
|
|
/******************************************************************************
|
|
*
|
|
* IMEInputHandler implementation (native event handlers)
|
|
*
|
|
******************************************************************************/
|
|
|
|
PRUint32
|
|
IMEInputHandler::ConvertToTextRangeType(PRUint32 aUnderlineStyle,
|
|
NSRange& aSelectedRange)
|
|
{
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::ConvertToTextRangeType, "
|
|
"aUnderlineStyle=%llu, aSelectedRange.length=%llu,",
|
|
this, aUnderlineStyle, aSelectedRange.length));
|
|
|
|
// 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
|
|
IMEInputHandler::GetRangeCount(NSAttributedString *aAttrString)
|
|
{
|
|
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++;
|
|
}
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::GetRangeCount, aAttrString=\"%s\", count=%llu",
|
|
this, GetCharacters([aAttrString string]), count));
|
|
|
|
return count;
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
|
|
}
|
|
|
|
void
|
|
IMEInputHandler::SetTextRangeList(nsTArray<nsTextRange>& aTextRangeList,
|
|
NSAttributedString *aAttrString,
|
|
NSRange& aSelectedRange)
|
|
{
|
|
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);
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::SetTextRangeList, "
|
|
"range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }",
|
|
this, range.mStartOffset, range.mEndOffset,
|
|
GetRangeTypeName(range.mRangeType)));
|
|
|
|
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);
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::SetTextRangeList, "
|
|
"range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }",
|
|
this, range.mStartOffset, range.mEndOffset,
|
|
GetRangeTypeName(range.mRangeType)));
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
bool
|
|
IMEInputHandler::DispatchTextEvent(const nsString& aText,
|
|
NSAttributedString* aAttrString,
|
|
NSRange& aSelectedRange,
|
|
bool aDoCommit)
|
|
{
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::DispatchTextEvent, "
|
|
"aText=\"%s\", aAttrString=\"%s\", "
|
|
"aSelectedRange={ location=%llu, length=%llu }, "
|
|
"aDoCommit=%s, Destroyed()=%s",
|
|
this, NS_ConvertUTF16toUTF8(aText).get(),
|
|
GetCharacters([aAttrString string]),
|
|
aSelectedRange.location, aSelectedRange.length,
|
|
TrueOrFalse(aDoCommit), TrueOrFalse(Destroyed())));
|
|
|
|
NS_ENSURE_TRUE(!Destroyed(), false);
|
|
|
|
nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
|
|
|
|
nsTextEvent textEvent(true, NS_TEXT_TEXT, mWidget);
|
|
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();
|
|
|
|
if (textEvent.theText != mLastDispatchedCompositionString) {
|
|
nsCompositionEvent compositionUpdate(true, NS_COMPOSITION_UPDATE,
|
|
mWidget);
|
|
compositionUpdate.time = textEvent.time;
|
|
compositionUpdate.data = textEvent.theText;
|
|
mLastDispatchedCompositionString = textEvent.theText;
|
|
DispatchEvent(compositionUpdate);
|
|
if (mIsInFocusProcessing || Destroyed()) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::DispatchTextEvent, compositionupdate causes "
|
|
"aborting the composition, mIsInFocusProcessing=%s, Destryoed()=%s",
|
|
this, TrueOrFalse(mIsInFocusProcessing), TrueOrFalse(Destroyed())));
|
|
if (Destroyed()) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return DispatchEvent(textEvent);
|
|
}
|
|
|
|
void
|
|
IMEInputHandler::InitCompositionEvent(nsCompositionEvent& aCompositionEvent)
|
|
{
|
|
aCompositionEvent.time = PR_IntervalNow();
|
|
}
|
|
|
|
void
|
|
IMEInputHandler::InsertTextAsCommittingComposition(
|
|
NSAttributedString* aAttrString)
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::InsertTextAsCommittingComposition, "
|
|
"aAttrString=\"%s\", Destroyed()=%s, IsIMEComposing()=%s, "
|
|
"mMarkedRange={ location=%llu, length=%llu }",
|
|
this, GetCharacters([aAttrString string]), TrueOrFalse(Destroyed()),
|
|
TrueOrFalse(IsIMEComposing()),
|
|
mMarkedRange.location, mMarkedRange.length));
|
|
|
|
if (Destroyed()) {
|
|
return;
|
|
}
|
|
|
|
nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
|
|
|
|
nsString str;
|
|
nsCocoaUtils::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(true, NS_COMPOSITION_START, mWidget);
|
|
InitCompositionEvent(compStart);
|
|
|
|
DispatchEvent(compStart);
|
|
if (Destroyed()) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::InsertTextAsCommittingComposition, "
|
|
"destroyed by compositionstart event", this));
|
|
return;
|
|
}
|
|
|
|
OnStartIMEComposition();
|
|
}
|
|
|
|
if (IgnoreIMECommit()) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::InsertTextAsCommittingComposition, "
|
|
"IgnoreIMECommit()=%s", this, TrueOrFalse(IgnoreIMECommit())));
|
|
str.Truncate();
|
|
}
|
|
|
|
NSRange range = NSMakeRange(0, str.Length());
|
|
DispatchTextEvent(str, aAttrString, range, true);
|
|
if (Destroyed()) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::InsertTextAsCommittingComposition, "
|
|
"destroyed by text event", this));
|
|
return;
|
|
}
|
|
|
|
OnUpdateIMEComposition([aAttrString string]);
|
|
|
|
nsCompositionEvent compEnd(true, NS_COMPOSITION_END, mWidget);
|
|
InitCompositionEvent(compEnd);
|
|
compEnd.data = mLastDispatchedCompositionString;
|
|
DispatchEvent(compEnd);
|
|
if (Destroyed()) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::InsertTextAsCommittingComposition, "
|
|
"destroyed by compositionend event", this));
|
|
return;
|
|
}
|
|
|
|
OnEndIMEComposition();
|
|
|
|
mMarkedRange = NSMakeRange(NSNotFound, 0);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void
|
|
IMEInputHandler::SetMarkedText(NSAttributedString* aAttrString,
|
|
NSRange& aSelectedRange)
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::SetMarkedText, "
|
|
"aAttrString=\"%s\", aSelectedRange={ location=%llu, length=%llu }, "
|
|
"Destroyed()=%s, IgnoreIMEComposition()=%s, IsIMEComposing()=%s, "
|
|
"mMarkedRange={ location=%llu, length=%llu }",
|
|
this, GetCharacters([aAttrString string]),
|
|
aSelectedRange.location, aSelectedRange.length, TrueOrFalse(Destroyed()),
|
|
TrueOrFalse(IgnoreIMEComposition()), TrueOrFalse(IsIMEComposing()),
|
|
mMarkedRange.location, mMarkedRange.length));
|
|
|
|
if (Destroyed() || IgnoreIMEComposition()) {
|
|
return;
|
|
}
|
|
|
|
nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
|
|
|
|
nsString str;
|
|
nsCocoaUtils::GetStringForNSString([aAttrString string], str);
|
|
|
|
mMarkedRange.length = str.Length();
|
|
|
|
if (!IsIMEComposing() && !str.IsEmpty()) {
|
|
nsQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT,
|
|
mWidget);
|
|
DispatchEvent(selection);
|
|
mMarkedRange.location = selection.mSucceeded ? selection.mReply.mOffset : 0;
|
|
|
|
nsCompositionEvent compStart(true, NS_COMPOSITION_START, mWidget);
|
|
InitCompositionEvent(compStart);
|
|
|
|
DispatchEvent(compStart);
|
|
if (Destroyed()) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::SetMarkedText, "
|
|
"destroyed by compositionstart event", this));
|
|
return;
|
|
}
|
|
|
|
OnStartIMEComposition();
|
|
}
|
|
|
|
if (IsIMEComposing()) {
|
|
OnUpdateIMEComposition([aAttrString string]);
|
|
|
|
bool doCommit = str.IsEmpty();
|
|
DispatchTextEvent(str, aAttrString, aSelectedRange, doCommit);
|
|
if (Destroyed()) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::SetMarkedText, "
|
|
"destroyed by text event", this));
|
|
return;
|
|
}
|
|
|
|
if (doCommit) {
|
|
nsCompositionEvent compEnd(true, NS_COMPOSITION_END, mWidget);
|
|
InitCompositionEvent(compEnd);
|
|
compEnd.data = mLastDispatchedCompositionString;
|
|
DispatchEvent(compEnd);
|
|
if (Destroyed()) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::SetMarkedText, "
|
|
"destroyed by compositionend event", this));
|
|
return;
|
|
}
|
|
OnEndIMEComposition();
|
|
}
|
|
}
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
NSInteger
|
|
IMEInputHandler::ConversationIdentifier()
|
|
{
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::ConversationIdentifier, Destroyed()=%s",
|
|
this, TrueOrFalse(Destroyed())));
|
|
|
|
if (Destroyed()) {
|
|
return reinterpret_cast<NSInteger>(mView);
|
|
}
|
|
|
|
nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
|
|
|
|
// NOTE: The size of NSInteger is same as pointer size.
|
|
nsQueryContentEvent textContent(true, NS_QUERY_TEXT_CONTENT, mWidget);
|
|
textContent.InitForQueryTextContent(0, 0);
|
|
DispatchEvent(textContent);
|
|
if (!textContent.mSucceeded) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::ConversationIdentifier, Failed", this));
|
|
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*
|
|
IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange)
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::GetAttributedSubstringFromRange, "
|
|
"aRange={ location=%llu, length=%llu }, Destroyed()=%s",
|
|
this, aRange.location, aRange.length, TrueOrFalse(Destroyed())));
|
|
|
|
if (Destroyed() || aRange.location == NSNotFound || aRange.length == 0) {
|
|
return nil;
|
|
}
|
|
|
|
nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
|
|
|
|
nsAutoString str;
|
|
nsQueryContentEvent textContent(true, NS_QUERY_TEXT_CONTENT, mWidget);
|
|
textContent.InitForQueryTextContent(aRange.location, aRange.length);
|
|
DispatchEvent(textContent);
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::GetAttributedSubstringFromRange, "
|
|
"textContent={ mSucceeded=%s, mReply.mString=\"%s\"",
|
|
this, TrueOrFalse(textContent.mSucceeded),
|
|
NS_ConvertUTF16toUTF8(textContent.mReply.mString).get()));
|
|
|
|
if (!textContent.mSucceeded || textContent.mReply.mString.IsEmpty()) {
|
|
return nil;
|
|
}
|
|
|
|
NSString* nsstr = nsCocoaUtils::ToNSString(textContent.mReply.mString);
|
|
NSAttributedString* result =
|
|
[[[NSAttributedString alloc] initWithString:nsstr
|
|
attributes:nil] autorelease];
|
|
return result;
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
|
|
}
|
|
|
|
bool
|
|
IMEInputHandler::HasMarkedText()
|
|
{
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::HasMarkedText, "
|
|
"mMarkedRange={ location=%llu, length=%llu }",
|
|
this, mMarkedRange.location, mMarkedRange.length));
|
|
|
|
return (mMarkedRange.location != NSNotFound) && (mMarkedRange.length != 0);
|
|
}
|
|
|
|
NSRange
|
|
IMEInputHandler::MarkedRange()
|
|
{
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::MarkedRange, "
|
|
"mMarkedRange={ location=%llu, length=%llu }",
|
|
this, mMarkedRange.location, mMarkedRange.length));
|
|
|
|
if (!HasMarkedText()) {
|
|
return NSMakeRange(NSNotFound, 0);
|
|
}
|
|
return mMarkedRange;
|
|
}
|
|
|
|
NSRange
|
|
IMEInputHandler::SelectedRange()
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::SelectedRange, Destroyed()=%s",
|
|
this, TrueOrFalse(Destroyed())));
|
|
|
|
NSRange range = NSMakeRange(NSNotFound, 0);
|
|
if (Destroyed()) {
|
|
return range;
|
|
}
|
|
|
|
nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
|
|
|
|
nsQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, mWidget);
|
|
DispatchEvent(selection);
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::SelectedRange, selection={ mSucceeded=%s, "
|
|
"mReply={ mOffset=%llu, mString.Length()=%llu } }",
|
|
this, TrueOrFalse(selection.mSucceeded), selection.mReply.mOffset,
|
|
selection.mReply.mString.Length()));
|
|
|
|
if (!selection.mSucceeded) {
|
|
return range;
|
|
}
|
|
|
|
return NSMakeRange(selection.mReply.mOffset,
|
|
selection.mReply.mString.Length());
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRange(0, 0));
|
|
}
|
|
|
|
NSRect
|
|
IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange)
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::FirstRectForCharacterRange, Destroyed()=%s"
|
|
"aRange={ location=%llu, length=%llu }",
|
|
this, TrueOrFalse(Destroyed()), aRange.location, aRange.length));
|
|
|
|
// 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 (Destroyed() || aRange.location == NSNotFound) {
|
|
return rect;
|
|
}
|
|
|
|
nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
|
|
|
|
nsIntRect r;
|
|
bool useCaretRect = (aRange.length == 0);
|
|
if (!useCaretRect) {
|
|
nsQueryContentEvent charRect(true, NS_QUERY_TEXT_RECT, mWidget);
|
|
charRect.InitForQueryTextRect(aRange.location, 1);
|
|
DispatchEvent(charRect);
|
|
if (charRect.mSucceeded) {
|
|
r = charRect.mReply.mRect;
|
|
} else {
|
|
useCaretRect = true;
|
|
}
|
|
}
|
|
|
|
if (useCaretRect) {
|
|
nsQueryContentEvent caretRect(true, NS_QUERY_CARET_RECT, mWidget);
|
|
caretRect.InitForQueryCaretRect(aRange.location);
|
|
DispatchEvent(caretRect);
|
|
if (!caretRect.mSucceeded) {
|
|
return rect;
|
|
}
|
|
r = caretRect.mReply.mRect;
|
|
r.width = 0;
|
|
}
|
|
|
|
nsIWidget* rootWidget = mWidget->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;
|
|
}
|
|
nsCocoaUtils::GeckoRectToNSRect(r, rect);
|
|
rect = [rootView convertRect:rect toView:nil];
|
|
rect.origin = [rootWindow convertBaseToScreen:rect.origin];
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::FirstRectForCharacterRange, "
|
|
"useCaretRect=%s rect={ x=%f, y=%f, width=%f, height=%f }",
|
|
this, TrueOrFalse(useCaretRect), rect.origin.x, rect.origin.y,
|
|
rect.size.width, rect.size.height));
|
|
|
|
return rect;
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRect(0.0, 0.0, 0.0, 0.0));
|
|
}
|
|
|
|
NSUInteger
|
|
IMEInputHandler::CharacterIndexForPoint(NSPoint& aPoint)
|
|
{
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::CharacterIndexForPoint, aPoint={ x=%f, y=%f }",
|
|
this, aPoint.x, aPoint.y));
|
|
|
|
//nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
|
|
|
|
// To implement this, we'd have to grovel in text frames looking at text
|
|
// offsets.
|
|
return 0;
|
|
}
|
|
|
|
NSArray*
|
|
IMEInputHandler::GetValidAttributesForMarkedText()
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::GetValidAttributesForMarkedText", this));
|
|
|
|
//nsRefPtr<IMEInputHandler> kungFuDeathGrip(this);
|
|
|
|
//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;
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
/******************************************************************************
|
|
*
|
|
* IMEInputHandler implementation #2
|
|
*
|
|
******************************************************************************/
|
|
|
|
IMEInputHandler::IMEInputHandler(nsChildView* aWidget,
|
|
NSView<mozView> *aNativeView) :
|
|
PluginTextInputHandler(aWidget, aNativeView),
|
|
mPendingMethods(0), mIMECompositionString(nsnull),
|
|
mIsIMEComposing(false), mIsIMEEnabled(true),
|
|
mIsASCIICapableOnly(false), mIgnoreIMECommit(false),
|
|
mIsInFocusProcessing(false)
|
|
{
|
|
InitStaticMembers();
|
|
|
|
mMarkedRange.location = NSNotFound;
|
|
mMarkedRange.length = 0;
|
|
}
|
|
|
|
IMEInputHandler::~IMEInputHandler()
|
|
{
|
|
if (mTimer) {
|
|
mTimer->Cancel();
|
|
mTimer = nsnull;
|
|
}
|
|
if (sFocusedIMEHandler == this) {
|
|
sFocusedIMEHandler = nsnull;
|
|
}
|
|
}
|
|
|
|
void
|
|
IMEInputHandler::OnFocusChangeInGecko(bool aFocus)
|
|
{
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::OnFocusChangeInGecko, aFocus=%s, Destroyed()=%s, "
|
|
"sFocusedIMEHandler=%p",
|
|
this, TrueOrFalse(aFocus), TrueOrFalse(Destroyed()), sFocusedIMEHandler));
|
|
|
|
// 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 false, we need to
|
|
// improve the nsIMEStateManager implementation.
|
|
if (!aFocus) {
|
|
if (sFocusedIMEHandler == this)
|
|
sFocusedIMEHandler = nsnull;
|
|
return;
|
|
}
|
|
|
|
sFocusedIMEHandler = this;
|
|
mIsInFocusProcessing = 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();
|
|
}
|
|
|
|
bool
|
|
IMEInputHandler::OnDestroyWidget(nsChildView* aDestroyingWidget)
|
|
{
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::OnDestroyWidget, aDestroyingWidget=%p, "
|
|
"sFocusedIMEHandler=%p, IsIMEComposing()=%s",
|
|
this, aDestroyingWidget, sFocusedIMEHandler,
|
|
TrueOrFalse(IsIMEComposing())));
|
|
|
|
// If we're not focused, the focused IMEInputHandler may have been
|
|
// created by another widget/nsChildView.
|
|
if (sFocusedIMEHandler && sFocusedIMEHandler != this) {
|
|
sFocusedIMEHandler->OnDestroyWidget(aDestroyingWidget);
|
|
}
|
|
|
|
if (!PluginTextInputHandler::OnDestroyWidget(aDestroyingWidget)) {
|
|
return false;
|
|
}
|
|
|
|
if (IsIMEComposing()) {
|
|
// If our view is in the composition, we should clean up it.
|
|
CancelIMEComposition();
|
|
OnEndIMEComposition();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
IMEInputHandler::OnStartIMEComposition()
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::OnStartIMEComposition, mView=%p, mWidget=%p"
|
|
"currentInputManager=%p, mIsIMEComposing=%s",
|
|
this, mView, mWidget, [NSInputManager currentInputManager],
|
|
TrueOrFalse(mIsIMEComposing)));
|
|
|
|
NS_ASSERTION(!mIsIMEComposing, "There is a composition already");
|
|
mIsIMEComposing = true;
|
|
|
|
mLastDispatchedCompositionString.Truncate();
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void
|
|
IMEInputHandler::OnUpdateIMEComposition(NSString* aIMECompositionString)
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::OnUpdateIMEComposition, mView=%p, mWidget=%p, "
|
|
"currentInputManager=%p, mIsIMEComposing=%s, aIMECompositionString=\"%s\"",
|
|
this, mView, mWidget, [NSInputManager currentInputManager],
|
|
TrueOrFalse(mIsIMEComposing), GetCharacters(aIMECompositionString)));
|
|
|
|
NS_ASSERTION(mIsIMEComposing, "We're not in composition");
|
|
|
|
if (mIMECompositionString)
|
|
[mIMECompositionString release];
|
|
mIMECompositionString = [aIMECompositionString retain];
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void
|
|
IMEInputHandler::OnEndIMEComposition()
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::OnEndIMEComposition, mView=%p, mWidget=%p, "
|
|
"currentInputManager=%p, mIsIMEComposing=%s",
|
|
this, mView, mWidget, [NSInputManager currentInputManager],
|
|
TrueOrFalse(mIsIMEComposing)));
|
|
|
|
NS_ASSERTION(mIsIMEComposing, "We're not in composition");
|
|
|
|
mIsIMEComposing = false;
|
|
|
|
if (mIMECompositionString) {
|
|
[mIMECompositionString release];
|
|
mIMECompositionString = nsnull;
|
|
}
|
|
|
|
mLastDispatchedCompositionString.Truncate();
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void
|
|
IMEInputHandler::SendCommittedText(NSString *aString)
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::SendCommittedText, mView=%p, mWidget=%p, "
|
|
"currentInputManager=%p, mIsIMEComposing=%s",
|
|
this, mView, mWidget, [NSInputManager currentInputManager],
|
|
TrueOrFalse(mIsIMEComposing), mWidget));
|
|
|
|
NS_ENSURE_TRUE(mWidget, );
|
|
// XXX We should send the string without mView.
|
|
if (!mView) {
|
|
return;
|
|
}
|
|
|
|
NSAttributedString* attrStr =
|
|
[[NSAttributedString alloc] initWithString:aString];
|
|
[mView insertText:attrStr];
|
|
[attrStr release];
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void
|
|
IMEInputHandler::KillIMEComposition()
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::KillIMEComposition, mView=%p, mWidget=%p, "
|
|
"currentInputManager=%p, mIsIMEComposing=%s, "
|
|
"Destroyed()=%s, IsFocused()=%s",
|
|
this, mView, mWidget, [NSInputManager currentInputManager],
|
|
TrueOrFalse(mIsIMEComposing), TrueOrFalse(Destroyed()),
|
|
TrueOrFalse(IsFocused())));
|
|
|
|
if (Destroyed()) {
|
|
return;
|
|
}
|
|
|
|
if (IsFocused()) {
|
|
[[NSInputManager currentInputManager] markedTextAbandoned: mView];
|
|
return;
|
|
}
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::KillIMEComposition, Pending...", this));
|
|
|
|
// 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
|
|
IMEInputHandler::CommitIMEComposition()
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
if (!IsIMEComposing())
|
|
return;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::CommitIMEComposition, mIMECompositionString=%s",
|
|
this, GetCharacters(mIMECompositionString)));
|
|
|
|
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
|
|
IMEInputHandler::CancelIMEComposition()
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
if (!IsIMEComposing())
|
|
return;
|
|
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::CancelIMEComposition, mIMECompositionString=%s",
|
|
this, GetCharacters(mIMECompositionString)));
|
|
|
|
// For canceling the current composing, we need to ignore the param of
|
|
// insertText. But this code is ugly...
|
|
mIgnoreIMECommit = true;
|
|
KillIMEComposition();
|
|
mIgnoreIMECommit = 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;
|
|
}
|
|
|
|
bool
|
|
IMEInputHandler::IsFocused()
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
NS_ENSURE_TRUE(!Destroyed(), false);
|
|
NSWindow* window = [mView window];
|
|
NS_ENSURE_TRUE(window, false);
|
|
return [window firstResponder] == mView &&
|
|
([window isMainWindow] || [window isSheet]) &&
|
|
[[NSApplication sharedApplication] isActive];
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
|
|
}
|
|
|
|
bool
|
|
IMEInputHandler::IsIMEOpened()
|
|
{
|
|
TISInputSourceWrapper tis;
|
|
tis.InitByCurrentInputSource();
|
|
return tis.IsOpenedIMEMode();
|
|
}
|
|
|
|
void
|
|
IMEInputHandler::SetASCIICapableOnly(bool aASCIICapableOnly)
|
|
{
|
|
if (aASCIICapableOnly == mIsASCIICapableOnly)
|
|
return;
|
|
|
|
CommitIMEComposition();
|
|
mIsASCIICapableOnly = aASCIICapableOnly;
|
|
SyncASCIICapableOnly();
|
|
}
|
|
|
|
void
|
|
IMEInputHandler::EnableIME(bool aEnableIME)
|
|
{
|
|
if (aEnableIME == mIsIMEEnabled)
|
|
return;
|
|
|
|
CommitIMEComposition();
|
|
mIsIMEEnabled = aEnableIME;
|
|
}
|
|
|
|
void
|
|
IMEInputHandler::SetIMEOpenState(bool aOpenIME)
|
|
{
|
|
if (!IsFocused() || IsIMEOpened() == aOpenIME)
|
|
return;
|
|
|
|
if (!aOpenIME) {
|
|
TISInputSourceWrapper tis;
|
|
tis.InitByCurrentASCIICapableInputSource();
|
|
tis.Select();
|
|
return;
|
|
}
|
|
|
|
// If we know the latest IME opened mode, we should select it.
|
|
if (sLatestIMEOpenedModeInputSourceID) {
|
|
TISInputSourceWrapper tis;
|
|
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 bool sIsPrefferredIMESearched = false;
|
|
if (sIsPrefferredIMESearched)
|
|
return;
|
|
sIsPrefferredIMESearched = true;
|
|
OpenSystemPreferredLanguageIME();
|
|
}
|
|
|
|
void
|
|
IMEInputHandler::OpenSystemPreferredLanguageIME()
|
|
{
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::OpenSystemPreferredLanguageIME", this));
|
|
|
|
CFArrayRef langList = ::CFLocaleCopyPreferredLanguages();
|
|
if (!langList) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::OpenSystemPreferredLanguageIME, langList is NULL",
|
|
this));
|
|
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;
|
|
}
|
|
|
|
bool changed = false;
|
|
CFStringRef lang = static_cast<CFStringRef>(
|
|
::CFLocaleGetValue(locale, kCFLocaleLanguageCode));
|
|
NS_ASSERTION(lang, "lang is null");
|
|
if (lang) {
|
|
TISInputSourceWrapper tis;
|
|
tis.InitByLanguage(lang);
|
|
if (tis.IsOpenedIMEMode()) {
|
|
#ifdef PR_LOGGING
|
|
if (PR_LOG_TEST(gLog, PR_LOG_ALWAYS)) {
|
|
CFStringRef foundTIS;
|
|
tis.GetInputSourceID(foundTIS);
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p IMEInputHandler::OpenSystemPreferredLanguageIME, "
|
|
"foundTIS=%s, lang=%s",
|
|
this, GetCharacters(foundTIS), GetCharacters(lang)));
|
|
}
|
|
#endif // #ifdef PR_LOGGING
|
|
tis.Select();
|
|
changed = true;
|
|
}
|
|
}
|
|
::CFRelease(locale);
|
|
if (changed) {
|
|
break;
|
|
}
|
|
}
|
|
::CFRelease(langList);
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
/******************************************************************************
|
|
*
|
|
* PluginTextInputHandler implementation
|
|
*
|
|
******************************************************************************/
|
|
|
|
PluginTextInputHandler::PluginTextInputHandler(nsChildView* aWidget,
|
|
NSView<mozView> *aNativeView) :
|
|
TextInputHandlerBase(aWidget, aNativeView),
|
|
mIgnoreNextKeyUpEvent(false),
|
|
#ifndef NP_NO_CARBON
|
|
mPluginTSMDoc(0), mPluginTSMInComposition(false),
|
|
#endif // #ifndef NP_NO_CARBON
|
|
mPluginComplexTextInputRequested(false)
|
|
{
|
|
}
|
|
|
|
PluginTextInputHandler::~PluginTextInputHandler()
|
|
{
|
|
#ifndef NP_NO_CARBON
|
|
if (mPluginTSMDoc) {
|
|
::DeleteTSMDocument(mPluginTSMDoc);
|
|
}
|
|
#endif // #ifndef NP_NO_CARBON
|
|
}
|
|
|
|
/* static */ void
|
|
PluginTextInputHandler::ConvertCocoaKeyEventToNPCocoaEvent(
|
|
NSEvent* aCocoaEvent,
|
|
NPCocoaEvent& aPluginEvent)
|
|
{
|
|
nsCocoaUtils::InitNPCocoaEvent(&aPluginEvent);
|
|
NSEventType nativeType = [aCocoaEvent type];
|
|
switch (nativeType) {
|
|
case NSKeyDown:
|
|
aPluginEvent.type = NPCocoaEventKeyDown;
|
|
break;
|
|
case NSKeyUp:
|
|
aPluginEvent.type = NPCocoaEventKeyUp;
|
|
break;
|
|
case NSFlagsChanged:
|
|
aPluginEvent.type = NPCocoaEventFlagsChanged;
|
|
break;
|
|
default:
|
|
NS_WARNING("Asked to convert key event of unknown type to Cocoa plugin event!");
|
|
}
|
|
aPluginEvent.data.key.modifierFlags = [aCocoaEvent modifierFlags];
|
|
aPluginEvent.data.key.keyCode = [aCocoaEvent keyCode];
|
|
// don't try to access character data for flags changed events,
|
|
// it will raise an exception
|
|
if (nativeType != NSFlagsChanged) {
|
|
aPluginEvent.data.key.characters = (NPNSString*)[aCocoaEvent characters];
|
|
aPluginEvent.data.key.charactersIgnoringModifiers =
|
|
(NPNSString*)[aCocoaEvent charactersIgnoringModifiers];
|
|
aPluginEvent.data.key.isARepeat = [aCocoaEvent isARepeat];
|
|
}
|
|
}
|
|
|
|
#ifndef NP_NO_CARBON
|
|
|
|
/* static */ bool
|
|
PluginTextInputHandler::ConvertUnicodeToCharCode(PRUnichar aUniChar,
|
|
unsigned char* aOutChar)
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
|
|
|
|
UnicodeToTextInfo converterInfo;
|
|
TextEncoding systemEncoding;
|
|
Str255 convertedString;
|
|
|
|
*aOutChar = nsnull;
|
|
|
|
OSStatus err =
|
|
::UpgradeScriptInfoToTextEncoding(smSystemScript,
|
|
kTextLanguageDontCare,
|
|
kTextRegionDontCare,
|
|
NULL,
|
|
&systemEncoding);
|
|
NS_ENSURE_TRUE(err == noErr, false);
|
|
|
|
err = ::CreateUnicodeToTextInfoByEncoding(systemEncoding, &converterInfo);
|
|
NS_ENSURE_TRUE(err == noErr, false);
|
|
|
|
err = ::ConvertFromUnicodeToPString(converterInfo, sizeof(PRUnichar),
|
|
&aUniChar, convertedString);
|
|
NS_ENSURE_TRUE(err == noErr, false);
|
|
|
|
*aOutChar = convertedString[1];
|
|
::DisposeUnicodeToTextInfo(&converterInfo);
|
|
return true;
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
|
|
}
|
|
|
|
/* static */ void
|
|
PluginTextInputHandler::ConvertCocoaKeyEventToCarbonEvent(
|
|
NSEvent* aCocoaKeyEvent,
|
|
EventRecord& aCarbonKeyEvent,
|
|
bool aMakeKeyDownEventIfNSFlagsChanged)
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
UInt32 charCode = 0;
|
|
if ([aCocoaKeyEvent type] == NSFlagsChanged) {
|
|
aCarbonKeyEvent.what = aMakeKeyDownEventIfNSFlagsChanged ? keyDown : keyUp;
|
|
} else {
|
|
if ([[aCocoaKeyEvent characters] length] > 0) {
|
|
charCode = [[aCocoaKeyEvent characters] characterAtIndex:0];
|
|
}
|
|
if ([aCocoaKeyEvent type] == NSKeyDown) {
|
|
aCarbonKeyEvent.what = [aCocoaKeyEvent isARepeat] ? autoKey : keyDown;
|
|
} else {
|
|
aCarbonKeyEvent.what = keyUp;
|
|
}
|
|
}
|
|
|
|
if (charCode >= 0x0080) {
|
|
switch (charCode) {
|
|
case NSUpArrowFunctionKey:
|
|
charCode = kUpArrowCharCode;
|
|
break;
|
|
case NSDownArrowFunctionKey:
|
|
charCode = kDownArrowCharCode;
|
|
break;
|
|
case NSLeftArrowFunctionKey:
|
|
charCode = kLeftArrowCharCode;
|
|
break;
|
|
case NSRightArrowFunctionKey:
|
|
charCode = kRightArrowCharCode;
|
|
break;
|
|
default:
|
|
unsigned char convertedCharCode;
|
|
if (ConvertUnicodeToCharCode(charCode, &convertedCharCode)) {
|
|
charCode = convertedCharCode;
|
|
}
|
|
//NSLog(@"charcode is %d, converted to %c, char is %@",
|
|
// charCode, convertedCharCode, [aCocoaKeyEvent characters]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
aCarbonKeyEvent.message =
|
|
(charCode & 0x00FF) | ([aCocoaKeyEvent keyCode] << 8);
|
|
aCarbonKeyEvent.when = ::TickCount();
|
|
::GetGlobalMouse(&aCarbonKeyEvent.where);
|
|
// XXX Is this correct? If ::GetCurrentKeyModifiers() returns "current"
|
|
// state and there is one or more pending modifier key events,
|
|
// the result is mismatch with the state at current key event.
|
|
aCarbonKeyEvent.modifiers = ::GetCurrentKeyModifiers();
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
EventHandlerRef PluginTextInputHandler::sPluginKeyEventsHandler = NULL;
|
|
|
|
/* static */ void
|
|
PluginTextInputHandler::InstallPluginKeyEventsHandler()
|
|
{
|
|
if (sPluginKeyEventsHandler) {
|
|
return;
|
|
}
|
|
static const EventTypeSpec sTSMEvents[] =
|
|
{ { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent } };
|
|
::InstallEventHandler(::GetEventDispatcherTarget(),
|
|
::NewEventHandlerUPP(PluginKeyEventsHandler),
|
|
GetEventTypeCount(sTSMEvents),
|
|
sTSMEvents,
|
|
NULL,
|
|
&sPluginKeyEventsHandler);
|
|
}
|
|
|
|
/* static */ void
|
|
PluginTextInputHandler::RemovePluginKeyEventsHandler()
|
|
{
|
|
if (!sPluginKeyEventsHandler) {
|
|
return;
|
|
}
|
|
::RemoveEventHandler(sPluginKeyEventsHandler);
|
|
sPluginKeyEventsHandler = NULL;
|
|
}
|
|
|
|
/* static */ void
|
|
PluginTextInputHandler::SwizzleMethods()
|
|
{
|
|
Class IMKInputSessionClass = ::NSClassFromString(@"IMKInputSession");
|
|
nsToolkit::SwizzleMethods(IMKInputSessionClass, @selector(handleEvent:),
|
|
@selector(PluginTextInputHandler_IMKInputSession_handleEvent:));
|
|
nsToolkit::SwizzleMethods(IMKInputSessionClass, @selector(commitComposition),
|
|
@selector(PluginTextInputHandler_IMKInputSession_commitComposition));
|
|
nsToolkit::SwizzleMethods(IMKInputSessionClass, @selector(finishSession),
|
|
@selector(PluginTextInputHandler_IMKInputSession_finishSession));
|
|
}
|
|
|
|
/* static */ OSStatus
|
|
PluginTextInputHandler::PluginKeyEventsHandler(EventHandlerCallRef aHandlerRef,
|
|
EventRef aEvent,
|
|
void *aUserData)
|
|
{
|
|
nsAutoreleasePool localPool;
|
|
|
|
TSMDocumentID activeDoc = ::TSMGetActiveDocument();
|
|
NS_ENSURE_TRUE(activeDoc, eventNotHandledErr);
|
|
|
|
ChildView *target = nil;
|
|
OSStatus status = ::TSMGetDocumentProperty(activeDoc,
|
|
kFocusedChildViewTSMDocPropertyTag,
|
|
sizeof(ChildView *), nil, &target);
|
|
NS_ENSURE_TRUE(status == noErr, eventNotHandledErr);
|
|
NS_ENSURE_TRUE(target, eventNotHandledErr);
|
|
|
|
EventRef keyEvent = NULL;
|
|
status = ::GetEventParameter(aEvent, kEventParamTextInputSendKeyboardEvent,
|
|
typeEventRef, NULL, sizeof(EventRef), NULL,
|
|
&keyEvent);
|
|
NS_ENSURE_TRUE(status == noErr, eventNotHandledErr);
|
|
NS_ENSURE_TRUE(keyEvent, eventNotHandledErr);
|
|
|
|
nsIWidget* widget = [target widget];
|
|
NS_ENSURE_TRUE(widget, eventNotHandledErr);
|
|
TextInputHandler* handler =
|
|
static_cast<nsChildView*>(widget)->GetTextInputHandler();
|
|
NS_ENSURE_TRUE(handler, eventNotHandledErr);
|
|
handler->HandleCarbonPluginKeyEvent(keyEvent);
|
|
|
|
return noErr;
|
|
}
|
|
|
|
// Called from PluginKeyEventsHandler() (a handler for Carbon TSM events) to
|
|
// process a Carbon key event for the currently focused plugin. Both Unicode
|
|
// characters and "Mac encoding characters" (in the MBCS or "multibyte
|
|
// character system") are (or should be) available from aKeyEvent, but here we
|
|
// use the MCBS characters. This is how the WebKit does things, and seems to
|
|
// be what plugins expect.
|
|
void
|
|
PluginTextInputHandler::HandleCarbonPluginKeyEvent(EventRef aKeyEvent)
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
|
|
|
if (Destroyed()) {
|
|
return;
|
|
}
|
|
|
|
NS_ASSERTION(mView, "mView must not be NULL");
|
|
|
|
nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
|
|
|
|
if ([mView pluginEventModel] == NPEventModelCocoa) {
|
|
UInt32 size;
|
|
OSStatus status =
|
|
::GetEventParameter(aKeyEvent, kEventParamKeyUnicodes, typeUnicodeText,
|
|
NULL, 0, &size, NULL);
|
|
NS_ENSURE_TRUE(status == noErr, );
|
|
|
|
UniChar* chars = (UniChar*)malloc(size);
|
|
NS_ENSURE_TRUE(chars, );
|
|
|
|
status = ::GetEventParameter(aKeyEvent, kEventParamKeyUnicodes,
|
|
typeUnicodeText, NULL, size, NULL, chars);
|
|
if (status != noErr) {
|
|
free(chars);
|
|
return;
|
|
}
|
|
|
|
CFStringRef text =
|
|
::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, chars,
|
|
(size / sizeof(UniChar)),
|
|
kCFAllocatorNull);
|
|
if (!text) {
|
|
free(chars);
|
|
return;
|
|
}
|
|
|
|
NPCocoaEvent cocoaTextEvent;
|
|
nsCocoaUtils::InitNPCocoaEvent(&cocoaTextEvent);
|
|
cocoaTextEvent.type = NPCocoaEventTextInput;
|
|
cocoaTextEvent.data.text.text = (NPNSString*)text;
|
|
|
|
nsPluginEvent pluginEvent(true, NS_PLUGIN_INPUT_EVENT, mWidget);
|
|
nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaTextEvent);
|
|
DispatchEvent(pluginEvent);
|
|
|
|
::CFRelease(text);
|
|
free(chars);
|
|
|
|
return;
|
|
}
|
|
|
|
UInt32 numCharCodes;
|
|
OSStatus status = ::GetEventParameter(aKeyEvent, kEventParamKeyMacCharCodes,
|
|
typeChar, NULL, 0, &numCharCodes, NULL);
|
|
NS_ENSURE_TRUE(status == noErr, );
|
|
|
|
nsAutoTArray<unsigned char, 3> charCodes;
|
|
charCodes.SetLength(numCharCodes);
|
|
status = ::GetEventParameter(aKeyEvent, kEventParamKeyMacCharCodes,
|
|
typeChar, NULL, numCharCodes, NULL,
|
|
charCodes.Elements());
|
|
NS_ENSURE_TRUE(status == noErr, );
|
|
|
|
UInt32 modifiers;
|
|
status = ::GetEventParameter(aKeyEvent, kEventParamKeyModifiers,
|
|
typeUInt32, NULL, sizeof(modifiers), NULL,
|
|
&modifiers);
|
|
NS_ENSURE_TRUE(status == noErr, );
|
|
|
|
NSUInteger cocoaModifiers = 0;
|
|
if (modifiers & shiftKey) {
|
|
cocoaModifiers |= NSShiftKeyMask;
|
|
}
|
|
if (modifiers & controlKey) {
|
|
cocoaModifiers |= NSControlKeyMask;
|
|
}
|
|
if (modifiers & optionKey) {
|
|
cocoaModifiers |= NSAlternateKeyMask;
|
|
}
|
|
if (modifiers & cmdKey) { // Should never happen
|
|
cocoaModifiers |= NSCommandKeyMask;
|
|
}
|
|
|
|
UInt32 macKeyCode;
|
|
status = ::GetEventParameter(aKeyEvent, kEventParamKeyCode,
|
|
typeUInt32, NULL, sizeof(macKeyCode), NULL,
|
|
&macKeyCode);
|
|
NS_ENSURE_TRUE(status == noErr, );
|
|
|
|
EventRef cloneEvent = ::CopyEvent(aKeyEvent);
|
|
for (PRUint32 i = 0; i < numCharCodes; ++i) {
|
|
status = ::SetEventParameter(cloneEvent, kEventParamKeyMacCharCodes,
|
|
typeChar, 1, charCodes.Elements() + i);
|
|
NS_ENSURE_TRUE(status == noErr, );
|
|
|
|
EventRecord eventRec;
|
|
if (::ConvertEventRefToEventRecord(cloneEvent, &eventRec)) {
|
|
nsKeyEvent keydownEvent(true, NS_KEY_DOWN, mWidget);
|
|
nsCocoaUtils::InitInputEvent(keydownEvent, cocoaModifiers);
|
|
|
|
PRUint32 keyCode = ComputeGeckoKeyCode(macKeyCode, @"");
|
|
PRUint32 charCode(charCodes.ElementAt(i));
|
|
|
|
keydownEvent.time = PR_IntervalNow();
|
|
keydownEvent.pluginEvent = &eventRec;
|
|
if (IsSpecialGeckoKey(macKeyCode)) {
|
|
keydownEvent.keyCode = keyCode;
|
|
} else {
|
|
keydownEvent.charCode = charCode;
|
|
keydownEvent.isChar = true;
|
|
}
|
|
DispatchEvent(keydownEvent);
|
|
if (Destroyed()) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
::ReleaseEvent(cloneEvent);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
|
}
|
|
|
|
void
|
|
PluginTextInputHandler::ActivatePluginTSMDocument()
|
|
{
|
|
if (!mPluginTSMDoc) {
|
|
// Create a TSM document that supports both non-Unicode and Unicode input.
|
|
// Though ProcessPluginKeyEvent() only sends Mac char codes to
|
|
// the plugin, this makes the input window behave better when it contains
|
|
// more than one kind of input (say Hiragana and Romaji). This is what
|
|
// the OS does when it creates a TSM document for use by an
|
|
// NSTSMInputContext class.
|
|
InterfaceTypeList supportedServices;
|
|
supportedServices[0] = kTextServiceDocumentInterfaceType;
|
|
supportedServices[1] = kUnicodeDocumentInterfaceType;
|
|
::NewTSMDocument(2, supportedServices, &mPluginTSMDoc, 0);
|
|
// We'll need to use the "input window".
|
|
::UseInputWindow(mPluginTSMDoc, YES);
|
|
::ActivateTSMDocument(mPluginTSMDoc);
|
|
} else if (::TSMGetActiveDocument() != mPluginTSMDoc) {
|
|
::ActivateTSMDocument(mPluginTSMDoc);
|
|
}
|
|
}
|
|
|
|
#endif // #ifndef NP_NO_CARBON
|
|
|
|
void
|
|
PluginTextInputHandler::HandleKeyDownEventForPlugin(NSEvent* aNativeKeyEvent)
|
|
{
|
|
if (Destroyed()) {
|
|
return;
|
|
}
|
|
|
|
NS_ASSERTION(mView, "mView must not be NULL");
|
|
|
|
#ifdef NP_NO_CARBON
|
|
|
|
if ([mView pluginEventModel] != NPEventModelCocoa) {
|
|
return;
|
|
}
|
|
|
|
ComplexTextInputPanel* ctiPanel =
|
|
[ComplexTextInputPanel sharedComplexTextInputPanel];
|
|
|
|
// If a composition is in progress then simply let the input panel continue
|
|
// it.
|
|
if (IsInPluginComposition()) {
|
|
// Don't send key up events for key downs associated with compositions.
|
|
mIgnoreNextKeyUpEvent = true;
|
|
|
|
NSString* textString = nil;
|
|
[ctiPanel interpretKeyEvent:aNativeKeyEvent string:&textString];
|
|
if (textString) {
|
|
DispatchCocoaNPAPITextEvent(textString);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Reset complex text input request flag.
|
|
mPluginComplexTextInputRequested = false;
|
|
|
|
// Send key down event to the plugin.
|
|
nsPluginEvent pluginEvent(true, NS_PLUGIN_INPUT_EVENT, mWidget);
|
|
NPCocoaEvent cocoaEvent;
|
|
ConvertCocoaKeyEventToNPCocoaEvent(aNativeKeyEvent, cocoaEvent);
|
|
nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaEvent);
|
|
DispatchEvent(pluginEvent);
|
|
if (Destroyed()) {
|
|
return;
|
|
}
|
|
|
|
// Start complex text composition if requested.
|
|
if (mPluginComplexTextInputRequested) {
|
|
// Don't send key up events for key downs associated with compositions.
|
|
mIgnoreNextKeyUpEvent = true;
|
|
|
|
NSString* textString = nil;
|
|
[ctiPanel interpretKeyEvent:aNativeKeyEvent string:&textString];
|
|
if (textString) {
|
|
DispatchCocoaNPAPITextEvent(textString);
|
|
}
|
|
}
|
|
|
|
#else // #ifdef NP_NO_CARBON
|
|
|
|
bool wasInComposition = false;
|
|
if ([mView pluginEventModel] == NPEventModelCocoa) {
|
|
if (IsInPluginComposition()) {
|
|
wasInComposition = true;
|
|
|
|
// Don't send key up events for key downs associated with compositions.
|
|
mIgnoreNextKeyUpEvent = true;
|
|
} else {
|
|
// Reset complex text input request flag.
|
|
mPluginComplexTextInputRequested = false;
|
|
|
|
// Send key down event to the plugin.
|
|
nsPluginEvent pluginEvent(true, NS_PLUGIN_INPUT_EVENT, mWidget);
|
|
NPCocoaEvent cocoaEvent;
|
|
ConvertCocoaKeyEventToNPCocoaEvent(aNativeKeyEvent, cocoaEvent);
|
|
nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaEvent);
|
|
DispatchEvent(pluginEvent);
|
|
if (Destroyed()) {
|
|
return;
|
|
}
|
|
|
|
// Only continue if plugin wants complex text input.
|
|
if (!mPluginComplexTextInputRequested) {
|
|
return;
|
|
}
|
|
|
|
// Don't send key up events for key downs associated with compositions.
|
|
mIgnoreNextKeyUpEvent = true;
|
|
}
|
|
|
|
// Don't send complex text input to a plugin in Cocoa event mode if
|
|
// either the Control key or the Command key is pressed -- even if the
|
|
// plugin has requested it, or we are already in IME composition. This
|
|
// conforms to our behavior in 64-bit mode and fixes bug 619217.
|
|
NSUInteger modifierFlags = [aNativeKeyEvent modifierFlags];
|
|
if ((modifierFlags & NSControlKeyMask) ||
|
|
(modifierFlags & NSCommandKeyMask)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// This will take care of all Carbon plugin events and also send Cocoa plugin
|
|
// text events when NSInputContext is not available (ifndef NP_NO_CARBON).
|
|
ActivatePluginTSMDocument();
|
|
|
|
// We use the active TSM document to pass a pointer to ourselves (the
|
|
// currently focused ChildView) to PluginKeyEventsHandler(). Because this
|
|
// pointer is weak, we should retain and release ourselves around the call
|
|
// to TSMProcessRawKeyEvent().
|
|
nsAutoRetainCocoaObject kungFuDeathGrip(mView);
|
|
::TSMSetDocumentProperty(mPluginTSMDoc,
|
|
kFocusedChildViewTSMDocPropertyTag,
|
|
sizeof(ChildView *), &mView);
|
|
::TSMProcessRawKeyEvent([aNativeKeyEvent _eventRef]);
|
|
::TSMRemoveDocumentProperty(mPluginTSMDoc,
|
|
kFocusedChildViewTSMDocPropertyTag);
|
|
|
|
#endif // #ifdef NP_NO_CARBON else
|
|
}
|
|
|
|
void
|
|
PluginTextInputHandler::HandleKeyUpEventForPlugin(NSEvent* aNativeKeyEvent)
|
|
{
|
|
if (mIgnoreNextKeyUpEvent) {
|
|
mIgnoreNextKeyUpEvent = false;
|
|
return;
|
|
}
|
|
|
|
if (Destroyed()) {
|
|
return;
|
|
}
|
|
|
|
NS_ASSERTION(mView, "mView must not be NULL");
|
|
|
|
NPEventModel eventModel = [mView pluginEventModel];
|
|
if (eventModel == NPEventModelCocoa) {
|
|
// Don't send key up events to Cocoa plugins during composition.
|
|
if (IsInPluginComposition()) {
|
|
return;
|
|
}
|
|
|
|
nsKeyEvent keyupEvent(true, NS_KEY_UP, mWidget);
|
|
InitKeyEvent(aNativeKeyEvent, keyupEvent);
|
|
NPCocoaEvent pluginEvent;
|
|
ConvertCocoaKeyEventToNPCocoaEvent(aNativeKeyEvent, pluginEvent);
|
|
keyupEvent.pluginEvent = &pluginEvent;
|
|
DispatchEvent(keyupEvent);
|
|
return;
|
|
}
|
|
|
|
#ifndef NP_NO_CARBON
|
|
if (eventModel == NPEventModelCarbon) {
|
|
// I'm not sure the call to TSMProcessRawKeyEvent() is needed here (though
|
|
// WebKit makes one).
|
|
::TSMProcessRawKeyEvent([aNativeKeyEvent _eventRef]);
|
|
|
|
// Don't send a keyUp event if the corresponding keyDown event(s) is/are
|
|
// still being processed (idea borrowed from WebKit).
|
|
ChildView* keydownTarget = nil;
|
|
OSStatus status =
|
|
::TSMGetDocumentProperty(mPluginTSMDoc,
|
|
kFocusedChildViewTSMDocPropertyTag,
|
|
sizeof(ChildView *), nil, &keydownTarget);
|
|
NS_ENSURE_TRUE(status == noErr, );
|
|
if (keydownTarget == mView) {
|
|
return;
|
|
}
|
|
|
|
// PluginKeyEventsHandler() never sends keyUp events to
|
|
// HandleCarbonPluginKeyEvent(), so we need to send them to Gecko here.
|
|
// (This means that when commiting text from IME, several keyDown events
|
|
// may be sent to Gecko (in processPluginKeyEvent) for one keyUp event here.
|
|
// But this is how the WebKit does it, and games expect a keyUp event to
|
|
// be sent when it actually happens (they need to be able to detect how
|
|
// long a key has been held down) -- which wouldn't be possible if we sent
|
|
// them from processPluginKeyEvent.)
|
|
nsKeyEvent keyupEvent(true, NS_KEY_UP, mWidget);
|
|
InitKeyEvent(aNativeKeyEvent, keyupEvent);
|
|
EventRecord pluginEvent;
|
|
ConvertCocoaKeyEventToCarbonEvent(aNativeKeyEvent, pluginEvent, false);
|
|
keyupEvent.pluginEvent = &pluginEvent;
|
|
DispatchEvent(keyupEvent);
|
|
return;
|
|
}
|
|
#endif // #ifndef NP_NO_CARBON
|
|
|
|
}
|
|
|
|
bool
|
|
PluginTextInputHandler::IsInPluginComposition()
|
|
{
|
|
return
|
|
#ifdef NP_NO_CARBON
|
|
[[ComplexTextInputPanel sharedComplexTextInputPanel] inComposition] != NO;
|
|
#else // #ifdef NP_NO_CARBON
|
|
mPluginTSMInComposition;
|
|
#endif // #ifdef NP_NO_CARBON else
|
|
}
|
|
|
|
bool
|
|
PluginTextInputHandler::DispatchCocoaNPAPITextEvent(NSString* aString)
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
|
|
|
|
NPCocoaEvent cocoaTextEvent;
|
|
nsCocoaUtils::InitNPCocoaEvent(&cocoaTextEvent);
|
|
cocoaTextEvent.type = NPCocoaEventTextInput;
|
|
cocoaTextEvent.data.text.text = (NPNSString*)aString;
|
|
|
|
nsPluginEvent pluginEvent(true, NS_PLUGIN_INPUT_EVENT, mWidget);
|
|
nsCocoaUtils::InitPluginEvent(pluginEvent, cocoaTextEvent);
|
|
return DispatchEvent(pluginEvent);
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
#ifndef NP_NO_CARBON
|
|
|
|
/******************************************************************************
|
|
*
|
|
* PluginTextInputHandler_IMKInputSession_* implementation
|
|
*
|
|
******************************************************************************/
|
|
|
|
// IMKInputSession is an undocumented class in the HIToolbox framework. It's
|
|
// present on both Leopard and SnowLeopard, and is used at a low level to
|
|
// process IME input regardless of which high-level API is used (Text Services
|
|
// Manager or Cocoa). It works the same way in both 32-bit and 64-bit code.
|
|
@interface NSObject (IMKInputSessionMethodSwizzling)
|
|
- (BOOL)PluginTextInputHandler_IMKInputSession_handleEvent:(EventRef)theEvent;
|
|
- (void)PluginTextInputHandler_IMKInputSession_commitComposition;
|
|
- (void)PluginTextInputHandler_IMKInputSession_finishSession;
|
|
@end
|
|
|
|
@implementation NSObject (IMKInputSessionMethodSwizzling)
|
|
|
|
- (BOOL)PluginTextInputHandler_IMKInputSession_handleEvent:(EventRef)theEvent
|
|
{
|
|
[self retain];
|
|
BOOL retval =
|
|
[self PluginTextInputHandler_IMKInputSession_handleEvent:theEvent];
|
|
NSUInteger retainCount = [self retainCount];
|
|
[self release];
|
|
// Return without doing anything if we've been deleted.
|
|
if (retainCount == 1) {
|
|
return retval;
|
|
}
|
|
|
|
NSWindow *mainWindow = [NSApp mainWindow];
|
|
NSResponder *firstResponder = [mainWindow firstResponder];
|
|
if (![firstResponder isKindOfClass:[ChildView class]]) {
|
|
return retval;
|
|
}
|
|
|
|
// 'charactersEntered' is the length (in bytes) of currently "marked text"
|
|
// -- text that's been entered in IME but not yet committed. If it's
|
|
// non-zero we're composing text in an IME session; if it's zero we're
|
|
// not in an IME session.
|
|
NSInteger entered = 0;
|
|
object_getInstanceVariable(self, "charactersEntered",
|
|
(void **) &entered);
|
|
nsIWidget* widget = [(ChildView*)firstResponder widget];
|
|
NS_ENSURE_TRUE(widget, retval);
|
|
TextInputHandler* handler =
|
|
static_cast<nsChildView*>(widget)->GetTextInputHandler();
|
|
NS_ENSURE_TRUE(handler, retval);
|
|
handler->SetPluginTSMInComposition(entered != 0);
|
|
|
|
return retval;
|
|
}
|
|
|
|
// This method is called whenever IME input is committed as a result of an
|
|
// "abnormal" termination -- for example when changing the keyboard focus from
|
|
// one input field to another.
|
|
- (void)PluginTextInputHandler_IMKInputSession_commitComposition
|
|
{
|
|
NSWindow *mainWindow = [NSApp mainWindow];
|
|
NSResponder *firstResponder = [mainWindow firstResponder];
|
|
if ([firstResponder isKindOfClass:[ChildView class]]) {
|
|
nsIWidget* widget = [(ChildView*)firstResponder widget];
|
|
if (widget) {
|
|
TextInputHandler* handler =
|
|
static_cast<nsChildView*>(widget)->GetTextInputHandler();
|
|
if (handler) {
|
|
handler->SetPluginTSMInComposition(false);
|
|
}
|
|
}
|
|
}
|
|
[self PluginTextInputHandler_IMKInputSession_commitComposition];
|
|
}
|
|
|
|
// This method is called just before we're deallocated.
|
|
- (void)PluginTextInputHandler_IMKInputSession_finishSession
|
|
{
|
|
NSWindow *mainWindow = [NSApp mainWindow];
|
|
NSResponder *firstResponder = [mainWindow firstResponder];
|
|
if ([firstResponder isKindOfClass:[ChildView class]]) {
|
|
nsIWidget* widget = [(ChildView*)firstResponder widget];
|
|
if (widget) {
|
|
TextInputHandler* handler =
|
|
static_cast<nsChildView*>(widget)->GetTextInputHandler();
|
|
if (handler) {
|
|
handler->SetPluginTSMInComposition(false);
|
|
}
|
|
}
|
|
}
|
|
[self PluginTextInputHandler_IMKInputSession_finishSession];
|
|
}
|
|
|
|
@end
|
|
|
|
#endif // #ifndef NP_NO_CARBON
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
/******************************************************************************
|
|
*
|
|
* TextInputHandlerBase implementation
|
|
*
|
|
******************************************************************************/
|
|
|
|
TextInputHandlerBase::TextInputHandlerBase(nsChildView* aWidget,
|
|
NSView<mozView> *aNativeView) :
|
|
mWidget(aWidget)
|
|
{
|
|
gHandlerInstanceCount++;
|
|
mView = [aNativeView retain];
|
|
}
|
|
|
|
TextInputHandlerBase::~TextInputHandlerBase()
|
|
{
|
|
[mView release];
|
|
if (--gHandlerInstanceCount == 0) {
|
|
FinalizeCurrentKeyboardLayout();
|
|
}
|
|
}
|
|
|
|
bool
|
|
TextInputHandlerBase::OnDestroyWidget(nsChildView* aDestroyingWidget)
|
|
{
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandlerBase::OnDestroyWidget, "
|
|
"aDestroyingWidget=%p, mWidget=%p",
|
|
this, aDestroyingWidget, mWidget));
|
|
|
|
if (aDestroyingWidget != mWidget) {
|
|
return false;
|
|
}
|
|
|
|
mWidget = nsnull;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
TextInputHandlerBase::DispatchEvent(nsGUIEvent& aEvent)
|
|
{
|
|
if (aEvent.message == NS_KEY_PRESS) {
|
|
nsInputEvent& inputEvent = static_cast<nsInputEvent&>(aEvent);
|
|
if (!inputEvent.IsMeta()) {
|
|
PR_LOG(gLog, PR_LOG_ALWAYS,
|
|
("%p TextInputHandlerBase::DispatchEvent, hiding mouse cursor", this));
|
|
[NSCursor setHiddenUntilMouseMoves:YES];
|
|
}
|
|
}
|
|
return mWidget->DispatchWindowEvent(aEvent);
|
|
}
|
|
|
|
void
|
|
TextInputHandlerBase::InitKeyEvent(NSEvent *aNativeKeyEvent,
|
|
nsKeyEvent& aKeyEvent)
|
|
{
|
|
NS_ASSERTION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL");
|
|
|
|
TISInputSourceWrapper tis;
|
|
if (mKeyboardOverride.mOverrideEnabled) {
|
|
tis.InitByLayoutID(mKeyboardOverride.mKeyboardLayout, true);
|
|
} else {
|
|
tis.InitByCurrentKeyboardLayout();
|
|
}
|
|
tis.InitKeyEvent(aNativeKeyEvent, aKeyEvent);
|
|
}
|
|
|
|
nsresult
|
|
TextInputHandlerBase::SynthesizeNativeKeyEvent(
|
|
PRInt32 aNativeKeyboardLayout,
|
|
PRInt32 aNativeKeyCode,
|
|
PRUint32 aModifierFlags,
|
|
const nsAString& aCharacters,
|
|
const nsAString& aUnmodifiedCharacters)
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
|
|
|
static const PRUint32 sModifierFlagMap[][2] = {
|
|
{ nsIWidget::CAPS_LOCK, NSAlphaShiftKeyMask },
|
|
{ nsIWidget::SHIFT_L, NSShiftKeyMask },
|
|
{ nsIWidget::SHIFT_R, NSShiftKeyMask },
|
|
{ nsIWidget::CTRL_L, NSControlKeyMask },
|
|
{ nsIWidget::CTRL_R, NSControlKeyMask },
|
|
{ nsIWidget::ALT_L, NSAlternateKeyMask },
|
|
{ nsIWidget::ALT_R, NSAlternateKeyMask },
|
|
{ nsIWidget::COMMAND_L, NSCommandKeyMask },
|
|
{ nsIWidget::COMMAND_R, NSCommandKeyMask },
|
|
{ nsIWidget::NUMERIC_KEY_PAD, NSNumericPadKeyMask },
|
|
{ nsIWidget::HELP, NSHelpKeyMask },
|
|
{ nsIWidget::FUNCTION, NSFunctionKeyMask }
|
|
};
|
|
|
|
PRUint32 modifierFlags = 0;
|
|
for (PRUint32 i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
|
|
if (aModifierFlags & sModifierFlagMap[i][0]) {
|
|
modifierFlags |= sModifierFlagMap[i][1];
|
|
}
|
|
}
|
|
|
|
NSInteger windowNumber = [[mView window] windowNumber];
|
|
bool sendFlagsChangedEvent = IsModifierKey(aNativeKeyCode);
|
|
NSEventType eventType = sendFlagsChangedEvent ? NSFlagsChanged : NSKeyDown;
|
|
NSEvent* downEvent =
|
|
[NSEvent keyEventWithType:eventType
|
|
location:NSMakePoint(0,0)
|
|
modifierFlags:modifierFlags
|
|
timestamp:0
|
|
windowNumber:windowNumber
|
|
context:[NSGraphicsContext currentContext]
|
|
characters:nsCocoaUtils::ToNSString(aCharacters)
|
|
charactersIgnoringModifiers:nsCocoaUtils::ToNSString(aUnmodifiedCharacters)
|
|
isARepeat:NO
|
|
keyCode:aNativeKeyCode];
|
|
|
|
NSEvent* upEvent = sendFlagsChangedEvent ?
|
|
nil : nsCocoaUtils::MakeNewCocoaEventWithType(NSKeyUp, downEvent);
|
|
|
|
if (downEvent && (sendFlagsChangedEvent || upEvent)) {
|
|
KeyboardLayoutOverride currentLayout = mKeyboardOverride;
|
|
mKeyboardOverride.mKeyboardLayout = aNativeKeyboardLayout;
|
|
mKeyboardOverride.mOverrideEnabled = true;
|
|
[NSApp sendEvent:downEvent];
|
|
if (upEvent) {
|
|
[NSApp sendEvent:upEvent];
|
|
}
|
|
// processKeyDownEvent and keyUp block exceptions so we're sure to
|
|
// reach here to restore mKeyboardOverride
|
|
mKeyboardOverride = currentLayout;
|
|
}
|
|
|
|
return NS_OK;
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
|
}
|
|
|
|
/* static */ bool
|
|
TextInputHandlerBase::IsPrintableChar(PRUnichar aChar)
|
|
{
|
|
return (aChar >= 0x20 && aChar <= 0x7E) || aChar >= 0xA0;
|
|
}
|
|
|
|
/* static */ PRUint32
|
|
TextInputHandlerBase::ComputeGeckoKeyCodeFromChar(PRUnichar aChar)
|
|
{
|
|
// We don't support the key code for non-ASCII characters
|
|
if (aChar > 0x7E) {
|
|
return 0;
|
|
}
|
|
|
|
// lowercase
|
|
if (aChar >= 'a' && aChar <= 'z') {
|
|
return PRUint32(toupper(aChar));
|
|
}
|
|
// uppercase
|
|
if (aChar >= 'A' && aChar <= 'Z') {
|
|
return PRUint32(aChar);
|
|
}
|
|
// numeric
|
|
if (aChar >= '0' && aChar <= '9') {
|
|
return PRUint32(aChar - '0' + NS_VK_0);
|
|
}
|
|
|
|
switch (aChar) {
|
|
case kReturnCharCode:
|
|
case kEnterCharCode:
|
|
case '\n':
|
|
return NS_VK_RETURN;
|
|
case '{':
|
|
case '[':
|
|
return NS_VK_OPEN_BRACKET;
|
|
case '}':
|
|
case ']':
|
|
return NS_VK_CLOSE_BRACKET;
|
|
case '\'':
|
|
case '"':
|
|
return NS_VK_QUOTE;
|
|
|
|
case '\\': return NS_VK_BACK_SLASH;
|
|
case ' ': return NS_VK_SPACE;
|
|
case ';': return NS_VK_SEMICOLON;
|
|
case '=': return NS_VK_EQUALS;
|
|
case ',': return NS_VK_COMMA;
|
|
case '.': return NS_VK_PERIOD;
|
|
case '/': return NS_VK_SLASH;
|
|
case '`': return NS_VK_BACK_QUOTE;
|
|
case '\t': return NS_VK_TAB;
|
|
case '-': return NS_VK_SUBTRACT;
|
|
case '+': return NS_VK_ADD;
|
|
|
|
default:
|
|
if (!IsPrintableChar(aChar)) {
|
|
NS_WARNING("ComputeGeckoKeyCodeFromChar() has failed.");
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* static */ PRUint32
|
|
TextInputHandlerBase::ComputeGeckoKeyCode(UInt32 aNativeKeyCode,
|
|
NSString *aCharacters)
|
|
{
|
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
|
|
|
|
switch (aNativeKeyCode) {
|
|
// modifiers. We don't get separate events for these
|
|
case kEscapeKeyCode: return NS_VK_ESCAPE;
|
|
case kRCommandKeyCode:
|
|
case kCommandKeyCode: return NS_VK_META;
|
|
case kRShiftKeyCode:
|
|
case kShiftKeyCode: return NS_VK_SHIFT;
|
|
case kCapsLockKeyCode: return NS_VK_CAPS_LOCK;
|
|
case kRControlKeyCode:
|
|
case kControlKeyCode: return NS_VK_CONTROL;
|
|
case kROptionKeyCode:
|
|
case kOptionkeyCode: return NS_VK_ALT;
|
|
case kClearKeyCode: return NS_VK_CLEAR;
|
|
|
|
// function keys
|
|
case kF1KeyCode: return NS_VK_F1;
|
|
case kF2KeyCode: return NS_VK_F2;
|
|
case kF3KeyCode: return NS_VK_F3;
|
|
case kF4KeyCode: return NS_VK_F4;
|
|
case kF5KeyCode: return NS_VK_F5;
|
|
case kF6KeyCode: return NS_VK_F6;
|
|
case kF7KeyCode: return NS_VK_F7;
|
|
case kF8KeyCode: return NS_VK_F8;
|
|
case kF9KeyCode: return NS_VK_F9;
|
|
case kF10KeyCode: return NS_VK_F10;
|
|
case kF11KeyCode: return NS_VK_F11;
|
|
case kF12KeyCode: return NS_VK_F12;
|
|
// case kF13KeyCode: return NS_VK_F13; // clash with the 3 below
|
|
// case kF14KeyCode: return NS_VK_F14;
|
|
// case kF15KeyCode: return NS_VK_F15;
|
|
case kPauseKeyCode: return NS_VK_PAUSE;
|
|
case kScrollLockKeyCode: return NS_VK_SCROLL_LOCK;
|
|
case kPrintScreenKeyCode: return NS_VK_PRINTSCREEN;
|
|
|
|
// keypad
|
|
case kKeypad0KeyCode: return NS_VK_NUMPAD0;
|
|
case kKeypad1KeyCode: return NS_VK_NUMPAD1;
|
|
case kKeypad2KeyCode: return NS_VK_NUMPAD2;
|
|
case kKeypad3KeyCode: return NS_VK_NUMPAD3;
|
|
case kKeypad4KeyCode: return NS_VK_NUMPAD4;
|
|
case kKeypad5KeyCode: return NS_VK_NUMPAD5;
|
|
case kKeypad6KeyCode: return NS_VK_NUMPAD6;
|
|
case kKeypad7KeyCode: return NS_VK_NUMPAD7;
|
|
case kKeypad8KeyCode: return NS_VK_NUMPAD8;
|
|
case kKeypad9KeyCode: return NS_VK_NUMPAD9;
|
|
|
|
case kKeypadMultiplyKeyCode: return NS_VK_MULTIPLY;
|
|
case kKeypadAddKeyCode: return NS_VK_ADD;
|
|
case kKeypadSubtractKeyCode: return NS_VK_SUBTRACT;
|
|
case kKeypadDecimalKeyCode: return NS_VK_DECIMAL;
|
|
case kKeypadDivideKeyCode: return NS_VK_DIVIDE;
|
|
|
|
// these may clash with forward delete and help
|
|
case kInsertKeyCode: return NS_VK_INSERT;
|
|
case kDeleteKeyCode: return NS_VK_DELETE;
|
|
|
|
case kBackspaceKeyCode: return NS_VK_BACK;
|
|
case kTabKeyCode: return NS_VK_TAB;
|
|
case kHomeKeyCode: return NS_VK_HOME;
|
|
case kEndKeyCode: return NS_VK_END;
|
|
case kPageUpKeyCode: return NS_VK_PAGE_UP;
|
|
case kPageDownKeyCode: return NS_VK_PAGE_DOWN;
|
|
case kLeftArrowKeyCode: return NS_VK_LEFT;
|
|
case kRightArrowKeyCode: return NS_VK_RIGHT;
|
|
case kUpArrowKeyCode: return NS_VK_UP;
|
|
case kDownArrowKeyCode: return NS_VK_DOWN;
|
|
case kVK_ANSI_1: return NS_VK_1;
|
|
case kVK_ANSI_2: return NS_VK_2;
|
|
case kVK_ANSI_3: return NS_VK_3;
|
|
case kVK_ANSI_4: return NS_VK_4;
|
|
case kVK_ANSI_5: return NS_VK_5;
|
|
case kVK_ANSI_6: return NS_VK_6;
|
|
case kVK_ANSI_7: return NS_VK_7;
|
|
case kVK_ANSI_8: return NS_VK_8;
|
|
case kVK_ANSI_9: return NS_VK_9;
|
|
case kVK_ANSI_0: return NS_VK_0;
|
|
|
|
default:
|
|
// if we haven't gotten the key code already, look at the char code
|
|
if ([aCharacters length] > 0) {
|
|
return ComputeGeckoKeyCodeFromChar([aCharacters characterAtIndex:0]);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
|
|
}
|
|
|
|
/* static */ bool
|
|
TextInputHandlerBase::IsSpecialGeckoKey(UInt32 aNativeKeyCode)
|
|
{
|
|
// this table is used to determine which keys are special and should not
|
|
// generate a charCode
|
|
switch (aNativeKeyCode) {
|
|
// modifiers - we don't get separate events for these yet
|
|
case kEscapeKeyCode:
|
|
case kShiftKeyCode:
|
|
case kRShiftKeyCode:
|
|
case kCommandKeyCode:
|
|
case kRCommandKeyCode:
|
|
case kCapsLockKeyCode:
|
|
case kControlKeyCode:
|
|
case kRControlKeyCode:
|
|
case kOptionkeyCode:
|
|
case kROptionKeyCode:
|
|
case kClearKeyCode:
|
|
|
|
// function keys
|
|
case kF1KeyCode:
|
|
case kF2KeyCode:
|
|
case kF3KeyCode:
|
|
case kF4KeyCode:
|
|
case kF5KeyCode:
|
|
case kF6KeyCode:
|
|
case kF7KeyCode:
|
|
case kF8KeyCode:
|
|
case kF9KeyCode:
|
|
case kF10KeyCode:
|
|
case kF11KeyCode:
|
|
case kF12KeyCode:
|
|
case kPauseKeyCode:
|
|
case kScrollLockKeyCode:
|
|
case kPrintScreenKeyCode:
|
|
|
|
case kInsertKeyCode:
|
|
case kDeleteKeyCode:
|
|
case kTabKeyCode:
|
|
case kBackspaceKeyCode:
|
|
|
|
case kHomeKeyCode:
|
|
case kEndKeyCode:
|
|
case kPageUpKeyCode:
|
|
case kPageDownKeyCode:
|
|
case kLeftArrowKeyCode:
|
|
case kRightArrowKeyCode:
|
|
case kUpArrowKeyCode:
|
|
case kDownArrowKeyCode:
|
|
case kReturnKeyCode:
|
|
case kEnterKeyCode:
|
|
case kPowerbookEnterKeyCode:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* static */ bool
|
|
TextInputHandlerBase::IsNormalCharInputtingEvent(const nsKeyEvent& aKeyEvent)
|
|
{
|
|
// this is not character inputting event, simply.
|
|
if (!aKeyEvent.isChar || !aKeyEvent.charCode || aKeyEvent.IsMeta()) {
|
|
return false;
|
|
}
|
|
// if this is unicode char inputting event, we don't need to check
|
|
// ctrl/alt/command keys
|
|
if (aKeyEvent.charCode > 0x7F) {
|
|
return true;
|
|
}
|
|
// ASCII chars should be inputted without ctrl/alt/command keys
|
|
return !aKeyEvent.IsControl() && !aKeyEvent.IsAlt();
|
|
}
|
|
|
|
/* static */ bool
|
|
TextInputHandlerBase::IsModifierKey(UInt32 aNativeKeyCode)
|
|
{
|
|
switch (aNativeKeyCode) {
|
|
case kCapsLockKeyCode:
|
|
case kRCommandKeyCode:
|
|
case kCommandKeyCode:
|
|
case kShiftKeyCode:
|
|
case kOptionkeyCode:
|
|
case kControlKeyCode:
|
|
case kRShiftKeyCode:
|
|
case kROptionKeyCode:
|
|
case kRControlKeyCode:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|