This is gvPasteboard.m in view mode; [Download] [Up]
#import "draw.h" @implementation GraphicView(Pasteboard) /* Methods to search through Pasteboard types lists. */ BOOL IncludesType(const NXAtom *types, NXAtom type) { if (types) while (*types) if (*types++ == type) return YES; return NO; } NXAtom MatchTypes(const NXAtom *typesToMatch, const NXAtom *orderedTypes) { while (orderedTypes && *orderedTypes) { if (IncludesType(typesToMatch, *orderedTypes)) return *orderedTypes; orderedTypes++; } return NULL; } NXAtom TextPasteType(const NXAtom *types) /* * Returns the pasteboard type in the passed list of types which is preferred * by the Draw program for pasting. The Draw program prefers PostScript over TIFF. */ { if (IncludesType(types, NXRTFPboardType)) return NXRTFPboardType; if (IncludesType(types, NXAsciiPboardType)) return NXAsciiPboardType; return NULL; } NXAtom ForeignPasteType(const NXAtom *types) /* * Returns the pasteboard type in the passed list of types which is preferred * by the Draw program for pasting. The Draw program prefers PostScript over TIFF. */ { NXAtom retval = TextPasteType(types); return retval ? retval : MatchTypes(types, [NXImage imagePasteboardTypes]); } NXAtom DrawPasteType(const NXAtom *types) /* * Returns the pasteboard type in the passed list of types which is preferred * by the Draw program for pasting. The Draw program prefers its own type * of course, then it prefers Text, then something NXImage can handle. */ { if (IncludesType(types, DrawPboardType)) return DrawPboardType; return ForeignPasteType(types); } NXAtom *TypesDrawExports(void) { static NXAtom *exportList = NULL; if (!exportList) { NX_MALLOC(exportList, NXAtom, NUM_TYPES_DRAW_EXPORTS); exportList[0] = DrawPboardType; exportList[1] = NXPostScriptPboardType; exportList[2] = NXTIFFPboardType; } return exportList; } /* Lazy Pasteboard evaluation handler */ /* * IMPORTANT: The pasteboard:provideData: method is a factory method since the * factory object is persistent and there is no guarantee that the INSTANCE of * GraphicView that put the Draw format into the Pasteboard will be around * to lazily put PostScript or TIFF in there, so we keep one around (actually * we only create it when we need it) to do the conversion (scrapper). * * If you find this part of the code confusing, then you need not even * use the provideData: mechanism--simply put the data for all the different * types your program knows how to put in the Pasteboard in at the time * that you declareTypes:. */ /* * Converts the data in the Pasteboard from Draw internal format to * either PostScript or TIFF using the writeTIFFToStream: and writePSToStream: * methods. It sends these messages to the scrapper (a GraphicView cached * to perform this very function). Note that the scrapper view is put in * a window, but that window is off-screen, has no backing store, and no * title (and is thus very cheap). */ + convert:(NXTypedStream *)ts to:(const char *)type using:(SEL)writer toPasteboard:(Pasteboard *)pb { Window *w; List *list; NXZone *zone; NXStream *stream; GraphicView *scrapper; NXRect scrapperFrame = {{0.0, 0.0}, {11.0*72.0, 14.0*72.0}}; if (!ts) return self; zone = NXCreateZone(vm_page_size, vm_page_size, NO); NXNameZone(zone, "Scrapper"); scrapper = [[GraphicView allocFromZone:zone] initFrame:&scrapperFrame]; NXSetTypedStreamZone(ts, zone); list = NXReadObject(ts); [scrapper getBBox:&scrapperFrame of:list]; scrapperFrame.size.width += scrapperFrame.origin.x; scrapperFrame.size.height += scrapperFrame.origin.y; scrapperFrame.origin.x = scrapperFrame.origin.y = 0.0; [scrapper sizeTo:scrapperFrame.size.width :scrapperFrame.size.height]; w = [[Window allocFromZone:zone] initContent:&scrapperFrame style:NX_PLAINSTYLE backing:NX_NONRETAINED buttonMask:0 defer:NO]; [w reenableDisplay]; [w setContentView:scrapper]; stream = NXOpenMemory(NULL, 0, NX_WRITEONLY); [scrapper perform:writer with:(id)stream with:list]; [pb writeType:type fromStream:stream]; NXCloseMemory(stream, NX_FREEBUFFER); [list freeObjects]; [list free]; [w free]; NXDestroyZone(zone); return self; } /* * Called by the Pasteboard whenever PostScript or TIFF data is requested * from the Pasteboard by some other application. The current contents of * the Pasteboard (which is in the Draw internal format) is taken out and loaded * into a stream, then convert:to:using:toPasteboard: is called. This * returns self if successful, nil otherwise. */ + pasteboard:(Pasteboard *)sender provideData:(const char *)type { id retval = nil; NXStream *stream; NXTypedStream *ts; if ((type == NXPostScriptPboardType) || (type == NXTIFFPboardType)) { if (stream = [sender readTypeToStream:DrawPboardType]) { if (ts = NXOpenTypedStream(stream, NX_READONLY)) { retval = self; if (type == NXPostScriptPboardType) { [self convert:ts to:type using:@selector(writePSToStream:usingList:) toPasteboard:sender]; } else if (type == NXTIFFPboardType) { [self convert:ts to:type using:@selector(writeTIFFToStream:usingList:) toPasteboard:sender]; } else { retval = nil; } NXCloseTypedStream(ts); } NXCloseMemory(stream, NX_FREEBUFFER); } } return retval; } /* Writing data in different forms (other than the internal Draw format) */ /* * Writes out the PostScript generated by drawing all the objects in the * glist. The bounding box of the generated encapsulated PostScript will * be equal to the bounding box of the objects in the glist (NOT the * bounds of the view). */ - writePSToStream:(NXStream *)stream { NXRect bbox; if (stream) { if (([glist count] == 1) && [[glist objectAt:0] canEmitEPS]) { [[glist objectAt:0] writeEPSToStream:stream]; } else { [self getBBox:&bbox of:glist]; [self copyPSCodeInside:&bbox to:stream]; } } return self; } /* * This is the same as writePSToStream:, but it lets you specify the list * of Graphics you want to generate PostScript for (does its job by swapping * the glist for the list you provide temporarily). */ - writePSToStream:(NXStream *)stream usingList:list { List *savedglist; savedglist = glist; glist = list; [self writePSToStream:stream]; glist = savedglist; return self; } /* * Images all of the objects in the glist and writes out the result in * the Tagged Image File Format (TIFF). The image will not have alpha in it. */ - writeTIFFToStream:(NXStream *)stream { NXRect sbounds; Window *tiffCache; NXBitmapImageRep *bm; if (!stream) return self; if (([glist count] == 1) && [[glist objectAt:0] canEmitTIFF]) { [[glist objectAt:0] writeTIFFToStream:stream]; } else { tiffCache = [self createCacheWindow:nil]; [tiffCache setDepthLimit:NX_TwentyFourBitRGBDepth]; [self cacheList:glist into:tiffCache withTransparentBackground:NO]; [self getBBox:&sbounds of:glist]; [[tiffCache contentView] lockFocus]; sbounds.origin.x = sbounds.origin.y = 0.0; bm = [[NXBitmapImageRep alloc] initData:NULL fromRect:&sbounds]; [[tiffCache contentView] unlockFocus]; [bm writeTIFF:stream usingCompression:NX_TIFF_COMPRESSION_LZW]; [bm free]; [tiffCache free]; } return self; } /* * This is the same as writeTIFFToStream:, but it lets you specify the list * of Graphics you want to generate TIFF for (does its job by swapping * the glist for the list you provide temporarily). */ - writeTIFFToStream:(NXStream *)stream usingList:list { List *savedglist; savedglist = glist; glist = list; [self writeTIFFToStream:stream]; glist = savedglist; return self; } /* Writing the selection to a stream */ - copySelectionAsPSToStream:(NXStream *)stream { return (stream && [slist count]) ? [self writePSToStream:stream usingList:slist] : nil; } - copySelectionAsTIFFToStream:(NXStream *)stream { return (stream && [slist count]) ? [self writeTIFFToStream:stream usingList:slist] : nil; } - copySelectionToStream:(NXStream *)stream { NXTypedStream *ts; if ([slist count]) { ts = NXOpenTypedStream(stream, NX_WRITEONLY); NXWriteRootObject(ts, slist); NXCloseTypedStream(ts); } else { return nil; } return self; } /* Pasteboard-related target/action methods */ - cut:sender /* * Calls copy: then delete:. */ { id change; if ([slist count] > 0) { change = [[CutGraphicsChange alloc] initGraphicView:self]; [change startChange]; [self copy:sender]; lastCutChangeCount = lastCopiedChangeCount; [self delete:sender]; consecutivePastes = 0; [change endChange]; return self; } else { return nil; } } - copy:sender { if ([slist count]) { [self copyToPasteboard:[Pasteboard new]]; lastPastedChangeCount = [[Pasteboard new] changeCount]; lastCopiedChangeCount = [[Pasteboard new] changeCount]; consecutivePastes = 1; originalPaste = [slist objectAt:0]; } return self; } - paste:sender { return [self paste:sender andLink:DontLink]; } - pasteAndLink:sender { return [self paste:sender andLink:Link]; } - link:sender { return [self paste:sender andLink:LinkOnly]; } /* Methods to write to/read from the pasteboard */ /* * Puts all the objects in the slist into the Pasteboard by archiving * the slist itself. Also registers the PostScript and TIFF types since * the GraphicView knows how to convert its internal type to PostScript * or TIFF via the write{PS,TIFF}ToStream: methods. */ - copyToPasteboard:(Pasteboard *)pboard types:(NXAtom *)typesList { char *data; NXStream *stream; const char *types[4]; int i = 0, length, maxlen; if ([slist count]) { types[i++] = DrawPboardType; if (!typesList || IncludesType(typesList, NXPostScriptPboardType)) { types[i++] = NXPostScriptPboardType; } if (!typesList || IncludesType(typesList, NXTIFFPboardType)) { types[i++] = NXTIFFPboardType; } [pboard declareTypes:types num:i owner:[self class]]; stream = NXOpenMemory(NULL, 0, NX_WRITEONLY); [self copySelectionToStream:stream]; NXGetMemoryBuffer(stream, &data, &length, &maxlen); [pboard writeType:DrawPboardType data:data length:length]; NXCloseMemory(stream, NX_FREEBUFFER); [self writeLinkToPasteboard:pboard types:typesList]; return self; } else { return nil; } } - copyToPasteboard:(Pasteboard *)pboard { return [self copyToPasteboard:pboard types:NULL]; } /* * Pastes any data that comes from another application. * Basically this is the "else" in pasteFromPasteboard: below if there * is no Draw internal format in the Pasteboard. This is also called * from the drag stuff (see gvDrag.m). */ - (BOOL)pasteForeignDataFromPasteboard:(Pasteboard *)pboard andLink:(LinkType)doLink at:(const NXPoint *)center { NXDataLink *link = nil; Graphic *graphic = nil; if (!linkManager) doLink = DontLink; if (doLink) link = [[NXDataLink alloc] initFromPasteboard:pboard]; if (link && (doLink == LinkOnly)) { graphic = [[Image allocFromZone:[self zone]] initWithLinkButton]; } else { graphic = [[TextGraphic allocFromZone:[self zone]] initFromPasteboard:pboard]; if (!graphic) graphic = [[Image allocFromZone:[self zone]] initFromPasteboard:pboard]; } [self deselectAll:self]; if (doLink && link) { if ([self addLink:link toGraphic:graphic at:center update:UPDATE_NORMALLY]) return YES; } else if (graphic) { if ([self placeGraphic:graphic at:center]) return YES; } return NO; } /* * Pastes any type available from the specified Pasteboard into the GraphicView. * If the type in the Pasteboard is the internal type, then the objects * are simply added to the slist and glist. If it is PostScript or TIFF, * then an Image object is created using the contents of * the Pasteboard. Returns a list of the pasted objects (which should be freed * by the caller). */ - pasteFromPasteboard:(Pasteboard *)pboard andLink:(LinkType)doLink at:(const NXPoint *)center { int i; id change; NXStream *stream; NXTypedStream *ts; List *pblist = nil; Graphic *graphic = nil; BOOL pasteDrawType = NO; if (!linkManager) doLink = DontLink; if (!doLink) pasteDrawType = IncludesType([pboard types], DrawPboardType); if (pasteDrawType) { stream = [pboard readTypeToStream:DrawPboardType]; ts = NXOpenTypedStream(stream, NX_READONLY); pblist = NXReadObject(ts); if (i = [pblist count]) { change = [[PasteGraphicsChange alloc] initGraphicView:self graphics:pblist]; [change startChange]; [self deselectAll:self]; while (i--) { graphic = [pblist objectAt:i]; [slist insertObject:graphic at:0]; [glist insertObject:graphic at:0]; if ([graphic mightBeLinked]) { [self readLinkForGraphic:graphic fromPasteboard:pboard useNewIdentifier:([pboard changeCount] != lastCutChangeCount)]; } gvFlags.groupInSlist = gvFlags.groupInSlist || [graphic isKindOf:[Group class]]; } [change endChange]; } else { [pblist free]; pblist = nil; } NXCloseTypedStream(ts); NXCloseMemory(stream, NX_FREEBUFFER); } else { [self pasteForeignDataFromPasteboard:pboard andLink:doLink at:center]; } return pblist; } /* * Pastes from the normal pasteboard. * This paste implements "smart paste" which goes like this: if the user * pastes in a single item (a Group is considered a single item), then * pastes that item again and moves that second item somewhere, then * subsequent pastes will be positioned at the same offset between the * first and second pastes (this is also known as "transform again"). */ - paste:sender andLink:(LinkType)doLink { List *pblist; NXPoint offset; Graphic *graphic; Pasteboard *pboard; NXRect originalBounds, secondBounds; static Graphic *secondPaste; static NXPoint pasteOffset; pboard = [Pasteboard new]; pblist = [self pasteFromPasteboard:pboard andLink:doLink at:NULL]; if (pblist && IncludesType([pboard types], DrawPboardType)) { graphic = ([pblist count] == 1) ? [pblist objectAt:0] : nil; if (lastPastedChangeCount != [pboard changeCount]) { consecutivePastes = 0; lastPastedChangeCount = [pboard changeCount]; originalPaste = graphic; } else { if (consecutivePastes == 1) { /* smart paste */ pasteOffset.x = 10.0; pasteOffset.y = -10.0; secondPaste = graphic; } else if ((consecutivePastes == 2) && graphic) { [originalPaste getBounds:&originalBounds]; [secondPaste getBounds:&secondBounds]; pasteOffset.x = secondBounds.origin.x - originalBounds.origin.x; pasteOffset.y = secondBounds.origin.y - originalBounds.origin.y; } offset.x = pasteOffset.x * consecutivePastes; offset.y = pasteOffset.y * consecutivePastes; [slist makeObjectsPerform:@selector(moveBy:) with:(id)&offset]; } consecutivePastes++; [self recacheSelection]; } [pblist free]; return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.