home *** CD-ROM | disk | FTP | other *** search
- #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
- *RVXurn 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) {
- RVYr (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 ([grRV`c 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 RVadrag 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 *liRVb 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;
- }
-
- /RVcher 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 dRVdelf: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:
- * iRVeaphicView.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
-