home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.3 (Developer) / NeXT_Developer-3.3.iso / NextDeveloper / Examples / AppKit / Draw / gvPasteboard.m < prev    next >
Encoding:
Text File  |  1993-01-09  |  15.0 KB  |  520 lines

  1. #import "draw.h"
  2.  
  3. @implementation GraphicView(Pasteboard)
  4.  
  5. /* Methods to search through Pasteboard types lists. */
  6.  
  7. BOOL IncludesType(const NXAtom *types, NXAtom type)
  8. {
  9.     if (types) while (*types) if (*types++ == type) return YES;
  10.     return NO;
  11. }
  12.  
  13. NXAtom MatchTypes(const NXAtom *typesToMatch, const NXAtom *orderedTypes)
  14. {
  15.     while (orderedTypes && *orderedTypes) {
  16.     if (IncludesType(typesToMatch, *orderedTypes)) return *orderedTypes;
  17.     orderedTypes++;
  18.     }
  19.     return NULL;
  20. }
  21.  
  22. NXAtom TextPasteType(const NXAtom *types)
  23. /*
  24.  * Returns the pasteboard type in the passed list of types which is preferred
  25.  * by the Draw program for pasting.  The Draw program prefers PostScript over TIFF.
  26.  */
  27. {
  28.     if (IncludesType(types, NXRTFPboardType)) return NXRTFPboardType;
  29.     if (IncludesType(types, NXAsciiPboardType)) return NXAsciiPboardType;
  30.     return NULL;
  31. }
  32.  
  33. NXAtom ForeignPasteType(const NXAtom *types)
  34. /*
  35.  * Returns the pasteboard type in the passed list of types which is preferred
  36.  * by the Draw program for pasting.  The Draw program prefers PostScript over TIFF.
  37.  */
  38. {
  39.     NXAtom retval = TextPasteType(types);
  40.     return retval ? retval : MatchTypes(types, [NXImage imagePasteboardTypes]);
  41. }
  42.  
  43. NXAtom DrawPasteType(const NXAtom *types)
  44. /*
  45.  * Returns the pasteboard type in the passed list of types which is preferred
  46.  * by the Draw program for pasting.  The Draw program prefers its own type
  47.  * of course, then it prefers Text, then something NXImage can handle.
  48.  */
  49. {
  50.     if (IncludesType(types, DrawPboardType)) return DrawPboardType;
  51.     return ForeignPasteType(types);
  52. }
  53.  
  54. NXAtom *TypesDrawExports(void)
  55. {
  56.     static NXAtom *exportList = NULL;
  57.     if (!exportList) {
  58.     NX_MALLOC(exportList, NXAtom, NUM_TYPES_DRAW_EXPORTS);
  59.     exportList[0] = DrawPboardType;
  60.     exportList[1] = NXPostScriptPboardType;
  61.     exportList[2] = NXTIFFPboardType;
  62.     }
  63.     return exportList;
  64. }
  65.  
  66. /* Lazy Pasteboard evaluation handler */
  67.  
  68. /*
  69.  * IMPORTANT: The pasteboard:provideData: method is a factory method since the
  70.  * factory object is persistent and there is no guarantee that the INSTANCE of
  71.  * GraphicView that put the Draw format into the Pasteboard will be around
  72.  * to lazily put PostScript or TIFF in there, so we keep one around (actually
  73.  * we only create it when we need it) to do the conversion (scrapper).
  74.  *
  75.  * If you find this part of the code confusing, then you need not even
  76.  * use the provideData: mechanism--simply put the data for all the different
  77.  * types your program knows how to put in the Pasteboard in at the time
  78.  * that you declareTypes:.
  79.  */
  80.  
  81. /*
  82.  * Converts the data in the Pasteboard from Draw internal format to
  83.  * either PostScript or TIFF using the writeTIFFToStream: and writePSToStream:
  84.  * methods.  It sends these messages to the scrapper (a GraphicView cached
  85.  * to perform this very function).  Note that the scrapper view is put in
  86.  * a window, but that window is off-screen, has no backing store, and no
  87.  * title (and is thus very cheap).
  88.  */
  89.  
  90. + convert:(NXTypedStream *)ts to:(const char *)type using:(SEL)writer toPasteboard:(Pasteboard *)pb
  91. {
  92.     Window *w;
  93.     List *list;
  94.     NXZone *zone;
  95.     NXStream *stream;
  96.     GraphicView *scrapper;
  97.     NXRect scrapperFrame = {{0.0, 0.0}, {11.0*72.0, 14.0*72.0}};
  98.  
  99.     if (!ts) return self;
  100.  
  101.     zone = NXCreateZone(vm_page_size, vm_page_size, NO);
  102.     NXNameZone(zone, "Scrapper");
  103.     scrapper = [[GraphicView allocFromZone:zone] initFrame:&scrapperFrame];
  104.     NXSetTypedStreamZone(ts, zone);
  105.     list = NXReadObject(ts);
  106.     [scrapper getBBox:&scrapperFrame of:list];
  107.     scrapperFrame.size.width += scrapperFrame.origin.x;
  108.     scrapperFrame.size.height += scrapperFrame.origin.y;
  109.     scrapperFrame.origin.x = scrapperFrame.origin.y = 0.0;
  110.     [scrapper sizeTo:scrapperFrame.size.width :scrapperFrame.size.height];
  111.     w = [[Window allocFromZone:zone] initContent:&scrapperFrame
  112.                        style:NX_PLAINSTYLE
  113.                      backing:NX_NONRETAINED
  114.                       buttonMask:0
  115.                        defer:NO];
  116.     [w reenableDisplay];
  117.     [w setContentView:scrapper];
  118.     stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  119.     [scrapper perform:writer with:(id)stream with:list];
  120.     [pb writeType:type fromStream:stream];
  121.     NXCloseMemory(stream, NX_FREEBUFFER);
  122.     [list freeObjects];
  123.     [list free];
  124.     [w free];
  125.     NXDestroyZone(zone);
  126.  
  127.     return self;
  128. }
  129.  
  130.  
  131. /*
  132.  * Called by the Pasteboard whenever PostScript or TIFF data is requested
  133.  * from the Pasteboard by some other application.  The current contents of
  134.  * the Pasteboard (which is in the Draw internal format) is taken out and loaded
  135.  * into a stream, then convert:to:using:toPasteboard: is called.  This
  136.  * returns self if successful, nil otherwise.
  137.  */
  138.  
  139. + pasteboard:(Pasteboard *)sender provideData:(const char *)type
  140. {
  141.     id retval = nil;
  142.     NXStream *stream;
  143.     NXTypedStream *ts;
  144.  
  145.     if ((type == NXPostScriptPboardType) || (type == NXTIFFPboardType)) {
  146.     if (stream = [sender readTypeToStream:DrawPboardType]) {
  147.         if (ts = NXOpenTypedStream(stream, NX_READONLY)) {
  148.         retval = self;
  149.         if (type == NXPostScriptPboardType) {
  150.             [self convert:ts to:type using:@selector(writePSToStream:usingList:) toPasteboard:sender];
  151.         } else if (type == NXTIFFPboardType) {
  152.             [self convert:ts to:type using:@selector(writeTIFFToStream:usingList:) toPasteboard:sender];
  153.         } else {
  154.             retval = nil;
  155.         }
  156.         NXCloseTypedStream(ts);
  157.         }
  158.         NXCloseMemory(stream, NX_FREEBUFFER);
  159.     }
  160.     }
  161.  
  162.     return retval;
  163. }
  164.  
  165. /* Writing data in different forms (other than the internal Draw format) */
  166.  
  167. /*
  168.  * Writes out the PostScript generated by drawing all the objects in the
  169.  * glist.  The bounding box of the generated encapsulated PostScript will
  170.  * be equal to the bounding box of the objects in the glist (NOT the
  171.  * bounds of the view).
  172.  */
  173.  
  174. - writePSToStream:(NXStream *)stream
  175. {
  176.     NXRect bbox;
  177.  
  178.     if (stream) {
  179.     if (([glist count] == 1) && [[glist objectAt:0] canEmitEPS]) {
  180.         [[glist objectAt:0] writeEPSToStream:stream];
  181.     } else {
  182.         [self getBBox:&bbox of:glist];
  183.         [self copyPSCodeInside:&bbox to:stream];
  184.     }
  185.     }
  186.  
  187.     return self;
  188. }
  189.  
  190. /*
  191.  * This is the same as writePSToStream:, but it lets you specify the list
  192.  * of Graphics you want to generate PostScript for (does its job by swapping
  193.  * the glist for the list you provide temporarily).
  194.  */
  195.  
  196. - writePSToStream:(NXStream *)stream usingList:list
  197. {
  198.     List *savedglist;
  199.  
  200.     savedglist = glist;
  201.     glist = list;
  202.     [self writePSToStream:stream];
  203.     glist = savedglist;
  204.  
  205.     return self;
  206. }
  207.  
  208. /*
  209.  * Images all of the objects in the glist and writes out the result in
  210.  * the Tagged Image File Format (TIFF).  The image will not have alpha in it.
  211.  */
  212.  
  213. - writeTIFFToStream:(NXStream *)stream
  214. {
  215.     NXRect sbounds;
  216.     Window *tiffCache;
  217.     NXBitmapImageRep *bm;
  218.  
  219.     if (!stream) return self;
  220.  
  221.     if (([glist count] == 1) && [[glist objectAt:0] canEmitTIFF]) {
  222.     [[glist objectAt:0] writeTIFFToStream:stream];
  223.     } else {
  224.     tiffCache = [self createCacheWindow:nil];
  225.     [tiffCache setDepthLimit:NX_TwentyFourBitRGBDepth];
  226.     [self cacheList:glist into:tiffCache withTransparentBackground:NO];
  227.     [self getBBox:&sbounds of:glist];
  228.     [[tiffCache contentView] lockFocus];
  229.     sbounds.origin.x = sbounds.origin.y = 0.0;
  230.     bm = [[NXBitmapImageRep alloc] initData:NULL fromRect:&sbounds];
  231.     [[tiffCache contentView] unlockFocus];
  232.     [bm writeTIFF:stream usingCompression:NX_TIFF_COMPRESSION_LZW];
  233.     [bm free];
  234.     [tiffCache free];
  235.     }
  236.  
  237.     return self;
  238. }
  239.  
  240. /*
  241.  * This is the same as writeTIFFToStream:, but it lets you specify the list
  242.  * of Graphics you want to generate TIFF for (does its job by swapping
  243.  * the glist for the list you provide temporarily).
  244.  */
  245.  
  246. - writeTIFFToStream:(NXStream *)stream usingList:list
  247. {
  248.     List *savedglist;
  249.  
  250.     savedglist = glist;
  251.     glist = list;
  252.     [self writeTIFFToStream:stream];
  253.     glist = savedglist;
  254.  
  255.     return self;
  256. }
  257.  
  258. /* Writing the selection to a stream */
  259.  
  260. - copySelectionAsPSToStream:(NXStream *)stream
  261. {
  262.     return (stream && [slist count]) ? [self writePSToStream:stream usingList:slist] : nil;
  263. }
  264.  
  265. - copySelectionAsTIFFToStream:(NXStream *)stream
  266. {
  267.     return (stream && [slist count]) ? [self writeTIFFToStream:stream usingList:slist] : nil;
  268. }
  269.  
  270. - copySelectionToStream:(NXStream *)stream
  271. {
  272.     NXTypedStream *ts;
  273.  
  274.     if ([slist count]) {
  275.     ts = NXOpenTypedStream(stream, NX_WRITEONLY);
  276.     NXWriteRootObject(ts, slist);
  277.     NXCloseTypedStream(ts);
  278.     } else {
  279.     return nil;
  280.     }
  281.  
  282.     return self;
  283. }
  284.  
  285. /* Pasteboard-related target/action methods */
  286.  
  287. - cut:sender
  288. /*
  289.  * Calls copy: then delete:.
  290.  */
  291. {
  292.     id change;
  293.  
  294.     if ([slist count] > 0) {
  295.     change = [[CutGraphicsChange alloc] initGraphicView:self];
  296.     [change startChange];
  297.         [self copy:sender];
  298.         lastCutChangeCount = lastCopiedChangeCount;
  299.         [self delete:sender];
  300.         consecutivePastes = 0;
  301.         [change endChange];
  302.     return self;
  303.     } else {
  304.     return nil;
  305.     }
  306. }
  307.  
  308. - copy:sender
  309. {
  310.     if ([slist count]) {
  311.     [self copyToPasteboard:[Pasteboard new]];
  312.     lastPastedChangeCount = [[Pasteboard new] changeCount];
  313.     lastCopiedChangeCount = [[Pasteboard new] changeCount];
  314.     consecutivePastes = 1;
  315.     originalPaste = [slist objectAt:0];
  316.     }
  317.     return self;
  318. }
  319.  
  320. - paste:sender
  321. {
  322.     return [self paste:sender andLink:DontLink];
  323. }
  324.  
  325. - pasteAndLink:sender
  326. {
  327.     return [self paste:sender andLink:Link];
  328. }
  329.  
  330. - link:sender
  331. {
  332.     return [self paste:sender andLink:LinkOnly];
  333. }
  334.  
  335. /* Methods to write to/read from the pasteboard */
  336.  
  337. /*
  338.  * Puts all the objects in the slist into the Pasteboard by archiving
  339.  * the slist itself.  Also registers the PostScript and TIFF types since
  340.  * the GraphicView knows how to convert its internal type to PostScript
  341.  * or TIFF via the write{PS,TIFF}ToStream: methods.
  342.  */
  343.  
  344. - copyToPasteboard:(Pasteboard *)pboard types:(NXAtom *)typesList
  345. {
  346.     char *data;
  347.     NXStream *stream;
  348.     const char *types[4];
  349.     int i = 0, length, maxlen;
  350.  
  351.     if ([slist count]) {
  352.     types[i++] = DrawPboardType;
  353.     if (!typesList || IncludesType(typesList, NXPostScriptPboardType)) {
  354.         types[i++] = NXPostScriptPboardType;
  355.     }
  356.     if (!typesList || IncludesType(typesList, NXTIFFPboardType)) {
  357.         types[i++] = NXTIFFPboardType;
  358.     }
  359.     [pboard declareTypes:types num:i owner:[self class]];
  360.     stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  361.     [self copySelectionToStream:stream];
  362.     NXGetMemoryBuffer(stream, &data, &length, &maxlen);
  363.     [pboard writeType:DrawPboardType data:data length:length];
  364.     NXCloseMemory(stream, NX_FREEBUFFER);
  365.     [self writeLinkToPasteboard:pboard types:typesList];
  366.     return self;
  367.     } else {
  368.     return nil;
  369.     }
  370. }
  371.  
  372. - copyToPasteboard:(Pasteboard *)pboard
  373. {
  374.     return [self copyToPasteboard:pboard types:NULL];
  375. }
  376.  
  377. /*
  378.  * Pastes any data that comes from another application.
  379.  * Basically this is the "else" in pasteFromPasteboard: below if there
  380.  * is no Draw internal format in the Pasteboard.  This is also called
  381.  * from the drag stuff (see gvDrag.m).
  382.  */
  383.  
  384. - (BOOL)pasteForeignDataFromPasteboard:(Pasteboard *)pboard andLink:(LinkType)doLink at:(const NXPoint *)center
  385. {
  386.     NXDataLink *link = nil;
  387.     Graphic *graphic = nil;
  388.  
  389.     if (!linkManager) doLink = DontLink;
  390.  
  391.     if (doLink) link = [[NXDataLink alloc] initFromPasteboard:pboard];
  392.     if (link && (doLink == LinkOnly)) {
  393.     graphic = [[Image allocFromZone:[self zone]] initWithLinkButton];
  394.     } else {
  395.     graphic = [[TextGraphic allocFromZone:[self zone]] initFromPasteboard:pboard];
  396.     if (!graphic) graphic = [[Image allocFromZone:[self zone]] initFromPasteboard:pboard];
  397.     }
  398.     [self deselectAll:self];
  399.     if (doLink && link) {
  400.     if ([self addLink:link toGraphic:graphic at:center update:UPDATE_NORMALLY]) return YES;
  401.     } else if (graphic) {
  402.     if ([self placeGraphic:graphic at:center]) return YES;
  403.     }
  404.  
  405.     return NO;
  406. }
  407.  
  408. /*
  409.  * Pastes any type available from the specified Pasteboard into the GraphicView.
  410.  * If the type in the Pasteboard is the internal type, then the objects
  411.  * are simply added to the slist and glist.  If it is PostScript or TIFF,
  412.  * then an Image object is created using the contents of
  413.  * the Pasteboard.  Returns a list of the pasted objects (which should be freed
  414.  * by the caller).
  415.  */
  416.  
  417. - pasteFromPasteboard:(Pasteboard *)pboard andLink:(LinkType)doLink at:(const NXPoint *)center
  418. {
  419.     int i;
  420.     id change;
  421.     NXStream *stream;
  422.     NXTypedStream *ts;
  423.     List *pblist = nil;
  424.     Graphic *graphic = nil;
  425.     BOOL pasteDrawType = NO;
  426.  
  427.     if (!linkManager) doLink = DontLink;
  428.     if (!doLink) pasteDrawType = IncludesType([pboard types], DrawPboardType);
  429.  
  430.     if (pasteDrawType) {
  431.     stream = [pboard readTypeToStream:DrawPboardType];
  432.     ts = NXOpenTypedStream(stream, NX_READONLY);
  433.     pblist = NXReadObject(ts);
  434.     if (i = [pblist count]) {
  435.         change = [[PasteGraphicsChange alloc] initGraphicView:self graphics:pblist];
  436.         [change startChange];
  437.         [self deselectAll:self];
  438.         while (i--) {
  439.             graphic = [pblist objectAt:i];
  440.             [slist insertObject:graphic at:0];
  441.             [glist insertObject:graphic at:0];
  442.             if ([graphic mightBeLinked]) {
  443.             BOOL useNewId = ([pboard changeCount] != lastCutChangeCount) || consecutivePastes;
  444.             [graphic readLinkFromPasteboard:pboard usingManager:linkManager useNewIdentifier:useNewId];
  445.             }
  446.             gvFlags.groupInSlist = gvFlags.groupInSlist || [graphic isKindOf:[Group class]];
  447.         }
  448.         [change endChange];
  449.     } else {
  450.         [pblist free];
  451.         pblist = nil;
  452.     }
  453.     NXCloseTypedStream(ts);
  454.     NXCloseMemory(stream, NX_FREEBUFFER);
  455.     } else {
  456.     [self pasteForeignDataFromPasteboard:pboard andLink:doLink at:center];
  457.     }
  458.  
  459.     return pblist;
  460. }
  461.  
  462. /*
  463.  * Pastes from the normal pasteboard.
  464.  * This paste implements "smart paste" which goes like this: if the user
  465.  * pastes in a single item (a Group is considered a single item), then
  466.  * pastes that item again and moves that second item somewhere, then
  467.  * subsequent pastes will be positioned at the same offset between the
  468.  * first and second pastes (this is also known as "transform again").
  469.  */
  470.  
  471. - paste:sender andLink:(LinkType)doLink
  472. {
  473.     List *pblist;
  474.     NXPoint offset;
  475.     Graphic *graphic;
  476.     Pasteboard *pboard;
  477.     NXRect originalBounds, secondBounds;
  478.     static Graphic *secondPaste;
  479.     static NXPoint pasteOffset;
  480.  
  481.     pboard = [Pasteboard new];
  482.     pblist = [self pasteFromPasteboard:pboard andLink:doLink at:NULL];
  483.  
  484.     if (pblist && IncludesType([pboard types], DrawPboardType)) {
  485.     graphic = ([pblist count] == 1) ? [pblist objectAt:0] : nil;
  486.     if (lastPastedChangeCount != [pboard changeCount]) {
  487.         consecutivePastes = 0;
  488.         lastPastedChangeCount = [pboard changeCount];
  489.         originalPaste = graphic;
  490.     } else {
  491.         if (consecutivePastes == 1) {    /* smart paste */
  492.         if (gvFlags.gridDisabled) {    /* offset to grid if turned off */
  493.             pasteOffset.x = 10.0;
  494.             pasteOffset.y = -10.0;
  495.         } else {
  496.             pasteOffset.x = (float)gvFlags.grid;
  497.             pasteOffset.y = -(float)gvFlags.grid;
  498.         }
  499.         secondPaste = graphic;
  500.         } else if ((consecutivePastes == 2) && graphic) {
  501.         [originalPaste getBounds:&originalBounds];
  502.         [secondPaste getBounds:&secondBounds];
  503.         pasteOffset.x = secondBounds.origin.x - originalBounds.origin.x;
  504.         pasteOffset.y = secondBounds.origin.y - originalBounds.origin.y;
  505.         }
  506.         offset.x = pasteOffset.x * consecutivePastes;
  507.         offset.y = pasteOffset.y * consecutivePastes;
  508.         [slist makeObjectsPerform:@selector(moveBy:) with:(id)&offset];
  509.     }
  510.     consecutivePastes++;
  511.     [self recacheSelection];
  512.     }
  513.  
  514.     [pblist free];
  515.  
  516.     return self;
  517. }
  518.  
  519. @end
  520.