ftp.nice.ch/pub/next/developer/objc/iconkit/IconKit.1.2.s.tar.gz#/IconKit-1.2/Classes/IKCell.m

This is IKCell.m in view mode; [Download] [Up]

/*

File IKCell.m

Release 1.2, 7 June 1994
Copyright (C) 1994 by H. Scott Roy

This code is part of IconKit, a general toolbox for drag-and-drop applications.  IconKit is free for noncommercial use, but costs money for a commercial license.  You should have received a copy of the license agreement with this file.  If not, a copy of the license and the complete source of IconKit can be obtained from the author:

		H. Scott Roy
		2573 Stowe Ct.
		Northbrook, IL  60062-8103
		iconkit@cs.stanford.edu

For your editing convenience, this file is best viewed using an editor that automatically wraps long lines, in a fixed point font at 80 columns, with tabs every 4 spaces.

*/


/* ========================================================================== */


/*

This is the type of cell used throughout the appkit, in file viewers and the like, to display an icon with an associated title.  The nomal use is for the icon to represent an object.  If given a delegate, the icon cell treats that delegate as the object it should display and sends it query messages to determine its title, icon, and dragging characteristics.

In conjuction with the IKIconPath class, IKCells support drag-and-drop, acting as both sources and destinations.  The title of an editable IKCell can be changed by clicking on the title region and typing a new one.  The delegate is kept informed throughout dragging and editing so that it can initiate appropriate changes throughout the application.

*/

#import <appkit/appkit.h>
#import "iconkit.h"
#import "IKCellPS.h"

@implementation IKCell


/* ========================================================================== */


#define DX				9
#define DY				2
#define OFFSET			0
#define GAP				0
#define INSET			4
#define HYSTERESIS		4


/* ========================================================================== */


/*

The class shares a global Cell that's used to draw and format the titles.  It's true that IKCell inherits from Cell, so that we might just call super, but it easier this way since we don't need to keep swapping values in and out to get super to behave the way we want.

*/


static TextFieldCell
	* text;

static char
	textBuffer [1000];
	

/* ========================================================================== */


/*

The class keeps a single element cache for ghosted images, since they appear far and away most frequently during dragging operations, when the same image is drawn repeatedly in multiple cells.

*/


static id
	ghostImage = nil,
	ghostHighlightMask = nil,
	ghosting = nil;

static void
	initGhostImages (NXSize);


/* ========================================================================== */


+ initialize
{
    if (self == [IKCell  class])
	{
		[self  setVersion: 1];
		text = [[[TextFieldCell		alloc]
									initTextCell: ""]
									setStringValueNoCopy: textBuffer];
	}
	
	return self;
}


/* ========================================================================== */


/*

Cells are initially set up in appropriately for an icon path in a browser.  The IKShelf class creates its own prototype to give the appropriate behavior for a shelf.

*/


- init
{
	if ((self = [super  initTextCell: ""]) != nil)
	{
		image = nil;
		delegate = nil;
		
		flags.showBranch = NO;
		flags.draggable = YES;
		flags.dragAccepting = YES;
		flags.editable = YES;
		flags.container = NO;
		flags.locked = YES;
		flags.reallyLocked = YES;
		flags.multipleLines = NO;
		flags.ghosted = NO;
		
		[self  setAlignment: NX_CENTERED];
	}
	
	return self;
}


- initTextCell: (const char *) theTitle
{
	return [[self	init]
					setTitle: theTitle];
}


- initIconCell: (const char *) iconName
{
	return [[self	init]
					setImage: [NXImage  findImageNamed: iconName]];
}


- initImage: (NXImage *) theImage
	  title: (const char *) theTitle
{
	return [[[self	init]
					setImage: theImage]
					setTitle: theTitle];
}


- initDelegate: (id <IKIconObject, IKDependency>) theDelegate
{
	return [[self	init]
					setDelegate: theDelegate];
}


- initFromCopy: (IKCell *) copy
{
	[[[[[[[[[[[[self	init]
						setBranch:			[copy  isBranch]]
						setDraggable:		[copy  isDraggable]]
						setDragAccepting:	[copy  isDragAccepting]]
						setEditable:		[copy  isEditable]]
						setContainer:		[copy  isContainer]]
						setLocked:			[copy  isLocked]]
						setReallyLocked:	[copy  isReallyLocked]]
						setMultipleLines:	[copy  isMultipleLines]]
						setGhosted:			[copy  isGhosted]]
						setTitle:			[copy  title]]
						setImage:			[copy  image]];
	
	return self;
}


- free
{
	[delegate  removeUser: self];
	return self = [super  free];
}


- (const char *) getInspectorClassName
{
     return "IKCellInspector";
}


/* ========================================================================== */


/*

Here are the archiving methods.  An IKCell saves its image and all its flags, but not its delegate.

*/


- read: (NXTypedStream *) stream
{
	char
		showBranch,
		draggable,
		dragAccepting,
		editable,
		container,
		locked,
		reallyLocked,
		multipleLines;
	
    [super  read: stream];
	[self  setAlignment: NX_CENTERED];
	
	switch (NXTypedStreamClassVersion (stream, "IKCell"))
	{
		case 1:
			image = NXReadObject (stream);
			NXReadTypes (stream, "cccccccc", &showBranch, &draggable,
					&dragAccepting, &editable, &container, &locked,
					&reallyLocked, &multipleLines);
			
			flags.showBranch = showBranch;
			flags.draggable = draggable;
			flags.dragAccepting = dragAccepting;
			flags.editable = editable;
			flags.container = container;
			flags.locked = locked;
			flags.reallyLocked = reallyLocked;
			flags.multipleLines = multipleLines;
			flags.ghosted = NO;
			break;
			
		case 0:
			image = NXReadObject (stream);
			NXReadTypes (stream, "ccccccc", &showBranch, &draggable,
					&dragAccepting, &editable, &container, &locked,
					&reallyLocked);
			
			flags.showBranch = showBranch;
			flags.draggable = draggable;
			flags.dragAccepting = dragAccepting;
			flags.editable = editable;
			flags.container = container;
			flags.locked = locked;
			flags.reallyLocked = reallyLocked;
			flags.multipleLines = NO;
			flags.ghosted = NO;
			break;
	}
	
	return self;
}


- write: (NXTypedStream *) stream
{
	char
		showBranch = flags.showBranch,
		draggable = flags.draggable,
		dragAccepting = flags.dragAccepting,
		editable = flags.editable,
		container = flags.container,
		locked = flags.locked,
		reallyLocked = flags.reallyLocked,
		multipleLines = flags.multipleLines;
	
    [super  write: stream];
	
	NXWriteObject (stream, image);    
	NXWriteTypes (stream, "cccccccc", &showBranch, &draggable, &dragAccepting,
			&editable, &container, &locked, &reallyLocked, &multipleLines);
	
	return self;
}


/* ========================================================================== */


/*

Here are all the set and get methods.  When a delegate is set, it is queried for its title, icon, and editing and drag attributes. 

*/


- delegate					{		return delegate;						}
- image						{		return image;							}
- (const char *) title		{		return contents;						}
- (BOOL) isBranch			{		return flags.showBranch;				}
- (BOOL) isContainer		{		return flags.container;					}
- (BOOL) isLocked			{		return flags.locked;					}
- (BOOL) isReallyLocked		{		return flags.reallyLocked;				}
- (BOOL) isEmptyContainer	{		return flags.container && !delegate;	}
- (BOOL) isMultipleLines	{		return flags.multipleLines;				}
- (BOOL) isGhosted			{		return flags.ghosted;					}


- (BOOL) isDraggable
{
	return flags.draggable && (!delegate || [delegate  isDraggable]);
}


- (BOOL) isDragAccepting
{
	return flags.dragAccepting && (!delegate || [delegate  isDragAccepting]);
}


- (BOOL) isEditable
{
	return flags.editable && (!delegate || [delegate  isEditable]);
}


- setImage: (NXImage *) theImage
{
	id		old = image;
	
	image = theImage;
	return old;
}


- setTitle: (const char *) theTitle
{
	NXRect
		oldFrame,
		newFrame;
	id
		editor = [self  editor];
	
	[self  setStringValue: theTitle  ?  theTitle : ""];
	if (editor)
	{
		if (theTitle == NULL)
				[[self  controlView]  endEditing];
		else
		{
			[[[editor	getFrame: &oldFrame]
						setAutodisplay: NO]
						setText: contents];
			
			if ([editor  isHorizResizable])
					[editor  sizeToFit];
			
			[[editor setAutodisplay: YES]  getFrame: &newFrame];
			[[self	controlView]
					display: NXUnionRect (&newFrame, &oldFrame)  : 1];
		}
	}
	
	return self;
}


- setBranch: (BOOL) flag		{	flags.showBranch = flag;	return self;   }
- setDraggable: (BOOL) flag		{	flags.draggable = flag;		return self;   }
- setDragAccepting: (BOOL) flag	{	flags.dragAccepting = flag;	return self;   }
- setEditable: (BOOL) flag;		{	flags.editable = flag;		return self;   }
- setContainer: (BOOL) flag		{	flags.container = flag;		return self;   }
- setLocked: (BOOL) flag		{	flags.locked = flag;		return self;   }
- setReallyLocked: (BOOL) flag	{	flags.reallyLocked = flag;	return self;   }
- setMultipleLines: (BOOL) flag	{	flags.multipleLines = flag;	return self;   }
- setGhosted: (BOOL) flag		{	flags.ghosted = flag;		return self;   }


- setDelegate: (id <IKIconObject, IKDependency>) theDelegate
{
	id		old = delegate;
	
	delegate = IKCheckConformance (theDelegate) ? theDelegate : nil;
	[delegate  addUser: self];
	[self  setImage: [delegate  image]];
	[self  setTitle: [delegate  name]];
	
	return [old  removeUser: self];
}


- willFree: who
{
	if (who == delegate)
	{
		delegate = nil;
		[self  setDelegate: nil];
		[[self  controlView]  updateCell: self];
	}
	
	return self;
}


/* ========================================================================== */


/*

The methods below provide the most convenient way to access and change a cell's shelf behavior.

*/


- (int) shelfMode
{
	if (!flags.container)		return IK_NOSHELF;			else
	if (flags.reallyLocked)		return IK_REALLYLOCKED;		else
	if (flags.locked)			return IK_LOCKED;			else
								return IK_UNLOCKED;
}


- setShelfMode: (int) mode
{
	switch (mode)
	{
		case IK_NOSHELF:
				[self		setContainer: NO];
				break;
		
		case IK_UNLOCKED:
				[[[self		setContainer: YES]
							setLocked: NO]
							setReallyLocked: NO];
				break;
				
		case IK_LOCKED:
				[[[self		setContainer: YES]
							setLocked: YES]
							setReallyLocked: NO];
				break;
		
		case IK_REALLYLOCKED:
				[[[self		setContainer: YES]
							setLocked: YES]
							setReallyLocked: YES];
				break;
	}
	
	return self;
}


/* ========================================================================== */


/*

The cell's title, icon, and attributes are automatically updated as needed.  The setTitle: method already redraws the cell if needed, so we only need to explicitly redraw when chaning the image.

*/


- didChangeName: sender
{
	if (sender == delegate)
			[self  setTitle: [delegate  name]];
	
	return self;
}


- didChangeImage: sender
{
	if (sender == delegate)
	{
		[self  setImage: [delegate  image]];
		[[self  controlView]  updateCellInside: self];
	}
	
	return self;
}


/* ========================================================================== */


/*

Highlighting is more elaborate in an IKCell than a normal cell.  When highlighted, the icon is displayed against an illuminated round corner rectangle.

*/


- highlight: (const NXRect *) cellFrame  inView: view  lit: (BOOL) flag
{
	if ((image != NULL) && (cFlags1.highlighted != flag))
	{
		cFlags1.highlighted = flag;
		[self  drawInside: cellFrame  inView: view];
	}
	
	return self;
}


/* ========================================================================== */


/*

The method below returns the image to use when drawing.  In this implementation, the image is either the one being stored in the cell, or else a ghosted image calculated on the spot.

*/


- _imageToDraw
{
	if (flags.ghosted  &&  image != ghosting)
	{
		NXPoint
			origin = { 0.0, 0.0 };
		NXSize
			size = { 0.0, 0.0 };
		
		[image  getSize: &size];
		initGhostImages (size);
		
		[ghostImage  lockFocus];
		[image  composite: NX_COPY  toPoint: &origin];
		[ghostHighlightMask  composite: NX_SATOP  toPoint: &origin];
		[ghostImage  unlockFocus];
		ghosting = image;
	}
	
	return flags.ghosted  ?
			ghostImage:
			image;
}


/* ========================================================================== */


/*

An IKCell is drawn in three parts: the icon, the title, and a branch symbol if needed.  There are two possible layouts.  If the title is to occupy a single line, then the title and icon are centered in the cellFrame.  If it should occupy multiple lines, then the icon is a fixed distance from the top of the cell, and the title occupies the remainder.

The title is drawn using an auxilliary cell shared by the entire IKCell class.  One could get rid of it by just calling super, but it's easier this way since the actual title may be truncated from the cell contents.

We use a static variable to keep track of the current view for drawTitle:, instead of calling [self  controlView], since InterfaceBuilder never bothers to set the control view when alt-dragging out a matrix.

*/


static id
	controlView = nil;
float
	background;


- drawInside: (const NXRect *) cellFrame
      inView: (View *) view
{
	IKdprintf ("\tdrawing cell: %s\n", contents);
	controlView = view;
	
	if (image != NULL)		[self  drawIcon: * cellFrame];
	if (contents != NULL)	[self  drawTitle: * cellFrame];
	if (flags.showBranch)	[self  drawBranch: * cellFrame];
	
	return self;
}


- drawIcon: (NXRect) iconRect
{
	[self  getIconRect: &iconRect];
	PSsetgray ((cFlags1.state | cFlags1.highlighted)  ?
			NX_WHITE:
			NX_LTGRAY);
	
	PSiconBackdrop(
						iconRect.origin.x - DX,
						iconRect.origin.y - DY,
						iconRect.size.width + 2.0 * DX,
						iconRect.size.height + 2.0 * DY
	);
	
	iconRect.origin.y += iconRect.size.height;
	[[self  _imageToDraw]  composite: NX_SOVER  toPoint: &iconRect.origin];
	
	return self;
}


- drawTitle: (NXRect) titleRect
{
	if ([self  editor] == nil)
	{
		[self  getTitleRect: &titleRect];
		strcpy (textBuffer, contents);
		
		[[[[[[text	setAlignment: cFlags1.alignment]
					setParameter: NX_CELLHIGHLIGHTED  to: 0]
					setFont: support]
					setTextGray: flags.ghosted  ?  NX_DKGRAY : NX_BLACK]
					setBackgroundGray: NX_LTGRAY]
					setWrap: !cFlags2.noWrap];
		
		if (!flags.multipleLines)
				IKShortenTitle (text, titleRect.size.width);
	
		[text  drawInside: &titleRect  inView: controlView];
	}
	
	return self;
}


- drawBranch: (NXRect) cellFrame
{
	id
		branchIcon = [NXBrowserCell  branchIcon];
	NXPoint
		origin = cellFrame.origin;
	NXSize
		size;
	
	[branchIcon  getSize: &size];
	origin.x += cellFrame.size.width - size.width;
	origin.y += (cellFrame.size.height + size.height) / 2.0;
	[branchIcon  composite: NX_SOVER  toPoint: &origin];
	
	return self;
}


/* ========================================================================== */


/*

The calcCellSize: method needs to take into account both the icon and title of the cell.  The input frame may be huge, so we just look at the actual sizes of the text and icon.

*/


- calcCellSize: (NXSize *) size  inRect: (const NXRect *) frame
{
	NXSize
		imageSize;

	[super  calcCellSize: size  inRect: frame];
	[image  getSize: &imageSize];
	
	if (contents[0] == '\0') size->height = 0.0;
	size->width = MAX (size->width, imageSize.width) + 2.0 * DX + 1.0;
	size->height = size->height + imageSize.height + GAP + OFFSET +
			2.0 * DY + 1.0;
	
	return self;
}


/* ========================================================================== */


/*

Here's the method used to compute the layout of the cell.  Several compile time defaults control the spacing:

	OFFSET		vertical offset of the cell contents away from true center
	GAP			distance between the title and highlighting
	INSET		gap from top of cell to icon in a multiline cell

A mutliline cell is drawn with the icon rectangle a fixed distance from the top of the cell, and with the title rectangle occupying the remainder.  In a single line cell, the title and icon rectangles are centered vertically.

*/


- _getIconRect: (NXRect *) iconRect  titleRect: (NXRect *) titleRect
{
	NXRect
		bigRect = {{ 0.0,  0.0 },  { 10000.0, 10000.0 }};
	NXSize
		title = { 0.0,  0.0 },
		icon  = { 0.0,  0.0 };
	float
		h = titleRect->origin.y;

	[image  getSize: &icon];
	iconRect->origin.x += (iconRect->size.width - icon.width) / 2.0;
	iconRect->origin.y += flags.multipleLines  ?
			INSET:
			(iconRect->size.height - icon.height) / 2.0;
	
	if (contents[0] != '\0')
	{
		if (flags.multipleLines)
		{
			titleRect->origin.y = iconRect->origin.y + icon.height + GAP + DY;
			title.height = titleRect->size.height - titleRect->origin.y + h;
			title.width = titleRect->size.width;
		}
		
		else
		{
			[super  calcCellSize: &title  inRect: &bigRect];
			
			title.width = MIN (titleRect->size.width, title.width);
			iconRect->origin.y += OFFSET - (title.height + GAP) / 2.0;
			titleRect->origin.y = iconRect->origin.y + icon.height + GAP + DY;
			titleRect->origin.x +=
					(int) (titleRect->size.width - title.width) / 2;
		}
	}
	
	iconRect->size = icon;
	titleRect->size = title;
	
	return self;
}


/* ========================================================================== */


/*

Here are the methods to calculate the icon and title rectangles.  It's proved simpler to always just calculate both simulatenously.

*/

- getIconRect: (NXRect *) iconRect
{
	NXRect
		titleRect = *iconRect;
	
	[self  _getIconRect: iconRect  titleRect: &titleRect];
	return self;
}


- getTitleRect: (NXRect *) titleRect
{
	NXRect
		iconRect = *titleRect;
	
	[self  _getIconRect: &iconRect  titleRect: titleRect];
	return self;
}


/* ========================================================================== */


/*

This method determines the part of an IKCell that contains a particular point.  An IKIconPath uses it to find out where the user has clicked to decide if it should initiate dragging or editing.

*/

- (int) hitPart: (NXPoint *) where  inRect: (const NXRect *) cellFrame
{
	NXRect
		iconRect  = * cellFrame,
		titleRect = * cellFrame;
	
	[self  getIconRect: &iconRect];
	[self  getTitleRect: &titleRect];
	
	return
			NXMouseInRect (where, &titleRect, YES)  ?	IK_TITLEPART:
			NXMouseInRect (where, &iconRect, NO)  ?		IK_ICONPART:
														IK_NOPART;
}


/* ========================================================================== */


/*

This method lets the user drag the cell's delegate and drop it elsewhere on the screen.  Dragging will work without a delegate, but the drag pasteboard will be empty.  The event mask is temporarily reset to enable dragging events.

A locked cell retains its contents despite the drag.  Unless the cell is reallyLocked, the user can temporarily unlock the cell by holding down the command key.  Notice that the cell keeps itself registered as a user of its current delegate until after the drag completes.  The pasteboard is not presently smart enough to do reference counting itself.

This code sidesteps [self  setDelegate: nil]  to avoid a clumsy looking erase followed by an immediate redraw.  It's not 100% semantically correct, though, so if a user manages to drag really, really, really fast, there might be trouble.

*/


- dragIcon: (NXEvent *) event  inRect: (const NXRect *) cellFrame  ofView: view
{
	Pasteboard
		* pboard = [Pasteboard  newName: NXDragPboard];
	NXEvent
		mouseDown = * event;
	NXRect
		iconRect = * cellFrame;
	NXPoint
		offset;
	id
		old = delegate,
		theImage = image;
	
	[[view  window]  addToEventMask: NX_MOUSEDRAGGEDMASK];
	while ((event = [NXApp  getNextEvent: NX_ALLEVENTS])->type ==
				 NX_MOUSEDRAGGED)
	{
		offset.x = event->location.x - mouseDown.location.x;
		offset.y = event->location.y - mouseDown.location.y;
		
		if (abs(offset.x) + abs(offset.y) > HYSTERESIS)
		{
			[old  addUser: self];
			if (delegate != nil)
					[delegate  copyToPasteboard: pboard];	else
					[pboard  declareTypes: NULL  num: 0  owner: self];
			
			[self  getIconRect: &iconRect];
			iconRect.origin.y += iconRect.size.height;
			
			if (flags.container && !flags.reallyLocked &&
					(!flags.locked || (event->flags & NX_COMMANDMASK)))
			{
				[delegate  removeUser: self];
				[self  setState: 0];
				delegate = nil;
				cFlags1.highlighted = NO;
			}
			
			[view  dragImage: theImage 
						  at: &iconRect.origin 
					  offset: &offset 
					   event: &mouseDown
				  pasteboard: pboard
					  source: view
				   slideBack: YES];
			
			if (delegate == nil  &&  old != nil) [self  setDelegate: nil];
			[old  removeUser: self];
			
			break;
		}
	}
	
	[[view  window]  removeFromEventMask: NX_MOUSEDRAGGEDMASK];
	return self;
}


/* ========================================================================== */


/*

The method below edits the cell's title.  The cell installs itself as the text delegate so that it can make appropriate changes when the editing finishes.  The appkit seems too slow to have the editor intercept an initial double click, hence the otherwise unnecessary check to make sure we're not already editing.

*/


- editTitle: (NXEvent *) event  inRect:(const NXRect *) cellFrame  ofView: view
{
	NXRect
		titleRect = * cellFrame;
	float
		background;
	id
		editor = [[view  window]  getFieldEditor: YES  for: self];
	BOOL
		canEdit = [self  isEditable],
		canSelect = canEdit;
	
	if ([editor  delegate] != self)
	{
		if (flags.multipleLines)
		{
			background = NX_LTGRAY;
			[self  getTitleRect: &titleRect];
		}
		
		else
		{
			background = NX_WHITE;
			titleRect.size.width += 10000;
			titleRect.origin.x -= 5000;
			[self  getTitleRect: &titleRect];
			[view  setClipping: YES];
		}
		
		{
			NXSize
				maxSize = { 10000.0,  titleRect.size.height },
				minSize = {     0.0,  titleRect.size.height };
			
			[view  endEditing];
			[[[[[[[[[[[[[[editor	setFrame: &titleRect]
									setHorizResizable: !flags.multipleLines]
									setVertResizable: NO]
									setMaxSize: &maxSize]
									setMinSize: &minSize]
									setAlignment: NX_CENTERED]
									setBackgroundGray: background]
									setTextGray: NX_BLACK]
									setOpaque: background != -1.0]
									setFont: support]
									setEditable: canEdit]
									setSelectable: canSelect]
									setText: contents]
									setDelegate: self];
		}
		
		[view  addSubview: editor];
		[editor  display];
	}
	
	if (event != NULL)
	{
		[[view  window]  makeFirstResponder: editor];
		[editor  mouseDown: event];
	}
	
	return self;
}


/* ========================================================================== */


/*

The cell updates its title and delegate's name whenever its editor resigns first responder status.

*/


- (BOOL) textWillEnd: sender
{
	char
		name [1000];
	
	if ([self  isEditable])
	{
		[sender  getSubstring: name  start: 0  length: 1000];
		if (name[0] == '\0') strcat (name, " ");
		if (strcmp (name, contents))
		{
			[self  setStringValue: name];
			[delegate  setName: name];
		}
	}
	
	return NO;
}


- textDidResize: sender
		oldBounds: (const NXRect *) oldBounds
		invalid: (NXRect *) invalidRect
{
	[[sender  window]  invalidateCursorRectsForView: [self  controlView]];
	return self;
}


- editor
{
	id
		editor = [[[self	controlView]
							window]
							getFieldEditor: NO  for: self];
	
	return ([editor  delegate] == self)  ?
				editor:
				nil;
}


@end


/* ========================================================================== */


/*

Here is the routine used to initialize the ghost image and highlight mask.  It's called anew every time a ghost image needs to be drawn so that it can resize everything to be large enough for the current image.

*/


static void
initGhostImages (NXSize size)
{
	NXSize
		imageSize = { 0.0,  0.0 },
		maskSize =  { 0.0,  0.0 };
	
	[ghostImage  getSize: &imageSize];
	[ghostHighlightMask  getSize: &maskSize];
	
	if (size.width > imageSize.width  ||  size.height > imageSize.height)
	{
		[ghostImage  free];
		ghostImage = [[NXImage  alloc]  initSize: &size];
	}
	
	if (size.width > maskSize.width  ||  size.height > maskSize.height)
	{
		[ghostHighlightMask  free];
		ghostHighlightMask = [[NXImage  alloc]  initSize: &size];
		
		[ghostHighlightMask  lockFocus];
			PSsetalpha (1.0 / 3.0);
			PSsetgray (1.0);
			PSrectfill (0.0, 0.0, size.width, size.height);
		[ghostHighlightMask  unlockFocus];
	}
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.