|
|
1.1 root 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
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.