|
|
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.