home *** CD-ROM | disk | FTP | other *** search
/ OpenStep 4.2J (Developer) / os42jdev.iso / NextDeveloper / Examples / AppKit / Draw / gvLinks.m < prev    next >
Text File  |  1996-04-05  |  18KB  |  604 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 NSSelection 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.  * return the allSelection NSSelection.  If the user dragged out a rectangle,
  14.  * then the dragRect rectangle is set and we return a ByRect NSSelection.
  15.  * Otherwise, we return a ByGraphic NSSelection.
  16.  *
  17.  * We use the identifierString 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. - (NSSelection *)currentSelection
  23. {
  24.     NSString *selectionString;
  25.  
  26.     if (![slist count]) return [NSSelection emptySelection];
  27.  
  28.     if (gvFlags.selectAll) {
  29.     if ([slist count] == [glist count]) {
  30.         return [NSSelection allSelection];
  31.     } else {
  32.         gvFlags.selectAll = NO;
  33.     }
  34.     }
  35.  
  36.     if (dragRect) {
  37.     selectionString = [NSString stringWithFormat:@"%d %d %d %d %d",
  38.         ByRect, dragRect->origin.x, dragRect->origin.y, (int)(dragRect->size.width+0.5), (int)(dragRect->size.height+0.5)];
  39.     } else {
  40.         int i = [slist count];
  41.     NSMutableString *mselString = [NSMutableString stringWithFormat:@"%d %d", ByList, i];
  42.     while (i--) [mselString appendFormat:@" %@", [[slist objectAtIndex:i] identifierString]];
  43.     selectionString = mselString;
  44.     }
  45.  
  46.     return [[NSSelection allocWithZone:(NSZone *)[self zone]] initWithDescriptionData:[selectionString dataUsingEncoding:NSASCIIStringEncoding]];
  47. }
  48.  
  49. /*
  50.  * Purely for code beautification.
  51.  * Returns an array of all the elements in an NSSelection.
  52.  * Assumes they are all space separated (which is the only kind of
  53.  * NSSelection's Draw creates).
  54.  */
  55.  
  56. static inline NSArray *NSArrayFromNSSelection(NSSelection *selection)
  57. {
  58.     return [[[[NSString alloc] initWithData:[selection descriptionData] encoding:NSASCIIStringEncoding] autorelease] componentsSeparatedByString:@" "];
  59. }
  60.  
  61. /*
  62.  * Used for destination selections only.
  63.  * Just extracts the unique identifier for the destination Image
  64.  * or TextGraphic and then searches through the glist to find that
  65.  * Graphic and returns it.
  66.  *
  67.  * Again, we use the graphicIdentifiedBy: mechanism so that we
  68.  * descend into Group's of Graphics to find a destination.
  69.  */
  70.  
  71. - (Graphic *)findGraphicInSelection:(NSSelection *)selection
  72. {
  73.     int i, identifier;
  74.     Graphic *graphic;
  75.     NSArray *selectionInfo;
  76.  
  77.     if ((selectionInfo = NSArrayFromNSSelection(selection))) {
  78.         if ([[selectionInfo objectAtIndex:0] intValue] == ByGraphic) {
  79.         identifier = [[selectionInfo objectAtIndex:1] intValue];
  80.         for (i = [glist count]-1; i >= 0; i--) {
  81.         if ((graphic = [[glist objectAtIndex:i] graphicIdentifiedBy:identifier])) return graphic;
  82.         }
  83.     }
  84.     }
  85.  
  86.     return nil;
  87. }
  88.  
  89. /*
  90.  * Returns YES and theRect is valid only if the selection is one which
  91.  * the user created by dragging out a rectangle.
  92.  */
  93.  
  94. - (BOOL)getRect:(NSRect *)theRect forSelection:(NSSelection *)selection
  95. {
  96.     NSString *selectionType;
  97.     NSArray *selectionInfo;
  98.  
  99.     if ((selectionInfo = NSArrayFromNSSelection(selection))) {
  100.     selectionType = [selectionInfo objectAtIndex:0];
  101.     if (([selectionType length] == 1) && ([selectionType intValue] == ByRect)) {
  102.             if (theRect) {
  103.                 theRect->origin.x = [[selectionInfo objectAtIndex:1] floatValue];
  104.                 theRect->origin.y = [[selectionInfo objectAtIndex:2] floatValue];
  105.                 theRect->size.width = [[selectionInfo objectAtIndex:3] floatValue];
  106.                 theRect->size.height = [[selectionInfo objectAtIndex:4] floatValue];
  107.         }
  108.             return YES;
  109.         }
  110.     }
  111.  
  112.     return NO;
  113. }
  114.  
  115. /*
  116.  * For source selections only.
  117.  * Returns the list of Graphics in the current document which were
  118.  * in the selection passed to this method.  Note that any Group 
  119.  * which includes a Graphic in the passed selection will be included
  120.  * in its entirety.
  121.  *
  122.  * Return value is autoreleased.
  123.  */
  124.  
  125. - (NSArray *)findGraphicsInSelection:(NSSelection *)selection
  126. {
  127.     Graphic *graphic;
  128.     NSArray *selectionInfo;
  129.     NSMutableArray *array = nil;
  130.     int i, j, count, selectionCount;
  131.     NSRect sBounds, gBounds;
  132.  
  133.     if ([selection isEqual:[NSSelection allSelection]]) {
  134.     count = [glist count];
  135.     array = [[NSMutableArray allocWithZone:(NSZone *)[self zone]] initWithCapacity:count];
  136.     for (i = 0; i < count; i++) [array addObject:[glist objectAtIndex:i]];
  137.     } else if ([self getRect:&sBounds forSelection:selection]) {
  138.     count = [glist count];
  139.     array = [[NSMutableArray allocWithZone:(NSZone *)[self zone]] init];
  140.     for (i = 0; i < count; i++) {
  141.         graphic = [glist objectAtIndex:i];
  142.         gBounds = [graphic bounds];
  143.         gBounds = NSInsetRect(gBounds, -0.1, -0.1);
  144.         if (!NSIsEmptyRect(NSIntersectionRect(gBounds, sBounds))) [array addObject:graphic];
  145.     }
  146.     } else if ((selectionInfo = NSArrayFromNSSelection(selection))) {
  147.     if ([[selectionInfo objectAtIndex:0] intValue] == ByList) {
  148.             selectionCount = [[selectionInfo objectAtIndex:1] intValue];
  149.             array = [[NSMutableArray allocWithZone:(NSZone *)[self zone]] init];
  150.             count = [glist count];
  151.             for (i = 0; i < count; i++) {
  152.                 graphic = [glist objectAtIndex:i];
  153.                 for (j = 0; j < selectionCount; j++) {
  154.                     if ([graphic graphicIdentifiedBy:[[selectionInfo objectAtIndex:j+2] intValue]]) {
  155.                         [array addObject:graphic];
  156.                         break;
  157.                     }
  158.                 }
  159.             }
  160.     }
  161.     }
  162.  
  163.     if (![array count]) {
  164.         [array release];
  165.     array = nil;
  166.     }
  167.  
  168.     return [array autorelease];
  169. }
  170.  
  171. /*
  172.  * Importing/Exporting links.
  173.  */
  174.  
  175. /*
  176.  * This method is called by copyToPasteboard:.  It just puts a link to the currentSelection
  177.  * (presumably just written to the pasteboard by copyToPasteboard:) into the specified
  178.  * pboard.  Note that it only does all this if we are writing all possible types to the
  179.  * pasteboard (typesList == NULL) or if we explicitly ask for the link to be written
  180.  * (typesList includes NSDataLinkPboardType).
  181.  */
  182.  
  183. - (void)writeLinkToPasteboard:(NSPasteboard *)pboard types:(NSArray *)typesList
  184. {
  185.     NSDataLink *link;
  186.  
  187.     if (linkManager && (!typesList || IncludesType(typesList, NSDataLinkPboardType))) {
  188.     NSArray *typesDrawExports = TypesDrawExports();
  189.     if ((link = [[NSDataLink alloc] initLinkedToSourceSelection:[self currentSelection] managedBy:linkManager supportingTypes:typesDrawExports])) {
  190.         [pboard addTypes:[[[NSArray alloc] initWithObjects:NSDataLinkPboardType, nil] autorelease] owner:[self class]];
  191.         [link writeToPasteboard:pboard];
  192.         [link release];
  193.     }
  194.     [linkManager writeLinksToPasteboard:pboard]; // for embedded linked things
  195.     } 
  196. }
  197.  
  198. /*
  199.  * Sets up a link from the Draw document to another document.
  200.  * This is called by the drag stuff (gvDrag.m) and the normal copy/paste stuff (gvPasteboard.m).
  201.  * We allow for the case of graphic being nil as long as the link is capable of supplying
  202.  * data of a type we can handle (currently Text or Image).
  203.  */
  204.  
  205. - (BOOL)addLink:(NSDataLink *)link toGraphic:(Graphic *)graphic at:(NSPoint)p update:(int)update
  206. {
  207.     NSSelection *selection = nil;
  208.  
  209.     if (!graphic && link && update != UPDATE_NEVER) {
  210.     if (TextPasteType([link types])) {
  211.         graphic = [[TextGraphic allocWithZone:(NSZone *)[self zone]] initEmpty];
  212.     } else if (MatchTypes([link types], [NSImage imagePasteboardTypes])) {
  213.         graphic = [[Image allocWithZone:(NSZone *)[self zone]] initEmpty];
  214.     }
  215.     update = UPDATE_IMMEDIATELY;
  216.     }
  217.  
  218.     if (graphic && link) {
  219.     selection = [graphic selection];
  220.     if ([linkManager addLink:link at:selection]) {
  221.         if (!update) [link setUpdateMode:NSUpdateNever];
  222.         [graphic setLink:link];
  223.         if ((graphic = [self placeGraphic:graphic at:&p])) {
  224.         if (update == UPDATE_IMMEDIATELY) {
  225.             [link updateDestination];
  226.             graphic = [self findGraphicInSelection:selection];
  227.             if (![graphic isValid]) {
  228.             NSRunAlertPanel(IMPORT_LINK, UNABLE_TO_IMPORT_LINK, nil, nil, nil);
  229.             [self removeGraphic:graphic];
  230.             } else {
  231.             return YES;
  232.             }
  233.         } else {
  234.             return YES;
  235.         }
  236.         }
  237.     }
  238.     }
  239.  
  240.     [graphic release];
  241.  
  242.     return NO;
  243. }
  244.  
  245. /*
  246.  * Keeping links up to date.
  247.  * These methods are called either to update a link that draw has to another
  248.  * document or to cause Draw to update another document that is linked to it.
  249.  */
  250.  
  251. /*
  252.  * Sent whenever NeXTSTEP wants us to update some data in our document which
  253.  * we get by being linked to some other document.
  254.  */
  255.  
  256. - (BOOL)pasteFromPasteboard:(NSPasteboard *)pboard at:(NSSelection *)selection
  257. {
  258.     id graphic;
  259.     NSRect gBounds;
  260.  
  261.     if ((graphic = [self findGraphicInSelection:selection])) {
  262.     gBounds = [graphic reinitWithPasteboard:pboard];
  263.     [self cache:gBounds];    // updating a destination link
  264.     [[self window] flushWindow];
  265.     [self dirty];
  266.     return YES;
  267.     }
  268.  
  269.     return NO;
  270. }
  271.  
  272. /*
  273.  * Lazy pasteboard method for cheapCopyAllowed case ONLY.
  274.  * See copyToPasteboard:at:cheapCopyAllowed: below.
  275.  */
  276.  
  277. - (void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type
  278. {
  279.     NSArray *array;
  280.     NSData *data = nil;
  281.     NSSelection *selection;
  282.  
  283.     selection = [[NSSelection allocWithZone:(NSZone *)[self zone]] initWithPasteboard:sender];
  284.     array = [self findGraphicsInSelection:selection];
  285.     if (array) {
  286.     if ([type  isEqual:NSPostScriptPboardType]) {
  287.         data = [self dataForEPSUsingList:array];
  288.     } else if ([type  isEqual:NSTIFFPboardType]) {
  289.         data = [self dataForTIFFUsingList:array];
  290.     }
  291.         if (data) [sender setData:data forType:type];
  292.     }
  293.     [selection release];
  294. }
  295.  
  296. /*
  297.  * Called by NeXTSTEP when some other document needs to be updated because
  298.  * they are linked to something in our document.
  299.  */
  300.  
  301. - copyToPasteboard:(NSPasteboard *)pboard at:(NSSelection *)selection cheapCopyAllowed:(BOOL)cheapCopyAllowed
  302. {
  303.     NSArray *array;
  304.     NSData *objectData = nil;
  305.     id retval = self;
  306.     NSString *firstType = (cheapCopyAllowed ?  NSSelectionPboardType : DrawPboardType);
  307.     NSArray *types = [[[NSArray alloc] initWithObjects:firstType, NSPostScriptPboardType, NSTIFFPboardType, nil] autorelease];
  308.  
  309.     if (cheapCopyAllowed) {
  310.     if ((array = [self findGraphicsInSelection:selection])) {
  311.         [pboard declareTypes:types owner:self];
  312.         [selection writeToPasteboard:pboard];
  313.     } else {
  314.         retval = nil;
  315.     }
  316.     } else {
  317.     [pboard declareTypes:types owner:[self class]];
  318.     array = [self findGraphicsInSelection:selection];
  319.     if (array) {
  320.         objectData = [NSArchiver archivedDataWithRootObject:array];
  321.         [pboard setData:objectData forType:DrawPboardType];
  322.     } else {
  323.         retval = nil;
  324.     }
  325.     }
  326.  
  327.     return retval;
  328. }
  329.  
  330.  
  331. /*
  332.  * Supports linking to an entire file (not just a selection therein).
  333.  * This occurs when you drag a file into Draw and link (see gvDrag).
  334.  * This is very analogous to the pasteFromPasteboard:at: above.
  335.  */
  336.  
  337. - (BOOL)importFile:(NSString *)filename at:(NSSelection *)selection
  338. {
  339.     id graphic;
  340.     NSRect gBounds;
  341.  
  342.     if ((graphic = [self findGraphicInSelection:selection])) {
  343.     gBounds = [graphic reinitFromFile:filename];
  344.     [self cache:gBounds];    // updating a link to an imported file
  345.     [[self window] flushWindow];
  346.     [self dirty];
  347.     return YES;
  348.     }
  349.  
  350.     return NO;
  351. }
  352.  
  353. /* Other Links methods */
  354.  
  355. /*
  356.  * Just makes the Link Inspector panel reflect whether any of the
  357.  * Graphic's currently selected are linked to some other document.
  358.  */
  359.  
  360. - (void)updateLinksPanel
  361. {
  362.     int i, linkCount = 0;
  363.     Graphic *foundGraphic = nil, *graphic = nil;
  364.  
  365.     if (linkManager) {
  366.     for (i = [slist count]-1; i >= 0; i--) {
  367.         if ((graphic = [[slist objectAtIndex:i] graphicLinkedBy:NULL])) {
  368.         if ([graphic isKindOfClass:[Group class]]) {
  369.             linkCount += 2;
  370.             break;
  371.         } else {
  372.             linkCount += 1;
  373.             foundGraphic = graphic;
  374.         }
  375.         }
  376.     }
  377.     if (linkCount == 1) {
  378.         [NSDataLinkPanel setLink:[foundGraphic link] manager:linkManager isMultiple:NO];
  379.     } else if (linkCount) {
  380.         [NSDataLinkPanel setLink:[foundGraphic link] manager:linkManager isMultiple:YES];
  381.     } else {
  382.         [NSDataLinkPanel setLink:nil manager:linkManager isMultiple:NO];
  383.     }
  384.     } 
  385. }
  386.  
  387. - (NSDataLinkManager *)linkManager
  388. {
  389.     return linkManager;
  390. }
  391.  
  392. /*
  393.  * When we get a linkManager via this method, we must go and revive all the links.
  394.  * This is due to the fact that we don't archive ANY link information when we
  395.  * save a Draw document.  However, the unique identifiers ARE archived, and thus,
  396.  * when we unarchive, we can recreate NSSelections with those unique identifiers
  397.  * and then ask the NSDataLinkManager for the link objects associated with those
  398.  * NSSelections.
  399.  *
  400.  * After we have revived all the links, we call breakLinkAndRedrawOutlines:
  401.  * with nil (meaning redraw the link outlines for all links).
  402.  */
  403.  
  404. - (void)setLinkManager:(NSDataLinkManager *)aLinkManager
  405. {
  406.     if (!linkManager) {
  407.     linkManager = aLinkManager;
  408.     [glist makeObjectsPerform:@selector(reviveLink:) withObject:linkManager];
  409.     [self breakLinkAndRedrawOutlines:nil];
  410.     } 
  411. }
  412.  
  413. /*
  414.  * This is called when the user chooses Open Source.
  415.  * It uses the trick of drawing directly into the GraphicView
  416.  * which, of course, is only ephemeral since the REAL contents
  417.  * of the GraphicView are stored in the backing store.
  418.  * This is convenient because Open Source is only a temporary
  419.  * the the user calls to see where the data for his link is
  420.  * coming from.
  421.  */
  422.  
  423. - (BOOL)showSelection:(NSSelection *)selection
  424. {
  425.     BOOL retval = YES;
  426.     NSArray *graphics = nil;
  427.     NSRect *newInvalidRect;
  428.     NSRect sBounds, linkBounds;
  429.     
  430.     [self lockFocus];
  431.     if (invalidRect) {
  432.     [self drawRect:*invalidRect];
  433.     newInvalidRect = invalidRect;
  434.     invalidRect = NULL;
  435.     } else{
  436.     newInvalidRect = NSZoneMalloc(NSDefaultMallocZone(), (1) * sizeof(NSRect));
  437.     }
  438.     if ([self getRect:&linkBounds forSelection:selection]) {
  439.     PSsetgray(NSLightGray);
  440.     NSFrameRectWithWidth(linkBounds, 2.0);
  441.     *newInvalidRect = linkBounds;
  442.     graphics = [self findGraphicsInSelection:selection];
  443.     if (graphics) {
  444.         sBounds = [self getBBoxOfArray:graphics];
  445.         *newInvalidRect = NSUnionRect(sBounds, *newInvalidRect);
  446.     } else {
  447.         invalidRect = newInvalidRect;
  448.         [self scrollRectToVisible:*invalidRect];
  449.         [[self window] flushWindow];
  450.         retval = NO;
  451.     }
  452.     } else {
  453.     graphics = [self findGraphicsInSelection:selection];
  454.     if (graphics) {
  455.         sBounds = [self getBBoxOfArray:graphics];
  456.         *newInvalidRect = sBounds;
  457.     } else {
  458.         retval = NO;
  459.     }
  460.     }
  461.  
  462.     if (retval) {
  463.     NSFrameLinkRect(sBounds, NO);
  464.     invalidRect = newInvalidRect;
  465.     *invalidRect = NSInsetRect(*invalidRect, -NSLinkFrameThickness(), -NSLinkFrameThickness());
  466.     [self scrollRectToVisible:*invalidRect];
  467.     [[self window] flushWindow];
  468.     }
  469.  
  470.     [self unlockFocus];
  471.  
  472.     return retval;
  473. }
  474.  
  475. /*
  476.  * Called when the Show Links button in the Link Inspector panel is clicked
  477.  * (the link argument will be nil in this case), or when a link is broken
  478.  * (the link argument will be the link that was broken).
  479.  */
  480.  
  481. - (void)breakLinkAndRedrawOutlines:(NSDataLink *)link
  482. {
  483.     int i;
  484.     Graphic *graphic;
  485.     BOOL gotOne = NO;
  486.     NSRect eBounds;
  487.     NSRect recacheBounds;
  488.  
  489.     for (i = [glist count]-1; i >= 0; i--) {
  490.     graphic = [glist objectAtIndex:i];
  491.     if ((graphic = [graphic graphicLinkedBy:link])) {
  492.         if (link && ([graphic link] == link) &&
  493.         ([link updateMode] == NSUpdateNever)) {
  494.             [self removeGraphic:graphic];
  495.         }
  496.         if (!link || [linkManager areLinkOutlinesVisible]) {
  497.         eBounds = [graphic extendedBounds];
  498.         if (gotOne) {
  499.             recacheBounds = NSUnionRect(eBounds, recacheBounds);
  500.         } else {
  501.             recacheBounds = eBounds;
  502.             gotOne = YES;
  503.         }
  504.         }
  505.     }
  506.     }
  507.     if (gotOne) {
  508.     [self cache:recacheBounds andUpdateLinks:NO];
  509.     [[self window] flushWindow];
  510.     } 
  511. }
  512.  
  513. /*
  514.  * Tracking Link Changes.
  515.  *
  516.  * This is how we get "Continuous" updating links.
  517.  *
  518.  * We simply assume that a thing someone is linked to in our document
  519.  * changes whenever we have to redraw any rectangle in the GraphicView
  520.  * which intersects the linked-to rectangle.  See cache:andUpdateLinks:
  521.  * in GraphicView.m.
  522.  *
  523.  * We should stop using Storage in this code!
  524.  */
  525.  
  526. typedef struct {
  527.     NSRect linkRect;
  528.     NSDataLink *link;
  529.     BOOL dragged, all;
  530. } LinkRect;
  531.  
  532. - (void)updateTrackedLinks:(NSRect)rect
  533. {
  534.     int i;
  535.     LinkRect *lr;
  536.     NSArray *graphics;
  537.     NSSelection *selection;
  538.     NSRect *lRect, newRect;
  539.  
  540.     for (i = [linkTrackingRects count]-1; i >= 0; i--) {
  541.     if (!NSIsEmptyRect(NSIntersectionRect(rect, *(NSRect *)[linkTrackingRects elementAt:i]))) {
  542.         lr = ((LinkRect *)[linkTrackingRects elementAt:i]);
  543.         [lr->link noteSourceEdited];
  544.         lRect = (NSRect *)[linkTrackingRects elementAt:i];
  545.         if (!lr->dragged && !lr->all && !NSContainsRect(*lRect, rect)) {
  546.         selection = [lr->link sourceSelection];
  547.         if ((graphics = [self findGraphicsInSelection:selection])) {
  548.             newRect = [self getBBoxOfArray:graphics];
  549.             *lRect = newRect;
  550.         }
  551.         }
  552.     }
  553.     } 
  554. }
  555.  
  556. /* Add to linkTrackingRects. */
  557.  
  558. - (void)startTrackingLink:(NSDataLink *)link
  559. {
  560.     LinkRect trackRect;
  561.     NSArray *graphics = nil;
  562.     NSSelection *selection;
  563.     BOOL all = NO, dragged = NO, piecemeal = NO;
  564.  
  565.     selection = [link sourceSelection];
  566.     if ([selection isEqual:[NSSelection allSelection]]) {
  567.     all = YES;
  568.     trackRect.linkRect = _bounds;
  569.     } else if ([self getRect:&trackRect.linkRect forSelection:selection]) {
  570.     dragged = YES;
  571.     } else if ((graphics = [self findGraphicsInSelection:selection])) {
  572.     trackRect.linkRect = [self getBBoxOfArray:graphics];
  573.     piecemeal = YES;
  574.     } else {
  575.     return;
  576.     }
  577.  
  578.     if (all || dragged || piecemeal) {
  579.     if (!linkTrackingRects) {
  580.         linkTrackingRects = [[Storage alloc] initCount:1 elementSize:sizeof(LinkRect) description:"{ffff@}"];
  581.     }
  582.     [self stopTrackingLink:link];
  583.     trackRect.link = link;
  584.     trackRect.dragged = dragged;
  585.     trackRect.all = all;
  586.     [linkTrackingRects addElement:&trackRect];
  587.     }
  588. }
  589.  
  590. /* Remove from linkTrackingRects. */
  591.  
  592. - (void)stopTrackingLink:(NSDataLink *)link
  593. {
  594.     int i;
  595.  
  596.     for (i = [linkTrackingRects count]-1; i >= 0; i--) {
  597.     if (((LinkRect *)[linkTrackingRects elementAt:i])->link == link) {
  598.         [linkTrackingRects removeElementAt:i];
  599.     }
  600.     }
  601. }
  602.  
  603. @end
  604.