ftp.nice.ch/pub/next/science/mathematics/HippoDraw.2.0.s.tar.gz#/HippoDraw/Hippo.bproj/HGraphicView.m

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

/* Hippo Graphic View	by Paul Kunz	June 1991
 * a subclass of GraphicView in /NextDeveloper/Examples/Draw
 * to add or over-ride so that it can handle hippo Graphic objects
 *
 * Copyright (C)  1991  The Board of Trustees of
 * The Leland Stanford Junior University.  All Rights Reserved.
 */

#import "Draw.subproj/draw.h"
#import "hippo.h" 
#import "HGraphicView.h"

const char HGraphicView_h_rcsid[] = HGRAPHICVIEW_H_ID;
const char HGraphicView_m_rcsid[] = "$Id: HGraphicView.m,v 2.47.2.3 1994/02/08 20:28:51 rensing Exp $";

#import "HDrawApp.h"
#import "HTuple.h"
#import "InspectCut.h"
#import "InspectPlot.h"
#import "InspectTuple.h"
#import "NewInspector.h"
#import "Plot.h"
#import "Overlay.h"
#import "PageMarker.h"
#import "SaveWindow.h"

#define DIRTY(condition) \
    if (condition && !gvFlags.dirty) { \
	gvFlags.dirty = YES; \
	[window setDocEdited:YES]; \
    }  
      
@interface HGraphicView(PrivateMethods)

- placeGraphic:graphic at:(const NXPoint *)location;
/*
 * Places the graphic centered at the given location on the page.
 * If the graphic is too big, the user is asked whether the graphic
 * should be scaled.
 */

- tupleList;
  /*
   * Returns a List object containing the HTuple objects needed
   * for the Plots in the View.
   */
   
@end

#define DEFAULTCOLS	"2"
#define DEFAULTROWS	"3"

#define CURRENT_VERSION		FIP_ARCHIVE_FIX

@implementation HGraphicView : GraphicView

+ initialize
{
    static NXDefaultsVector HippoDrawDefaults = {
	{ "NumPlotCols", DEFAULTCOLS },
	{ "NumPlotRows", DEFAULTROWS },
	{ NULL, NULL }
    };
  
  /* WARNING: GraphicView also has dependency on class version,
   * see its -read: method.   Watch out of future releases of
   * that class, in case they conflict with our version numbering.
   */
    [self setVersion:CURRENT_VERSION];

    NXRegisterDefaults("HippoDraw", HippoDrawDefaults);
    return self;
}
+ convert:(NXTypedStream *)ts to:(const char *)type 
           using:(SEL)writer toPasteboard:pb
 /*
  * Over ride this method in order to make scrapper a HGraphicView
  */
{
    id w, list;
    NXZone *zone;
    NXStream *stream;
    HGraphicView *scrapper;
    NXRect scrapperFrame = {{0.0, 0.0}, {11.0*72.0, 14.0*72.0}};
    
    if (!ts) return self;

    zone = NXCreateZone(vm_page_size, vm_page_size, NO);
    NXNameZone(zone, "Scrapper");
    scrapper = [[HGraphicView allocFromZone:zone] initFrame:&scrapperFrame];
    NXSetTypedStreamZone(ts, zone);
    list = NXReadObject(ts);
    [scrapper getBBox:&scrapperFrame of:list];
    scrapperFrame.size.width += scrapperFrame.origin.x;
    scrapperFrame.size.height += scrapperFrame.origin.y;
    scrapperFrame.origin.x = scrapperFrame.origin.y = 0.0;
    [scrapper sizeTo:scrapperFrame.size.width :scrapperFrame.size.height];
    w = [[Window allocFromZone:zone] initContent:&scrapperFrame
					   style:NX_PLAINSTYLE
					 backing:NX_NONRETAINED
				      buttonMask:0
					   defer:NO];
    [w reenableDisplay];
    [w setContentView:scrapper];
    stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
    [scrapper perform:writer with:(id)stream with:list];
    [pb writeType:type fromStream:stream];
    [list freeObjects];
    [list free];
    NXCloseMemory(stream, NX_FREEBUFFER);
    [w free];
    NXDestroyZone(zone);

    return self;
}
- initFrame:(const NXRect *)frameRect
{
    [super initFrame:frameRect];
    rotateCursor = NULL;
    hippoDraw = NXGetNamedObject("HDrawInstance", NXApp);
    return self;
}

- (BOOL)validateCommand:menuCell
{
    id			g;
    SEL			action;
    unsigned int	i, scount, pcount;
    BOOL		retval;
    
    retval = [super validateCommand:menuCell];
    if ( retval == NO ) {
        return NO;
    }
    action = [menuCell action];
    scount = [slist count]; 
    
    if ( action == @selector(duplicate:) && scount == 0)
    {
	 return NO;
    }
    
    else if ( action == @selector(overlay:) ||
	     action == @selector(alignXRange:) ||
	     action == @selector(alignXNumBins:) ||
	     action == @selector(alignYRange:) ||
	     action == @selector(alignYNumBins:) ) 
    {
        if ( scount == 0 ) return NO;

	pcount = 0;
	for ( i = 0; i < scount && pcount < 2; i ++ ) {
	    g = [slist objectAt:i];
	    if ( [g isKindOf:[Plot class]] ) {
	        pcount++;
	    }
	}
	if ( pcount < 2 ) {
	    return NO;
	}
    }
    else if ( action == @selector(unoverlay:) ) {
	if ( scount == 0 ) return NO;

	for ( i = 0; i < scount; i++ ) {
	    g = [slist objectAt:i];
	    if ( [g isKindOf:[Overlay class]] ) {
	        return YES;
	    }
	}
	return NO;
    } else if ( action == @selector(saveWindowTo:) ) {
 	for ( i = 0; i < scount; i++ ) {
	    g = [slist objectAt:i];
	    if ( [g isKindOf:[SaveWindow class]] ) {
	        return YES;
	    }
	}
	return NO;
    }       

    return YES;
}

- addPages:(int) n
{
     NXRect 	box;
     int i, j=n;
     id g;
          
     [[[self window] delegate] getPageFrame:&box];
     
     while (j--) 
     {
        /*
         * add a page marker 
         * it is inserted at the very top of the document.
         */
        [self notifyAncestorWhenFrameChanged:YES];
        [[[PageMarker allocFromZone:[self zone]] init] addSelf: self];
   
        /*
         * extend the document
         */
        [ self sizeBy: 0.0 : box.size.height ];
      }
      
      /*
       * the pages got added at the top, so move everything up
       */
      box.size.width = 0.0;
      box.size.height *= n;      
        i = [glist count];
        while(i--) {
   	     g = [glist objectAt:i];
	     if (![g isKindOf:[PageMarker class]])
		 [g moveBy: (const NXPoint *) &box.size];
     }

     [self getFrame:&box ];
     [self cache:&box ];
     [window flushWindow];

     return self;
}

- (BOOL) hasCutsSelected
{
    id		g;
    int		i;
    
    i = [slist count];
    while (i-- ) {
        g = [slist objectAt:i];
        if ( [g isKindOf:[Plot class]] ) {
	    if ( [g isCutPlot] ) {
	        if ( [g dependCount] ) {
		    return YES;
		}
	    }
	    if ( [g hasCut] ) {
	        return YES;
	    }
	}
    }
    return NO;
}
- addPlotOfType:(graphtype_t) type
{
    InspectTuple	*inspector;
    Plot		*plot;
    
    inspector = [hippoDraw inspectTuple];
    [inspector load:self];
    plot = [inspector addPlotOfType:type];
    [self addPlot:plot andSelect:YES];
    return plot;
}

- addPlot:plot andSelect:(BOOL) selFlag
{
    id		theInspector;
    NXRect	bbox;
    NXPoint location;
    
    if ( selFlag ) {
        [ self deselectAll:self];
    }

    [[[drawInstance inspectorPanel] delegate] initializeGraphic:plot];
    [self calcDefaultPlotSize:&bbox];
    [self calcPlacement: &(bbox.size) result: &location];
    bbox.origin.x = location.x;
    bbox.origin.y = location.y;
    [ plot setBounds: &bbox ];

    /*
     * add to cutplot list if needed
     */
    if ([plot isCutPlot]) 
    {
         /* using the method causes it to be initialized */
	 [[self cutList] addObject:plot];
    }
    [plot setGraphicView: self];
    
    if (selFlag) {
	 [ self insertGraphic: plot];
    } else {
	 [ self insertGraphicNoSelect: plot];
	 [self recacheSelection];
    }
    
    [self scrollRectToVisible: &bbox];
    [window makeKeyWindow];
    if ( selFlag ) {
	 [NXApp updateWindows]; 
	 theInspector = [hippoDraw newInspector];
	 [theInspector orderFrontPanel:self];
    }
    
    [self dirty];
    return self;
}
- hTupleForFile:(const char *)filename index:(int) iValue
{
    id		inspectTuple;
    
    inspectTuple = [hippoDraw inspectTuple];
    return [inspectTuple hTupleForFile:filename index:iValue];
}
/*
- addCut:plot
{
    id	inspectCut;
    
    inspectCut = [hippoDraw inspectCut];
    [inspectCut addCut:plot];
    return self;
}
*/
     
- cutList
{
    if ( !cutList ) {
        cutList = [[List allocFromZone:[self zone]] initCount:0];
    }
    return cutList;
}
- plotList
{
    volatile id	plotList = nil;
    
    if ( !plotList ) {
        plotList = [[List allocFromZone:[self zone]] initCount:0];
    } else {
        [plotList empty];
    }
    [glist makeObjectsPerform:@selector(addPlotToList:) with:plotList];
    return plotList;
}
- (display *) displayList;
{
    List 	*plotList;
    Plot	*g;
    static display	*dispList = NULL;
    int		i, count, num_disp;
    
    plotList = [self plotList];
    count = [plotList count];
    if ( dispList != NULL ) {
	NX_ZONEREALLOC( [self zone], dispList, display, count+1 );
    } else {
        NX_ZONEMALLOC( [self zone], dispList, display, count+1 );
    }
    num_disp = 0;
    for ( i = 0; i < count; i ++ ) {
        g = [plotList objectAt:i];
	dispList[num_disp++] = [g histDisplay];
    }
    dispList[num_disp] = NULL;
    return dispList;
}
- firstPlot
{
    id		g;
    
    if ([slist count] == 1) {
	g = [slist objectAt:0];
	if ( [g isKindOf:[Overlay class]] ) {
	    return [g firstPlot];
	}
	if ( [ g isKindOf:[Plot class]] ) {
	    return g;
	}
    }
    return nil;
}


- reDrawPlot
{
    [self selectAll:self];
    [self deselectAll:self];
    [window flushWindow];

    return self;
}
#define stopTimer(timer) if (timer) { \
    NXEndTimer(timer); \
    timer = NULL; \
}

#define startTimer(timer) if (!timer) timer = NXBeginTimer(NULL, 0.1, 0.1);
#define MOVE_MASK NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK

- (BOOL)move:(NXEvent *)event
 /*
 * Moves the selection by cacheing the selected graphics into the
 * selection cache, then compositing them repeatedly as the user
 * moves the mouse.  The tracking loop uses TIMER events to autoscroll
 * at regular intervals.  TIMER events do not have valid mouse coordinates,
 * so the last coordinates are saved and restore when there is a TIMER event.
 */
{
    NXEvent peek;
    NXCoord dx, dy;
    NXPoint p, last;
    BOOL tracking = YES,command;
	NXPoint	mine;
	
	command = (event->flags & NX_COMMANDMASK) ? YES : NO;
	if (!command)
		command = (event->flags & NX_CONTROLMASK) ? YES : NO;
	if (command == NO) return [super move:event];
	
	/* change cursor */
	if (!rotateCursor) rotateCursor = [[NXCursor alloc] initFromImage:
					[NXImage findImageNamed:"rotCursor"]];
	[rotateCursor push];
	
    last = event->location;
    
    event = [NXApp getNextEvent:MOVE_MASK];
    if (event->type == NX_MOUSEUP) return NO;

    [self convertPoint:&last fromView:nil];
    
    [self lockFocus];

    while (tracking) {
	p = event->location;
	[self convertPoint:&p fromView:nil];
	dx = p.x - last.x;
	dy = p.y - last.y;
	mine.x= dx; mine.y = dy;
/*****	if (dx || dy) { *****/
/***** Even though we are doing an extra draw for the case MOUSEUP, where
     * dx and dy are zero, we need to pass-on the mouseup to 3D drawing.  The
     * threeD drawing sets a 'speedy flag' during mouse dragging, and we
     * need the mouseup to turn the speedy flag off and resume full drawing.
*****/
           [self graphicsPerform:@selector(mouseMoved:withKey:) with:(id)&mine
		 with:(id)&(event->flags) andDraw:YES];
		[window flushWindow];
		last = p;
/*****	}     *****/
	tracking = (event->type != NX_MOUSEUP);
	if (tracking) {
	  	p = event->location;
	    if (![NXApp peekNextEvent:MOVE_MASK into:&peek]) {
		event = [NXApp getNextEvent:MOVE_MASK|NX_TIMERMASK];
	    } else {
		event = [NXApp getNextEvent:MOVE_MASK];
	    }
	    if (event->type == NX_TIMER) {
			event->location = p;
		}
	}
    }

      
	[rotateCursor pop];
	
    [window flushWindow];
    [self unlockFocus];

    return YES;
}
- graphicsPerformNOP: g
{
    NXRect              affectedBounds;

    [g getExtendedBounds:&affectedBounds];
    [self cache:&affectedBounds];

    return self;
}
- graphicsPerform:(SEL)aSelector with:(void *)argument
                   andDraw:(BOOL)flag inList:aList
{
    id		savesList;
    
    savesList = slist;
    slist = aList;
    [self graphicsPerform:aSelector with:argument andDraw:flag];
    slist = savesList;

    [self dirty];
    return self;
}
- graphicsPerform:(SEL)aSelector with:(void *)argument1 with:(void *)argument2 andDraw:(BOOL)flag
{
    id g;
    int i, count;
    NXRect eb, affectedBounds;

    if (flag) {
	count = [slist count];
	if (count) {
	    [[slist objectAt:0] getExtendedBounds:&affectedBounds];
	    for (i = 1; i < count; i++) {
		g = [slist objectAt:i];
		NXUnionRect([g getExtendedBounds:&eb], &affectedBounds);
	    }
	    for (i = 0; i < count; i++) {
		g = [slist objectAt:i];
		[g perform:aSelector with:argument1 with:argument2];
		NXUnionRect([g getExtendedBounds:&eb], &affectedBounds);
	    }
	    [self cache:&affectedBounds];
	}
    } else {
	[self graphicsPerform:aSelector with:argument1 with:argument2];
    }

    [self dirty];
    return self;
}

- graphicsPerform:(SEL)aSelector with:arg1 with:arg2
{
    id		g;
    unsigned	i, count;
    
    count = [slist count];
    for ( i = 0; i < count; i++ ) {
        g = [slist objectAt:i];
	[g perform:aSelector with:arg1 with:arg2];
    }

    [self dirty];
    return self;
}

- copyPSToPasteboard:pboard
{
    char *data;
    NXStream *stream;   
    const char *types[1];
    int length, maxlen;

    if ([slist count]) {
	types[0] = NXPostScriptPboardType;
	stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
	[ self copySelectionAsPS:stream];
	NXGetMemoryBuffer(stream, &data, &length, &maxlen);
	[pboard declareTypes:types num:1 owner:[self class]];
	[pboard writeType:NXPostScriptPboardType data:data length:length];
	NXCloseMemory(stream, NX_FREEBUFFER);
	return self;
    } else {
	return nil;
    }
}
- writePSToStream:(NXStream *)stream
 /* over-ride GraphicView method to add one line */
{
    NXRect bbox;

    if (stream) {
	[self getBBox:&bbox of:glist];
	[self bindDisplaysInList:glist]; /* Added line */
	[self copyPSCodeInside:&bbox to:stream];
    }

    return self;
}

- bindDisplays
{
    [ self bindDisplaysInList:glist];
    return self;
}

- bindDisplaysInList:list
{
    [list makeObjectsPerform:@selector(setGraphicView:) with:self];
    [list makeObjectsPerform:@selector(bindReference)];
    [list makeObjectsPerform:@selector(bindCuts)];
    [list makeObjectsPerform:@selector(replaceDispFuncs)];
    return self;
}
- replace:oldTuple with:newTuple
{
    id			g;
    unsigned int	i;
    
    i = [glist count];
    while ( i-- ) {
        g = [glist objectAt:i];
	[g replace:oldTuple with:newTuple];
    }
    return self;
}
- closeTupleFile:(const char *) filename
{
    List		*plotList;
    InspectTuple	*inspectTuple;
    HTuple		*htuple;
    Plot		*plot;
    int			i, j, pcount;
    
    plotList = [self plotList];
    pcount = [plotList count];
    if ( !pcount ) {
        return self;
    }
    inspectTuple = [hippoDraw inspectTuple];
    tupleList = [inspectTuple tupleListForFile:filename];
    if ( ![tupleList count] ) {
        return self;
    }
    for ( i = 0; i < pcount; i++) {
        plot = [plotList objectAt:i];
	htuple = [plot hTuple];
	j = [tupleList indexOf:htuple];
	if ( j != NX_NOT_IN_LIST ) {
	    [ plot closeTuple ];
        }
    }
    return self;
}
	       
- openTuple:pasteBoard
    userData:(const char *)args
    error:(char **)errorMsg
{
    id		inspectTuple;
    id		retval;
    int         graphtype;

    inspectTuple = [hippoDraw inspectTuple];
    retval = [inspectTuple openTuple:pasteBoard
                           userData:args
			   error:errorMsg];
    if ( retval == nil ) {
        return self;
    }
    sscanf(args, "%i", &graphtype );
    [self addPlotOfType:graphtype];
    return self;
}
- calcDefaultPlotSize:(NXRect *) bbox
{
    NXRect		frameRect;
    const char		*defaultValue;
    unsigned int	cols, rows;
    float		h, w;
    
    defaultValue = NXGetDefaultValue("HippoDraw", "NumPlotCols" );
    sscanf( defaultValue, "%u", &cols );
    if ( cols <= 0 ) {
        sscanf( DEFAULTCOLS, "%u", &cols );
    }
    defaultValue = NXGetDefaultValue("HippoDraw", "NumPlotRows" );
    sscanf( defaultValue, "%u", &rows );
    if ( rows <= 0 ) {
        sscanf( DEFAULTROWS, "%u", &rows );
    }

    [[[self window] delegate] getPageFrame:&frameRect];
    if ( frameRect.size.height > frameRect.size.width ) {
	w = (frameRect.size.width - 10.0*(cols-1)) / cols;
	h = (frameRect.size.height - 10.0*(rows-1)) / rows;
    } else {
	w = (frameRect.size.width - 10.0*(rows-1)) / rows;
	h = (frameRect.size.height - 10.0*(cols-1)) / cols;
    }
    /* truncate to the factor of 10 */
    w = 10.0 * (int)(w/10.0);
    h = 10.0 * (int)(h/10.0);
    NXSetRect( bbox, 0, 0, w, h );
    return self;
}
- startArchivingTo:(const char *)directory
{
    HTuple	*hTuple;
    id		inspector;
    int		i;
    
    tupleList = [self tupleList];
    i = [tupleList count];
    while( i-- ) {
        hTuple = [tupleList objectAt:i];
	[hTuple startArchivingTo:directory];
    }
    if ( [self hasPFunctions] ) {
        inspector = [hippoDraw inspectPFunc];
	[inspector load:self];
 	[inspector startArchivingTo:directory];
    }
    return self;
}
- (BOOL) hasPFunctions
{
    List	*funcList;
    List	*classList;
    List	*plotList;
    Plot	*plot;
    int		i;
    
    classList = [[List allocFromZone:[self zone]] initCount:0];
    plotList = [self plotList];
    i = [plotList count];    
    while (i--) {
        plot = [plotList objectAt:i];
	funcList = [plot functionList];
	if ( funcList && [funcList count] ) {
	    return YES;
	}
    }
    return NO;
}
- (int) finishUnarchivingFrom:(const char *)directory
{
    HTuple	 *hTuple;
    InspectTuple *inspector;
    int		 i, irc;
    
    i = [tupleList count];
    while ( i-- ) {
        hTuple = [tupleList objectAt:i];
	irc = [hTuple finishUnarchivingFrom:directory];
	if ( irc != 0 ) return irc;
    }
    inspector = [hippoDraw inspectTuple];
    [inspector updateColBrowser]; /* in case functions added columns */
  /* Now can safely bind the displays to the n-tuples */
    [self bindDisplays];
    
  /* The following was in GraphicView awake method, but was taken
   * out so we can bind the displays first and we need the know the
   * directory before be can un-archive functions */
    if (!InMsgPrint) {
        [self createCacheWindow];
	[self cache:&bounds andUpdateLinks:NO];
    }
    return 0;
}
- write:(NXTypedStream *)stream
{
    id			cut;
    unsigned int	i, count;
    
    [super write:stream];
    NXWriteObject( stream, tupleList );
    if ( cutList ) {
        count = [cutList count];
    } else {
        count = 0;
    }
    NXWriteType( stream, "i", &count );
    for ( i = 0; i < count; i++ ) {
        cut = [cutList objectAt:i];
	NXWriteObjectReference( stream, cut );
    }
    return self;
}
- read:(NXTypedStream *)stream
{
    id			cut;
    unsigned int	i, count;
    
    [super read:stream];
    
    tupleList = NXReadObject( stream );
    NXReadType( stream, "i", &count );
    if ( count ) {
        cutList = [[List allocFromZone:[self zone]] initCount:0];
    }
    for ( i = 0; i < count; i++ ) {
        cut = NXReadObject( stream );
	if ( cut ) {
	    [cutList addObject:cut];
	}
    }
    return self;
}
- awake
{
    id		inspectTuple;
    
    hippoDraw = NXGetNamedObject("HDrawInstance", NXApp);
    if ( tupleList && [tupleList count] ) {
	[ hippoDraw orderFrontTupleInsp:self];
	inspectTuple = [hippoDraw inspectTuple];
	if ([inspectTuple addTuplesIfAbsent:tupleList] != HD_OK) {
	    NX_RAISE(NX_APPBASE,NULL,NULL);
	}
    }
    return [super awake];
}

/* Following methods makes up for used method not implemented
 * in the NS 3.0 version of GraphicView.
 */

- graphicsPerformSingle:(SEL)aSelector with:(void *)argument on:(id) g
{
    NXRect  affectedBounds;

	    	[g getExtendedBounds:&affectedBounds];
		[g perform:aSelector with:argument];
	    	[self cache:&affectedBounds];

    return self;
}
- loadImageFromStream:(NXStream *)stream at:(const NXPoint *)location allowAlpha:(BOOL)alphaOk
/*
 * Creates a new Image object using the PostScript or TIFF found in the
 * given stream and inserts it as the only item in the selection (i.e.
 * it deslects everything else).   The new Graphic is centered at the
 * given point p, and the GraphicView is scrolled to make the Graphic
 * visible.
 */
{
//    return [self placeGraphic:[[Image allocFromZone:[self zone]] initFromStream:stream allowAlpha:alphaOk] at:location];
    
    return [self placeGraphic:[[Image allocFromZone:[self zone]] initFromStream:stream] at:location];
}
- insertGraphicNoSelect:graphic
/*
 * Inserts the specified graphic into the glist and draws it.
 */
{
    NXRect eb;

    if (graphic) {
	[graphic deselect];
	[glist insertObject:graphic at:0];
	[self cache:[graphic getExtendedBounds:&eb]];
	[window flushWindow];
//	DIRTY(YES);
    }

    return self;
}

- copySelectionAsPS:(NXStream *)stream
{
    id savedglist;

    if (stream && [slist count]) {
	savedglist = glist;
	glist = slist;
	[self writePSToStream:stream];
	glist = savedglist;
    } else {
	return nil;
    }

    return self;
}

- pasteFromPasteboard:(Pasteboard *)pboard andLink:(LinkType)doLink at:(const NXPoint *)center
{
    List 	*pblist = nil;
    Plot 	*graphic;
    NXRect	visibleRect;
    NXRect	graphicRect;
    NXRect	pageRect;
    NXCoord	delta;
    int		i, ndely;
    
    pblist = [super pasteFromPasteboard:pboard andLink:doLink at:center];
    if ( pblist ) {
        i = [pblist count];
	while (i--) {
	    graphic = [pblist objectAt:i];
	    if ( [graphic isKindOf:[Plot class]] ) {
		[graphic setGraphicView:self];
	    }
	    if ( [self getVisibleRect:&visibleRect] ) {
	        [graphic getBounds:&graphicRect];
		if ( !NXContainsRect(&frame, &graphicRect) ) {
		    delta = frame.origin.y - graphicRect.origin.y;
		    [[[self window] delegate] getPageFrame:&pageRect];
		    ndely = delta/pageRect.size.height;
		    graphicRect.origin.y += ndely * pageRect.size.height;
		    [graphic setBounds:&graphicRect];
		}
		if ( !NXContainsRect(&frame, &graphicRect) ) {
		    delta = frame.origin.x - graphicRect.origin.x;
		    [[[self window] delegate] getPageFrame:&pageRect];
		    ndely = delta/pageRect.size.width;
		    graphicRect.origin.x += ndely * pageRect.size.width;
		    [graphic setBounds:&graphicRect];
		}
	    }
	}
    	[self bindDisplaysInList:pblist];  /* Hippo added */
    }
    return pblist;
}

- (BOOL) isAllowed
{
    id		g;
    int		i;
    
    i = [slist count];
    while (i-- ) {
        g = [slist objectAt:i];
        if ( [g isKindOf:[Plot class]] ) {
	    if ( [g isCutPlot] ) {
	        if ( [g dependCount] ) {
		    NXRunAlertPanel("Can not delete",
			      "Selected graphic include plots that display"
			      " a cut.  Remove cut from target plot(s) first.",
			      "OK", NULL, NULL);
		    return NO;
		}
	    }
	}
    }
    return YES;
}

- calcPlacement: (const NXSize *)theSize result:(NXPoint *)location
{
     NXRect	curRect;
     NXRect	frameRect, box;
     NXRect	sliceRect, filledRect;
     float	pageHeight;
     id 	g, myglist;
     int 	below=1, i;
     
     [ self getFrame: &frameRect ];

     myglist = [[List allocFromZone:[self zone]] init];
     for (i=[glist count]-1; i>=0; i--)
     {
	  g = [glist objectAt:i];
	  if (![g isKindOf:[PageMarker class]])
	       [myglist addObject:g];
     }
     [ self getBBox:&curRect of:myglist ];
     
     /*
      * when there is nothing in the document, bounding box comes back with 
      *  all 0's. Reset to region at top of page.
      */
     if ( NXEmptyRect(&curRect) )  {
	  curRect.origin.x = 0.0;
	  curRect.origin.y = frameRect.size.height;
     } else if (curRect.size.height >= theSize->height) {
	  /*
	   * see if there is room on right at bottom of bounding box.
	   */
	  NXDivideRect( &curRect, &sliceRect, theSize->height, 1 );
	  NXSetRect( &filledRect, 0.0, 0.0, 0.0, 0.0 );
	  i = [myglist count];
	  while ( i-- ) {
	       g = [myglist objectAt:i];
	       [g getBounds:&box];
	       NXIntersectionRect( &sliceRect, &box );
	       NXUnionRect( &box, &filledRect );
	  }
	  location->x = filledRect.origin.x + filledRect.size.width + 10.0;
	  location->y = filledRect.origin.y;
	  below = (location->x + theSize->width) > frameRect.size.width;
	  NXUnionRect( &sliceRect, &curRect );
     }

     [[[self window] delegate] getPageFrame:&box];
     pageHeight = box.size.height;
     if (below) {
	  location->x = 0.0;
	  location->y = curRect.origin.y - theSize->height;
	  if ((curRect.origin.y-frameRect.origin.y) < theSize->height)
	  {
	       int npages = (int)((frameRect.origin.y - location->y)
				  / pageHeight) + 1;
	       [ self addPages: npages];
	       /* 
	        * everything was moved up by npages pages, so change 
		* the current bounding box.
	        */
	       location->y += npages * pageHeight;
	  }
     }

     /*
      * if size is shorter than a page, make sure stuff
      *  does not cross page boundary.
      */
     if (theSize->height < pageHeight &&
         (int)(location->y/pageHeight) != 
	 (int)((location->y + theSize->height)/pageHeight) )
     {
	  location->y = pageHeight * (int)(location->y/pageHeight + 1) 
	       - theSize->height;
     }
     
     location->x = 10.0 * (int)(location->x / 10.0); /* round to nearest 10 */
     location->y = 10.0 * (int)(location->y / 10.0);

     [myglist free];
     return self;
}


@end

/* Private methods, i.e. only called by self */

@implementation HGraphicView(PrivateMethods)

- placeGraphic:graphic at:(const NXPoint *)location
/*
 * Places the graphic centered at the given location on the page.
 * If the graphic is too big, the user is asked whether the graphic
 * should be scaled.
 */
{
    int scale;
    NXPoint offset;
    float sx, sy, factor;
    NXRect gbounds, myBounds;

    if (graphic) {
	[graphic getExtendedBounds:&gbounds];
	if (gbounds.size.width > bounds.size.width || gbounds.size.height > bounds.size.height) {
	    scale = NXRunAlertPanel("Load Image",
		"The image is too large to fit on the page.  Scale it to fit?",
		"Scale", "Don't Scale", "Cancel");
	    if (scale < 0) {
		[graphic free];
		return self;
	    } else if (scale > 0) {
		sx = (bounds.size.width / gbounds.size.width) * 0.95;
		sy = (bounds.size.height / gbounds.size.height) * 0.95;
		factor = MIN(sx, sy);
		gbounds.size.width *= factor;
		gbounds.size.height *= factor;
		[graphic sizeTo:&gbounds.size];
	    }
	}
	if (location) [graphic centerAt:location];
	[graphic getExtendedBounds:&gbounds];
	myBounds = bounds;
	NXContainRect(&myBounds, &gbounds);
	offset.x = bounds.origin.x - myBounds.origin.x;
	offset.y = bounds.origin.y - myBounds.origin.y;
	if (offset.x || offset.y) [graphic moveBy:&offset];
	[self deselectAll:self];
	[self insertGraphic:graphic];
	[self scrollGraphicToVisible:graphic];
    }

    return graphic;
}

- tupleList
{
    if ( !tupleList ) {
        tupleList = [[List allocFromZone:[self zone]] initCount:0];
    } else {
        [tupleList empty];
    }
    [glist makeObjectsPerform:@selector(addHTupleToList:) with:tupleList];
    return tupleList;
}

@end

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