Home | History | Annotate | Download | only in macos
      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