TweetFollow Us on Twitter

A Select Few

Volume Number: 16 (2000)
Issue Number: 10
Column Tag: Carbon Development

A Select Few...

by Daniel Jalkut

Taking advantage of Apple's standardized Type Selection API

Introduction

A feature frequently overlooked by new Mac OS users is the ability to select items from a list by typing part of its name. Apple calls this functionality "type selection." Seasoned Mac users are accustomed to type selection and navigate rapidly through Finder hierarchies and StandardFile dialogs without ever letting their hands leave the keyboard. When a user discovers this feature, it is probably with some disappointment that they find support for type selection varies widely amongst third-party applications. Every application that uses StandardFile and Navigation Services dialogs gets type selection for free in those portions of their program. For custom application dialogs and views, developers have been required to write their own, custom type selection code. The result is that some applications don't support type selection at all, some support it to varying extents, and almost none support it in a way that mimics every last nuance of Apple's own type selection algorithm. The opportunity to end these inconsistencies is before us, for Apple's Type Selection APIs have made their public debut.

Without fanfare, Apple included their time-tested internal Type Selection APIs in the first public releases of the CarbonLib extension for Mac OS. As of this writing, the latest version is CarbonLib 1.0.4, and it is available from Apple's web site at http://asu.info.apple.com/swupdates.nsf/artnum/n11673. CarbonLib is the library that enables applications ported to Mac OS X's Carbon APIs to continue running on Mac OS 9 and earlier (as early as 8.1) systems. There is no shortage of compelling reasons to port your application to Carbon. Apple's decision to support two platforms with a nearly identical API set provides an irresistibly easy way of adding Mac OS X to the list of your supported platforms, without losing your existing customer base. Access to standardized type selection is by no means the greatest benefit of porting to Carbon, but it has the potential of causing a vast improvement in the user's experience on the Mac.

What's The Big Deal?

You might be wondering why something as seemingly straightforward as selecting list items with the keyboard should require a standardized API. The user hits a key, you select a matching item, and we're done - right? Actually, the apparent simplicity of type selection is a testament to its internal complexity. You may not have even considered much of the functionality Apple's TypeSelect APIs provide unless you have spent a great deal of time focusing only on the dynamics of type selection. Most developers do not have the time to become type selection experts, so they implement something that makes sense to them, yet lacks the subtle elegance of Apple's code. Some of the features of Apple's API are:

International Support

Support for international scripts, which Apple has always promoted, is becoming more and more important as the customer base for Apple outside of the United States continues to grow. As a developer, anything that provides international support for free is a big win for your product, because it is one fewer thing you need to worry about when pushing to make a localized release available. Apple's type selection APIs use script-aware string comparison functions, which in turn guarantee that every selection made by the user causes a match as expected for the appropriate script.

Multi-Character Matching

A major shortcoming of some third-party implementations of type selection is that only the last key pressed is ever used as a criterion for matching against items. If you type characters in a Finder view, you will notice that the active selection changes as the characters you type grow into a more specific match with the item you're seeking. For instance, if there are three icons in a view, "Belize", "Biafra", and "Burundi", typing just "B" will select the first, "Bi" the second, and "Bu" the third. In an implementation that didn't support multi-character matching, the user would be forced to type "B" to locate the first item, and then navigate with arrow keys to the desired item. No amount of typing would select either "Biafra," or "Burundi" without moving a hand from the keyboard to the mouse.

Time-Out and Cancellation

One unexpectedly complicated aspect of a proper type selection implementation is elegantly guessing the train of thought a user has embarked on: knowing when to stop one selection and begin another. This task is impossible to do correctly every time, but Apple comes close with comfortable and consistent behavior that gives the user a great deal of control.

The type selection APIs keep a running tab of the keys that have been pressed, and when no keys have been pressed for a duration of time, that buffer is flushed, with the assumption that the user has finished typing the fragment they were seeking. To accommodate the user who knows they have mistyped, or who has quickly changed their mind, the APIs also respond to the escape key, which forces type selection to clear its buffers and begin matching keys as a new string.

Implementing Type Selection In A List

To demonstrate the use of the type selection APIs, I have included with this article a sample application, "ListSample", which simply displays a list of selectable items in a dialog. The applications responds to keyDown events by passing them to the Type Selection API, "TypeSelectNewKey", which determines whether the key pressed justifies searching for a new match. If it does, another API, "TypeSelectFindItem" is called, which uses the state of the key buffer and a list of items provided by the client to determine the most appropriate selection.

Application Prerequisites

Before I could implement type selection in ListSample, I first had to ensure that the application had been carbonized and compiled with Apple's latest Universal Headers. If you have already carbonized your application, then you're ready to go. If you haven't, then you will need to do so before you can call any of the Type Selection APIs described in this article.

ListSample Implementation

The easiest way to demonstrate the use of the Type Selection APIs is to explain the three pieces of code in ListSample which interact with the APIs:

  • A TypeSelectRecord is initialized before any type selection can occur.
  • Key down events are passed to Type Selection, and a new match is found if appropriate.
  • A callback routine for finding matches is implemented.

First, initialize a TypeSelectRecord. This is done by calling TypeSelectClear() with an empty TypeSelectRecord as its parameter. In ListSample, this is done in main()

Listing 1: main

Main

Setup the menu bar, initialize TypeSelection, create our sample dialog and run the event loop until we are quit.

main()
{   
   // Setup the menu bar
   SetMenuBar(GetNewMBar(rAppMenuBarID));
   DrawMenuBar();
   
   // Initialize the type selection record
   TypeSelectClear(&gTypeSelectState);
   
   // Prepare the sample dialog
   gTheDialog = SetupSampleDialog();
   if (gTheDialog != NULL)
   {
      // Handle Events!
      while (gQuitApplication == false)
      {
         ApplicationEventLoop();
      }
      DisposeDialog(gTheDialog);
   }
   
   ExitToShell();
   return 0;
}


Next, for each keyDown event that pertains to the list, ask if a new item needs to be matched. This is done by calling TypeSelectNewKey() - if it returns true, then it is time to find a new match for the list by calling TypeSelectFindItem. I have implemented this functionality in the ListSample routine that handles all keyDown events:

Listing 2: HandleKeyDownEvent

HandleKeyDownEvent

Handle key down events not handled by the Dialog Manager. If the key is not a menu-shortcut, then ask the TypeSelection APIs whether the key might change the state of the current selection, and if so, ask it to determine the new selection

void HandleKeyDownEvent(EventRecord* theEvent)
{
   Boolean               eventHandled = false;
   static IndexToStringUPP   myStringGetter = NULL;
   
   // Handle the cmd-key menu case first
   if(theEvent->modifiers & cmdKey)
   {
      long   menuKeyResult;
      
      menuKeyResult = MenuKey(theEvent->message &charCodeMask);
   DoMenuSelection(HiWord(menuKeyResult),
                           LoWord(menuKeyResult));
   }                        
   else if (gTheDialogList != NULL)
   {
      // Only allocate the IndexToStringUPP once
      if (myStringGetter == NULL)
      {
         // This call-back function is used by Type Selection to
         // fetch elements of the list the user is navigating.
         myStringGetter =
                        NewIndexToStringUPP(GetStringFromIndex);      
      }      

      // Ask Type Select APIs whether we need to re-check
      // the selection
      if (TypeSelectNewKey(theEvent, &gTypeSelectState))
      {
         short            listCount;
         short            newListIndex = 0;
         Cell               newSelection;
         
         // How many items are we dealing with?
         listCount = (**gTheDialogList).dataBounds.bottom;
         
         // Ask Type Select for a new index, based
         // on the current state of typing
         newListIndex = TypeSelectFindItem(&gTypeSelectState,
                                       listCount, tsNormalSelectMode,
                                       myStringGetter, NULL);
         
         // Unset any selected items before choosing
         // a new selection
         SetPt(&newSelection, 0, 0);                              
         // Starting at the beginning, get a selected cell
      while (LGetSelect(true, &newSelection, gTheDialogList))
         {
            // Unselect it
            LSetSelect(false, newSelection, gTheDialogList);
         }
         
         // Set the new item to selected
         SetPt(&newSelection, 0, newListIndex - 1);
         LSetSelect(true, newSelection, gTheDialogList);      
         
         // Auto scroll to make sure the selection is visible
         LAutoScroll(gTheDialogList);
         
         // Workaround to List Box control behavior - LsetSelect
         // causes an immediate redraw into the list's port,
         // which in the ListBox control case is an offscreen
         // buffer allocated by the control manager, and doesn't
         // cause an update event to be generated for the parent
         // window, so we need to force a redraw.
         DrawOneDialogItem(gTheDialog, rDialogListBoxItem);         
      }
   }
}



One of the parameters to the TypeSelectFindItem call is a callback routine that you provide to allow TypeSelection to determine the elements of the list it is choosing from. Depending on the circumstances of your implementation, the items you are selecting from might not be a straightforward list. In our case the routine is quite simple. It simply looks up the desired index in the list control we are searching in:

Listing 3: GetStringFromIndex

GetStringFromIndex

Callback routine for the type selection routine TypeSelectFindItem(). This routine is called to fetch the items in a list, and determines which of the items is the best choice considering the key strokes that have been pressed.

pascal Boolean
GetStringFromIndex(short theIndex, ScriptCode *whichScript,
                           StringPtr *whichString, void *ignored)
{
#pragma unused (ignored)
   Boolean            returnValue;
   static Str255   thisString;      // static because we return a
                                       // pointer to this string
   
   // Set the script - in this sample, we know that
   // all the list items are in Roman script.
   *whichScript = smRoman;
   
   if (gTheDialogList != NULL)
   {
      Cell   desiredCell;
      short   stringLength = 255;
      
      // Fetch the item from the list, 

      // and get the cell data (a string) into the result

      SetPt(&desiredCell, 0, theIndex - 1);      
      LGetCell(thisString + 1, &stringLength, desiredCell, 
                     gTheDialogList);
      thisString[0] = stringLength;
      *whichString = thisString;
      returnValue = true;

   }
   else
   {
      *whichString = NULL;
      returnValue = false;   
   }
   
   return returnValue;
}

Summary

Type selection is certainly not the top selling point of Carbon. The APIs I have described would have been appreciated if they were publicized years ago in the classic Mac OS arena, but at least they have finally arrived in Carbon. I hope this article has demonstrated that it's not difficult at all to implement this functionality, and that the benefits to the customer are overwhelming. I hope to see all of your applications sporting fancy new type selection capabilities in their next releases. Happy typing!

Acknowledgements

Thanks to Darren Litzinger for reviewing this article.


The type selection APIs consist of only four routines and one callback routine. Here is a short description of their functionality:

TypeSelectClear

Used to initialize a TypeSelectRecord, which is the data structure that holds the state of type selection at any given time. Call this routine at least once, when your application is starting up. After you launch, only call this routine if you want to intentionally void the typing the user has made at a given point.

void         TypeSelectClear(TypeSelectRecord *tsr);

tsr   Points to a TypeSelectRecord, which requires initialization.

TypeSelectNewKey

Every time your application receives a keyDown event that might pertain to selection of a list item, you should pass the event to this routine. It examines the current buffer of characters and the value of the key event it is receiving, and returns true if the new keystroke has warranted the need to update the active list selection.

Boolean   TypeSelectNewKey(const EventRecord *theEvent,
                                    TypeSelectRecord *tsr);

theEvent   A pointer to an event record containing a keyDown event. 

tsr   Points to a TypeSelectRecord previously initialized with TypeSelectClear.

TypeSelectFindItem

When TypeSelectNewKey returns true, use this routine to actually determine the most appropriate list item to receive the new selection. This routine takes as a parameter a callback function that you use to supply the algorithm with the contents of your list.

short       TypeSelectFindItem(const TypeSelectRecord *tsr,
                                    short listSize, TSCode selectMode,
                                    IndexToStringUPP getStringProc,
                                    void *yourDataPtr);

tsr   Points to a TypeSelectRecord previously initialized with TypeSelectClear.

listSize The number of items in the list you are selecting from. If the number of items is unknown, pass 0x7FFF, and be sure that your callback function returns false when a requested index is not found.

selectMode Specify one of "tsPreviousSelectMode", "tsNormalSelectMode", or "tsNextSelectMode" to request the item before the matched item, the matched item itself, or the item after the matched item. The previous and next modes are what the Finder uses to respond to the Tab and Cmd-Tab keys. Typically you will pass tsNormalSelectMode.

getStringProc   Pass a UPP to a routine that will fetch strings by index from
the list of items you are selecting from.

yourDataPtr   Pass any value you would like to have passed back to your callback
function.

TypeSelectCompare

TypeSelectCompare is used to compare specific items from a list, using the exact same comparison that TypeSelect would use in a call to TypeSelectFindItem. This is useful if you have a sorted list, and want to optimize item selection based on knowledge about the sorting in the list. TypeSelectFindItem compares each and every item in a list, so for very long lists pre-sorting and using TypeSelectCompare might result in a performance gain.

short       TypeSelectCompare(const TypeSelectRecord * tsr,
                                    ScriptCode testStringScript,
                                    StringPtr testStringPtr);      

tsr   Points to a TypeSelectRecord previously initialized with TypeSelectClear.

testStringScript   Indicates the script of the string that is being compared.

testStringPtr   Points to the string that is being compared.

MyIndexToStringProc

This routine is defined by your code, and serves as an access point for TypeSelection to determine the items of the list that you are selecting from. When called, you will need to fetch and return a string from your list that is indexed by the given item number. You must return both a pointer to the string and the script code for that item.

Boolean   MyIndexToStringProc(short item, 
                                    ScriptCode *itemsScript,
                                    StringPtr *itemsStringPtr,
                                    void *yourDataPtr);

item   The index of the list item being requested.

itemsScript   You return the script of the requested string here.

itemsStringPtr   You return a pointer to the requested string here.

yourDataPtr   A reference pointer for whatever you choose.

Daniel Jalkut is a software engineer in the Mac OS X Carbon API group at Apple Computer. In his spare time, Daniel works on his guitar playing and book reading. You can contact him at jalkut@red-sweater.com, or view his home page at http://www.red-sweater.com.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Typinator 9.1 - Speedy and reliable text...
Typinator turbo-charges your typing productivity. Type a little. Typinator does the rest. We've all faced projects that require repetitive typing tasks. With Typinator, you can store commonly used... Read more
ESET Cyber Security 6.11.414.0 - Basic i...
ESET Cyber Security provides powerful protection against phishing, viruses, worms, and spyware. Offering similar functionality to ESET NOD32 Antivirus for Windows, ESET Cyber Security for Mac allows... Read more
Opera 105.0.4970.29 - High-performance W...
Opera is a fast and secure browser trusted by millions of users. With the intuitive interface, Speed Dial and visual bookmarks for organizing favorite sites, news feature with fresh, relevant content... Read more
Slack 4.35.131 - Collaborative communica...
Slack brings team communication and collaboration into one place so you can get more work done, whether you belong to a large enterprise or a small business. Check off your to-do list and move your... Read more
Viber 21.5.0 - Send messages and make fr...
Viber lets you send free messages and make free calls to other Viber users, on any device and network, in any country! Viber syncs your contacts, messages and call history with your mobile device, so... Read more
Hazel 5.3 - Create rules for organizing...
Hazel is your personal housekeeper, organizing and cleaning folders based on rules you define. Hazel can also manage your trash and uninstall your applications. Organize your files using a familiar... Read more
Duet 3.15.0.0 - Use your iPad as an exte...
Duet is the first app that allows you to use your iDevice as an extra display for your Mac using the Lightning or 30-pin cable. Note: This app requires a iOS companion app. Release notes were... Read more
DiskCatalogMaker 9.0.3 - Catalog your di...
DiskCatalogMaker is a simple disk management tool which catalogs disks. Simple, light-weight, and fast Finder-like intuitive look and feel Super-fast search algorithm Can compress catalog data for... Read more
Maintenance 3.1.2 - System maintenance u...
Maintenance is a system maintenance and cleaning utility. It allows you to run miscellaneous tasks of system maintenance: Check the the structure of the disk Repair permissions Run periodic scripts... Read more
Final Cut Pro 10.7 - Professional video...
Redesigned from the ground up, Final Cut Pro combines revolutionary video editing with a powerful media organization and incredible performance to let you create at the speed of thought.... Read more

Latest Forum Discussions

See All

‘Sonic Dream Team’ Apple Arcade Review –...
What an unusual day we have arrived upon today. Now, Sonic the Hedgehog games aren’t a new thing for iOS gaming. The original Sonic the Hedgehog appeared on the classic iPod, so the Blue Blur got in the doors as fast as you would expect him to. The... | Read more »
PvP Basketball Game ‘NBA Infinite’ Annou...
Level Infinite and Lightspeed Studios just announced a new real-time PvP basketball game for mobile in the form of NBA Infinite (). NBA Infinite includes solo modes as well, collecting and upgrading current NBA players, managing teams, and more. It... | Read more »
New ‘Dysmantle’ iOS Update Adds Co-Op Mo...
We recently had a major update hit mobile for the open world survival and crafting adventure game Dysmantle ($4.99) from 10tons Ltd. Dysmantle was one of our favorite games of 2022, and with all of its paid DLC and updates, it is even better. | Read more »
PUBG Mobile pulls a marketing blinder wi...
Over the years, there have been a lot of different marketing gimmicks tried by companies and ambassadors, some of them land like Snoop Dog and his recent smoking misdirection, and some are just rather frustrating, let’s no lie. Tencent, however,... | Read more »
‘Goat Simulator 3’ Mobile Now Available...
Coffee Stain Publishing and Coffee Stain Malmo, the new mobile publishing studio have just released Goat Simulator 3 on iOS and Android as a premium release. Goat Simulator 3 debuted on PS5, Xbox Series X|S, and PC platforms. This is the second... | Read more »
‘Mini Motorways’ Huge Aurora Borealis Up...
Mini Motorways on Apple Arcade, Nintendo Switch, and Steam has gotten a huge update today with the Aurora Borealis patch bringing in Reykjavik, new achievements, challenges, iCloud improvements on Apple Arcade, and more. Mini Motorways remains one... | Read more »
Fan-Favorite Action RPG ‘Death’s Door’ i...
Last month Netflix revealed during their big Geeked Week event a number of new titles that would be heading to their Netflix Games service. Among them was Acid Nerve and Devolver Digital’s critically acclaimed action RPG Death’s Door, and without... | Read more »
SwitchArcade Round-Up: Reviews Featuring...
Hello gentle reader, and welcome to the SwitchArcade Round-Up for December 4th, 2023. I’ve been catching up on my work as much as possible lately, and that translates to a whopping six reviews for you to read today. The list includes Astlibra... | Read more »
‘Hi-Fi Rush’ Anniversary Interview: Dire...
Back in January, Tango Gameworks and Bethesda released one of my favorite games of all time with Hi-Fi Rush. As someone who adores character action and rhythm games, blending both together seemed like a perfect fit for my taste, but Hi-Fi Rush did... | Read more »
Best iPhone Game Updates: ‘Pizza Hero’,...
Hello everyone, and welcome to the week! It’s time once again for our look back at the noteworthy updates of the last seven days. Things are starting to chill out for the year, but we still have plenty of holiday updates ahead of us I’m sure. Some... | Read more »

Price Scanner via MacPrices.net

Apple is clearing out last year’s M1-powered...
Apple has Certified Refurbished 11″ M1 iPad Pros available starting at $639 and ranging up to $310 off Apple’s original MSRP. Each iPad Pro comes with Apple’s standard one-year warranty, features a... Read more
Save $50 on these HomePods available today at...
Apple has Certified Refurbished White and Midnight HomePods available for $249, Certified Refurbished. That’s $50 off MSRP and the lowest price currently available for a full-size Apple HomePod this... Read more
New 16-inch M3 Pro MacBook Pros are on sale f...
Holiday MacBook deals are live at B&H Photo. Apple 16″ MacBook Pros with M3 Pro CPUs are in stock and on sale for $200-$250 off MSRP. Their prices are among the lowest currently available for... Read more
Christmas Deal Alert! Apple AirPods Pro with...
Walmart has Apple’s 2023 AirPods Pro with USB-C in stock and on sale for $189.99 on their online store as part of their Holiday sale. Their price is $60 off MSRP, and it’s currently the lowest price... Read more
Apple has Certified Refurbished iPhone 12 Pro...
Apple has unlocked Certified Refurbished iPhone 12 Pro models in stock starting at $589 and ranging up to $350 off original MSRP. Apple includes a standard one-year warranty and new outer shell with... Read more
Holiday Sale: Take $50 off every 10th-generat...
Amazon has Apple’s 10th-generation iPads on sale for $50 off MSRP, starting at $399, as part of their Holiday Sale. Their discount applies to all models and all colors. With the discount, Amazon’s... Read more
The latest Mac mini Holiday sales, get one to...
Apple retailers are offering Apple’s M2 Mac minis for $100 off MSRP as part of their Holiday sales. Prices start at only $499. Here are the lowest prices available: (1): Amazon has Apple’s M2-powered... Read more
Save $300 on a 24-inch iMac with these Certif...
With the recent introduction of new M3-powered 24″ iMacs, Apple dropped prices on clearance M1 iMacs in their Certified Refurbished store. Models are available starting at $1049 and range up to $300... Read more
Apple M1-powered iPad Airs are back on Holida...
Amazon has 10.9″ M1 WiFi iPad Airs back on Holiday sale for $100 off Apple’s MSRP, with prices starting at $499. Each includes free shipping. Their prices are the lowest available among the Apple... Read more
Sunday Sale: Apple 14-inch M3 MacBook Pro on...
B&H Photo has new 14″ M3 MacBook Pros, in Space Gray, on Holiday sale for $150 off MSRP, only $1449. B&H offers free 1-2 day delivery to most US addresses: – 14″ 8-Core M3 MacBook Pro (8GB... Read more

Jobs Board

Mobile Platform Engineer ( *Apple* /AirWatch)...
…systems, installing and maintaining certificates, navigating multiple network segments and Apple /IOS devices, Mobile Device Management systems such as AirWatch, and Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Senior Product Manager - *Apple* - DISH Net...
…Responsibilities** We are seeking an ambitious, data-driven thinker to assist the Apple Product Development team as our Wireless Product division continues to grow Read more
Senior Product Manager - *Apple* - DISH Net...
…Responsibilities** We are seeking an ambitious, data-driven thinker to assist the Apple Product Development team as our Wireless Product division continues to grow Read more
Senior Software Engineer - *Apple* Fundamen...
…center of Microsoft's efforts to empower our users to do more. The Apple Fundamentals team focused on defining and improving the end-to-end developer experience in Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.