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.