home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.0 / NeXTSTEP3.0.iso / NextDeveloper / Examples / AppKit / Draw / gvLinks.m < prev    next >
Text File  |  1992-07-20  |  19KB  |  676 lines

  1. #import "draw.h"
  2.  
  3. @implementation GraphicView(Links)
  4.  
  5. /* See the Links.rtf file for overview about Object Links in Draw. */
  6.  
  7. #define BUFFER_SIZE 1100
  8. #define INT_WIDTH 11
  9.  
  10. /*
  11.  * Returns an NXSelection describe the current Graphic's selected in the view.
  12.  * If the user did Select All, then the gvFlags.selectAll bit is set and we
  13.  *RVXurn the allSelection NXSelection.  If the user dragged out a rectangle,
  14.  * then the dragRect rectangle is set and we return a ByRect NXSelection.
  15.  * Otherwise, we return a ByGraphic NXSelection.
  16.  *
  17.  * We use the writeIdentifierTo: mechanism so that Group Graphic's can ask
  18.  * their components to write out all their identifiers so that we have a
  19.  * maximal chance of getting all the objects.
  20.  */
  21.  
  22. - (NXSelection *)currentSelection
  23. {
  24.     NXRect sbounds;
  25.     int i, graphicCount;
  26.     NXSelection *retval = nil;
  27.     char *s, *selbuf, buffer[BUFFER_SIZE];
  28.  
  29.     if (![slist count]) return [NXSelection emptySelection];
  30.  
  31.     if (gvFlags.selectAll) {
  32.     if ([slist count] == [glist count]) {
  33.         return [NXSelection allSelection];
  34.     } else {
  35.         gvFlags.selectAll = NO;
  36.     }
  37.     }
  38.  
  39.     if (dragRect) {
  40.     sbounds = *dragRect;
  41.     sprintf(buffer, "%d %d %d %d %d", ByRect, 
  42.         (int)sbounds.origin.x, (int)sbounds.origin.y,
  43.         (int)(sbounds.size.width+0.5), (int)(sbounds.size.height+0.5));
  44.     selbuf = buffer;
  45.     } else {
  46.     graphicCount = 0;
  47.     i = [slist count];
  48.     while (i--) graphicCount += [[slist objectAt:i] graphicCount];
  49.     if (graphicCount > (BUFFER_SIZE / INT_WIDTH)) {
  50.         NX_MALLOC(selbuf, char, graphicCount * INT_WIDTH);
  51.     } else {
  52.         selbuf = buffer;
  53.     }
  54.     sprintf(buffer, "%d %d", ByList, graphicCount);
  55.     s = selbuf + strlen(selbuf);
  56.     i = [slist count];
  57.     while (i--) {
  58.         *s++ = ' ';
  59.         [[slist objectAt:i] writeIdentifierTo:s];
  60.         s += strlen(s);
  61.     }
  62.     }
  63.  
  64.     retval = [[NXSelection allocFromZone:[self zone]] initWithDescription:selbuf length:strlen(selbuf)+1];
  65.  
  66.     if (selbuf != buffer) free(selbuf);
  67.  
  68.     return retval;
  69. }
  70.  
  71. /*
  72.  * Used for destination selections only.
  73.  * Just extracts the unique identifier for the destination Image
  74.  * or TextGraphic and then searches through the glist to find that
  75.  * Graphic and returns it.
  76.  *
  77.  * Again, we use the graphicIdentifiedBy: mechanism so that we
  78.  * descend into Group's of Graphics to find a destination.
  79.  */
  80.  
  81. - (Graphic *)findGraphicInSelection:(NXSelection *)selection
  82. {
  83.     int i;
  84.     Graphic *graphic;
  85.     const char *selectionInfo;
  86.     int selectionInfoLength, identifier, selectionType;
  87.  
  88.     selectionInfo = [selection descriptionOfLength:&selectionInfoLength];
  89.     if (selectionInfo) {
  90.     sscanf(selectionInfo, "%d %d", &selectionType, &identifier);
  91.     if (selectionType == ByGraphic) {
  92.       RVYr (i = [glist count]-1; i >= 0; i--) {
  93.         if (graphic = [[glist objectAt:i] graphicIdentifiedBy:identifier]) return graphic;
  94.         }
  95.     }
  96.     }
  97.  
  98.     return nil;
  99. }
  100.  
  101. /*
  102.  * Returns YES and theRect is valid only if the selection is one which
  103.  * the user created by dragging out a rectangle.
  104.  */
  105.  
  106. - (BOOL)getRect:(NXRect *)theRect forSelection:(NXSelection *)selection
  107. {
  108.     NXRect stackRect;
  109.     const char *selectionInfo;
  110.     int selectionInfoLength;
  111.     DrawSelectionType selectionType;
  112.  
  113.     if (selectionInfo = [selection descriptionOfLength:&selectionInfoLength]) {
  114.     if (!theRect) theRect = &stackRect;
  115.     sscanf(selectionInfo, "%d %f %f %f %f", (int *)&selectionType,
  116.         &(theRect->origin.x), &(theRect->origin.y),
  117.         &(theRect->size.width), &(theRect->size.height));
  118.     if (selectionType == ByRect) return YES;
  119.     }
  120.  
  121.     return NO;
  122. }
  123.  
  124. /*
  125.  * For source selections only.
  126.  * Returns the list of Graphics in the current document which were
  127.  * in the selection passed to this method.  Note that any Group 
  128.  * which includes a Graphic in the passed selection will be included
  129.  * in its entirety.
  130.  */
  131.  
  132. - (List *)findGraphicsInSelection:(NXSelection *)selection
  133. {
  134.     int i, count;
  135.     Graphic *graphic;
  136.     List *list = nil;
  137.     NXRect sBounds, gBounds;
  138.     int selectionInfoLength;
  139.     const char *s, *theGraphics;
  140.     DrawSelectionType selectionType;
  141.  
  142.     if ([selection isEqual:[NXSelection allSelection]]) {
  143.     count = [glist count];
  144.     list = [[List allocFromZone:[self zone]] initCount:count];
  145.     for (i = 0; i < count; i++) [list addObject:[glist objectAt:i]];
  146.     } else if ([self getRect:&sBounds forSelection:selection]) {
  147.     count = [glist count];
  148.     list = [[List allocFromZone:[self zone]] init];
  149.     for (i = 0; i < count; i++) {
  150.         graphic = [glist objectAt:i];
  151.         [graphic getBounds:&gBounds];
  152.         NXInsetRect(&gBounds, -0.1, -0.1);
  153.         if (NXIntersectsRect(&gBounds, &sBounds)) [list addObject:graphic];
  154.     }
  155.     } else if (s = [selection descriptionOfLength:&selectionInfoLength]) {
  156.     sscanf(s, "%d %d", (int *)&selectionType, &count);
  157.     if (selectionType == ByList) {
  158.         if (s = strchr(s, ' ')) s = strchr(s+1, ' ');
  159.         if (s++) {
  160.         theGraphics = s;
  161.         list = [[List allocFromZone:[self zone]] init];
  162.         count = [glist count];
  163.         for (i = 0; i < count; i++) {
  164.             graphic = [glist objectAt:i];
  165.             s = theGraphics;
  166.             while (s && *s) {
  167.             if ([grRV`c graphicIdentifiedBy:atoi(s)]) {
  168.                 [list addObject:graphic];
  169.                 break;
  170.             }
  171.             if (s = strchr(s, ' ')) s++;
  172.             }
  173.         }
  174.         }
  175.     }
  176.     }
  177.  
  178.     if (![list count]) {
  179.     [list free];
  180.     list = nil;
  181.     }
  182.  
  183.     return list;
  184. }
  185.  
  186. /*
  187.  * Importing/Exporting links.
  188.  */
  189.  
  190. /*
  191.  * This method is called by copyToPasteboard:.  It just puts a link to the currentSelection
  192.  * (presumably just written to the pasteboard by copyToPasteboard:) into the specified
  193.  * pboard.  Note that it only does all this if we are writing all possible types to the
  194.  * pasteboard (typesList == NULL) or if we explicitly ask for the link to be written
  195.  * (typesList includes NXDataLinkPboardType).
  196.  */
  197.  
  198. - writeLinkToPasteboard:(Pasteboard *)pboard types:(const NXAtom *)typesList
  199. {
  200.     NXDataLink *link;
  201.  
  202.     if (linkManager && (!typesList || IncludesType(typesList, NXDataLinkPboardType))) {
  203.     if (link = [[NXDataLink alloc] initLinkedToSourceSelection:[self currentSelection] managedBy:linkManager supportingTypes:TypesDrawExports() count:NUM_TYPES_DRAW_EXPORTS]) {
  204.         [pboard addTypes:&NXDataLinkPboardType num:1 owner:[self class]];
  205.         [link writeToPasteboard:pboard];
  206.         [link free];
  207.     }
  208.     [linkManager writeLinksToPasteboard:pboard]; // for embedded linked things
  209.     }
  210.  
  211.     return self;
  212. }
  213.  
  214. /*
  215.  * This is called by pasteFromPasteboard: when we paste a Graphic (i.e. copied/pasted from
  216.  * another Draw document) in case that Graphic was linked to something when it was copied.
  217.  * Since we called writeLinksToPasteboard: when we put the Graphic into the pasteboard (see
  218.  * writeLinkToPasteboard:types: above) we can simply retrieve all the link information for
  219.  * that graphic by using the linkManager method addLinkPreviouslyAt:fromPasteboard:at:.
  220.  */
  221.  
  222. - readLinkForGraphic:(Graphic *)graphic fromPasteboard:(Pasteboard *)pboard useNewIdentifier:(BOOL)useNewIdentifier
  223. {
  224.     NXDataLink *link;
  225.     NXSelection *oldSelection;
  226.  
  227.     oldSelection = [graphic selection];
  228.     if (linkManager && graphic && oldSelection) {
  229.     if (useNewIdentifier) [graphic resetIdentifier];
  230.     link = [linkManager addLinkPreviouslyAt:oldSelection
  231.                      fromPasteboard:pboard
  232.                          at:[graphic selection]];
  233.     [graphic setLink:link];
  234.     }
  235.     if (useNewIdentifier) [oldSelection free];
  236.  
  237.     return self;
  238. }
  239.  
  240. /*
  241.  * Sets up a link from the Draw document to another document.
  242.  * This is called by RVadrag stuff (gvDrag.m) and the normal copy/paste stuff (gvPasteboard.m).
  243.  * We allow for the case of graphic being nil as long as the link is capable of supplying
  244.  * data of a type we can handle (currently Text or Image).
  245.  */
  246.  
  247. - (BOOL)addLink:(NXDataLink *)link toGraphic:(Graphic *)graphic at:(const NXPoint *)p update:(int)update
  248. {
  249.     NXSelection *selection = nil;
  250.  
  251.     if (!graphic && link && update != UPDATE_NEVER) {
  252.     if (TextPasteType([link types])) {
  253.         graphic = [[TextGraphic allocFromZone:[self zone]] init];
  254.     } else if (MatchTypes([link types], [NXImage imagePasteboardTypes])) {
  255.         graphic = [[Image allocFromZone:[self zone]] init];
  256.     }
  257.     update = UPDATE_IMMEDIATELY;
  258.     }
  259.  
  260.     if (graphic && link) {
  261.     selection = [graphic selection];
  262.     if ([linkManager addLink:link at:selection]) {
  263.         if (!update) [link setUpdateMode:NX_UpdateNever];
  264.         [graphic setLink:link];
  265.         if (graphic = [self placeGraphic:graphic at:p]) {
  266.         if (update == UPDATE_IMMEDIATELY) {
  267.             [link updateDestination];
  268.             graphic = [self findGraphicInSelection:selection];
  269.             if (![graphic isValid]) {
  270.             NXRunLocalizedAlertPanel(NULL, "Import Link",
  271.                 "Unable to import linked data.", NULL, NULL, NULL,
  272.                 "Message given to user when import of linked data fails.");
  273.             [self removeGraphic:graphic];
  274.             } else {
  275.             return YES;
  276.             }
  277.         } else {
  278.             return YES;
  279.         }
  280.         }
  281.     }
  282.     }
  283.  
  284.     [link free];
  285.     [selection free];
  286.     [graphic free];
  287.  
  288.     return NO;
  289. }
  290.  
  291. /*
  292.  * Keeping links up to date.
  293.  * These methods are called either to update a link that draw has to another
  294.  * document or to cause Draw to update another document that is linked to it.
  295.  */
  296.  
  297. /*
  298.  * Sent whenever NeXTSTEP wants us to update some data in our document which
  299.  * we get by being linked to some other document.
  300.  */
  301.  
  302. - pasteFromPasteboard:(Pasteboard *)pboard at:(NXSelection *)selection
  303. {
  304.     id graphic;
  305.     NXRect gBounds;
  306.  
  307.     if (graphic = [self findGraphicInSelection:selection]) {
  308.     gBounds = [graphic reinitFromPasteboard:pboard];
  309.     [self cache:&gBounds];    // updating a destination link
  310.     [window flushWindow];
  311.     [self dirty];
  312.     return self;
  313.     }
  314.  
  315.     return nil;
  316. }
  317.  
  318. /*
  319.  * Lazy pasteboard method for cheapCopyAllowed case ONLY.
  320.  * See copyToPasteboard:at:cheapCopyAllowed: below.
  321.  */
  322.  
  323. - pasteboard:(Pasteboard *)sender provideData:(const char *)type
  324. {
  325.     List *liRVb    NXStream *stream;
  326.     NXSelection *selection;
  327.  
  328.     selection = [[NXSelection allocFromZone:[self zone]] initFromPasteboard:sender];
  329.     list = [self findGraphicsInSelection:selection];
  330.     if (list && (stream = NXOpenMemory(NULL, 0, NX_WRITEONLY))) {
  331.     if (type == NXPostScriptPboardType) {
  332.         [self writePSToStream:stream usingList:list];
  333.     } else if (type == NXTIFFPboardType) {
  334.         [self writeTIFFToStream:stream usingList:list];
  335.     }
  336.     [sender writeType:type fromStream:stream];
  337.     NXCloseMemory(stream, NX_FREEBUFFER);
  338.     }
  339.     [list free];
  340.     [selection free];
  341.  
  342.     return self;
  343. }
  344.  
  345. /*
  346.  * Called by NeXTSTEP when some other document needs to be updated because
  347.  * they are linked to something in our document.
  348.  */
  349.  
  350. - copyToPasteboard:(Pasteboard *)pboard at:(NXSelection *)selection cheapCopyAllowed:(BOOL)cheapCopyAllowed
  351. {
  352.     List *list;
  353.     NXStream *stream;
  354.     NXTypedStream *ts;
  355.     id retval = self;
  356.     const char *types[3];
  357.  
  358.     types[1] = NXPostScriptPboardType;
  359.     types[2] = NXTIFFPboardType;
  360.  
  361.     if (cheapCopyAllowed) {
  362.     if (list = [self findGraphicsInSelection:selection]) {
  363.         types[0] = NXSelectionPboardType;
  364.         [pboard declareTypes:types num:3 owner:self];
  365.         [selection writeToPasteboard:pboard];
  366.     } else {
  367.         retval = nil;
  368.     }
  369.     [list free];
  370.     } else {
  371.     types[0] = DrawPboardType;
  372.     [pboard declareTypes:types num:3 owner:[self class]];
  373.     list = [self findGraphicsInSelection:selection];
  374.     if (list && (stream = NXOpenMemory(NULL, 0, NX_WRITEONLY))) {
  375.         if (ts = NXOpenTypedStream(stream, NX_WRITEONLY)) {
  376.         NXWriteRootObject(ts, list);
  377.         NXCloseTypedStream(ts);
  378.         }
  379.         [pboard writeType:DrawPboardType fromStream:stream];
  380.         NXCloseMemory(stream, NX_FREEBUFFER);
  381.     } else {
  382.         retval = nil;
  383.     }
  384.     [list free];
  385.     }
  386.  
  387.     return retval;
  388. }
  389.  
  390.  
  391. /*
  392.  * Supports linking to an entire file (not just a selection therein).
  393.  * This occurs when you drag a file into Draw and link (see gvDrag).
  394.  * This is very analogous to the pasteFromPasteboard:at: above.
  395.  */
  396.  
  397. - importFile:(const char *)filename at:(NXSelection *)selection
  398. {
  399.     id graphic;
  400.     NXRect gBounds;
  401.  
  402.     if (graphic = [self findGraphicInSelection:selection]) {
  403.     gBounds = [graphic reinitFromFile:filename];
  404.     [self cache:&gBounds];    // updating a link to an imported file
  405.     [window flushWindow];
  406.     [self dirty];
  407.     return self;
  408.     }
  409.  
  410.     return nil;
  411. }
  412.  
  413. /RVcher Links methods */
  414.  
  415. /*
  416.  * Just makes the Link Inspector panel reflect whether any of the
  417.  * Graphic's currently selected are linked to some other document.
  418.  */
  419.  
  420. - updateLinksPanel
  421. {
  422.     int i, linkCount = 0;
  423.     Graphic *foundGraphic = nil, *graphic = nil;
  424.  
  425.     if (linkManager) {
  426.     for (i = [slist count]-1; i >= 0; i--) {
  427.         if (graphic = [[slist objectAt:i] graphicLinkedBy:NULL]) {
  428.         if ([graphic isKindOf:[Group class]]) {
  429.             linkCount += 2;
  430.             break;
  431.         } else {
  432.             linkCount += 1;
  433.             foundGraphic = graphic;
  434.         }
  435.         }
  436.     }
  437.     if (linkCount == 1) {
  438.         [NXDataLinkPanel setLink:[foundGraphic link] andManager:linkManager isMultiple:NO];
  439.     } else if (linkCount) {
  440.         [NXDataLinkPanel setLink:[foundGraphic link] andManager:linkManager isMultiple:YES];
  441.     } else {
  442.         [NXDataLinkPanel setLink:nil andManager:linkManager isMultiple:NO];
  443.     }
  444.     }
  445.  
  446.     return self;
  447. }
  448.  
  449. - (NXDataLinkManager *)linkManager
  450. {
  451.     return linkManager;
  452. }
  453.  
  454. /*
  455.  * When we get a linkManager via this method, we must go and revive all the links.
  456.  * This is due to the fact that we don't archive ANY link information when we
  457.  * save a Draw document.  However, the unique identifiers ARE archived, and thus,
  458.  * when we unarchive, we can recreate NXSelections with those unique identifiers
  459.  * and then ask the NXDataLinkManager for the link objects associated with those
  460.  * NXSelections.
  461.  *
  462.  * After we have revived all the links, we call breakLinkAndRedrawOutlines:
  463.  * with nil (meaning redraw the link outlines for all links).
  464.  */
  465.  
  466. - setLinkManager:(NXDataLinkManager *)aLinkManager
  467. {
  468.     if (!linkManager) {
  469.     linkManager = aLinkManager;
  470.     [glist makeObjectsPerform:@selector(reviveLink:) with:linkManager];
  471.     [self breakLinkAndRedrawOutlines:nil];
  472.     }
  473.     return self;
  474. }
  475.  
  476. /*
  477.  * This is called when the user chooses Open Source.
  478.  * It uses the trick of drawing directly into the GraphicView
  479.  * which, of course, is only ephemeral since the REAL contents
  480.  * of the GraphicView are stored in the backing store.
  481.  * This is convenient because Open Source is only a temporary
  482.  * the the user calls to see where the data for his link is
  483.  * coming from.
  484.  */
  485.  
  486. - showSelection:(NXSelection *)selection
  487. {
  488.     id retval = self;
  489.     List *graphics = nil;
  490.     NXRect *newInvalidRect;
  491.     NXRect sBounds, linkBounds;
  492.     
  493.     [self lockFocus];
  494.     if (invalidRect) {
  495.     [self dRVdelf:invalidRect :1];
  496.     newInvalidRect = invalidRect;
  497.     invalidRect = NULL;
  498.     } else{
  499.     NX_MALLOC(newInvalidRect, NXRect, 1);
  500.     }
  501.     if ([self getRect:&linkBounds forSelection:selection]) {
  502.     PSsetgray(NX_LTGRAY);
  503.     NXFrameRectWithWidth(&linkBounds, 2.0);
  504.     *newInvalidRect = linkBounds;
  505.     graphics = [self findGraphicsInSelection:selection];
  506.     if (graphics) {
  507.         [self getBBox:&sBounds of:graphics];
  508.         NXUnionRect(&sBounds, newInvalidRect);
  509.     } else {
  510.         invalidRect = newInvalidRect;
  511.         [self scrollRectToVisible:invalidRect];
  512.         [window flushWindow];
  513.         retval = nil;
  514.     }
  515.     } else {
  516.     graphics = [self findGraphicsInSelection:selection];
  517.     if (graphics) {
  518.         [self getBBox:&sBounds of:graphics];
  519.         *newInvalidRect = sBounds;
  520.     } else {
  521.         retval = nil;
  522.     }
  523.     }
  524.  
  525.     if (retval) {
  526.     NXFrameLinkRect(&sBounds, NO);
  527.     invalidRect = newInvalidRect;
  528.     NXInsetRect(invalidRect, -NXLinkFrameThickness(), -NXLinkFrameThickness());
  529.     [self scrollRectToVisible:invalidRect];
  530.     [window flushWindow];
  531.     }
  532.  
  533.     [self unlockFocus];
  534.     [graphics free];
  535.  
  536.     return retval;
  537. }
  538.  
  539. /*
  540.  * Called when the Show Links button in the Link Inspector panel is clicked
  541.  * (the link argument will be nil in this case), or when a link is broken
  542.  * (the link argument will be the link that was broken).
  543.  */
  544.  
  545. - breakLinkAndRedrawOutlines:(NXDataLink *)link
  546. {
  547.     int i;
  548.     Graphic *graphic;
  549.     BOOL gotOne = NO;
  550.     NXRect eBounds, recacheBounds;
  551.  
  552.     for (i = [glist count]-1; i >= 0; i--) {
  553.     graphic = [glist objectAt:i];
  554.     if (graphic = [graphic graphicLinkedBy:link]) {
  555.         if (link && ([graphic link] == link) &&
  556.         ([link updateMode] == NX_UpdateNever)) {
  557.             [self removeGraphic:graphic];
  558.         }
  559.         if (!link || [linkManager areLinkOutlinesVisible]) {
  560.         [graphic getExtendedBounds:&eBounds];
  561.         if (gotOne) {
  562.             NXUnionRect(&eBounds, &recacheBounds);
  563.         } else {
  564.             recacheBounds = eBounds;
  565.             gotOne = YES;
  566.         }
  567.         }
  568.     }
  569.     }
  570.     if (gotOne) {
  571.     [self cache:&recacheBounds andUpdateLinks:NO];
  572.     [window flushWindow];
  573.     }
  574.  
  575.     return self;
  576. }
  577.  
  578. /*
  579.  * Tracking Link Changes.
  580.  *
  581.  * This is how we get "Continuous" updating links.
  582.  *
  583.  * We simply assume that a thing someone is linked to in our document
  584.  * changes whenever we have to redraw any rectangle in the GraphicView
  585.  * which intersects the linked-to rectangle.  See cache:andUpdateLinks:
  586.  * iRVeaphicView.m.
  587.  */
  588.  
  589. typedef struct {
  590.     NXRect linkRect;
  591.     NXDataLink *link;
  592.     BOOL dragged, all;
  593. } LinkRect;
  594.  
  595. - updateTrackedLinks:(const NXRect *)sRect
  596. {
  597.     int i;
  598.     LinkRect *lr;
  599.     List *graphics;
  600.     NXSelection *selection;
  601.     NXRect *lRect, newRect;
  602.  
  603.     for (i = [linkTrackingRects count]-1; i >= 0; i--) {
  604.     if (NXIntersectsRect(sRect, (NXRect *)[linkTrackingRects elementAt:i])) {
  605.         lr = ((LinkRect *)[linkTrackingRects elementAt:i]);
  606.         [lr->link sourceEdited];
  607.         lRect = (NXRect *)[linkTrackingRects elementAt:i];
  608.         if (!lr->dragged && !lr->all && !NXContainsRect(lRect, sRect)) {
  609.         selection = [lr->link sourceSelection];
  610.         if (graphics = [self findGraphicsInSelection:selection]) {
  611.             [self getBBox:&newRect of:graphics];
  612.             *lRect = newRect;
  613.             [graphics free];
  614.         }
  615.         }
  616.     }
  617.     }
  618.  
  619.     return self;
  620. }
  621.  
  622. /* Add to linkTrackingRects. */
  623.  
  624. - startTrackingLink:(NXDataLink *)link
  625. {
  626.     LinkRect trackRect;
  627.     List *graphics = nil;
  628.     NXSelection *selection;
  629.     BOOL all = NO, dragged = NO, piecemeal = NO;
  630.  
  631.     selection = [link sourceSelection];
  632.     if ([selection isEqual:[NXSelection allSelection]]) {
  633.     all = YES;
  634.     trackRect.linkRect = bounds;
  635.     } else if ([self getRect:&trackRect.linkRect forSelection:selection]) {
  636.     dragged = YES;
  637.     } else if (graphics = [self findGraphicsInSelection:selection]) {
  638.     [self getBBox:&trackRect.linkRect of:graphics];
  639.     piecemeal = YES;
  640.     [graphics free];
  641.     } else {
  642.     return nil;
  643.     }
  644.  
  645.     if (all || dragged || piecemeal) {
  646.     if (!linkTrackingRects) {
  647.         linkTrackingRects = [[Storage allocFromZone:[self zone]] initCount:1 elementSize:sizeof(LinkRect) description:"{ffff@}"];
  648.     }
  649.     [self stopTrackingLink:link];
  650.     trackRect.link = link;
  651.     trackRect.dragged = dragged;
  652.     trackRect.all = all;
  653.     [linkTrackingRects addElement:&trackRect];
  654.     }
  655.  
  656.     return nil;
  657. }
  658.  
  659. /* Remove from linkTrackingRects. */
  660.  
  661. - stopTrackingLink:(NXDataLink *)link
  662. {
  663.     int i;
  664.  
  665.     for (i = [linkTrackingRects count]-1; i >= 0; i--) {
  666.     if (((LinkRect *)[linkTrackingRects elementAt:i])->link == link) {
  667.         [linkTrackingRects removeElementAt:i];
  668.         return self;
  669.     }
  670.     }
  671.  
  672.     return nil;
  673. }
  674.  
  675. @end
  676.