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.