TweetFollow Us on Twitter

Gearing Up for Asia With the Text Services Manager and TSMTE

Gearing Up for Asia With the Text Services Manager and TSMTE

Tague Griffith

Are you eyeing Asian markets for your application? If so, the smartest thing you can do to gear up is to enlist the aid of the Text Services Manager (TSM), introduced with System 7.1 to help applications communicate with utilities that provide text services. Making your application TSM-aware, an easy matter if you're using TextEdit and TSMTE, will enable it to use the services of utilities designed to handle text input in Chinese, Japanese, and Korean. Your application will also be poised to take advantage of the wide variety of text services that eventually will be supported by the Text Services Manager.

Localizing your application for Asian markets, or for Asian language-speaking customers in the United States, may seem like a daunting task to you, but take heart: the Text Services Manager (TSM) makes one aspect of localization, handling keyboard input, easier than you might imagine. Part of the WorldScript technology in the Macintosh Toolbox, the Text Services Manager enables applications and text service utilities to communicate without knowing anything about each other's internal structures or identities. When you make your application TSM-aware, you make it possible for your Asian language-speaking customers to use your application in concert with a utility program that does the necessary conversion of keyboard input.

This article shows you how to modify your TextEdit-based application to make it TSM-aware -- that is, so that it makes the appropriate calls to the Text Services Manager. It doesn't take a lot of modifications, as you'll see from the sample application (called InlineInputSample) that accompanies this article on this issue's CD and develop's Web site. Our application uses TSMTE, an extension that's shipped with the system), which extends TextEdit to handle the details of TSM awareness with minimal effort on the part of application writers. Using TSMTE should be sufficient for most applications; however, for intensive text-processing applications or applications using a different text-editing engine, you may need to handle all TSM processing yourself.

Before we look at the changes you need to make to your application to make it TSM-aware, I'll briefly explain how keyboard input works for Chinese, Japanese, and Korean. If you'd like to read more about common problems of localization, see "Writing Localizable Applications" in develop Issue 14. For details on the Text Services Manager, consult Chapter 7 of Inside Macintosh: Text.

ASIAN LANGUAGES AND KEYBOARD INPUT

As you can guess, supporting keyboard input for Asian languages isn't the same as handling English, because they're written in different scripts. A script is a writing system that can be used to represent one or more human languages.

English and other European languages are written in the Roman script, which is an alphabetic script. In alphabetic scripts, the various characters of the script are combined in different ways to form words. Alphabetic scripts have a small repertoire of characters compared to other types of writing systems. It's a simple matter to represent all the characters in an alphabetic script on a keyboard. Because there are fewer than 256 characters in such scripts, it takes only one byte to uniquely identify each character, so these scripts are known as 1-byte scripts.

Asian languages are quite different, being written in scripts that include ideographic characters borrowed from ancient China. An ideograph is a symbolic character that usually represents a single concept, action, or thing. Figure 1 shows some examples of Japanese and simplified Chinese ideographs. Because each character represents a single concept, there are -- by necessity -- many, many more characters than in the Roman script. Most literate Chinese speakers know around 5000 ideographs, and a literate Japanese knows around 3000 ideographs. Two bytes are required to uniquely identify each character in an ideographic script, and thus these scripts are known as 2-byte scripts. Chinese, Japanese, and Korean also incorporate alternative script systems based on syllabic or phonetic characters (characters that represent certain sounds).

Figure 1. Some Japanese and Chinese ideographs and their English translations

INPUT METHODS

How is it possible for users of 2-byte script systems to get by with a standard Macintosh keyboard? Obviously, they can't simply press the key corresponding to the one character they want out of 3000 or 5000 characters. Enter the text service utility known as an input method or a front-end processor (FEP). An input method allows users to type phonetic or syllabic characters on a standard keyboard and automatically converts what they type into ideographic representations.

For Chinese speakers, the appropriate input method converts keyboard input from Pinyin (Roman) or Zhuyinfuhao (phonetic, also known colloquially as Bopomofo) to ideographic Hanzi. For Japanese speakers, the input method converts input from phonetic Katakana or Hiragana into ideographic Kanji, as illustrated by the example in Figure 2. The input method for Korean speakers converts phonetic Jamo into nonideographic Hangul (complex clusters of Jamo).

Figure 2. The same sentence as entered in Hiragana and as converted to Kanji

Apple currently ships four 2-byte keyboard input methods: Kotoeri (Japanese), Power Input Method (Korean), Traditional Chinese (as used in Taiwan), and Simplified Chinese (as used in the People's Republic of China). The same input methods are shipped with the Apple Language Kits, and third-party input methods are also available.

Regardless of the language, all input methods have a similar user interface. When more than one script is installed on the Mac OS, as is the case for localized systems since all systems have the Roman script installed, the Keyboard menu becomes available in the menu bar. Each available keyboard layout and input method is listed in the Keyboard menu; the icon for the active keyboard layout or input method appears as the menu's title in the menu bar. Figure 3 shows a Keyboard menu displaying items for Apple's Simplified Chinese and Kotoeri (Japanese) input methods, as well as keyboard layouts from some other script systems. The Simplified Chinese input method is active; it's checked in the menu and its icon appears highlighted in the menu bar. The pencil icon in the menu bar is displayed only when an input method is active (in other words, not when the user is typing in English or another language that doesn't require an input method); it's the title for the menu belonging to that input method. Some input methods use a different icon, but it appears in the same place as the pencil icon.

Figure 3. Input method icons in the Keyboard menu and the menu bar

BOTTOMLINE VS. INLINE INPUT

When the user begins typing, the raw text appears on the screen as entered, either in a floating input window that's usually displayed in the lower portion of the screen or in the application window where the text is intended to appear. The first style of text entry is known as bottomline input, while the second is called inline input (see Figure 4). Applications that aren't TSM-aware can make indirect use of the Text Services Manager's floating window service to enable bottomline input (as explained on page 7-13 of Inside Macintosh: Text), but users generally prefer inline input, which only TSM-aware applications can offer. TSM-aware applications can also offer bottomline input, which users may prefer if the size of the text displayed in the document makes reading the characters difficult.

Bottomline input

Inline input

Figure 4. Bottomline vs. inline input

In the case of inline input, the just-entered text appears in what is known as the active input area or inline hole. Text in the active input area or the floating input window is underlined in gray or highlighted in some other manner, depending on the application.

With either bottomline or inline input, the raw text is converted from its phonetic or syllabic representation to ideographic or complex syllabic characters, and the gray underline (if there is one) turns to black or changes in some other manner determined by the application, when the user gives a signal such as pressing the space bar after entering a sequence of characters. There may be more than one possible reading of a given character sequence, in which case the input method will display a list of candidates in a candidate window, as shown in Figure 5. When the user selects one of the candidate readings, the raw text is converted.

Figure 5. Selecting a conversion option for inline input in a candidate window

The user then confirms the converted text, generally by pressing Return. (In Korean, conversion happens continuously and automatically, and the text is confirmed when the user presses either Return or the space bar.) In the case of bottomline input, the confirmed text is flushed from the input window and sent to the application as key-down events. For inline input, the confirmed text is copied into the application's text buffer (as shown in Figure 5) and the active input area is closed. When the user begins typing again, the underline beneath the confirmed text disappears entirely and a new active input area opens.

Before you start feeling overwhelmed by all this, realize that most of the user interface elements I've just described are handled by the input method or TSMTE and not your application. The input method takes all the keystrokes and processes them; your application simply draws the input method's text buffer in the application window. All you need to do to get the benefit of this kind of text service is to make a few modifications to your application. Once your application is TSM-aware, you can work with any input method regardless of language and thus offer your Asian language-speaking customers the convenience of inline input.

MAKING YOUR APPLICATION TSM-AWARE

Making your application TSM-aware is a matter of adding calls to send information to input methods by way of the Text Services Manager. Most of the popular text-editing engines for the Mac OS other than TextEdit are already TSM-aware. One of these, WASTE (the WorldScript-Aware Styled Text Engine, developed by Marco Piovanelli), makes all but four of the necessary calls: InitTSMAwareApplication, CloseTSMAwareApplication, TSMEvent, and SetTSMCursor. These calls need to be made by the application. Optionally, a WASTE-based application can install pre- and post-TSM-update callback routines. If you use WASTE for your text-editing engine, most of the techniques described in this section apply. The WASTE source code is available online at many popular Macintosh ftp sites; I highly recommend looking at it for examples of how to handle the TSM protocol directly.

Using TSMTE, as our application InlineInputSample does, you can make your TextEdit-based application TSM-aware with a few modifications to your event-handling, cursor-handling, window, and menu code. Most of the changes are quite simple and limited to particular subroutines of the application, as demonstrated by InlineInputSample. Our application is a version of TESample, a program written by Apple's Developer Technical Support group and provided with many development environments as part of the example code (it's also on this issue's CD). The code that makes our version of the program TSM-aware is conditionalized with qInline conditionals so that you can easily pick it out. You might want to take a look at that code as you read this section.

    To see the full capabilities of the InlineInputSample application, you need a Macintosh with System 7.1 or later localized for Chinese, Japanese, or Korean, or with one or more of the Asian language kits installed.*

TESTING FOR THE TEXT SERVICES MANAGER AND TSMTE

Before using the Text Services Manager and TSMTE, we need to check and see if they're available. The Text Services Manager is available on all versions of the system after 7.1. However, TSMTE ships only with localized versions of the system and with the Apple Language Kits for Chinese, Japanese, and Korean. The support for inline input discussed in this article will be active only while you're using one of these languages. Listing 1 shows the code we use to check for availability of the Text Services Manager and TSMTE. If we were writing our own protocol handlers, we would eliminate the gestaltTSMTEAttr test.


Listing 1. Testing for TSM and TSMTE availability

static void CheckForTextServices(void)
{
   long  gestaltResponse;

   gHasTextServices = false;     // unless proven otherwise
   gHasTSMTE = false;            // unless proven otherwise
    
   if (TrapAvailable(_Gestalt)) {
      if ((Gestalt(gestaltTSMgrVersion, &gestaltResponse) == noErr)
            && (gestaltResponse >= 1)) {
         gHasTextServices = true;
         if (Gestalt(gestaltTSMTEAttr, &gestaltResponse) == noErr)
            gHasTSMTE = BTst(gestaltResponse, gestaltTSMTEPresent);
      }
   }
}

The selector gestaltTSMgrVersion returns the version number of the Text Services Manager if it's installed. You should test to make sure that the version is greater than or equal to 1, the current version of the Text Services Manager. This will allow your application to work with future TSM versions as well.

INITIALIZING THE APPLICATION

Once we've established that the Text Services Manager and TSMTE are available, we need to extend our Toolbox initialization sequence to initialize the Text Services Manager. This is done by calling InitTSMAwareApplication. We also want to store the current state of the Script Manager's smFontForce variable (the font force flag) and set it to false while our application is running. This flag ensures the correct text-handling behavior in applications that don't use the Script Manager. Since we're using the Script Manager to support text in different languages, we should turn this off, as shown in Listing 2.


Listing 2. Initializing as a TSM-aware application

if (!(gHasTSMTE && InitTSMAwareApplication() == noErr)) {
   // If this happens, just move on without text services.
   gHasTextServices = false;
   gHasTSMTE = false;
}
// Get global font force flag; make sure it's off whenever we run. 
// Do this even if text services don't exist.
gSavedFontForce = GetScriptManagerVariable(smFontForce);
(void) SetScriptManagerVariable(smFontForce, 0);

Of course, since we do this work at initialization, we need to clean up when our application quits. In our termination routine, we restore the value of the font force flag and call CloseTSMAwareApplication. The font force flag also needs to be restored anytime control passes from the application to the system when we're dealing with fonts and such; it particularly should be restored in the case of a suspend event.

EXTENDING THE DOCUMENT STRUCTURE

Now we need to extend our document record to store the additional data structures related to TSM awareness. Our application's original DocumentRecord data structure is extended to include two additional fields, as follows:
typedef struct {
   WindowRecord   docWindow;
   TEHandle       docTE;
   ControlHandle  docVScroll;
   ControlHandle  docHScroll;
   TEClickLoopUPP docClick;
   Boolean        modified;
   TSMTERecHandle docTSMTERecHandle;   // added
   TSMDocumentID  docTSMDoc;           // added
} DocumentRecord, *DocumentPeek;

The TSMTERecHandle is used by TSMTE to maintain the data it uses. The TSMDocumentID identifies a TSM document, which is an opaque data structure used by the Text Services Manager to maintain the current status of the input methods in use. Generally, one TSM document (defined by the TSMDocument data type) is allocated per application window, but some applications may allocate a single global TSM document. Since the TSM document maintains context/state information about the current input method, you should customize allocation of the TSM document for your application's text-handling behavior.

Each TSM document also maintains a reference constant, which can be set by the application. Applications using TSMTE must stuff the TSMTERecHandle into the refCon; if you're creating your own handlers, the refCon can be customized to suit the needs of your application.

CREATING AND DELETING A TSM DOCUMENT

When creating a user document, we need to set up the TSM document and the TSMTERecHandle correctly, as illustrated in Listing 3. We pass four parameters to NewTSMDocument. The first parameter indicates the version of the TSM protocol that we're using; currently, the only defined protocol version is 1. The next parameter, supportedInterfaces, is an array of OSTypes that the Text Services Manager uses to find components that support the service the client is interested in. For our application, we set supportedInterfaces[0] to kTSMTEInterfaceType, indicating that we're using TSMTE support. Other applications providing full TSM support will want to set supportedInterfaces[0] to kTextService. Currently these are the only defined interface types. The final two parameters are a pointer to the TSMDocumentID storage and a pointer to our TSM refCon (in this case the TSMTERecHandle).


Listing 3. Creating a TSM document

if (good && gHasTSMTE) {
   supportedInterfaces[0] = kTSMTEInterfaceType;
   if (NewTSMDocument(1, supportedInterfaces, &doc->docTSMDoc,
            (long) &doc->docTSMTERecHandle) == noErr) {
      TSMTERecPtr tsmteRecPtr = *(doc->docTSMTERecHandle);
      tsmteRecPtr->textH = doc->docTE;
      tsmteRecPtr->preUpdateProc = gTSMTEPreUpdateUPP;
      tsmteRecPtr->postUpdateProc = gTSMTEPostUpdateUPP;
      tsmteRecPtr->updateFlag = kTSMTEAutoScroll;
      tsmteRecPtr->refCon = (long) window;
   } 
   else
      good = false;
}

Once we've allocated the TSM document, we need to set up the TSMTERecHandle. We set the TextEdit record for the handle and install UniversalProcPtrs for the pre- and post-update handlers. These handlers are called before and after TSMTE handles the update event. The pre-update handler in our sample application works around a bug in TSMTE version 1.0, and the post-update handler adjusts the scroll bar to bring the new text into view. We also set the updateFlag and the refCon.

Obviously, since we allocate certain structures when we create our TSM document, we need to deallocate those structures before we destroy the TSM document (that is, when we close the window). Listing 4 shows how to handle deleting a TSM document.


Listing 4. Deleting a TSM document

if (theDocument->docTSMDoc != nil) {
   (void) FixTSMDocument(theDocument->docTSMDoc);
   // DeleteTSMDocument might cause a crash if we don't deactivate
   // first, so...
   (void) DeactivateTSMDocument(theDocument->docTSMDoc);
   (void) DeleteTSMDocument(theDocument->docTSMDoc);
}

The FixTSMDocument call causes the Text Services Manager to confirm the text in the active input area and enter it into the user document. In a real application you'd then give the user the opportunity to save the user document before deleting the TSM document and closing the window, but we skip that step here. After "fixing" the document, we call DeactivateTSMDocument and then DeleteTSMDocument. We need to deactivate the document before deleting it because in certain circumstances DeleteTSMDocument may crash if we call it on an active document.

MODIFYING THE EVENT LOOP

Making your application TSM-aware with TSMTE requires very little modification to your existing event loop code. First we need to give the Text Services Manager an opportunity to handle events that might actually be for an input method and not our application. This is accomplished by calling TSMEvent and passing in the EventRecord returned from either WaitNextEvent or GetNextEvent. In our sample application, we wrap the call to TSMEvent in the IntlTSMEvent subroutine, as shown in Listing 5, to work around a bug that could cause the port to be set to the wrong window. Listing 6 shows how we modified the application's event loop to call TSMEvent before handling an event.


Listing 5. The IntlTSMEvent subroutine

static Boolean IntlTSMEvent(EventRecord *event)
{
   short        oldFont;
   ScriptCode   keyboardScript;
    
   // Make sure we have a port and it's not the Window Manager port.
   if (qd.thePort != nil && FrontWindow() != nil) {
      oldFont = qd.thePort->txFont;
      keyboardScript = GetScriptManagerVariable(smKeyScript);
      if (FontToScript(oldFont) != keyboardScript)
         TextFont(GetScriptVariable(keyboardScript,
             smScriptAppFond));
   }
   return TSMEvent(event);
}

Listing 6. The application's event loop

void EventLoop(void)
{
   RgnHandle   cursorRgn;
   Boolean     gotEvent, handledByTSM;
   EventRecord event;
   Point       mouse;

   cursorRgn = NewRgn();
   while (!gQuitting) {
      // Set global font force flag so other apps don't get confused.
      (void) SetScriptManagerVariable(smFontForce, gSavedFontForce);
        
      if (gHasWaitNextEvent) {
         GetGlobalMouse(&mouse);
         AdjustCursor(mouse, cursorRgn);
         gotEvent = WaitNextEvent(everyEvent, &event, GetSleep(),
                     cursorRgn);
      }
      else {
         SystemTask();
         gotEvent = GetNextEvent(everyEvent, &event);
      }

      // Clear font force flag again so it doesn't upset our
      // operations.
      gSavedFontForce = GetScriptManagerVariable(smFontForce);
      (void) SetScriptManagerVariable(smFontForce, 0);
        
      if (gHasTextServices) {
         handledByTSM = IntlTSMEvent(&event);
         if (!handledByTSM && gotEvent) {
            AdjustCursor(event.where, cursorRgn);
            DoEvent(&event);
         }
         else
            DoIdle();
      }
      else {
         if (gotEvent) {
            AdjustCursor(event.where, cursorRgn);
            DoEvent(&event);
         }
         else
            DoIdle();
      }
   }
}

Next, input methods must be given a chance to handle mouse events for the pencil menu. Whenever the user presses the mouse button in the menu bar, we need to pass the menu selection to the Text Services Manager and see if it's a system menu before we try to handle it as an application menu. We modify the subroutine for handling mouse-down events as shown in Listing 7.


Listing 7. Checking the menu selection in the mouse-down event handling

case inMenuBar:       // Process a mouse-down in menu bar (if any).
   AdjustMenus();
   menuResult = MenuSelect(event->where);
   if (!(gHasTextServices && TSMMenuSelect(menuResult)))
      DoMenuCommand(menuResult);
   HiliteMenu(0);     // Needed even if TSM or Script Manager handles
                      // the menu.
   break;

After calling our subroutine to adjust the menus so that the invalid items are dimmed, we pass the menu selection to the Text Services Manager. We call TSMMenuSelect with the menu result as a parameter. If the selection is in the pencil menu, the Text Services Manager will handle it for your application and return true; otherwise, it will return false and your application should handle the selection normally. Regardless of the results of TSMMenuSelect, your application needs to call HiliteMenu(0) to finish handling the selection.

The next step in handling a menu selection is to confirm the text in the active input area with a call to FixTSMDocument before performing the menu action. This is recommended in the current Macintosh Human Interface Guidelines. But this guideline is somewhat contested, so if you don't think it makes sense for your application, you might consider a more selective policy for automatically confirming inline input. Whatever you decide, remember to be consistent and try it out on real users.

Now that we've finished modifying our menu-handling code, we'll move on to modifying our window event handler, as shown in Listing 8. We need to make sure that the active TSM document is changed when necessary. Assuming that we're allocating one TSM document per user document or window, we also need to handle window activate and deactivate events differently. When we receive a deactivate event, we need to call DeactivateTSMDocument on the TSM document associated with that window. Similarly, we need to call ActivateTSMDocument when we receive an activate event for our window.


Listing 8. Handling window events

void DoActivate(WindowPtr window, Boolean becomingActive)
{
   RgnHandle      tempRgn, clipRgn;
   Rect           growRect;
   DocumentPeek   doc;
    
   if (IsDocumentWindow(window)) {
      doc = (DocumentPeek) window;
      if (becomingActive) {
         ...    // TextEdit-handling code
         if (doc->docTSMDoc != nil)
            (void) ActivateTSMDocument(doc->docTSMDoc);
      }
      else {
         if (doc->docTSMDoc != nil)
            (void) DeactivateTSMDocument(doc->docTSMDoc);
         ...    // TextEdit-handling code
      }
   }
}

If you use your own scheme for allocating TSM documents, be sure to call ActivateTSMDocument and DeactivateTSMDocument when appropriate. If you don't activate TSM documents correctly, the system can get confused and revert to Roman, in which case the user may click on a run of 2-byte text and not get inline input. If you're debugging your program and this happens, check to see what the current keyboard script is by calling

keyScript = GetScriptMgrVariable(keyScript);
If the keyboard script is smRoman, the problem could be related to activating and deactivating TSM documents.

We finish up our event loop modifications by ensuring that our application handles mouse-moved events in a way that works with the Text Services Manager and input methods. Input methods need to track mouse-moved events within the content area of your application, because when the user moves the cursor over an active input area, the input method needs to change the cursor's shape. This is demonstrated in the first half of Listing 9. Before attempting to change the cursor's appearance, your application should call SetTSMCursor, which returns a Boolean indicating whether the input method has already changed the cursor's appearance.


Listing 9. Allowing the input method to change the cursor

// Before we commit to anything, let's check whether some text
// service has a different idea.
if (!(gHasTextServices && SetTSMCursor(mouse))) {
   // Change the cursor and the region parameter.
   if (PtInRgn(mouse, iBeamRgn)) {
      SetCursor(*GetCursor(iBeamCursor));
      CopyRgn(iBeamRgn, region);
   }
   else {
      SetCursor(&qd.arrow);
      CopyRgn(arrowRgn, region);
   }
}
// No matter how nice the region, with text services it can't be
// bigger than a point. Yes, this defeats the purpose of all the
// calculations.
if (gHasTextServices)
   SetRectRgn(region, mouse.h, mouse.v, mouse.h, mouse.v);

Now we need to set up the correct mouseRgn parameter to WaitNextEvent. To work correctly with input methods, our application should create a mouseRgn that's no larger than a point, as shown in the second half of Listing 9. If we don't set up this region correctly, the input method may not be able to interact with our users as they would expect. Yes, being forced to use a point-sized region sort of defeats the purpose of a mouseRgn. We're working on extending the Text Services Manager so that you can obtain the region that the input method is interested in, but for now you'll just have to live with this limitation.

ADDING FONT-KEYBOARD SYNCHRONIZATION

Both TextEdit and WASTE automatically perform font-keyboard synchronization, meaning that they adjust the Keyboard menu so that the current keyboard layout or input method reflects the user's font selection. For instance, if the user selects Chicago or Courier (Roman fonts) from the Font menu, the application sets the current keyboard to a Roman keyboard layout (U.S., German, Italian, or whatever the default Roman keyboard is, according to the localizer). Similarly, if the user selects Osaka or HonMincho (Japanese fonts), the keyboard is synchronized with the font selection.

If you're not using TextEdit or WASTE, you can add the code in Listing 10 to provide font-keyboard synchronization. Using Toolbox routines, we determine the script of the font from the font ID. Then we call KeyScript, which changes the Keyboard menu settings to the default keyboard and input method combination for the new script.


Listing 10. Synchronizing the keyboard with the font selection

case mFont:
   GetMenuItemText(GetMenuHandle(mFont), menuItem, theFontName);
   GetFNum(theFontName, &theFontID);
   theTextStyle.tsFont = theFontID;
   TESetStyle(doFont, &theTextStyle, true, te);
   if ((*te)->selEnd - (*te)->selStart > 0)
      theDocument->modified = true;
   AdjustScrollbars(window, false);
   theScript = FontToScript(theFontID);
   KeyScript(theScript);
   break;

Font-keyboard synchronization is another one of those contested international human interface issues. Japanese users tend to be very divided on the issue, since this feature makes it difficult to set Roman characters to the Osaka font (the workaround is to select the Roman mode within the input method after selecting the Osaka font); however, users working in most other 2-byte languages like font-keyboard synchronization. Again, the best idea is to try out this interface feature on several real users and get their impressions.

OTHER USER INTERFACE ISSUES

When you make your application TSM-aware, you may need to work around a couple of other user interface issues. One of them is implementing passwords. When the user is typing a password, you don't want an input method to come up, so you should change the current keyboard layout to the Roman default. This is accomplished similarly to font-keyboard synchronization, using the following code:
KeyScript(smRoman);                   // Switch to Roman. 
KeyScript(smKeyDisableKybdSwitch);    // Lock out keyboard switching.
... // Do your password stuff here.
KeyScript(smKeyEnableKeyboards);
KeyScript(smKeySwapScript);

If you're writing game software, in play mode the user can't or shouldn't be manipulating input with an input method. In this case, you should also change the current keyboard layout to Roman, and you might want to lock out keyboard switching. In certain other modes (such as the high score list), users do need the services of an input method, so you should restore the original keyboard layout.

Also, even though most users prefer inline input, some actually don't like it, or they prefer the bottomline style of input in certain situations. For example, it's difficult to distinguish Kanji characters at point sizes smaller than 12, so when the user is working on a document in a small font, looking at the raw input in a larger font in the floating input window may be desirable. You should provide a preference mechanism so that the user can select either inline or bottomline input. This is implemented by calling the TSM function UseInputWindow and then passing it the document ID and a Boolean indicating whether the user wants inline input.

TESTING YOUR APPLICATION

Once you've made your application TSM-aware, you'll want to test it with a variety of input methods, since not all of them interpret the TSM protocol in quite the same way. If you're fortunate enough to have testers who speak Chinese, Japanese, or Korean, you've got it made. But if you don't have anyone around who speaks these languages, it's still possible to test your application in English with an input method. For some suggestions on how non-Asian language speakers can successfully test your code, see "Testing Two-Byte Script Support."


    TESTING TWO-BYTE SCRIPT SUPPORT

    BY GREG ANDERSON

    I know what you're thinking: "Great, now I can add support for 2-byte scripts to my application. But I can't read Japanese or Chinese characters, so how am I supposed to test my code to make sure it does the right thing with 2-byte characters?"

    Good news: You don't have to learn Japanese or Chinese. Instead, you can test your code using the 2-byte Roman input mode provided by Kotoeri. That's right -- I said "2-byte Roman."

    Most of the characters in the ASCII character set are replicated in one section of the Japanese 2-byte character set; you'll find the numbers and letters around $8250 to $829A. The 2-byte Roman characters look like their 1-byte counterparts, except that they're always monospaced and they take about twice as much space horizontally as the 1-byte characters do. These characters are very useful for testing 2-byte script support, because they look the same as the characters you use every day but they behave like other 2-byte characters such as Chinese and Japanese ideographs.

    Entering the 2-byte Roman characters is easy. After you've installed the Japanese Language Kit, choose Kotoeri from the Keyboard menu (shown earlier in Figure 3) and click the button labeled with the wide uppercase A in the operations palette, which will then look like this:

    Then just type Roman characters the same way you normally would. For example, if you want a Q, hold down the Shift key and press the Q key on the keyboard. You're now ready to edit 2-byte Roman characters in your application to make sure it handles them correctly. Here are some things to check:

    • Is the insertion point drawn in the right place, or are you using a Roman font to measure Japanese text?

    • If the insertion point is positioned after a 2-byte character and you press the Delete key, is the entire character deleted, or do you leave the first byte dangling in your document?

    • If you add a 2-byte uppercase B (which has an encoded value of $8261) to your document and use your Find command to search for a 1-byte lowercase a (which has an encoded value of $61), does your application find the 2-byte B? (It shouldn't, and it won't if you're using CharacterByteType correctly.)

    If these things work for you, you're well on your way to supporting 2-byte scripts in your application.


THE TSM PROTOCOL

Although TSMTE should provide enough support for most application developers who want to make their TextEdit-based applications TSM-aware, some developers may want or need to handle all the details themselves. This section provides a very brief overview of the TSM 1.0 protocol for the benefit of the latter.

The TSM protocol is based on the exchange of Apple events between the application and the input method, allowing them to share information about the active input area. The protocol consists of a suite of three required events (Position To Offset, Offset To Position, and Update Active Input Area) and an optional fourth event (Get Text).

THE POSITION TO OFFSET EVENT

The Position To Offset event is used to convert a global position into a meaningful offset in the document's text buffer. This event is sent from the input method to the application when the input method handles an event that occurs within the context of the application content. (Remember that you need to call TSMEvent to give the Text Services Manager a chance to handle mouse and keyboard events.)

The application must respond to this event by extracting a point on the screen from the Apple event and converting that point to an offset in the text of the particular document. In the TSM protocol, offsets are defined as long integers. The application returns the offset as a key in the reply event.

THE OFFSET TO POSITION EVENT

The Offset To Position event is sent by the input method when it needs to determine the global position of a particular document offset. When responding to this event, the application must return a global point corresponding to this offset. Optionally, the application can also return information about the typographical style and orientation (vertical vs. horizontal) of the text. Each of these elements is returned as a key in the reply event.

THE UPDATE ACTIVE INPUT AREA EVENT

The Update Active Input Area event is used by the input method to ask the application to update the active input area. The event contains an offset range array that breaks the updated text into particular ranges. The offset range is accompanied by a highlight range, which indicates how the application should highlight the particular range of text.

THE GET TEXT EVENT

Get Text is an optional event used only by Kotoeri, the Japanese input method, and some third-party Japanese input methods. It's documented in a March 1994 technical note by Takayuki Mizuno, available only in Japanese and roughly entitled "Kotoeri's Private Apple Event, Get Text." It's an extension to the TSM protocol developed by Apple as a way for an input method to retrieve text that has already been confirmed. Since most localized Japanese applications support this event, your users will probably expect it from your application as well.

TSMTE provides support for the Get Text event, so if you use the techniques in this article your application will be able to take advantage of this extension. The Get Text event is defined in Table 1. Listing 11 demonstrates how to handle the event directly.


Table 1. Definition of the Get Text event

Event classkTextServiceClass
Event IDkGetText (= 'gtxt')
Requested actionReturns the current selection as the return parameter
Keyword Descriptor type Data
Required parameters
keyDocumentRefcontypeLongIntegerStandard refCon parameter
keyAEServerInstancetypeComponentInstanceStandard component instance parameter
Optional parameters
keyAEBufferSize (= 'buff')typeLongInteger Maximum number of bytes the input method can receive
Return parameters
keyAETheDatatypeText The text specified by the current selection. The maximum byte length is specified by the keyAEBufferSize parameter. Any portion of the text that exceeds the buffer length shouldn't be returned.


Listing 11. Handling the Get Text event

pascal OSErr HandleGetText(const AppleEvent *theAppleEvent, 
                        const AppleEvent *reply, long handlerRefcon)
{
#pragma unused (handlerRefcon)

   OSErr               err;
   ComponentInstance   serverInstance;
   TSMTERecHandle      docRefcon;
   Handle              text;
   Size                textLen;
    
   // Identify event and get document refCon and server instance.
   err = IdentifyTSMCallback(theAppleEvent, &docRefcon, 
                              &serverInstance);
   if (err) return err;
    
   // Get selected text from document.
   HLock((Handle) docRefcon);
   err = DoGetText(*docRefcon, serverInstance, &text, &textLen);
   HUnlock((Handle) docRefcon);
   if (err) return err;
   // Add selected text as return parameter.
   HLock(text);
   err = AEPutParamPtr(reply, keyAETheData, typeText, (Ptr) *text,
                        textLen);
   DisposeHandle(text);

   return err;
}
OSErr DoGetText(TSMTERecPtr tsmteRecPtr, ComponentInstance 
                  serverInstance, Handle *text, Size *textLen)
                  
{
#pragma unused (serverInstance)

   TEHandle hTE;
   short      selStart, selEnd;
    
   hTE = tsmteRecPtr->textH;
   selStart = (*hTE)->selStart;
   selEnd  = (*hTE)->selEnd;
   *textLen = selEnd - selStart;
   *text = NewHandleClear(*textLen);
   HLock((Handle) *text);
   BlockMove((Ptr) (*((*hTE)->hText) + selStart), (Ptr) (**text),
               *textLen);
   HUnlock((Handle) *text);
   return noErr;
}

TSM NOW AND FOREVER

Although the changes required to make your TextEdit-based application TSM-aware are small, they dramatically overhaul the experience available to Asian language-speaking users. Try compiling our sample application both with and without the inline support flag set so that you can see the difference it makes to users. Even if you don't have plans to ship your application localized for Chinese, Japanese, or Korean, making it TSM-aware will please your users who have an Apple Language Kit installed and want to use your application with their non-English data.

Besides having an immediate payoff, the work you do to make your application TSM-aware will have a future payoff as well. While version 1.0 of the Text Services Manager offers support solely for keyboard input methods, future versions will be part of the framework for supporting handwriting and speech/dictation input methods as well as more general text services such as interactive spelling checkers and intelligent document scanners. By making your application TSM-aware now, you'll be poised to take advantage of the wide variety of services that will be available with TSM 2.0.


    RELATED READING

    • "Writing Localizable Applications" by Joseph Ternasky and Bryan K. "Beaker" Ressler, develop Issue 14.

    • Guide to Macintosh Software Localization by Apple Computer, Inc. (Addison-Wesley, 1992).

    • Inside Macintosh: Text by Apple Computer, Inc. (Addison-Wesley, 1993), Chapter 7, "Text Services Manager."

    • Technotes TE 27, "Inline Input for TextEdit With TSMTE," and OV 20, "Internationalization Checklist."

    • Understanding Japanese Information Processing by Ken Lunde (O'Reilly & Associates, 1993).

    • Writing Systems of the World by Akira Nakanishi (English edition, Charles E. Tuttle, 1980).

    • IDRIS scripts, http://idris.com/scripts/Scripts.html.


TAGUE GRIFFITH (tague@apple.com) has appeared on stage with several famous rock bands and would like to marry a rock star when he grows up. In his spare time, Tague works in Apple's Text and International Engineering group. If anyone out there knows Courtney Love's e-mail address, please send it to Tague.*

Thanks to our technical reviewers David Bice, Deborah Grits, Osman Güracar, John Harvey, and Yasuo Kida.*

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Challenge those pesky wyverns to a dance...
After recently having you do battle against your foes by wildly flailing Hello Kitty and friends at them, GungHo Online has whipped out another surprising collaboration for Puzzle & Dragons. It is now time to beat your opponents by cha-cha... | Read more »
Pack a magnifying glass and practice you...
Somehow it has already been a year since Torchlight: Infinite launched, and XD Games is celebrating by blending in what sounds like a truly fantastic new update. Fans of Cthulhu rejoice, as Whispering Mist brings some horror elements, and tests... | Read more »
Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »

Price Scanner via MacPrices.net

Free iPhone 15 plus Unlimited service for $60...
Boost Infinite, part of MVNO Boost Mobile using AT&T and T-Mobile’s networks, is offering a free 128GB iPhone 15 for $60 per month including their Unlimited service plan (30GB of premium data).... Read more
$300 off any new iPhone with service at Red P...
Red Pocket Mobile has new Apple iPhones on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide MVNO using all the major wireless carrier... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, available for $759 for 8-Core CPU/7-Core GPU/256GB models and $929 for 8-Core CPU/8-Core GPU/512GB models. Apple’s one-year warranty is... Read more
Updated Apple MacBook Price Trackers
Our Apple award-winning MacBook Price Trackers are continually updated with the latest information on prices, bundles, and availability for 16″ and 14″ MacBook Pros along with 13″ and 15″ MacBook... Read more
Every model of Apple’s 13-inch M3 MacBook Air...
Best Buy has Apple 13″ MacBook Airs with M3 CPUs in stock and on sale today for $100 off MSRP. Prices start at $999. Their prices are the lowest currently available for new 13″ M3 MacBook Airs among... Read more
Sunday Sale: Apple iPad Magic Keyboards for 1...
Walmart has Apple Magic Keyboards for 12.9″ iPad Pros, in Black, on sale for $150 off MSRP on their online store. Sale price for online orders only, in-store price may vary. Order online and choose... Read more
Apple Watch Ultra 2 now available at Apple fo...
Apple has, for the first time, begun offering Certified Refurbished Apple Watch Ultra 2 models in their online store for $679, or $120 off MSRP. Each Watch includes Apple’s standard one-year warranty... Read more
AT&T has the iPhone 14 on sale for only $...
AT&T has the 128GB Apple iPhone 14 available for only $5.99 per month for new and existing customers when you activate unlimited service and use AT&T’s 36 month installment plan. The fine... Read more
Amazon is offering a $100 discount on every M...
Amazon is offering a $100 instant discount on each configuration of Apple’s new 13″ M3 MacBook Air, in Midnight, this weekend. These are the lowest prices currently available for new 13″ M3 MacBook... Read more
You can save $300-$480 on a 14-inch M3 Pro/Ma...
Apple has 14″ M3 Pro and M3 Max MacBook Pros in stock today and available, Certified Refurbished, starting at $1699 and ranging up to $480 off MSRP. Each model features a new outer case, shipping is... Read more

Jobs Board

*Apple* Systems Administrator - JAMF - Activ...
…**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
IT Systems Engineer ( *Apple* Platforms) - S...
IT Systems Engineer ( Apple Platforms) at SpaceX Hawthorne, CA SpaceX was founded under the belief that a future where humanity is out exploring the stars is Read more
Nurse Anesthetist - *Apple* Hill Surgery Ce...
Nurse Anesthetist - Apple Hill Surgery Center Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Now Read more
Housekeeper, *Apple* Valley Village - Cassi...
Apple Valley Village Health Care Center, a senior care campus, is hiring a Part-Time Housekeeper to join our team! We will train you for this position! In this role, Read more
Sublease Associate Optometrist- *Apple* Val...
Sublease Associate Optometrist- Apple Valley, CA- Target Optical Date: Apr 20, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92307 **Requisition Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.