/* HDraw	By Paul Kunz	June 1991
 * derived from DrawApp in Draw, a NeXT, inc. example application
 * Global object for application incorporating HippoDraw.
 *
 * Copyright (C)  1991  The Board of Trustees of
 * The Leland Stanford Junior University.  All Rights Reserved.
 */
 
#import "HDraw.h"

const char HDraw_h_rcsid[] = HDRAW_H_ID;
const char HDraw_m_rcsid[] = "$Id: HDraw.m,v 1.32 1992/04/07 17:49:12 pfkeb Rel $";

#import "DrawDocument.h"
#import "HGraphicView.h"
#import "DrawPageLayout.h"
#import "Inspector.h"
#import "InspectAxes.h"
#import "InspectCut.h"
#import "InspectData.h"
#import "InspectPlot.h"
#import "InspectStat.h"
#import "InspectTuple.h"
#import "NewInspector.h"
#import "Plot.h"
#import "draw.h"

#import <appkit/Application.h>
#import <appkit/Cursor.h>
#import <appkit/Listener.h>
#import <appkit/Matrix.h>
#import <appkit/Menu.h>
#import <appkit/MenuCell.h>
#import <appkit/NXColorPanel.h>
#import <appkit/OpenPanel.h>
#import <appkit/Pasteboard.h>
#import <appkit/Text.h>
#import <appkit/defaults.h>
#import <appkit/nextstd.h>
#import <objc/hashtable.h>	/* for NXCopyStringBuffer() */
#import <objc/List.h>
#import <sys/param.h>
#import <ldsyms.h>
#import <sys/loader.h>
#import <libc.h>
#import <string.h>
#import <mach.h>

const int DrawVersion = 18;	/* minor version of the program */

@implementation HDraw : Object
/*
 * This class is used primarily to handle the opening of new documents
 * and other application-wide activity (such as responding to messages from
 * the tool palette).  It listens for requests from the Workspace Manager
 * to open a draw-format file as well as target/action messages from the
 * New and Open... menu items.  It also keeps the menus in sync by
 * fielding the menu items' updateActions.
 */

/* Private C functions used to implement methods in this class. */

static void initMenu(id menu)
/*
 * Sets the updateAction for every menu item which sends to the
 * First Responder (i.e. their target is nil).  When autoupdate is on,
 * every event will be followed by an update of each of the menu items
 * which is visible.  This keep all unavailable menu items dimmed out
 * so that the user knows what options are available at any given time.
 * Returns the activate menu if is found in this menu.
 */ 
{
    int count;
    id matrix, cell;
    id matrixTarget, cellTarget;

    matrix = [menu itemList];
    matrixTarget = [matrix target];

    count = [matrix cellCount];
    while (count--) {
	cell = [matrix cellAt:count :0];
	cellTarget = [cell target];
	if (!matrixTarget && !cellTarget) {
	    [cell setUpdateAction:@selector(menuItemUpdate:) forMenu:menu];
	} else if ([cell hasSubmenu]) {
	    initMenu(cellTarget);
	}
    }
}

static id documentInWindow(id window)
/*
 * Checks to see if the passed window's delegate is a DrawDocument.
 * If it is, it returns that document, otherwise it returns nil.
 */
{
    id document = [window delegate];
    return [document isKindOf:[DrawDocument class]] ? document : nil;
}

static id findDocument(const char *name)
/*
 * Searches the window list looking for a DrawDocument with the specified name.
 * Returns the window containing the document if found.
 * If name == NULL then the first document found is returned.
 */
{
    int count;
    id document, window, windowList;

    windowList = [NXApp windowList];
    count = [windowList count];
    while (count--) {
	window = [windowList objectAt:count];
	document = documentInWindow(window);
	if (document && (!name || !strcmp([document filename], name))) {
	    return window;
	}
    }

    return nil;
}

static id openFile(const char *directory, const char *name, BOOL display)
/*
 * Opens a file with the given name in the specified directory.
 * If we already have that file open, it is ordered front.
 * Returns the document if successful, nil otherwise.
 */
{
    id window;
    char buffer[MAXPATHLEN+1], path[MAXPATHLEN+1];

    if (name && *name) {
	if (!directory) {
	    directory = ".";
	} else if (*directory != '/') {
	    strcpy(buffer, "./");
	    strcat(buffer, directory);
	    directory = buffer;
	}
	if (!chdir(directory) && getwd(path)) {
	    strcpy(path, directory);	/* to avoid /private/Net/... */
	    strcat(path, "/");
	    strcat(path, name);
	    window = findDocument(path);
	    if (window) {
		if (display) [window makeKeyAndOrderFront:window];
		return [window delegate];
	    } else {
		return [DrawDocument newFromFile:path];
	    }
	} else {
	    NXRunAlertPanel("Open", "Invalid path: %s", "OK", NULL, NULL, directory);
	}
    }

    return nil;
}

/* Public methods */

+ initialize
/*
 * Initializes the defaults.
 */
{
    const NXDefaultsVector DrawDefaults = {
	{ "KnobWidth", "5" },
	{ "KnobHeight", "5" },
	{ "KeyMotionDelta", "1"},
	{ "Quit", NULL },
	{ NULL, NULL }
    };

    NXRegisterDefaults("Draw", DrawDefaults);

    return self;
}
+ allocFromZone:(NXZone *)aZone
{
    NXZone	*zone;
    
    zone = NXCreateZone(vm_page_size, vm_page_size, YES);
    NXNameZone(zone, "HippoDraw");
    self = [super allocFromZone:zone];
    return self;
}

+ alloc
{
    return [self allocFromZone:NULL];
}

- init
 /*
  * PSInit() defines all the PostScript defs we use (see draw.psw).
  * setAutoupdate:YES means that updateWindows will be called after
  * every event is processed (this is how we keep our inspector and
  * our menu items up to date).
  */
{
    [NXApp loadNibSection:"HDraw.nib" owner:self
           withNames:YES fromZone:[self zone]];
    PSInit();
    [NXApp setAutoupdate:YES];
    
    theNewInspector = [[NewInspector allocFromZone:[self zone]] init ]; 
    inspectTuple = [[InspectTuple allocFromZone:[self zone]] initInspFor:self];
    inspectData =  [[InspectData allocFromZone:[self zone]] initInspFor:self];
    inspectPlot =  [[InspectPlot allocFromZone:[self zone]] initInspFor:self];
    inspectAxes =  [[InspectAxes allocFromZone:[self zone]] initInspFor:self];
    inspectStat =  [[InspectStat allocFromZone:[self zone]] initInspFor:self];
    inspectCut =   [[InspectCut allocFromZone:[self zone]]  initInspFor:self];
    return self;
}
- infoPanel:sender
{
    if( ! infoPanel ){
        [NXApp loadNibSection:"Info.nib" owner:self
               withNames:NO fromZone:[self zone]];
    }
    [infoPanel makeKeyAndOrderFront:sender];
    return self;    
}
- legalPanel:sender
{
    if( ! legalPanel ){
        [NXApp loadNibSection:"Legal.nib" owner:self
               withNames:NO fromZone:[self zone]];
    }
    [legalPanel makeKeyAndOrderFront:sender];
    return self;    
}
/* General application status and information querying/modifying methods. */

- currentGraphic
{
    return currentGraphic;
}

- currentDocument
{
    return documentInWindow([NXApp mainWindow]);
}

- (const char *)currentDirectory
{
    id openpanel = [OpenPanel new];

    const char *retval = [[self currentDocument] directory];
    if (!retval || !*retval) {
        [openpanel setDirectory:NXHomeDirectory()];
    	retval = [openpanel directory];
    }
    return retval;
}

/* Application-wide shared panels */

- saveToPanel
/*
 * Returns a SavePanel with the accessory view which allows the user to
 * pick which type of file she wants to save.
 */
{
    id savepanel = [SavePanel new];
    [savepanel setAccessoryView:savePanelAccessory];
    [spamatrix selectCellAt:0 :0];
    [savepanel setRequiredFileType:"hdraw"];
    return savepanel;
}

- saveAsPanel
/*
 * Returns a regular SavePanel with "hdraw" as the required file type.
 */
{
    id savepanel = [SavePanel new];
    [savepanel setAccessoryView:nil];
    [savepanel setRequiredFileType:"hdraw"];
    return savepanel;
}

- gridInspector
/*
 * Returns the application-wide inspector for a document's grid.
 * Note that if we haven't yet loaded the GridView panel, we do it.
 * The instance variable gridInspector is set in setGridInspector:
 * since it is set as an outlet of the owner (self, i.e. DrawApp).
 */
{
    if (!gridInspector) {
	NXZone *zone = NXCreateChildZone([self zone], vm_page_size, vm_page_size, YES);
	NXNameZone(zone, "GridView");
	[NXApp loadNibSection:"GridView.nib" owner:self withNames:NO fromZone:zone];
	NXMergeZone(zone);
    }
    return gridInspector;
}

- inspectorPanel
/*
 * Returns the application-wide inspector for Graphics.
 */
{
    return inspectorPanel;
}

- newInspector
{
    return theNewInspector;
}
- pageLayout
/*
 * Returns the application-wide DrawPageLayout panel.
 */
{
    static id dpl = nil;

    if (!dpl) {
	dpl = [DrawPageLayout new];
	[dpl setAccessoryView:plpAccessory];
	[dpl setSideForm:[plpAccessory findViewWithTag:1]];
	[dpl setTopBotForm:[plpAccessory findViewWithTag:2]];
    }

    return dpl;
}

- orderFrontInspectorPanel:sender
/*
 * Creates the inspector panel if it doesn't exist, then orders it front.
 */
{
    id accessory;
    NXZone *zone;
    NXRect ipFrame;

    if (!inspectorPanel) {
	inspectorPanel = [NXColorPanel newColorMask:NX_ALLMODESMASK];
	zone = [inspectorPanel zone];
	accessory = [NXApp loadNibSection:"InspectorPanel.nib" owner:self withNames:NO fromZone:zone];
	[inspectorPanel setDelegate:[accessory delegate]];
	[inspectorPanel setAccessoryView:[[[[accessory contentView] subviews] objectAt:0] removeFromSuperview]];
	[inspectorPanel setTitle:"Inspector"];
	[inspectorPanel setMode:NX_GRAYMODE];
	[inspectorPanel setTarget:[accessory delegate]];
	[inspectorPanel setShowAlpha:NO];
	[accessory getFrame:&ipFrame];
	[inspectorPanel moveTo:ipFrame.origin.x :ipFrame.origin.y];
	[accessory free];
	[[inspectorPanel delegate] preset];
    }
    [inspectorPanel orderFront:self];

    return self;
}
- orderFrontTupleInsp:sender
{

    theNewInspector = [ self newInspector];
    [theNewInspector orderFrontPanel:self];
    return self;
}
- inspectTuple
{
    return inspectTuple;
}
- addTuple:(ntuple) nt
{
    return [inspectTuple addTuple:nt];
}
- inspectPlot
{
    return inspectPlot;
}
- inspectCut
{
    return inspectCut;
}
- orderFrontTools:sender
{
    [[tools window] orderFront:self];
    return self;
}
/* Target/Action methods */

- help:sender
{
    int size;
    char *data;
    NXStream *stream;
    id window, document;

    if (!(window = findDocument("/tmp/Help"))) {
	data = getsectdata("HELP", "document", &size);
	if (data) {
	    stream = NXOpenMemory(data, size, NX_READONLY);
	    if (stream) {
		document = [DrawDocument newFromStream:stream];
		[document setName:"Help" andDirectory:"/tmp"];
	    }
	    NXClose(stream);
	}
    } else {
	[window makeKeyAndOrderFront:self];
    }

    return self;
}

- new:sender
/*
 * Called by pressing New in the Window menu.
 */
{
    [DrawDocument new];
    return self;
}

- open:sender
/*
 * Called by pressing Open... in the Window menu.
 */
{
    const char *directory;
    const char *const *files;
    const char **myfiles;
    static const char *const drawType[2] = {"hdraw", NULL};
    id openpanel = [[OpenPanel new] allowMultipleFiles:YES];
    int		i, nfiles;

    directory = [[self currentDocument] directory];
    if (directory && (*directory == '/')) [openpanel setDirectory:directory];
    if ([openpanel runModalForTypes:drawType]) {
	files = [openpanel filenames];
	directory = [openpanel directory];
	/*
	 * we made need to use the open panel again, so need to
	 * save results now is separate array
	 */
	nfiles = 0;
	while ( files && *files ) {
	    nfiles++;
	    files++;
	}
	files = [openpanel filenames];
	NX_MALLOC( myfiles, const char *, nfiles );
	for ( i = 0; i < nfiles; i++ ) {
	    myfiles[i] = NXCopyStringBuffer(*files);
	    files++;
	}
	for ( i = 0; i < nfiles; i++ ) {
	    haveOpenedDocument = openFile(directory, myfiles[i], YES)
	                      || haveOpenedDocument;
	    NX_FREE( myfiles[i] );
	}
	NX_FREE( myfiles );
    }

    return self;
}
- openTuple:sender
{
    [inspectTuple openTuple:sender];
    return self;
}
- openTupleAsText:sender
{
    [inspectTuple openTupleAsText:sender];
    return self;
}
- openTuple:pasteBoard
    userData:(const char *)args
    error:(char **)errorMsg
{
    [[[DrawDocument new] view] openTuple:pasteBoard
                                   userData:args
				   error:errorMsg];

    return self;
}
- saveAll:sender
/*
 * Saves all the documents.
 */
{
    int count;
    id window, windowList;

    windowList = [NXApp windowList];
    count = [windowList count];
    while (count--) {
	window = [windowList objectAt:count];
	[documentInWindow(window) save:self];
    }

    return nil;
}

- terminate:sender
/*
 * Overridden to be sure all documents get an opportunity to be saved
 * before exiting the program.
 */
{
    int count, choice;
    id window, windowList, document;

    windowList = [NXApp windowList];
    count = [windowList count];
    while (count--) {
	window = [windowList objectAt:count];
 	document = [window delegate];
	if ([document respondsTo:@selector(needsSaving)] && [document needsSaving]) {
	    choice = NXRunAlertPanel("Quit", "You have unsaved documents.", "Review Unsaved", "Quit Anyway", "Cancel");
	    if (choice == NX_ALERTOTHER)  {
		return self;
	    } else if (choice == NX_ALERTDEFAULT) {
		count = [windowList count];
		while (count--) {
		    window = [windowList objectAt:count];
		    document = [window delegate];
		    if ([document respondsTo:@selector(windowWillClose:action:)]) {
			if ([document windowWillClose:window action:"Quit"]) {
			    [window close];
			} else {
			    return self;
			}
		    }
		}
	    }
	    break;
	}
    }
    [NXApp terminate:sender];

    return nil;
}
    
/*
 * Application object delegate methods.
 * Since we don't have an application delegate, messages that would
 * normally be sent there are sent to the Application object itself instead.
 */

- appDidInit:sender
/*
 * Makes the tool palette not ever become the key window.
 * Check for files to open specified on the command line.
 * Initialize the menus.
 * If there are no open documents, then open a blank one.
 */
{
    int i;
    char buffer[MAXPATHLEN+1];
    char *directory, *name, *ext;

    [[NXApp appListener ] setServicesDelegate:self];
    [[tools window] removeFromEventMask:NX_KEYDOWNMASK|NX_KEYUPMASK];
    [[tools window] orderFront:self];
    [[tools window] setFloatingPanel:YES];

    if (NXArgc > 1) {
	for (i = 1; i < NXArgc; i++) {
	    strcpy(buffer, NXArgv[i]);
	    ext = strrchr(buffer, '.');
	    if (!ext || strcmp(ext, ".hdraw")) strcat(buffer, ".hdraw");
	    name = strrchr(buffer, '/');
	    if (name) {
		*name++ = '\0';
		directory = buffer;
	    } else {
		name = buffer;
		directory = NULL;
	    }
	    haveOpenedDocument = openFile(directory, name, YES) || haveOpenedDocument;
	}
    }

    if (!haveOpenedDocument) [self new:self];

    if (NXGetDefaultValue([NXApp appName], "Quit")) {
	[NXApp activateSelf:YES];
	NXPing();
	[self terminate:self];
    }

    initMenu([NXApp mainMenu]);

    return self;
}

- (int)appOpenFile:(const char *)path type:(const char *)type
/*
 * This method is performed whenever a user double-clicks on an icon
 * in the Workspace Manager representing a Draw program document.
 */
{
    id   currentDoc;
    char *name;
    char directory[MAXPATHLEN+1];

    if (type && !strcmp(type, "hdraw")) {
	strcpy(directory, path);
	name = strrchr(directory, '/');
	if (name) {
	    *name++ = '\0';
	    if (openFile(directory, name, YES)) {
		haveOpenedDocument = YES;
		return YES;
	    }
	}
    }
    
    if ( type && !strcmp(type, "hippo") ) {
        currentDoc = [self currentDocument];
	if ( !currentDoc ) {
	    currentDoc = [DrawDocument new];
	}
	[ currentDoc openTupleFile:path ];
	haveOpenedDocument = YES;
	return YES;
    }

    return NO;
}

- (BOOL)appAcceptsAnotherFile:sender
/*
 * We accept any number of appOpenFile:type: messages.
 */
{
    return YES;
}

/* Listener/Speaker remote methods */

- (int)msgDirectory:(const char **)fullPath ok:(int *)flag
{
    *fullPath = [self currentDirectory];
    if (*fullPath) {
	*flag = YES;
    } else {
	*fullPath = "";
	*flag = NO;
    }
    return 0;
}

- (int)msgVersion:(const char **)aString ok:(int *)flag
{
    char buf[20];

    sprintf(buf, "2.0 (v%2d)", DrawVersion);
    *aString = NXCopyStringBuffer(buf);
    *flag = YES;

    return 0;
}

- (int)msgFile:(const char **)fullPath ok:(int *)flag
{
    const char *file;

    file = [[self currentDocument] filename];
    if (file) {
	*fullPath = file;
	*flag = YES;
    } else {
	*fullPath = "";
	*flag = NO;
    }

    return 0;
}

- (BOOL)shouldRunPrintPanel:sender
/*
 * When NXApp prints, don't bring up the PrintPanel.
 */
{
    return NO;
}

- (int)msgPrint:(const char *)fullPath ok:(int *)flag
{
    BOOL close;
    id document = nil;
    char *directory, *name;
    char path[MAXPATHLEN+1];
    char buffer[MAXPATHLEN+1];

    InMsgPrint = YES;
    strcpy(buffer, fullPath);
    name = strrchr(buffer, '/');
    if (name) {
	*name++ = '\0';
	directory = buffer;
    } else {
	name = buffer;
	directory = NULL;
    }
    if (!chdir(directory) && getwd(path)) {
	strcat(path, "/");
	strcat(path, name);
	document = [findDocument(path) delegate];
    }
    if (document) {
	close = NO;
    } else {
	document = openFile(directory, name, NO);
	if (document) haveOpenedDocument = YES;
	close = YES;
    }
    if (document && ![[document view] isEmpty]) {
	[NXApp setPrintInfo:[document printInfo]];
	[[document view] printPSCode:self];
	if (close) [[[document view] window] close];
	*flag = YES;
    } else {
	*flag = NO;
    }
    InMsgPrint = NO;

    return 0;
}

- (int)msgSelection:(const char **)bytes length:(int *)len
    asType:(const char *)aType ok:(int *)flag
{
    int maxlen;
    NXStream *stream;

    if (!strcmp(aType, DrawPboardType)) {
	stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
	if ([[[self currentDocument] view] copySelectionToStream:stream]) {
	    NXGetMemoryBuffer(stream, bytes, len, &maxlen);
	    *flag = YES;
	} else {
	    *flag = NO;
	}
	NXClose(stream);
    } else if (!strcmp(aType, NXPostScriptPboardType)) {
	stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
	if ([[[self currentDocument] view] copySelectionAsPS:stream]) {
	    NXGetMemoryBuffer(stream, bytes, len, &maxlen);
	    *flag = YES;
	} else {
	    *flag = NO;
	}
	NXClose(stream);
    } else if (!strcmp(aType, NXTIFFPboardType)) {
	stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
	if ([[[self currentDocument] view] copySelectionAsTIFF:stream]) {
	    NXGetMemoryBuffer(stream, bytes, len, &maxlen);
	    *flag = YES;
	} else {
	    *flag = NO;
	}
	NXClose(stream);
    } else {
	*flag = NO;
    }

    if (!*flag) {
	*bytes = "";
	*len = 0;
    }

    return 0;
}

- (int)msgCopyAsType:(const char *)aType ok:(int *)flag
{
    if (!strcmp(aType, NXPostScriptPboardType) ||
	!strcmp(aType, NXTIFFPboardType) ||
	!strcmp(aType, DrawPboardType)) {
	*flag = ([[[self currentDocument] view] copy:self] ? YES : NO);
    } else {
	*flag = NO;
    }
    return 0;
}

- (int)msgCutAsType:(const char *)aType ok:(int *)flag
{
    if (!strcmp(aType, NXPostScriptPboardType) ||
	!strcmp(aType, NXTIFFPboardType) ||
	!strcmp(aType, DrawPboardType)) {
	*flag = ([[[self currentDocument] view] cut:self] ? YES : NO);
    } else {
	*flag = NO;
    }
    return 0;
}

- (int)msgPaste:(int *)flag;
{
    *flag = ([[[self currentDocument] view] paste:self] ? YES : NO);
    return 0;
}

- (int)msgQuit:(int *)flag
{
    *flag = ([self terminate:self] ? NO : YES);
    return 0;
}

/* Global cursor setting */

- cursor
/*
 * This is called by DrawDocument objects who want to set the cursor
 * depending on what the currently selected tool is (as well as on whether
 * the Control key has been pressed indicating that the select tool is
 * temporarily set--see sendEvent:).
 */
{
    id theCursor = nil;
    if (!cursorPushed) theCursor = [[self currentGraphic] cursor];
    return theCursor ? theCursor : NXArrow;
}

- sendEvent:(NXEvent *)event
/*
 * We override this because we need to find out when the control key is down
 * so we can set the arrow cursor so the user knows she is (temporarily) in
 * select mode.
 */
{
    if (event && event->type < NX_KITDEFINED) {	/* mouse or keyboard event */
	if (event->flags & NX_CONTROLMASK) {
	    if (!cursorPushed && currentGraphic) {
		cursorPushed = YES;
		[[self currentDocument] resetCursor];
	    }
	} else if (cursorPushed) {
	    cursorPushed = NO;
	    [[self currentDocument] resetCursor];
	}
    }

    //	return [super sendEvent:event];
    return self;
}

/* Automatic update methods */

- (BOOL)menuItemUpdate:menuCell
/*
 * Method called by all menu items which send their actions to the
 * First Responder.  First, if the object which would respond were the
 * action sent down the responder chain also responds to the message
 * validateCommand:, then it is sent validateCommand: to determine
 * whether that command is valid now, otherwise, if there is a responder
 * to the message, then it is assumed that the item is valid.
 * The method returns YES if the cell has changed its appearance (so that
 * the caller (a Menu) knows to redraw it).
 */
{
    SEL action;
    id responder, target;
    BOOL enable;

    target = [menuCell target];
    enable = [menuCell isEnabled];

    if (!target) {
	action = [menuCell action];
	responder = [NXApp calcTargetForAction:action];
	if ([responder respondsTo:@selector(validateCommand:)]) {
	    enable = [responder validateCommand:menuCell];
	} else {
	    enable = responder ? YES : NO;
	}
    }

    if ([menuCell isEnabled] != enable) {
	[menuCell setEnabled:enable];
	return YES;
    } else {
	return NO;
    }
}

- (BOOL)validateCommand:menuCell
{
    SEL action = [menuCell action];

    if (action == @selector(saveAll:)) {
	return findDocument(NULL) ? YES : NO;
    }

    return YES;
}

/*
 * This is a very funky method and tricks of this sort are not generally
 * recommended, but this hack is done so that new Graphic subclasses can
 * be added to the program without changing even one line of code (except,
 * of course, to implement the subclass itself).
 *
 * The objective-C method objc_getClass() is used to find the factory object
 * corresponding to the name of the cell sending the setCurrentGraphic:
 * message (this is set in InterfaceBuilder using the Miscellaneous panel
 * in the Inspector window).
 *
 * Again, this is not recommended procedure, but it illustrates how
 * objective-C can be used to make some funky runtime dependent decisions.
 */

- setCurrentGraphic:sender
 /*
 * The sender's name is queried.  If that name corresponds to the name
 * of a class, then that class is set as the currentGraphic.  If not,
 * then the select tool is put into effect.
 *
 * Modified to handle Hippo 1D and 2D plots differently.   This is
 * needed until their Graphic can draw rectangle while resizing, 
 * then display plot when done. 
 * also avoid error message associated with selecting
 * selection tool -- Paul Kunz
 */
{
    id cell;
    const char *className;
    int 	tag;

    cell = [sender selectedCell];
    if (cell) {
        tag = [cell tag];
      /*
       * tag = -1 for selection tool
       *     = -2 for Graphic object
       *     = graphictype_t for Plot object
       * tag is set with Interface Builder
       */
	if ( tag < 0 ) {
	    if ( tag != (-1) ) { /* avoid selection tool */
		className = NXGetObjectName(cell);
		if (className) {
		    currentGraphic = objc_getClass(className);
		} else {
		    currentGraphic = nil;
		}
	    } else {
	        currentGraphic = nil;
	    }
	    if (!currentGraphic) {
	        [tools selectCellWithTag:(-1)];
	    }
	    [[self currentDocument] resetCursor];
	} else {
	    currentGraphic = [Plot class];
	    [self orderFrontTupleInsp:self];
	    [[[self currentDocument] view] addPlotOfType:tag];
	}
    } else {
        currentGraphic = nil;
        [tools selectCellWithTag:(-1)];
	[[self currentDocument] resetCursor];
    }

    return self;
}

@end
