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

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

#import "draw.h"

@implementation GraphicView(Links)

/* See the Links.rtf file for overview about Object Links in Draw. */

#define BUFFER_SIZE 1100
#define INT_WIDTH 11

/*
 * Returns an NXSelection describe the current Graphic's selected in the view.
 * If the user did Select All, then the gvFlags.selectAll bit is set and we
 * return the allSelection NXSelection.  If the user dragged out a rectangle,
 * then the dragRect rectangle is set and we return a ByRect NXSelection.
 * Otherwise, we return a ByGraphic NXSelection.
 *
 * We use the writeIdentifierTo: mechanism so that Group Graphic's can ask
 * their components to write out all their identifiers so that we have a
 * maximal chance of getting all the objects.
 */

- (NXSelection *)currentSelection
{
    NXRect sbounds;
    int i, graphicCount;
    NXSelection *retval = nil;
    char *s, *selbuf, buffer[BUFFER_SIZE];

    if (![slist count]) return [NXSelection emptySelection];

    if (gvFlags.selectAll) {
	if ([slist count] == [glist count]) {
	    return [NXSelection allSelection];
	} else {
	    gvFlags.selectAll = NO;
	}
    }

    if (dragRect) {
	sbounds = *dragRect;
	sprintf(buffer, "%d %d %d %d %d", ByRect, 
	    (int)sbounds.origin.x, (int)sbounds.origin.y,
	    (int)(sbounds.size.width+0.5), (int)(sbounds.size.height+0.5));
	selbuf = buffer;
    } else {
	graphicCount = 0;
	i = [slist count];
	while (i--) graphicCount += [[slist objectAt:i] graphicCount];
	if (graphicCount > (BUFFER_SIZE / INT_WIDTH)) {
	    NX_MALLOC(selbuf, char, graphicCount * INT_WIDTH);
	} else {
	    selbuf = buffer;
	}
	sprintf(buffer, "%d %d", ByList, graphicCount);
	s = selbuf + strlen(selbuf);
	i = [slist count];
	while (i--) {
	    *s++ = ' ';
	    [[slist objectAt:i] writeIdentifierTo:s];
	    s += strlen(s);
	}
    }

    retval = [[NXSelection allocFromZone:[self zone]] initWithDescription:selbuf length:strlen(selbuf)+1];

    if (selbuf != buffer) free(selbuf);

    return retval;
}

/*
 * Used for destination selections only.
 * Just extracts the unique identifier for the destination Image
 * or TextGraphic and then searches through the glist to find that
 * Graphic and returns it.
 *
 * Again, we use the graphicIdentifiedBy: mechanism so that we
 * descend into Group's of Graphics to find a destination.
 */

- (Graphic *)findGraphicInSelection:(NXSelection *)selection
{
    int i;
    Graphic *graphic;
    const char *selectionInfo;
    int selectionInfoLength, identifier, selectionType;

    selectionInfo = [selection descriptionOfLength:&selectionInfoLength];
    if (selectionInfo) {
	sscanf(selectionInfo, "%d %d", &selectionType, &identifier);
	if (selectionType == ByGraphic) {
	    for (i = [glist count]-1; i >= 0; i--) {
		if (graphic = [[glist objectAt:i] graphicIdentifiedBy:identifier]) return graphic;
	    }
	}
    }

    return nil;
}

/*
 * Returns YES and theRect is valid only if the selection is one which
 * the user created by dragging out a rectangle.
 */

- (BOOL)getRect:(NXRect *)theRect forSelection:(NXSelection *)selection
{
    NXRect stackRect;
    const char *selectionInfo;
    int selectionInfoLength;
    DrawSelectionType selectionType;

    if (selectionInfo = [selection descriptionOfLength:&selectionInfoLength]) {
	if (!theRect) theRect = &stackRect;
	sscanf(selectionInfo, "%d %f %f %f %f", (int *)&selectionType,
		&(theRect->origin.x), &(theRect->origin.y),
		&(theRect->size.width), &(theRect->size.height));
	if (selectionType == ByRect) return YES;
    }

    return NO;
}

/*
 * For source selections only.
 * Returns the list of Graphics in the current document which were
 * in the selection passed to this method.  Note that any Group 
 * which includes a Graphic in the passed selection will be included
 * in its entirety.
 */

- (List *)findGraphicsInSelection:(NXSelection *)selection
{
    int i, count;
    Graphic *graphic;
    List *list = nil;
    NXRect sBounds, gBounds;
    int selectionInfoLength;
    const char *s, *theGraphics;
    DrawSelectionType selectionType;

    if ([selection isEqual:[NXSelection allSelection]]) {
	count = [glist count];
	list = [[List allocFromZone:[self zone]] initCount:count];
	for (i = 0; i < count; i++) [list addObject:[glist objectAt:i]];
    } else if ([self getRect:&sBounds forSelection:selection]) {
	count = [glist count];
	list = [[List allocFromZone:[self zone]] init];
	for (i = 0; i < count; i++) {
	    graphic = [glist objectAt:i];
	    [graphic getBounds:&gBounds];
	    NXInsetRect(&gBounds, -0.1, -0.1);
	    if (NXIntersectsRect(&gBounds, &sBounds)) [list addObject:graphic];
	}
    } else if (s = [selection descriptionOfLength:&selectionInfoLength]) {
	sscanf(s, "%d %d", (int *)&selectionType, &count);
	if (selectionType == ByList) {
	    if (s = strchr(s, ' ')) s = strchr(s+1, ' ');
	    if (s++) {
		theGraphics = s;
		list = [[List allocFromZone:[self zone]] init];
		count = [glist count];
		for (i = 0; i < count; i++) {
		    graphic = [glist objectAt:i];
		    s = theGraphics;
		    while (s && *s) {
			if ([graphic graphicIdentifiedBy:atoi(s)]) {
			    [list addObject:graphic];
			    break;
			}
			if (s = strchr(s, ' ')) s++;
		    }
		}
	    }
	}
    }

    if (![list count]) {
	[list free];
	list = nil;
    }

    return list;
}

/*
 * Importing/Exporting links.
 */

/*
 * This method is called by copyToPasteboard:.  It just puts a link to the currentSelection
 * (presumably just written to the pasteboard by copyToPasteboard:) into the specified
 * pboard.  Note that it only does all this if we are writing all possible types to the
 * pasteboard (typesList == NULL) or if we explicitly ask for the link to be written
 * (typesList includes NXDataLinkPboardType).
 */

- writeLinkToPasteboard:(Pasteboard *)pboard types:(const NXAtom *)typesList
{
    NXDataLink *link;

    if (linkManager && (!typesList || IncludesType(typesList, NXDataLinkPboardType))) {
	if (link = [[NXDataLink alloc] initLinkedToSourceSelection:[self currentSelection] managedBy:linkManager supportingTypes:TypesDrawExports() count:NUM_TYPES_DRAW_EXPORTS]) {
	    [pboard addTypes:&NXDataLinkPboardType num:1 owner:[self class]];
	    [link writeToPasteboard:pboard];
	    [link free];
	}
	[linkManager writeLinksToPasteboard:pboard]; // for embedded linked things
    }

    return self;
}

/*
 * This is called by pasteFromPasteboard: when we paste a Graphic (i.e. copied/pasted from
 * another Draw document) in case that Graphic was linked to something when it was copied.
 * Since we called writeLinksToPasteboard: when we put the Graphic into the pasteboard (see
 * writeLinkToPasteboard:types: above) we can simply retrieve all the link information for
 * that graphic by using the linkManager method addLinkPreviouslyAt:fromPasteboard:at:.
 */

- readLinkForGraphic:(Graphic *)graphic fromPasteboard:(Pasteboard *)pboard useNewIdentifier:(BOOL)useNewIdentifier
{
    NXDataLink *link;
    NXSelection *oldSelection;

    oldSelection = [graphic selection];
    if (linkManager && graphic && oldSelection) {
	if (useNewIdentifier) [graphic resetIdentifier];
	link = [linkManager addLinkPreviouslyAt:oldSelection
			         fromPasteboard:pboard
					     at:[graphic selection]];
	[graphic setLink:link];
    }
    if (useNewIdentifier) [oldSelection free];

    return self;
}

/*
 * Sets up a link from the Draw document to another document.
 * This is called by the drag stuff (gvDrag.m) and the normal copy/paste stuff (gvPasteboard.m).
 * We allow for the case of graphic being nil as long as the link is capable of supplying
 * data of a type we can handle (currently Text or Image).
 */

- (BOOL)addLink:(NXDataLink *)link toGraphic:(Graphic *)graphic at:(const NXPoint *)p update:(int)update
{
    NXSelection *selection = nil;

    if (!graphic && link && update != UPDATE_NEVER) {
	if (TextPasteType([link types])) {
	    graphic = [[TextGraphic allocFromZone:[self zone]] init];
	} else if (MatchTypes([link types], [NXImage imagePasteboardTypes])) {
	    graphic = [[Image allocFromZone:[self zone]] init];
	}
	update = UPDATE_IMMEDIATELY;
    }

    if (graphic && link) {
	selection = [graphic selection];
	if ([linkManager addLink:link at:selection]) {
	    if (!update) [link setUpdateMode:NX_UpdateNever];
	    [graphic setLink:link];
	    if (graphic = [self placeGraphic:graphic at:p]) {
		if (update == UPDATE_IMMEDIATELY) {
		    [link updateDestination];
		    graphic = [self findGraphicInSelection:selection];
		    if (![graphic isValid]) {
			NXRunLocalizedAlertPanel(NULL, "Import Link",
			    "Unable to import linked data.", NULL, NULL, NULL,
			    "Message given to user when import of linked data fails.");
			[self removeGraphic:graphic];
		    } else {
			return YES;
		    }
		} else {
		    return YES;
		}
	    }
	}
    }

    [link free];
    [selection free];
    [graphic free];

    return NO;
}

/*
 * Keeping links up to date.
 * These methods are called either to update a link that draw has to another
 * document or to cause Draw to update another document that is linked to it.
 */

/*
 * Sent whenever NeXTSTEP wants us to update some data in our document which
 * we get by being linked to some other document.
 */

- pasteFromPasteboard:(Pasteboard *)pboard at:(NXSelection *)selection
{
    id graphic;
    NXRect gBounds;

    if (graphic = [self findGraphicInSelection:selection]) {
	gBounds = [graphic reinitFromPasteboard:pboard];
	[self cache:&gBounds];	// updating a destination link
	[window flushWindow];
	[self dirty];
	return self;
    }

    return nil;
}

/*
 * Lazy pasteboard method for cheapCopyAllowed case ONLY.
 * See copyToPasteboard:at:cheapCopyAllowed: below.
 */

- pasteboard:(Pasteboard *)sender provideData:(const char *)type
{
    List *list;
    NXStream *stream;
    NXSelection *selection;

    selection = [[NXSelection allocFromZone:[self zone]] initFromPasteboard:sender];
    list = [self findGraphicsInSelection:selection];
    if (list && (stream = NXOpenMemory(NULL, 0, NX_WRITEONLY))) {
	if (type == NXPostScriptPboardType) {
	    [self writePSToStream:stream usingList:list];
	} else if (type == NXTIFFPboardType) {
	    [self writeTIFFToStream:stream usingList:list];
	}
	[sender writeType:type fromStream:stream];
	NXCloseMemory(stream, NX_FREEBUFFER);
    }
    [list free];
    [selection free];

    return self;
}

/*
 * Called by NeXTSTEP when some other document needs to be updated because
 * they are linked to something in our document.
 */

- copyToPasteboard:(Pasteboard *)pboard at:(NXSelection *)selection cheapCopyAllowed:(BOOL)cheapCopyAllowed
{
    List *list;
    NXStream *stream;
    NXTypedStream *ts;
    id retval = self;
    const char *types[3];

    types[1] = NXPostScriptPboardType;
    types[2] = NXTIFFPboardType;

    if (cheapCopyAllowed) {
	if (list = [self findGraphicsInSelection:selection]) {
	    types[0] = NXSelectionPboardType;
	    [pboard declareTypes:types num:3 owner:self];
	    [selection writeToPasteboard:pboard];
	} else {
	    retval = nil;
	}
	[list free];
    } else {
	types[0] = DrawPboardType;
	[pboard declareTypes:types num:3 owner:[self class]];
	list = [self findGraphicsInSelection:selection];
	if (list && (stream = NXOpenMemory(NULL, 0, NX_WRITEONLY))) {
	    if (ts = NXOpenTypedStream(stream, NX_WRITEONLY)) {
		NXWriteRootObject(ts, list);
		NXCloseTypedStream(ts);
	    }
	    [pboard writeType:DrawPboardType fromStream:stream];
	    NXCloseMemory(stream, NX_FREEBUFFER);
	} else {
	    retval = nil;
	}
	[list free];
    }

    return retval;
}


/*
 * Supports linking to an entire file (not just a selection therein).
 * This occurs when you drag a file into Draw and link (see gvDrag).
 * This is very analogous to the pasteFromPasteboard:at: above.
 */

- importFile:(const char *)filename at:(NXSelection *)selection
{
    id graphic;
    NXRect gBounds;

    if (graphic = [self findGraphicInSelection:selection]) {
	gBounds = [graphic reinitFromFile:filename];
	[self cache:&gBounds];	// updating a link to an imported file
	[window flushWindow];
	[self dirty];
	return self;
    }

    return nil;
}

/* Other Links methods */

/*
 * Just makes the Link Inspector panel reflect whether any of the
 * Graphic's currently selected are linked to some other document.
 */

- updateLinksPanel
{
    int i, linkCount = 0;
    Graphic *foundGraphic = nil, *graphic = nil;

    if (linkManager) {
	for (i = [slist count]-1; i >= 0; i--) {
	    if (graphic = [[slist objectAt:i] graphicLinkedBy:NULL]) {
		if ([graphic isKindOf:[Group class]]) {
		    linkCount += 2;
		    break;
		} else {
		    linkCount += 1;
		    foundGraphic = graphic;
		}
	    }
	}
	if (linkCount == 1) {
	    [NXDataLinkPanel setLink:[foundGraphic link] andManager:linkManager isMultiple:NO];
	} else if (linkCount) {
	    [NXDataLinkPanel setLink:[foundGraphic link] andManager:linkManager isMultiple:YES];
	} else {
	    [NXDataLinkPanel setLink:nil andManager:linkManager isMultiple:NO];
	}
    }

    return self;
}

- (NXDataLinkManager *)linkManager
{
    return linkManager;
}

/*
 * When we get a linkManager via this method, we must go and revive all the links.
 * This is due to the fact that we don't archive ANY link information when we
 * save a Draw document.  However, the unique identifiers ARE archived, and thus,
 * when we unarchive, we can recreate NXSelections with those unique identifiers
 * and then ask the NXDataLinkManager for the link objects associated with those
 * NXSelections.
 *
 * After we have revived all the links, we call breakLinkAndRedrawOutlines:
 * with nil (meaning redraw the link outlines for all links).
 */

- setLinkManager:(NXDataLinkManager *)aLinkManager
{
    if (!linkManager) {
	linkManager = aLinkManager;
	[glist makeObjectsPerform:@selector(reviveLink:) with:linkManager];
	[self breakLinkAndRedrawOutlines:nil];
    }
    return self;
}

/*
 * This is called when the user chooses Open Source.
 * It uses the trick of drawing directly into the GraphicView
 * which, of course, is only ephemeral since the REAL contents
 * of the GraphicView are stored in the backing store.
 * This is convenient because Open Source is only a temporary
 * the the user calls to see where the data for his link is
 * coming from.
 */
 
- showSelection:(NXSelection *)selection
{
    id retval = self;
    List *graphics = nil;
    NXRect *newInvalidRect;
    NXRect sBounds, linkBounds;
    
    [self lockFocus];
    if (invalidRect) {
	[self drawSelf:invalidRect :1];
	newInvalidRect = invalidRect;
	invalidRect = NULL;
    } else{
	NX_MALLOC(newInvalidRect, NXRect, 1);
    }
    if ([self getRect:&linkBounds forSelection:selection]) {
	PSsetgray(NX_LTGRAY);
	NXFrameRectWithWidth(&linkBounds, 2.0);
	*newInvalidRect = linkBounds;
	graphics = [self findGraphicsInSelection:selection];
	if (graphics) {
	    [self getBBox:&sBounds of:graphics];
	    NXUnionRect(&sBounds, newInvalidRect);
	} else {
	    invalidRect = newInvalidRect;
	    [self scrollRectToVisible:invalidRect];
	    [window flushWindow];
	    retval = nil;
	}
    } else {
	graphics = [self findGraphicsInSelection:selection];
	if (graphics) {
	    [self getBBox:&sBounds of:graphics];
	    *newInvalidRect = sBounds;
	} else {
	    retval = nil;
	}
    }

    if (retval) {
	NXFrameLinkRect(&sBounds, NO);
	invalidRect = newInvalidRect;
	NXInsetRect(invalidRect, -NXLinkFrameThickness(), -NXLinkFrameThickness());
	[self scrollRectToVisible:invalidRect];
	[window flushWindow];
    }

    [self unlockFocus];
    [graphics free];

    return retval;
}

/*
 * Called when the Show Links button in the Link Inspector panel is clicked
 * (the link argument will be nil in this case), or when a link is broken
 * (the link argument will be the link that was broken).
 */

- breakLinkAndRedrawOutlines:(NXDataLink *)link
{
    int i;
    Graphic *graphic;
    BOOL gotOne = NO;
    NXRect eBounds, recacheBounds;

    for (i = [glist count]-1; i >= 0; i--) {
	graphic = [glist objectAt:i];
	if (graphic = [graphic graphicLinkedBy:link]) {
	    if (link && ([graphic link] == link) &&
		([link updateMode] == NX_UpdateNever)) {
		    [self removeGraphic:graphic];
	    }
	    if (!link || [linkManager areLinkOutlinesVisible]) {
		[graphic getExtendedBounds:&eBounds];
		if (gotOne) {
		    NXUnionRect(&eBounds, &recacheBounds);
		} else {
		    recacheBounds = eBounds;
		    gotOne = YES;
		}
	    }
	}
    }
    if (gotOne) {
	[self cache:&recacheBounds andUpdateLinks:NO];
	[window flushWindow];
    }

    return self;
}

/*
 * Tracking Link Changes.
 *
 * This is how we get "Continuous" updating links.
 *
 * We simply assume that a thing someone is linked to in our document
 * changes whenever we have to redraw any rectangle in the GraphicView
 * which intersects the linked-to rectangle.  See cache:andUpdateLinks:
 * in GraphicView.m.
 */

typedef struct {
    NXRect linkRect;
    NXDataLink *link;
    BOOL dragged, all;
} LinkRect;

- updateTrackedLinks:(const NXRect *)sRect
{
    int i;
    LinkRect *lr;
    List *graphics;
    NXSelection *selection;
    NXRect *lRect, newRect;

    for (i = [linkTrackingRects count]-1; i >= 0; i--) {
	if (NXIntersectsRect(sRect, (NXRect *)[linkTrackingRects elementAt:i])) {
	    lr = ((LinkRect *)[linkTrackingRects elementAt:i]);
	    [lr->link sourceEdited];
	    lRect = (NXRect *)[linkTrackingRects elementAt:i];
	    if (!lr->dragged && !lr->all && !NXContainsRect(lRect, sRect)) {
		selection = [lr->link sourceSelection];
		if (graphics = [self findGraphicsInSelection:selection]) {
		    [self getBBox:&newRect of:graphics];
		    *lRect = newRect;
		    [graphics free];
		}
	    }
	}
    }

    return self;
}

/* Add to linkTrackingRects. */

- startTrackingLink:(NXDataLink *)link
{
    LinkRect trackRect;
    List *graphics = nil;
    NXSelection *selection;
    BOOL all = NO, dragged = NO, piecemeal = NO;

    selection = [link sourceSelection];
    if ([selection isEqual:[NXSelection allSelection]]) {
	all = YES;
	trackRect.linkRect = bounds;
    } else if ([self getRect:&trackRect.linkRect forSelection:selection]) {
	dragged = YES;
    } else if (graphics = [self findGraphicsInSelection:selection]) {
	[self getBBox:&trackRect.linkRect of:graphics];
	piecemeal = YES;
	[graphics free];
    } else {
	return nil;
    }

    if (all || dragged || piecemeal) {
	if (!linkTrackingRects) {
	    linkTrackingRects = [[Storage allocFromZone:[self zone]] initCount:1 elementSize:sizeof(LinkRect) description:"{ffff@}"];
	}
	[self stopTrackingLink:link];
	trackRect.link = link;
	trackRect.dragged = dragged;
	trackRect.all = all;
	[linkTrackingRects addElement:&trackRect];
    }

    return nil;
}

/* Remove from linkTrackingRects. */

- stopTrackingLink:(NXDataLink *)link
{
    int i;

    for (i = [linkTrackingRects count]-1; i >= 0; i--) {
	if (((LinkRect *)[linkTrackingRects elementAt:i])->link == link) {
	    [linkTrackingRects removeElementAt:i];
	    return self;
	}
    }

    return nil;
}

@end

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