TweetFollow Us on Twitter

Contextual Menu Modules

Volume Number: 14 (1998)
Issue Number: 2
Column Tag: Toolbox Techniques

Contextual Menu Modules

by Steve Sheets

By creating Contextual Menu Extension Modules for Mac OS 8, you can extend the behavior of all applications

A New type of Extension

The Contextual Menu (CM) Manager was introduced with Mac OS 8.0. Using this manager, developers now can provide contextual menus in their programs. When a user clicks some data while holding the control key down, a list of data specific commands can appear. For example, the Finder uses this API to display various commands (Open, Move to Trash, Get Info, Duplicate) when files & folders are control-clicked. As mentioned in last month's article, the commands that are listed come from two sources; the program and CM extension modules. This month's article will demonstrate how to create the example plug-in "CaseCharm".

At any point in a program that uses the CM API where "ContextualMenuSelect" is called, the programmer can provide 2 important pieces of information. First, he can provide the list of the commands to be displayed. Secondly, he can provide the data that is currently selected. CM extensions, also known as plug-ins, can use this data to decide what additional commands also can be added to the menu. If the user selects one of the commands that comes from the plug-in instead of from the program, the plug-in takes care of that action. The calling program is not aware that anything was selected; it just functions as if the user failed to select an item from the menu. Except for being able to look at the selected data, the plug-ins are transparent to the calling program, and the calling program is not aware of the plug-ins.

So what are CM extensions? They are PowerPC code fragments (that is, shared libraries) using the SOM Object model. Because of this, plug-ins can only run on PowerPC Macintoshes. The API is standard on Mac OS 8.0 computers, but it also can be installed on System 7.x machines. Do not assume that all the Mac OS 8.0 APIs are available for a contextual menu. Though uncommon, contextual menus can be used on a pre-8.0 machine.

CM plug-in files must have the file type of 'cmpi'. If they have the creator of 'cmnu', they will display the generic CM icon. The extension file must reside in the "Contextual Menu Items" folder in the System Folder. On Mac OS 8.0, dragging the extension onto the System Folder will automatically move the file to the correct location. On pre-Mac OS 8.0, you must drag the files manually. Once they are in the correct location, the System must be rebooted for the plug-in to be installed.

Being SOM objects, CM plug-ins are object-oriented subclasses of the super class AbstractCMPlugin. AbstractCMPlugin object has several calls that must be overridden for the extension to work. While you can create CM plug-ins that do not look at the selected data, but just provide some function, this is not a recommended. There are plenty of other way to provide commands in the Macintosh. Apple recommends that third party developers work on CM plug-ins that are data sensitive.

While the Contextual Menu Manager passes the selected data to the plug-in, the plug-in can not modify this data. At most, it can make a copy of the data, modify the copy, and then return the data by placing it in the Clipboard. The example project CaseCharm, discussed below, takes text data and shifts it to either upper or lower case. For example, in a word processor, a user would select a word, control-click that word, select the CaseCharm command, then paste the content of the Clipboard (the word all in upper or lower case) back into the word processor. This passing of modified data using the Clipboard is a common function of CM plug-ins.

Creating a Module Project

While a CM project can be created from scratch, the easiest way to set up one is to take an existing CM sample project and modify it for your needs. It is to easy to incorrectly set one of the required preferences in the project. In the CM Manager SDK (available from Developer CDs and websites), Apple provides a good sample program to start with. The example used in this article is based on Apple's sample code.

You must change a couple of project preferences when creating a new CM plug-in. For this project you must first change the PPC Target file name to "CaseCharm". Next, the PPC Linker Entry Point for Initialization must be changed to a new name. While it can be anything, the common usage is XXXInitialize, XXX is the name of the CM plug-in, in this case "CaseCharmInitialize".

Then you must modify the source files to match the name changes. The .cp, .h & .r files should be renamed to match the CM plug-in name. In this case, "CaseCharm.h", CaseCharm.cp" and "CaseCharm.r". Following sections will explain the changes within these files. In addition to these source files, the sample project includes the "UContextualMenuTools.cp" file. This code provides many useful routines to handle functions common to all CM plug-ins.

Additionally, various libraries must be linked in the project. These include AbstractCMPlugin, InterfaceLib, MSL RuntimePPC.Lib and SOMObjects(tm) for Mac OS. The file names for some of these libraries may be different for different development environments. The AppearanceLib library is required only if the CM plug-in uses the Appearance Manager.

Once all the changes are done, compile and link the code. As mentioned above, the resulting shared library must be dragged to the "Contextual Menu Items" folder and the Mac rebooted to test the code.

Adding Resources

A CM plug-in does not require much in the area of resources. Like all code fragments, the 'cfrg' resource is required. The fields containing the code fragment name & help information (in this case the strings "CaseCharm" and "A Contextual Menu Plugin to change the case of selected text") should be changed. It is always a good idea to include version information ('vers' resources) with any code.

Listing 1

CaseCharm.r
Typical resources for a Contextual Menu plug-in, in this case CaseCharm.

#define UseExtendedCFRGTemplate 1

#include "SysTypes.r"
#include "CodeFragmentTypes.r"

/*  Code Fragment Info */
resource 'cfrg' (0) {
  {  /* array memberArray: 4 elements */
    extendedEntry {
      kPowerPC,
      kFullLib,
      kNoVersionNum,
      kNoVersionNum,
      kDefaultStackSize,
      kNoAppSubFolder,
      kIsLib,
      kOnDiskFlat,
      kZeroOffset,
      kSegIDZero,
      "CaseCharm",           /* Changed for each Plug-In */
      kFragSOMClassLibrary,
      "AbstractCMPlugin",
      "",
      "",
      "A Contextual Menu Plugin to change the case of "
        "selected text."      /* Changed for each Plug-In */
    }
  }
};

/*  Version Info  */
resource 'vers' (1) {
  1, 0, final, 0, verUS, "1.0", "Mageware, 1997"
};

resource 'vers' (2) {
  1, 0, final, 0, verUS, "1.0", "CaseCharm 1.0"
};

Methods and Methods

As will be stated over and over, a CM plug-in is a SOM object. In this example, CaseCharm is a subclass of class AbstractCMPlugin. This new class overrides 4 methods of AbstractCMPlugin. CaseCharm also requires one special initialization routine for SOM to identify it. All CM plug-ins override these 4 methods (and provide an Initialization routine), so the header code is generally identical. Listing 2 shows the CaseCharm.h file. The name of the new subclass of AbstractCMPlugin must match the name given in the resource file (in this case, "CaseCharm").

Listing 2

CaseCharm.h
Typical header file for a Contextual Menu plug-in, in this case CaseCharm.

#pragma once
#include <AbstractCMPlugin.h>

class CaseCharm : virtual AbstractCMPlugin {

#pragma SOMReleaseOrder (Initialize, ExamineContext, HandleSelection, PostMenuCleanup)

public:

  virtual  OSStatus Initialize(Environment* ev,
            FSSpec *inFileSpec);
            
  virtual  OSStatus ExamineContext(Environment* ev,
            AEDesc *inContextDescriptor,
            SInt32 inTimeOutInTicks,
            AEDescList* ioCommands,
            Boolean* outNeedMoreTime);
            
  virtual  OSStatus HandleSelection(Environment* ev,
            AEDesc *inContextDescriptor,
            SInt32 inCommandID);
            
  virtual  OSStatus PostMenuCleanup(Environment* ev);
};

The four methods shown here are called by the CM API when needed. Initialize() handles the initialization of the current CM. This is different from the SOM Initialization routine (defined below). It is easy to confuse the two calls. When the CM Manager is started up with the computer, a single instance of all CM extension objects is created. At that time, the Initialize() call is made. This may be before other Managers have started, so generally nothing is done in this routine. The other 3 routines are all called when a contextual menu is being created for display. The ExamineContext() method is where the plug-in adds menu items to the popup menu. This is the first time the code can examine the selected data (if any). Based on the data, the method can put any number of menu items or sub menus into the current contextual menu. The HandleSelection() call is only called if a given menu item has been selected by the user. It can then handle the actual function selected. The PostMenuCleanUp() call is the routine to be called after the current contextual menu has been handled, regardless to who handled it. The first parameter for all four of the routines is a code fragment environment parameter and is rarely used.

SOM Fragment Initializing

Being a SOM object, CaseCharm is required to have an initialization routine. The name of this routine can be anything, but it must match the name entered in the Linker preferences (CaseCharmInitialize in this case). Listing 3 shows the section of the code dealing with this call. The function calls the SOM __initialize() routine, and defines the new class. Except for the name, there is no reason to modify this code.

Listing 3

CaseCharm.cp
SOM Fragment Initializer

//  Includes
#include <CodeFragments.h>
#include <som.xh>

//  External Functions
extern pascal OSErr __initialize(CFragInitBlockPtr);

//  Initializer
pascal OSErr CaseCharmInitialize(CFragInitBlockPtr init)
{
#pragma unused (init)

  OSErr theError = __initialize(init);
  
  if (theError == noErr)
    somNewClass(CaseCharm);

  return theError;
  
}

Setup and Shutdown

The Initialize() routine is called at startup of the current instance of a contextual menu, and is surprisingly useless. Memory and resources should be allocated when the contextual menu is being used and not kept around all the times. The PostMenuCleanUp() call can take care of anything started by ExamineContext(). Remember the HandleSelection() method may not be called if the user didn't select an items. It is more common, as in the CaseCharm example, for them to do nothing but return a noErr result.

Listing 4

CaseCharm.cp
Setup & Shutdown

//  CM Initialize
OSStatus CaseCharm::Initialize(Environment* ev,
            FSSpec* inFileSpec)
{
#pragma unused (ev, inFileSpec)

  return noErr;
}

//  CM Clean Up
OSStatus CaseCharm::PostMenuCleanup(Environment* ev)
{
#pragma unused (ev)

  return noErr;
}

Coercing Text Data

Both the ExamineContext() and the HandleSelection() call are passed inContextDescriptor, a pointer to an AEDesc. This descriptor contains the selected data that the user control-clicked. This CM extension can use this descriptor to examine the selected data. UContextualMenuTools has a few routines that provide commonly used requests. Listing 5 show G_ContextualMenuTools_Has_Text_Data() (which checks if text data has been passed), G_ContextualMenuTools_Get_Text_Hdl() (which returns a copy of text passed as handle) and G_ContextualMenuTools_Get_Text_Str() (which returns a copy of text passed as string).

Since AECoerceDesc is being used, the Apple Event Manager tries very hard to return text data, even if the original data was not exactly text. For example, control-clicking a file from the finder would pass a file reference. However, AECoerceDesc would successfully convert the name of the file into text, and return that text. This is not dangerous, just something that should be noted.

Listing 5

UContextualMenuTools.cp
Handling Text Data

//  Descriptor has text data stored in it
Boolean G_ContextualMenuTools_Has_Text_Data
          (AEDesc* p_context_descriptor_ptr)
{
  Boolean a_flag = false;
  
  AEDesc a_text_desc = { typeNull, NULL };
  if (AECoerceDesc(p_context_descriptor_ptr, typeChar, 
        &a_text_desc) == noErr)
    if (a_text_desc.descriptorType==typeChar) 
      if (a_text_desc.dataHandle) {
        long a_size = GetHandleSize(a_text_desc.dataHandle);
        
        a_flag = (a_size>0);
      }

  AEDisposeDesc(&a_text_desc);
  
  return a_flag;
}

//  Get Text Data from descriptor as handle
Handle G_ContextualMenuTools_Get_Text_Hdl
          (AEDesc* p_context_descriptor_ptr)
{
  AEDesc a_text_desc = { typeNull, NULL };
  if (AECoerceDesc(p_context_descriptor_ptr, typeChar, 
        &a_text_desc) == noErr)
    if (a_text_desc.descriptorType==typeChar)
      return a_text_desc.dataHandle;

  AEDisposeDesc(&a_text_desc);
    
  return NULL;
}

//  Get Text Data from descriptor as string
void G_ContextualMenuTools_Get_Text_Str
        (AEDesc* p_context_descriptor_ptr, Str255 p_str)
{
  p_str[0] = 0;
  
  Handle a_handle = G_ContextualMenuTools_Get_Text_Hdl(p_context_descriptor_ptr);
  if (a_handle) {
    HLock(a_handle);
    
    short a_len = GetHandleSize(a_handle);
    if (a_len>255)
      a_len = 255;
      
    BlockMove(*a_handle, &p_str[1], a_len);
    p_str[0] = a_len;

    HUnlock(a_handle);
    DisposeHandle(a_handle);
  }
}

Adding Menus & Submenus

Once the CM plug-in has identified that it was passed data that it can use, the code must add items and submenus to the contextual menu being created. Not surprisingly, the CM Manager uses ioCommands (an AEDesc) to do this. Each menu item requires a string and a ID number. The ID number is strictly unique for this CM plug-in. The CM Manager keeps track of ID between different plug-ins. Assuming the user selects one of the items this CM plug-in created, the HandleSelection() call will be passed that ID.

Listing 6 shows a number of routines that are useful for creating these menu items, including a routine to create a menu item: G_ContextualMenuTools_Add_MenuItem(), a routine to create a blank (dotted) menu item: G_ContextualMenuTools_Add_Blank_MenuItem(), a routine to start a sub menu: G_ContextualMenuTools_Start_SubMenu() and a routine to finish creating a sub menu: G_ContextualMenuTools_Finish_SubMenu().

Listing 6

UContextualMenuTools.cp
Building Menus

//  Add Menu Item to AEDescList
OSStatus G_ContextualMenuTools_Add_MenuItem
            (Str255 p_name, long p_command,
            AEDescList* p_command_list_ptr)
{
  OSStatus a_err = noErr;
  
  AERecord a_command_record = { typeNull, NULL };
  a_err = AECreateList(NULL, 0, true, &a_command_record);
    
  if (a_err==noErr) {
    a_err = AEPutKeyPtr(&a_command_record, keyAEName, 
              typeChar, &p_name[1], p_name[0]);
      
    if (a_err==noErr)
      a_err = AEPutKeyPtr(&a_command_record, 
                keyContextualMenuCommandID, typeLongInteger, 
                &p_command, sizeof (p_command));
    
    if (a_err==noErr)
      a_err = AEPutDesc
              (p_command_list_ptr, 0, &a_command_record);
    AEDisposeDesc(&a_command_record);
  }

  return a_err;
} 

//  Add Blank Menu Item
OSStatus G_ContextualMenuTools_Add_Blank_MenuItem
            (AEDescList* p_command_list_ptr)
{
  return G_ContextualMenuTools_Add_MenuItem
            ("\p-", 0, p_command_list_ptr);
}

//  Start making a SubMenu 
OSStatus G_ContextualMenuTools_Start_SubMenu
          (AEDescList* p_submenu_command_list_ptr)
{
  p_submenu_command_list_ptr->descriptorType = typeNull;
  p_submenu_command_list_ptr->dataHandle = NULL;
  
  return AECreateList
          (NULL, 0, false, p_submenu_command_list_ptr);
}

//  Finish making a SubMenu 
OSStatus G_ContextualMenuTools_Finish_SubMenu
            (AEDescList* p_command_list_ptr,
            AEDescList* p_submenu_command_list_ptr,
            Str255 p_supermenu_name)
{
  OSStatus a_err = noErr;
  
  AERecord p_supermenu_command_list = { typeNull, NULL };
  
  a_err = AECreateList
            (NULL, 0, true, &p_supermenu_command_list);
      
  if (a_err==noErr)
    a_err = AEPutKeyPtr(&p_supermenu_command_list, keyAEName, 
              typeChar, &p_supermenu_name[1], 
              p_supermenu_name[0]);
      
  if (a_err==noErr)
    a_err = AEPutKeyDesc(&p_supermenu_command_list, 
              keyContextualMenuSubmenu, 
              p_submenu_command_list_ptr);
      
  if (a_err==noErr)
    a_err = AEPutDesc(p_command_list_ptr, 0, 
              &p_supermenu_command_list);
  
  AEDisposeDesc(p_submenu_command_list_ptr);
  AEDisposeDesc(&p_supermenu_command_list);

  return a_err;
}

The add menu item function is passed the name of the menu item, the ID, and the AEDesc to attach the item to. It creates a temporary list descriptor, adds the data to it, and puts the list into the passed AEDesc. The temporary list is then disposed of. The function G_ContextualMenuTools_Add_Blank_MenuItem() calls the function G_ContextualMenuTools_Add_MenuItem() with the correct values to create such a blank menu item. When the ioCommands parameter is used with these routines, the menu item is put on the top-most menu. However, these routines also can be used on submenus.

To create a submenu, the call G_ContextualMenuTools_Start_SubMenu() to create an AEDesc. Use this descriptor just as if it was the ioCommands parameter and add menu items to it. When all the menu items are added, used G_ContextualMenuTools_Finish_SubMenu() to attach the submenu (with name) to the ioCommands. In this manner, you can create any number of submenus. Listing 7 shows how to do this in the CaseCharm code. A submenu is created. Assuming text is available, two commands (and a blank line) are added. No matter what, the About command is added, and the entire submenu is then added to the main menu.

The only other two yet to be explained parameters for ExamineContext are inTimeOutInTicks and outNeedMoreTime. The inTimeOutInTicks value is the amount of time the CM plug-in can use to examine the selected data. If the call takes longer than this, it should return, with the outNeedMoreTime parameter set to true. This allows the CM Manager to handle creating larger or more difficult menu items. For simple contextual menus that do not access the disk (like the example), it is safe to set outNeedMoreTime to false.

Listing 7

CaseCharm.cp
CM Exaime Content

OSStatus CaseCharm::ExamineContext(Environment* ev,
            AEDesc *inContextDescriptor,
            SInt32 inTimeOutInTicks,
            AEDescList* ioCommands,
            Boolean* outNeedMoreTime)
{
#pragma unused(ev, inTimeOutInTicks)
  
  if (inContextDescriptor != NULL) {
    AEDescList a_sub_menu_commands = { typeNull, NULL };
    
    G_ContextualMenuTools_Start_SubMenu(&a_sub_menu_commands);
    
    if (G_ContextualMenuTools_Has_Text_Data
        (inContextDescriptor)) 
    {
      G_ContextualMenuTools_Add_MenuItem
        ("\pConvert to Uppercase", 1002, 
        &a_sub_menu_commands);

      G_ContextualMenuTools_Add_MenuItem
        ("\pConvert to Lowercase", 1003, 
        &a_sub_menu_commands);

      G_ContextualMenuTools_Add_Blank_MenuItem
        (&a_sub_menu_commands);
    }
    
    G_ContextualMenuTools_Add_MenuItem
      ("\pAbout CaseCharm...", 1001, &a_sub_menu_commands);
      
    G_ContextualMenuTools_Finish_SubMenu
      (ioCommands, &a_sub_menu_commands, "\pCaseCharm");
  }
  
  *outNeedMoreTime = false;
  
  return noErr;
  
}

More and more CM plug-ins are currently being created. The room on the contextual menus is starting to run out. If the CM plug-in you create as more than one or two commands, I suggest putting the entire list of commands into a submenu. This is becoming a common user interface; a submenu with the name of the product, a number of command menu items, a blank line, and the About command menu item(s).

Outputting Result Through The Scrap

When a user selects a given menu item in the contextual menu, the ID is passed to the associated CM plug-in. Again, the selected data can be parsed out. Assuming some manipulation is done on it, the result can be passed back to the user in the Clipboard (that is the Scrap). Listing 8 shows some simple routines to return text (as handles or strings) to the user this way. Notice that G_ContextualMenuTools_Set_Scrap_Text_Hdl() does not dispose of the handle, so the program must do it explicitly after the routine is called.

Listing 8

UContextualMenuTools.cp
Set Clipboard Text

//  Set Text Scrap using handle
void G_ContextualMenuTools_Set_Scrap_Text_Hdl
        (Handle p_handle)
{
  ZeroScrap();

  if (p_handle) {
    HLock(p_handle);
    PutScrap(GetHandleSize(p_handle), 'TEXT', *p_handle);
    HUnlock(p_handle);
  }
}

//  Set Text Scrap using str
void G_ContextualMenuTools_Set_Scrap_Text_Str(Str255 p_str)
{
  ZeroScrap();

  if (p_str[0]) {
    PutScrap(p_str[0], 'TEXT', &p_str[1]);
  }
}

User Interface

Contextual menus can have user interfaces. If a user has selected an item, dialogs and alerts can be displayed. They have to be modal, and be cleaned up before the HandleSelection() routine returns. Listing 9 provides some tools for this. The function G_ContextualMenuTools_Standard_Alert() creates a standard information alert using the new Appearance Manager StandardAlert() call. Since the CM Manager may run on machine without the Appearance Manager, it uses the G_ContextualMenuTools_Get_Gestalt_Flag() to make sure the call is available.

More complex user interfaces can be created using resources stored in the CM plug-in file. However, this file is not open as a resource file when the code is called. The G_ContextualMenuTools_Open_Resfile() takes care of this. It uses FindFolder() to search for the Contextual Menu Folder. If that fails, it searches for the folder by name. (In pre-Mac OS 8 computers, the CM Installer appears to fail to set the Contextual Menu Folder to the correct ID.) Once the file is open, any type of resource can be accessed from it. Just remember to close the resource file when you are done.

Listing 9

UContextualMenuTools.cp
User Interfaces

//  Check bit flag of Gestalt Selector 
Boolean G_ContextualMenuTools_Get_Gestalt_Flag
          (OSType p_selector, short p_bit_num)
{
  long a_result;

  if (Gestalt(p_selector, &a_result) == noErr)
    return BitTst(&a_result, 31-p_bit_num);
  else
    return 0;
}

//  Standard Alert
void G_ContextualMenuTools_Standard_Alert
          (Str255 p_title, Str255 p_info)
{
  if (G_ContextualMenuTools_Get_Gestalt_Flag
        (gestaltFSAttr, gestaltHasFSSpecCalls)) {
    short a_hit;
    StandardAlert(kAlertNoteAlert, p_title, 
        p_info, NULL, &a_hit);
  }
}

//  Open ContextualMenu Resource File
short G_ContextualMenuTools_Open_Resfile(Str255 p_name)
{
  short a_result = -1;
  OSErr a_err;
  
  if (p_name[0]) {
    if (G_ContextualMenuTools_Get_Gestalt_Flag
        (gestaltFSAttr, gestaltHasFSSpecCalls) 
      && G_ContextualMenuTools_Get_Gestalt_Flag
        (gestaltFindFolderAttr, gestaltFindFolderPresent)) {
      long a_dir_id;
      short a_vol_ref_num;
      FSSpec a_file_spec;
      if (FindFolder(kOnSystemDisk, 'cmnu', kCreateFolder, 
          &a_vol_ref_num, &a_dir_id) == noErr) {
        BlockMove(&p_name[0], &a_file_spec.name[0], 64);
        a_file_spec.vRefNum = a_vol_ref_num;
        a_file_spec.parID = a_dir_id;
        
        a_result = FSpOpenResFile(&a_file_spec, fsRdPerm);
      }
      else if (FindFolder(kOnSystemDisk, kSystemFolderType, 
              kCreateFolder, &a_vol_ref_num, &a_dir_id) 
              == noErr) {
        Str255 a_dir_name = "\p:Contextual Menu Items:";
        short a_dir_name_len = a_dir_name[0];
        short a_name_len = p_name[0];
        
        BlockMove(&p_name[1], &a_dir_name[a_dir_name_len+1], 
            a_name_len);
        a_dir_name[0] = a_dir_name_len + a_name_len;

        a_err = FSMakeFSSpec(a_vol_ref_num, a_dir_id, 
                  a_dir_name, &a_file_spec);
        if (a_err==noErr)
          a_result = FSpOpenResFile(&a_file_spec, fsRdPerm);
      }
    }
  }

  return a_result;
}

Sample Code

The final listing, Listing 10, shows the HandleSelection() code for the example, CaseCharm. Depending on the command passed in inCommandID, it shows the About Box using the G_ContextualMenuTools_Standard_Alert() call, or it retrieves the text using the G_ContextualMenuTools_Get_Text_Hdl() call and passes it to the CaseCharmUpperLower(). That routine shifts the case, and returns it using G_ContextualMenuTools_Set_Scrap_Text_Hdl().

Listing 10

UContextualMenuTools.cp
Handle Selection

//  Convert the given text (upper or lower)
//    and place it in scrap book.

void CaseCharmUpperLower(Handle p_text,
            Boolean p_is_upper)
{
  if (p_text) {
    long a_length = GetHandleSize(p_text);
    if (a_length>0) {
      HLock(p_text);
      
      char a_char;
      for (long a_count = 0; a_count<a_length; a_count++) {
        a_char = *(*p_text+a_count);
        
        if (p_is_upper) {
          if ((a_char>='a') && (a_char<='z'))
            *(*p_text+a_count) = a_char - 'a' + 'A';
        }
        else {
          if ((a_char>='A') && (a_char<='Z'))
            *(*p_text+a_count) = a_char - 'A' + 'a';
        }
      }
      
      HUnlock(p_text);

      G_ContextualMenuTools_Set_Scrap_Text_Hdl(p_text);
    }
  }
}

Steve Sheets has been happily programming the Macintosh since 1983, which makes him older than he wishes, but not as young as he acts. Being a native Californian, his developer wanderings have led him to Northern Virginia. For those interested, his non-computer interests involve his family (wife & 2 daughters), Society for Creative Anachronism (Medieval Reenactment) and Martial Arts (Fencing, Tai Chi). He is currently an independent developer, taking on any and all projects and can be reached at MageSteve@aol.com.

 
AAPL
$96.13
Apple Inc.
+0.53
MSFT
$42.86
Microsoft Corpora
-0.30
GOOG
$566.07
Google Inc.
-5.53

MacTech Search:
Community Search:

Software Updates via MacUpdate

Data Rescue 3.2.4 - Recover lost data on...
Data Rescue is a robust and reliable hard-drive recovery solution for your Mac. Recover lost or deleted files, mount corrupted drives, and more -- Data Rescue offers complete relief from crippling... Read more
Adobe Lightroom 5.6 - Import, develop, a...
Adobe Lightroom software helps you bring out the best in your photographs, whether you're perfecting one image, searching for ten, processing hundreds, or organizing thousands. Create incredible... Read more
OneNote 15.2 - Free digital notebook fro...
OneNote is your very own digital notebook. With OneNote, you can capture that flash of genius, that moment of inspiration, or that list of errands that’s too important to forget. Whether you’re at... Read more
iStat Menus 4.22 - Monitor your system r...
iStat Menus lets you monitor your system right from the menubar. Included are 8 menu extras that let you monitor every aspect of your system. Some features: CPU -- Monitor cpu usage. 7 display... Read more
Ember 1.8 - Versatile digital scrapbook....
Ember (formerly LittleSnapper) is your digital scrapbook of things that inspire you: websites, photos, apps or other things. Just drag in images that you want to keep, organize them into relevant... Read more
OmniPlan 2.3.6 - Robust project manageme...
With OmniPlan, you can create logical, manageable project plans with Gantt charts, schedules, summaries, milestones, and critical paths. Break down the tasks needed to make your project a success,... Read more
Command-C 1.1.1 - Clipboard sharing tool...
Command-C is a revolutionary app which makes easy to share your clipboard between iOS and OS X using your local WiFi network, even if the app is not currently opened. Copy anything (text, pictures,... Read more
Knock 1.1.7 - Unlock your Mac by knockin...
Knock is a faster, safer way to sign in. You keep your iPhone with you all the time. Now you can use it as a password. You never have to open the app -- just knock on your phone twice, even when it's... Read more
Mellel 3.3.6 - Powerful word processor w...
Mellel is the leading word processor for OS X and has been widely considered the industry standard since its inception. Mellel focuses on writers and scholars for technical writing and multilingual... Read more
LibreOffice 4.3.0.4 - Free Open Source o...
LibreOffice is an office suite (word processor, spreadsheet, presentations, drawing tool) compatible with other major office suites. The Document Foundation is coordinating development and... Read more

Latest Forum Discussions

See All

Bio Inc. is $0.99 for the Weekend, Recei...
Bio Inc. is $0.99 for the Weekend, Receives Small Update Posted by Ellis Spice on August 1st, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Happy 7th Birthday Readdle! Thank You fo...
Happy 7th Birthday Readdle! | Read more »
Sharknado: The Video Game Review
Sharknado: The Video Game Review By Lee Hamlet on August 1st, 2014 Our Rating: :: SHARKNA-DON'TUniversal App - Designed for iPhone and iPad Sharknado: The Video Game brings the craziness of the movies to iOS, though it quickly... | Read more »
Clima (Weather)
Clima 1.0 Device: iOS iPhone Category: Weather Price: $.99, Version: 1.0 (iTunes) Description: Clima show you all weather information, just beautifully simple. A series of color bars can tell you at a glance exactly current... | Read more »
Sticky Soccer Review
Sticky Soccer Review By Andrew Fisher on August 1st, 2014 Our Rating: :: STICK THIS GAMEUniversal App - Designed for iPhone and iPad Sticky Soccer puts too much emphasis on the ‘sticky’ and not enough on the ‘Soccer’ or ‘Fun’.   | Read more »
Graphics-less Apocalyptic Adventure A Da...
Graphics-less Apocalyptic Adventure A Dark Room Goes Free for a Limited Time Posted by Rob Rich on August 1st, 2014 [ permalink ] | Read more »
Fraud Tycoon Review
Fraud Tycoon Review By Rob Thomas on August 1st, 2014 Our Rating: :: UNHEALTHY CREDITUniversal App - Designed for iPhone and iPad Fraud Tycoon is a half-baked, messy, promotional tie-in that does their sponsor no favors whatsoever... | Read more »
Guardians of the Galaxy: The Universal W...
Guardians of the Galaxy: The Universal Weapon is on Sale for the Weekend Posted by Rob Rich on August 1st, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Mister Beam Review
Mister Beam Review By Jordan Minor on August 1st, 2014 Our Rating: :: ILLUMINATINGUniversal App - Designed for iPhone and iPad Mister Beam’s puzzles are great. But its platforming? Not so much.   | Read more »
Hook Some More Fun With MapHook’s New Up...
Hook Some More Fun With MapHook’s New Update Posted by Jessica Fisher on August 1st, 2014 [ permalink ] iPhone App - Designed for the iPhone, compatible with the iPad | Read more »

Price Scanner via MacPrices.net

13-inch MacBook Airs on sale for $100 off MSR...
B&H Photo has the new 2014 13″ MacBook Airs on sale $100 off MSRP. Shipping is free, and B&H charges NY sales tax only. They also include free copies of Parallels Desktop and LoJack for... Read more
16GB iPad Air on sale for $399, save $100
Best Buy is offering the 16GB WiFi iPad Air for $399.99 on their online store for a limited time. Their price is $100 off MSRP. Choose free shipping or free store pickup (if available). Price is for... Read more
All Over For Tablets Or Just A Maturing, Evol...
CNN’s David Goldman weighs in on tablet sector doom and gloom, asking rhetorically: “Is this the beginning of the end for the tablet?” Answering that, he contends that hysteria and panic are... Read more
Letterspace 1.0.1 – New Free iOS Text Editor...
Bangkok, Thailand based independent developer Sittipon Simasanti has released Letterspace, a new text editor for iPhone, iPad, and iPod touch devices. Letterspace is a note taking app with an... Read more
Save up to $130 on an iPad mini with Apple re...
The Apple Store has Certified Refurbished 2nd generation iPad minis with Retina Displays available for up to $130 off the cost of new models, starting at $339. Apple’s one-year warranty is included... Read more
iPad Cannibalization Threat “Overblown”
Seeking Alpha’s Kevin Greenhalgh observes that while many commentators think Apple’s forthcoming 5.5-inch panel iPhone 6 will cannibalize iPad sales, in his estimation, these concerns are being... Read more
Primate Labs Releases July 2014 MacBook Pro P...
Primate Labs’ John Poole has posted Geekbench 3 results for most of the new MacBook Pro models that Apple released on Tuesday. Poole observes that overall performance improvements for the new MacBook... Read more
Apple Re-Releases Bugfixed MacBook Air EFI Fi...
Apple has posted a bugfixed version EFI Firmware Update 2.9 a for MacBook Air (Mid 2011) models. The update addresses an issue where systems may take longer to wake from sleep than expected, and... Read more
Save $50 on the 2.5GHz Mac mini, plus free sh...
B&H Photo has the 2.5GHz Mac mini on sale for $549.99 including free shipping. That’s $50 off MSRP, and B&H will also include a free copy of Parallels Desktop software. NY sales tax only. Read more
Save up to $140 on an iPad Air with Apple ref...
Apple is offering Certified Refurbished iPad Airs for up to $140 off MSRP. Apple’s one-year warranty is included with each model, and shipping is free. Stock tends to come and go with some of these... Read more

Jobs Board

Position Opening at *Apple* - Apple (United...
**Job Summary** The Apple Store is a retail environment like no other - uniquely focused on delivering amazing customer experiences. As an Expert, you introduce people Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** At the Apple Store, you connect business professionals and entrepreneurs with the tools they need in order to put Apple solutions to work in their Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
Sr. Product Leader, *Apple* Store Apps - Ap...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
Sr Software Lead Engineer, *Apple* Online S...
Sr Software Lead Engineer, Apple Online Store Publishing Systems Keywords: Company: Apple Job Code: E3PCAK8MgYYkw Location (City or ZIP): Santa Clara Status: Full Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.