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.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Duplicate Annihilator 5.7.5 - Find and d...
Duplicate Annihilator takes on the time-consuming task of comparing the images in your iPhoto library using effective algorithms to make sure that no duplicate escapes. Duplicate Annihilator... Read more
BusyContacts 1.0.2 - Fast, efficient con...
BusyContacts is a contact manager for OS X that makes creating, finding, and managing contacts faster and more efficient. It brings to contact management the same power, flexibility, and sharing... Read more
Capture One Pro 8.2.0.82 - RAW workflow...
Capture One Pro 8 is a professional RAW converter offering you ultimate image quality with accurate colors and incredible detail from more than 300 high-end cameras -- straight out of the box. It... Read more
Backblaze 4.0.0.872 - Online backup serv...
Backblaze is an online backup service designed from the ground-up for the Mac.With unlimited storage available for $5 per month, as well as a free 15-day trial, peace of mind is within reach with... Read more
Little Snitch 3.5.2 - Alerts you about o...
Little Snitch gives you control over your private outgoing data. Track background activity As soon as your computer connects to the Internet, applications often have permission to send any... Read more
Monolingual 1.6.4 - Remove unwanted OS X...
Monolingual is a program for removing unnecesary language resources from OS X, in order to reclaim several hundred megabytes of disk space. If you use your computer in only one (human) language, you... Read more
CleanApp 5.0 - Application deinstaller a...
CleanApp is an application deinstaller and archiver.... Your hard drive gets fuller day by day, but do you know why? CleanApp 5 provides you with insights how to reclaim disk space. There are... Read more
Fantastical 2.0 - Create calendar events...
Fantastical is the Mac calendar you'll actually enjoy using. Creating an event with Fantastical is quick, easy, and fun: Open Fantastical with a single click or keystroke Type in your event details... Read more
Cocktail 8.2 - General maintenance and o...
Cocktail is a general purpose utility for OS X that lets you clean, repair and optimize your Mac. It is a powerful digital toolset that helps hundreds of thousands of Mac users around the world get... Read more
Skype 7.6.0.409 - Voice-over-internet ph...
Skype allows you to talk to friends, family and co-workers across the Internet without the inconvenience of long distance telephone charges. Using peer-to-peer data transmission technology, Skype... Read more

Pie In The Sky: A Pizza Odyssey (Games)
Pie In The Sky: A Pizza Odyssey 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: A game about delivering pizza. In space. | Read more »
Android's Popular OfficeSuite Now A...
Once only available for Android devices, OfficeSuite has finally landed on the app store. The Mobile Systems app lets you view, edit, create, and share Word, Excel, and PowerPoint documents as well as convert them to/from PDFs. It's touted as being... | Read more »
Fast & Furious: Legacy's Creati...
| Read more »
N-Fusion and 505's Ember is Totally...
| Read more »
These are All the Apple Watch Apps and G...
The Apple Watch is less than a month from hitting store shelves, and once you get your hands on it you're probably going to want some apps and games to install. Fear not! We've compiled a list of all the Apple Watch apps and games we've been able to... | Read more »
Appy to Have Known You - Lee Hamlet Look...
Being at 148Apps these past 2 years has been an awesome experience that has taught me a great deal, and working with such a great team has been a privilege. Thank you to Rob Rich, and to both Rob LeFebvre and Jeff Scott before him, for helping me... | Read more »
Hands-On With Allstar Heroes - A Promisi...
Let’s get this out of the way quickly. Allstar Heroes looks a lot like a certain other recent action RPG release, but it turns out that while it’s not yet available here, Allstar Heroes has been around for much longer than that other title. Now that... | Read more »
Macho Man and Steve Austin Join the Rank...
WWE Immortals, by Warner Bros. Interactive Entertainment and WWE, has gotten a superstar update. You'll now have access to Macho Man Randy Savage and Steve Austin. Both characters have two different versions: Macho Man Randy Savage Renegade or Macho... | Read more »
Fearless Fantasy is Fantastic for the iF...
I actually had my first look at Fearless Fantasy last year at E3, but it was on a PC so there wasn't much for me to talk about. But now that I've been able to play with a pre-release version of the iOS build, there's quite a bit for me to talk... | Read more »
MLB Manager 2015 (Games)
MLB Manager 2015 5.0.14 Device: iOS Universal Category: Games Price: $4.99, Version: 5.0.14 (iTunes) Description: Guide your favorite MLB franchise to glory! MLB Manager 2015, officially licensed by MLB.com and based on the award-... | Read more »

Price Scanner via MacPrices.net

Apple offering refurbished 27-inch 5K iMacs f...
The Apple Store is offering Apple Certified Refurbished 27″ 3.5GHz 5K iMacs for $2119 including free shipping. Their price is $380 off the price of new models, and it’s the lowest price available for... Read more
16GB iPad mini on sale for $199, save $50
Walmart has 16GB iPad minis (1st generation) available for $199.99 on their online store, including free shipping. Their price is $50 off MSRP. Online orders only. Read more
New 128GB MacBook Airs on sale for $50 off MS...
 B&H Photo has 128GB 11″ and 13″ 2015 MacBook Airs on sale today for $50 off MSRP including free shipping plus NY sales tax only: - 11″ 1.6GHz/128GB MacBook Air (Model #MJVM2LL/A): $849 $50 off... Read more
13-inch 2.6GHz Retina MacBook Pro (refurbishe...
The Apple Store has Apple Certified Refurbished 13″ 2.6GHz/128GB Retina MacBook Pros available for $979 including free shipping. Original MSRP for this model was $1299. Read more
Save up to $600 with Apple refurbished Mac Pr...
The Apple Store is offering Apple Certified Refurbished Mac Pros for up to $600 off the cost of new models. An Apple one-year warranty is included with each Mac Pro, and shipping is free. The... Read more
Samsung Galaxy S 6 and Galaxy S 6 edge U.S. P...
Samsung Electronics America, Inc. has announced the Galaxy S 6 and Galaxy S 6 edge will be available in the U.S. beginning April 10, with pre-orders being accepted now. “We have completely reimagined... Read more
13-inch 2.5GHz MacBook Pro (refurbished) avai...
The Apple Store has Apple Certified Refurbished 13″ 2.5GHz MacBook Pros available for $829, or $270 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free: - 13″ 2.... Read more
Save up to $80 on iPad Air 2s, NY tax only, f...
 B&H Photo has iPad Air 2s on sale for $80 off MSRP including free shipping plus NY sales tax only: - 16GB iPad Air 2 WiFi: $469.99 $30 off - 64GB iPad Air 2 WiFi: $549.99 $50 off - 128GB iPad... Read more
iMacs on sale for up to $205 off MSRP
B&H Photo has 21″ and 27″ iMacs on sale for up to $205 off MSRP including free shipping plus NY sales tax only: - 21″ 1.4GHz iMac: $1019 $80 off - 21″ 2.7GHz iMac: $1189 $110 off - 21″ 2.9GHz... Read more
Färbe Technik Offers iPhone Battery Charge LI...
Färbe Technik, which manufactures and markets of mobile accessories for Apple, Blackberry and Samsung mobile devices, is offering tips on how to keep your iPhone charged while in the field: •... Read more

Jobs Board

DevOps Software Engineer - *Apple* Pay, iOS...
**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
*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. Technical Services Consultant, *Apple*...
**Job Summary** Apple Professional Services (APS) has an opening for a senior technical position that contributes to Apple 's efforts for strategic and transactional Read more
Lead *Apple* Solutions Consultant - Retail...
**Job Summary** Job Summary The Lead ASC is an Apple employee who serves as the Apple business manager and influencer in a hyper-business critical Reseller's store Read more
*Apple* Pay - Site Reliability Engineer - 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
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.