1/*
2 * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26#import <Cocoa/Cocoa.h>
27#include <objc/objc-runtime.h>
28
29#import "sun_lwawt_macosx_CInputMethod.h"
30#import "sun_lwawt_macosx_CInputMethodDescriptor.h"
31#import "ThreadUtilities.h"
32#import "AWTView.h"
33
34#import <JavaNativeFoundation/JavaNativeFoundation.h>
35#import <JavaRuntimeSupport/JavaRuntimeSupport.h>
36
37#define JAVA_LIST @"JAVA_LIST"
38#define CURRENT_KB_DESCRIPTION @"CURRENT_KB_DESCRIPTION"
39
40static JNF_CLASS_CACHE(jc_localeClass, "java/util/Locale");
41static JNF_CTOR_CACHE(jm_localeCons, jc_localeClass, "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
42static JNF_CLASS_CACHE(jc_arrayListClass, "java/util/ArrayList");
43static JNF_CTOR_CACHE(jm_arrayListCons, jc_arrayListClass, "()V");
44static JNF_MEMBER_CACHE(jm_listAdd, jc_arrayListClass, "add", "(Ljava/lang/Object;)Z");
45static JNF_MEMBER_CACHE(jm_listContains, jc_arrayListClass, "contains", "(Ljava/lang/Object;)Z");
46
47
48
49//
50// NOTE: This returns a JNI Local Ref. Any code that calls must call DeleteLocalRef with the return value.
51//
52static jobject CreateLocaleObjectFromNSString(JNIEnv *env, NSString *name)
53{
54    // Break apart the string into its components.
55    // First, duplicate the NSString into a C string, since we're going to modify it.
56    char * language = strdup([name UTF8String]);
57    char * country;
58    char * variant;
59
60    // Convert _ to NULL -- this gives us three null terminated strings in place.
61    for (country = language; *country != '_' && *country != '\0'; country++);
62    if (*country == '_') {
63        *country++ = '\0';
64        for (variant = country; *variant != '_' && *variant != '\0'; variant++);
65        if (*variant == '_') {
66            *variant++ = '\0';
67        }
68    } else {
69        variant = country;
70    }
71
72    // Create the java.util.Locale object
73    jobject localeObj = NULL;
74    jobject langObj = (*env)->NewStringUTF(env, language);
75    if (langObj != NULL) {
76        jobject ctryObj = (*env)->NewStringUTF(env, country);
77        if(ctryObj != NULL) {
78            jobject vrntObj = (*env)->NewStringUTF(env, variant);
79            if (vrntObj != NULL) {
80                localeObj = JNFNewObject(env, jm_localeCons,langObj, ctryObj,
81                                         vrntObj);
82                (*env)->DeleteLocalRef(env, vrntObj);
83            }
84            (*env)->DeleteLocalRef(env, ctryObj);
85        }
86        (*env)->DeleteLocalRef(env, langObj);
87    }
88    // Clean up and return.
89    free(language);
90    return localeObj;
91}
92
93static id inputMethodController = nil;
94
95static void initializeInputMethodController() {
96    static BOOL checkedJRSInputMethodController = NO;
97    if (!checkedJRSInputMethodController && (inputMethodController == nil)) {
98        id jrsInputMethodController = objc_lookUpClass("JRSInputMethodController");
99        if (jrsInputMethodController != nil) {
100            inputMethodController = [jrsInputMethodController performSelector:@selector(controller)];
101        }
102        checkedJRSInputMethodController = YES;
103    }
104}
105
106
107@interface CInputMethod : NSObject {}
108@end
109
110@implementation CInputMethod
111
112+ (void) setKeyboardLayout:(NSString *)theLocale
113{
114    AWT_ASSERT_APPKIT_THREAD;
115    if (!inputMethodController) return;
116
117    [inputMethodController performSelector:@selector(setCurrentInputMethodForLocale) withObject:theLocale];
118}
119
120+ (void) _nativeNotifyPeerWithView:(AWTView *)view inputMethod:(JNFJObjectWrapper *) inputMethod {
121    AWT_ASSERT_APPKIT_THREAD;
122
123    if (!view) return;
124    if (!inputMethod) return;
125
126    [view setInputMethod:[inputMethod jObject]];
127}
128
129+ (void) _nativeEndComposition:(AWTView *)view {
130    if (view == nil) return;
131
132    [view abandonInput];
133}
134
135
136@end
137
138/*
139 * Class:     sun_lwawt_macosx_CInputMethod
140 * Method:    nativeInit
141 * Signature: ();
142 */
143JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CInputMethod_nativeInit
144(JNIEnv *env, jclass klass)
145{
146    initializeInputMethodController();
147}
148
149/*
150 * Class:     sun_lwawt_macosx_CInputMethod
151 * Method:    nativeGetCurrentInputMethodInfo
152 * Signature: ()Ljava/lang/String;
153 */
154JNIEXPORT jobject JNICALL Java_sun_lwawt_macosx_CInputMethod_nativeGetCurrentInputMethodInfo
155(JNIEnv *env, jclass klass)
156{
157    if (!inputMethodController) return NULL;
158    jobject returnValue = 0;
159    __block NSString *keyboardInfo = NULL;
160JNF_COCOA_ENTER(env);
161
162    [ThreadUtilities performOnMainThreadWaiting:YES block:^(){
163        keyboardInfo = [inputMethodController performSelector:@selector(currentInputMethodName)];
164        [keyboardInfo retain];
165    }];
166
167    if (keyboardInfo == nil) return NULL;
168    returnValue = JNFNSToJavaString(env, keyboardInfo);
169    [keyboardInfo release];
170
171JNF_COCOA_EXIT(env);
172    return returnValue;
173}
174
175/*
176 * Class:     sun_lwawt_macosx_CInputMethod
177 * Method:    nativeActivate
178 * Signature: (JLsun/lwawt/macosx/CInputMethod;)V
179 */
180JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CInputMethod_nativeNotifyPeer
181(JNIEnv *env, jobject this, jlong nativePeer, jobject inputMethod)
182{
183JNF_COCOA_ENTER(env);
184    AWTView *view = (AWTView *)jlong_to_ptr(nativePeer);
185    JNFJObjectWrapper *inputMethodWrapper = [[JNFJObjectWrapper alloc] initWithJObject:inputMethod withEnv:env];
186    [ThreadUtilities performOnMainThreadWaiting:NO block:^(){
187        [CInputMethod _nativeNotifyPeerWithView:view inputMethod:inputMethodWrapper];
188    }];
189
190JNF_COCOA_EXIT(env);
191
192}
193
194/*
195 * Class:     sun_lwawt_macosx_CInputMethod
196 * Method:    nativeEndComposition
197 * Signature: (J)V
198 */
199JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CInputMethod_nativeEndComposition
200(JNIEnv *env, jobject this, jlong nativePeer)
201{
202JNF_COCOA_ENTER(env);
203   AWTView *view = (AWTView *)jlong_to_ptr(nativePeer);
204
205   [ThreadUtilities performOnMainThreadWaiting:NO block:^(){
206        [CInputMethod _nativeEndComposition:view];
207    }];
208
209JNF_COCOA_EXIT(env);
210}
211
212/*
213 * Class:     sun_lwawt_macosx_CInputMethod
214 * Method:    getNativeLocale
215 * Signature: ()Ljava/util/Locale;
216 */
217JNIEXPORT jobject JNICALL Java_sun_lwawt_macosx_CInputMethod_getNativeLocale
218(JNIEnv *env, jobject this)
219{
220    if (!inputMethodController) return NULL;
221    jobject returnValue = 0;
222    __block NSString *isoAbbreviation;
223JNF_COCOA_ENTER(env);
224
225    [ThreadUtilities performOnMainThreadWaiting:YES block:^(){
226        isoAbbreviation = (NSString *) [inputMethodController performSelector:@selector(currentInputMethodLocale)];
227        [isoAbbreviation retain];
228    }];
229
230    if (isoAbbreviation == nil) return NULL;
231
232    static NSString *sLastKeyboardStr = nil;
233    static jobject sLastKeyboardLocaleObj = NULL;
234
235    if (![isoAbbreviation isEqualTo:sLastKeyboardStr]) {
236        [sLastKeyboardStr release];
237        sLastKeyboardStr = [isoAbbreviation retain];
238        jobject localObj = CreateLocaleObjectFromNSString(env, isoAbbreviation);
239        [isoAbbreviation release];
240
241        if (sLastKeyboardLocaleObj) {
242            JNFDeleteGlobalRef(env, sLastKeyboardLocaleObj);
243            sLastKeyboardLocaleObj = NULL;
244        }
245        if (localObj != NULL) {
246            sLastKeyboardLocaleObj = JNFNewGlobalRef(env, localObj);
247            (*env)->DeleteLocalRef(env, localObj);
248        }
249    }
250
251    returnValue = sLastKeyboardLocaleObj;
252
253JNF_COCOA_EXIT(env);
254    return returnValue;
255}
256
257
258/*
259 * Class:     sun_lwawt_macosx_CInputMethod
260 * Method:    setNativeLocale
261 * Signature: (Ljava/lang/String;Z)Z
262 */
263JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_CInputMethod_setNativeLocale
264(JNIEnv *env, jobject this, jstring locale, jboolean isActivating)
265{
266JNF_COCOA_ENTER(env);
267    NSString *localeStr = JNFJavaToNSString(env, locale);
268    [localeStr retain];
269
270    [ThreadUtilities performOnMainThreadWaiting:YES block:^(){
271        [CInputMethod setKeyboardLayout:localeStr];
272    }];
273
274    [localeStr release];
275JNF_COCOA_EXIT(env);
276    return JNI_TRUE;
277}
278
279/*
280 * Class:     sun_lwawt_macosx_CInputMethodDescriptor
281 * Method:    nativeInit
282 * Signature: ();
283 */
284JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CInputMethodDescriptor_nativeInit
285(JNIEnv *env, jclass klass)
286{
287    initializeInputMethodController();
288}
289
290/*
291 * Class:     sun_lwawt_macosx_CInputMethodDescriptor
292 * Method:    nativeGetAvailableLocales
293 * Signature: ()[Ljava/util/Locale;
294     */
295JNIEXPORT jobject JNICALL Java_sun_lwawt_macosx_CInputMethodDescriptor_nativeGetAvailableLocales
296(JNIEnv *env, jclass klass)
297{
298    if (!inputMethodController) return NULL;
299    jobject returnValue = 0;
300
301    __block NSArray *selectableArray = nil;
302JNF_COCOA_ENTER(env);
303
304    [ThreadUtilities performOnMainThreadWaiting:YES block:^(){
305        selectableArray = (NSArray *)[inputMethodController performSelector:@selector(availableInputMethodLocales)];
306        [selectableArray retain];
307    }];
308
309    if (selectableArray == nil) return NULL;
310
311     // Create an ArrayList to return back to the caller.
312    returnValue = JNFNewObject(env, jm_arrayListCons);
313
314    for(NSString *locale in selectableArray) {
315        jobject localeObj = CreateLocaleObjectFromNSString(env, locale);
316        if (localeObj == NULL) {
317            break;
318        }
319
320        if (JNFCallBooleanMethod(env, returnValue, jm_listContains, localeObj) == JNI_FALSE) {
321            JNFCallBooleanMethod(env, returnValue, jm_listAdd, localeObj);
322        }
323
324        (*env)->DeleteLocalRef(env, localeObj);
325    }
326    [selectableArray release];
327JNF_COCOA_EXIT(env);
328    return returnValue;
329}
330
331