1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright (c) 2008 Sun Microsystems, Inc. All Rights Reserved. 5 * 6 * The contents of this file are subject to the terms of either the GNU Lesser 7 * General Public License Version 2.1 only ("LGPL") or the Common Development and 8 * Distribution License ("CDDL")(collectively, the "License"). You may not use this 9 * file except in compliance with the License. You can obtain a copy of the CDDL at 10 * http://www.opensource.org/licenses/cddl1.php and a copy of the LGPLv2.1 at 11 * http://www.opensource.org/licenses/lgpl-license.php. See the License for the 12 * specific language governing permissions and limitations under the License. When 13 * distributing the software, include this License Header Notice in each file and 14 * include the full text of the License in the License file as well as the 15 * following notice: 16 * 17 * NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE 18 * (CDDL) 19 * For Covered Software in this distribution, this License shall be governed by the 20 * laws of the State of California (excluding conflict-of-law provisions). 21 * Any litigation relating to this License shall be subject to the jurisdiction of 22 * the Federal Courts of the Northern District of California and the state courts 23 * of the State of California, with venue lying in Santa Clara County, California. 24 * 25 * Contributor(s): 26 * 27 * If you wish your version of this file to be governed by only the CDDL or only 28 * the LGPL Version 2.1, indicate your decision by adding "[Contributor]" elects to 29 * include this software in this distribution under the [CDDL or LGPL Version 2.1] 30 * license." If you don't indicate a single choice of license, a recipient has the 31 * option to distribute your version of this file under either the CDDL or the LGPL 32 * Version 2.1, or to extend the choice of license to its licensees as provided 33 * above. However, if you add LGPL Version 2.1 code and therefore, elected the LGPL 34 * Version 2 license, then the option applies only if the new code is made subject 35 * to such option by the copyright holder. 36 */ 37 38 #import "SunPinyinInputController.h" 39 #import "SunPinyinApplicationDelegate.h" 40 #import "imi_imkitwin.h" 41 42 // forward declaration of 'Private' category 43 @interface SunPinyinController(Private) 44 -(void)tryToSwitchStyle; 45 -(void)createSession; 46 -(void)destroySession; 47 @end 48 49 // implementation of the public interface 50 @implementation SunPinyinController 51 52 /* 53 Implement one of the three ways to receive input from the client. 54 Here are the three approaches: 55 56 1. Support keybinding. 57 In this approach the system takes each keydown and trys to map the keydown 58 to an action method that the input method has implemented. If an action 59 is found the system calls didCommandBySelector:client:. If no action 60 method is found inputText:client: is called. An input method choosing 61 this approach should implement 62 63 -(BOOL)inputText:(NSString*)string client:(id)sender; 64 -(BOOL)didCommandBySelector:(SEL)aSelector client:(id)sender; 65 66 2. Receive all key events without the keybinding, but do "unpack" the relevant text data. 67 Key events are broken down into the Unicodes, the key code that generated 68 them, and modifier flags. This data is then sent to the input method's 69 inputText:key:modifiers:client: method. For this approach implement: 70 71 -(BOOL)inputText:(NSString*)string key:(NSInteger)keyCode modifiers:(NSUInteger)flags client:(id)sender; 72 73 3. Receive events directly from the Text Services Manager as NSEvent objects. 74 For this approach implement: 75 76 -(BOOL)handleEvent:(NSEvent*)event client:(id)sender; 77 */ 78 79 /*! 80 @method 81 @abstract Receive incoming event 82 @discussion This method receives key events from the client application. 83 */ 84 85 -(BOOL)handleEvent:(NSEvent*)event client:(id)sender 86 { 87 // Return YES to indicate the the key input was received and dealt with. 88 // Key processing will not continue in that case. In other words the 89 // system will not deliver a key down event to the application. 90 // Returning NO means the original key down will be passed on to the client. 91 if (!_pv) return NO; 92 93 _currentClient = sender; 94 bool handled = NO; 95 NSUInteger modifiers = [event modifierFlags]; 96 SwitchingPolicies policy = [[NSApp delegate] switchingPolicy]; 97 98 if (SWITCH_BY_CAPS == policy) 99 _englishMode = (modifiers & NSAlphaShiftKeyMask); 100 101 switch ([event type]) { 102 case NSFlagsChanged: 103 if (SWITCH_BY_SHIFT == policy && modifiers == 0 && 104 _lastEventTypes[1] == NSFlagsChanged && _lastModifiers[1] == NSShiftKeyMask) 105 { 106 if (!(_lastModifiers[0] & NSShiftKeyMask)) 107 _englishMode = !_englishMode; 108 109 if (_englishMode) 110 [[NSApp delegate] messageNotify:NSLocalizedString(@"Switched to English mode", nil)]; 111 } 112 113 if (_englishMode) { 114 // We need two spaces to commit in modern style 115 if (_currentStyle == CIMIViewFactory::SVT_MODERN) 116 [self commitComposition:nil]; 117 else 118 _pv->onKeyEvent (' ', ' ', 0); 119 } 120 break; 121 case NSKeyDown: 122 NSInteger keyCode = [event keyCode]; 123 NSString* string = [event characters]; 124 unsigned char keyChar = [string UTF8String][0]; 125 126 if (modifiers & NSCommandKeyMask) 127 break; 128 129 if (_englishMode) { 130 if (SWITCH_BY_CAPS == policy && isprint(keyChar)) { 131 string = (modifiers & NSShiftKeyMask)? string: [string lowercaseString]; 132 [self commitString:string]; 133 handled = YES; 134 } 135 break; 136 } 137 138 [self tryToSwitchStyle]; 139 handled = _pv->onKeyEvent (keyCode, keyChar, modifiers); 140 break; 141 defaults: 142 break; 143 } 144 145 _lastModifiers [0] = _lastModifiers[1]; 146 _lastEventTypes[0] = _lastEventTypes[1]; 147 _lastModifiers [1] = modifiers; 148 _lastEventTypes[1] = [event type]; 149 return handled; 150 } 151 152 -(NSUInteger)recognizedEvents:(id)sender 153 { 154 return NSKeyDownMask | NSFlagsChangedMask; 155 } 156 157 -(void)activateServer:(id)sender 158 { 159 if ([[NSApp delegate] usingUSKbLayout]) 160 [sender overrideKeyboardWithKeyboardNamed:@"com.apple.keylayout.US"]; 161 } 162 163 -(id)initWithServer:(IMKServer*)server delegate:(id)delegate client:(id)inputClient 164 { 165 if (self = [super initWithServer:server delegate:delegate client:inputClient]) 166 [self createSession]; 167 168 return self; 169 } 170 171 -(void)deactivateServer:(id)sender 172 { 173 [[[NSApp delegate] candiWin] hideCandidates]; 174 } 175 176 /*! 177 @method 178 @abstract Called when a user action was taken that ends an input session. 179 Typically triggered by the user selecting a new input method 180 or keyboard layout. 181 @discussion When this method is called your controller should send the 182 current input buffer to the client via a call to 183 insertText:replacementRange:. Additionally, this is the time 184 to clean up if that is necessary. 185 */ 186 187 -(void)commitComposition:(id)sender 188 { 189 NSString *string = _currentStyle == CIMIViewFactory::SVT_MODERN? 190 _preeditString: [_preeditString stringByReplacingOccurrencesOfString:@" " withString:@""]; 191 if (string && [string length]) 192 [self commitString:string]; 193 if (_pv) _pv->clearIC(); 194 } 195 196 -(NSMenu*)menu 197 { 198 return [[NSApp delegate] menu]; 199 } 200 201 -(void)showPrefPanel:(id)sender 202 { 203 [[NSApp delegate] showPrefPanel:sender]; 204 } 205 206 -(void)toggleChinesePuncts:(id)sender 207 { 208 [[NSApp delegate] toggleChinesePuncts:sender]; 209 if (_pv) _pv->setStatusAttrValue(CIMIWinHandler::STATUS_ID_FULLPUNC, 210 [[NSApp delegate] inputChinesePuncts]); 211 } 212 213 -(void)toggleFullSymbols:(id)sender 214 { 215 [[NSApp delegate] toggleFullSymbols:sender]; 216 if (_pv) _pv->setStatusAttrValue(CIMIWinHandler::STATUS_ID_FULLSIMBOL, 217 [[NSApp delegate] inputFullSymbols]); 218 } 219 220 -(void)dealloc 221 { 222 [self destroySession]; 223 [super dealloc]; 224 } 225 226 -(void)commitString:(NSString*)string 227 { 228 // fixed that IME does not work with M$ powerpoint 2008 229 _caret = [string length]; 230 [self showPreeditString:[string retain]]; 231 232 [_currentClient insertText:string 233 replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; 234 235 [_preeditString release]; 236 _preeditString = nil; 237 238 [[[NSApp delegate] candiWin] hideCandidates]; 239 } 240 241 // firefox would call 'commitComposition:' when preedit is emptied 242 -(void)showPreeditString:(NSString*)string 243 { 244 // cache the preedit string 245 [_preeditString release]; 246 _preeditString = [string retain]; 247 248 NSDictionary* attrs; 249 NSAttributedString* attrString; 250 251 attrs = [self markForStyle:kTSMHiliteSelectedRawText atRange:NSMakeRange(0, [string length])]; 252 attrString = [[NSAttributedString alloc] initWithString:string attributes:attrs]; 253 254 // Range (0, 0) will clear the marked text 255 [_currentClient setMarkedText:attrString 256 selectionRange:NSMakeRange(_caret, 0) 257 replacementRange:NSMakeRange(NSNotFound, NSNotFound)]; 258 259 [attrString release]; 260 } 261 262 -(void)setCaret:(int)caret andCandiStart:(int)start 263 { 264 _caret = caret; 265 _candiStart = start; 266 } 267 268 -(void)showCandidates:(NSArray*)candidates 269 { 270 NSRect cursorRect; 271 int curIdx = _currentStyle == CIMIViewFactory::SVT_MODERN? 272 _caret:_candiStart; 273 [_currentClient attributesForCharacterIndex:curIdx lineHeightRectangle:&cursorRect]; 274 [[[NSApp delegate] candiWin] showCandidates:candidates around:cursorRect]; 275 } 276 277 -(void)updateStatus:(int)key withValue:(int)value 278 { 279 switch (key) { 280 case CIMIWinHandler::STATUS_ID_FULLPUNC: 281 if (value != [[NSApp delegate] inputChinesePuncts]) 282 [self toggleChinesePuncts:nil]; 283 break; 284 case CIMIWinHandler::STATUS_ID_FULLSIMBOL: 285 if (value != [[NSApp delegate] inputFullSymbols]) 286 [self toggleFullSymbols:nil]; 287 break; 288 default: 289 break; 290 } 291 } 292 293 @end // SunPinyinController 294 295 296 // implementation of private interface 297 @implementation SunPinyinController(Private) 298 299 -(void)tryToSwitchStyle 300 { 301 CSunpinyinOptions *opts = [[NSApp delegate] preferences]; 302 303 if (*opts == _pv->getPreference()) 304 return; 305 306 if (_currentStyle == opts->m_ViewType && 307 _currentCharset == opts->m_GBK) { 308 _pv->s_CandiWindowSize = opts->m_CandiWindowSize; 309 _pv->setPreference(opts); 310 return; 311 } 312 313 /* input style changed, have to recreate session */ 314 [self destroySession]; 315 [self createSession]; 316 } 317 318 -(void)createSession 319 { 320 if (![[NSApp delegate] sysData]) 321 return; 322 323 CSunpinyinOptions *opts = [[NSApp delegate] preferences]; 324 _currentStyle = opts->m_ViewType; 325 _currentCharset = opts->m_GBK; 326 327 // create view and set its properties and preferences 328 _pv = CIMIViewFactory::createView(_currentStyle); 329 _pv->setStatusAttrValue(CIMIWinHandler::STATUS_ID_FULLPUNC, 330 [[NSApp delegate] inputChinesePuncts]); 331 _pv->setStatusAttrValue(CIMIWinHandler::STATUS_ID_FULLSIMBOL, 332 [[NSApp delegate] inputFullSymbols]); 333 _pv->s_CandiWindowSize = opts->m_CandiWindowSize; 334 _pv->setPreference(opts); 335 336 // create ic and attach to the view 337 CIMIContext *pic = new CIMIContext(); 338 pic->setNonCompleteSyllable(true); 339 pic->setCoreData ([[NSApp delegate] sysData]); 340 pic->setHistoryMemory([[NSApp delegate] history]); 341 pic->clear(); 342 _pv->attachIC(pic); 343 344 // create callback handler and attach to the view 345 CIMKitWindowHandler* pwh = new CIMKitWindowHandler(self); 346 _pv->attachWinHandler(pwh); 347 } 348 349 -(void)destroySession 350 { 351 if (!_pv) return; 352 353 [[NSApp delegate] saveHistory]; 354 delete _pv->getIC(); 355 delete _pv->getWinHandler(); 356 delete _pv; 357 _pv = nil; 358 } 359 360 @end // SunPinyinController(Private) 361