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

Box Sync 4.0.6567 - Online synchronizati...
Box Sync gives you a hard-drive in the Cloud for online storage. Note: You must first sign up to use Box. What if the files you need are on your laptop -- but you're on the road with your iPhone? No... Read more
Carbon Copy Cloner 4.1.4 - Easy-to-use b...
Carbon Copy Cloner backups are better than ordinary backups. Suppose the unthinkable happens while you're under deadline to finish a project: your Mac is unresponsive and all you hear is an ominous,... Read more
OmniGraffle Pro 6.3.1 - Create diagrams,...
OmniGraffle Pro helps you draw beautiful diagrams, family trees, flow charts, org charts, layouts, and (mathematically speaking) any other directed or non-directed graphs. We've had people use... Read more
Monosnap 3.1.2 - Versatile screenshot ut...
Monosnap lets you capture screenshots, share files, and record video and .gifs! Capture: Capture full screen, just part of the screen, or a selected window Make your crop area pixel perfect with... Read more
Alfred 2.7.2 - Quick launcher for apps a...
Alfred is an award-winning productivity application for OS X. Alfred saves you time when you search for files online or on your Mac. Be more productive with hotkeys, keywords, and file actions at... Read more
Microsoft Remote Desktop 8.0.19 - Connec...
With Microsoft Remote Desktop, you can connect to a remote PC and your work resources from almost anywhere. Experience the power of Windows with RemoteFX in a Remote Desktop client designed to help... Read more
OmniGraffle 6.3 - Create diagrams, flow...
OmniGraffle helps you draw beautiful diagrams, family trees, flow charts, org charts, layouts, and (mathematically speaking) any other directed or non-directed graphs. We've had people use Graffle to... Read more
PDFKey Pro 4.3.2 - Edit and print passwo...
PDFKey Pro can unlock PDF documents protected for printing and copying when you've forgotten your password. It can now also protect your PDF files with a password to prevent unauthorized access and/... Read more
Ableton Live 9.2.2 - Record music using...
Ableton Live lets you create and record music on your Mac. Use digital instruments, pre-recorded sounds, and sampled loops to arrange, produce, and perform your music like never before. Ableton Live... Read more
Macs Fan Control 1.3.1.0 - Monitor and c...
Macs Fan Control allows you to monitor and control almost any aspect of your computer's fans, with support for controlling fan speed, temperature sensors pane, menu-bar icon, and autostart with... Read more

This Week at 148Apps: August 24-28, 2015
The Apps of August With 148Apps How do you know what apps are worth your time and money? Just look to the review team at 148Apps. We sort through the chaos and find the apps you're looking for. The ones we love become Editor’s Choice, standing out... | Read more »
NASCAR in Real Racing 3? Sure, Why Not?
I have to give Firemonkeys credit - it's very cool of them to add NASCAR to Real Racing 3 via an update rather than making a separate game for it. But that's a different discussion for another time; for now let's sit back and enjoy driving in... | Read more »
The nuyu is an Inexpensive Activity Moni...
Today, Health o Meter nuyu has announced a series of health and fitness-related products, including the aforementioned activity monitor along with a wireless scale. All at a decent pricepoint, no less. [Read more] | Read more »
The Makers of Overkill are Trying Someth...
Craneballs, the studio responsible for the Overkill series, is taking a little break from all that violence (a little break) to bring us Cube Worm - a 3D take on one of the most classic PC/calculator games in existence. [Read more] | Read more »
The Sandbox Welcomes Cutethulu in its La...
Another month, another update to Pixowl's The Sandbox. This time players can say hello to "Cutethulu" - an adorable little rendition of perhaps the most well known (and infamous) of the Elder Gods. [Read more] | Read more »
ReBoard: Revolutionary Keyboard (Utilit...
ReBoard: Revolutionary Keyboard 1.0 Device: iOS Universal Category: Utilities Price: $1.99, Version: 1.0 (iTunes) Description: Do everything within the keyboard without switching apps! If you are in WhatsApp, how do you schedule a... | Read more »
Tiny Empire (Games)
Tiny Empire 1.1.3 Device: iOS Universal Category: Games Price: $2.99, Version: 1.1.3 (iTunes) Description: Launch cannonballs and blow tiny orcs into thousands of pieces in this intuitive fantasy-themed puzzle shooter! Embark on an... | Read more »
Astropad Mini (Productivity)
Astropad Mini 1.0 Device: iOS iPhone Category: Productivity Price: $4.99, Version: 1.0 (iTunes) Description: *** 50% off introductory price! ​*** Get the high-end experience of a Wacom tablet at a fraction of the price with Astropad... | Read more »
Emo Chorus (Music)
Emo Chorus 1.0.0 Device: iOS Universal Category: Music Price: $1.99, Version: 1.0.0 (iTunes) Description: Realistic Choir simulator ranging from simple Chorus emulation to full ensemble Choir with 128 members. ### introductory offer... | Read more »
Forest Spirit (Games)
Forest Spirit 1.0.5 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.5 (iTunes) Description: | Read more »

Price Scanner via MacPrices.net

WaterField Designs Unveils American-Made, All...
San Francisco’s WaterField Designs today unveiled their all-leather Cozmo 2.0 — an elegant attach laptop bag with carefully-designed features to suit any business environment. The Cozmo 2.0 is... Read more
Apple’s 2015 Back to School promotion: Free B...
Purchase a new Mac or iPad at The Apple Store for Education and take up to $300 off MSRP. All teachers, students, and staff of any educational institution qualify for the discount. Shipping is free,... Read more
128GB MacBook Airs on sale for $100 off MSRP,...
B&H Photo has 11″ & 13″ MacBook Airs with 128GB SSDs on sale for $100 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 11″ 1.6GHz/128GB MacBook Air: $799.99, $100 off MSRP... 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
27-inch 3.2GHz iMac on sale for $1679, save $...
B&H Photo has the 27″ 3.2GHz iMac on sale for $1679.99 including free shipping plus NY sales tax only. Their price is $120 off MSRP. Read more
Apple and Cisco Partner to Deliver Fast-Lane...
Apple and Cisco have announced a partnership to create a “fast lane” for iOS business users by optimizing Cisco networks for iOS devices and apps. The alliance integrates iPhone with Cisco enterprise... Read more
Apple offering refurbished 2015 13-inch Retin...
The Apple Store is offering Apple Certified Refurbished 2015 13″ Retina MacBook Pros for up to $270 (15%) off the cost of new models. An Apple one-year warranty is included with each model, and... Read more
Apple refurbished 2015 MacBook Airs available...
The Apple Store has Apple Certified Refurbished 2015 11″ and 13″ MacBook Airs (the latest models), available for up to $180 off the cost of new models. An Apple one-year warranty is included with... Read more
21-inch iMacs on sale for up to $120 off MSRP
B&H Photo has 21″ iMacs on sale for up to $120 off MSRP including free shipping plus NY sales tax only: - 21″ 1.4GHz iMac: $999.99 $100 off - 21″ 2.7GHz iMac: $1199.99 $100 off - 21″ 2.9GHz iMac... Read more
5K iMacs on sale for up to $150 off MSRP, fre...
B&H Photo has the 27″ 3.3GHz 5K iMac on sale for $1899.99 including free shipping plus NY tax only. Their price is $100 off MSRP. They have the 27″ 3.5GHz 5K iMac on sale for $2149.99 $2199.99, $... Read more

Jobs Board

*Apple* Desktop Analyst - KDS Staffing (Unit...
…field and consistent professional recruiting achievement. Job Description: Title: Apple Desktop AnalystPosition Type: Full-time PermanentLocation: White Plains, NYHot Read more
Simply Mac- *Apple* Specialist- Store Manag...
Simply Mac is the largest premier retailer for Apple products and solutions. We're looking for dedicated individuals with a passion to simplify and enhance the Read more
*Apple* Evangelist - JAMF Software (United S...
The Apple Evangelist is responsible for building and cultivating strategic relationships with Apple 's small and mid-market business development field teams. This 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
*Apple* Desktop Analyst - KDS Staffing (Unit...
…field and consistent professional recruiting achievement. Job Description: Title: Apple Desktop AnalystPosition Type: Full-time PermanentLocation: White Plains, NYHot Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.