|
|
1.1 ! root 1: #import "draw.h" ! 2: ! 3: @implementation TextGraphic ! 4: /* ! 5: * This uses a text object to draw and edit text. ! 6: * ! 7: * The one quirky thing to understand here is that growable Text objects ! 8: * in NeXTSTEP must be subviews of flipped view. Since a GraphicView is not ! 9: * flipped, we must have a flipped view into the view heirarchy when we ! 10: * edit (this editing view is permanently installed as a subview of the ! 11: * GraphicView--see GraphicView's newFrame: method). ! 12: */ ! 13: ! 14: + initialize ! 15: { ! 16: [TextGraphic setVersion:6]; /* class version, see read: */ ! 17: return self; ! 18: } ! 19: ! 20: static Text *drawText = nil; /* shared Text object used for drawing */ ! 21: ! 22: static void initClassVars() ! 23: /* ! 24: * Create the class variable drawText here. ! 25: */ ! 26: { ! 27: if (!drawText) { ! 28: drawText = [Text new]; ! 29: [drawText setMonoFont:NO]; ! 30: [drawText setEditable:NO]; ! 31: [drawText setSelectable:NO]; ! 32: [drawText setFlipped:YES]; ! 33: } ! 34: } ! 35: ! 36: + (BOOL)canInitFromPasteboard:(Pasteboard *)pboard ! 37: { ! 38: return IncludesType([pboard types], NXRTFPboardType) || ! 39: IncludesType([pboard types], NXAsciiPboardType); ! 40: } ! 41: ! 42: - init ! 43: /* ! 44: * Creates a "blank" TextGraphic. ! 45: * This is TextGraphic's designated initializer, ! 46: * but be wary because by the time this returns, the ! 47: * TextGraphic may not be full initialized (it'll be ! 48: * valid, just perhaps not fully initialized). ! 49: * Override finishedWithInit if you want that. ! 50: */ ! 51: { ! 52: initClassVars(); ! 53: [super init]; ! 54: return self; ! 55: } ! 56: ! 57: - initEmpty ! 58: /* ! 59: * Creates an empty TextGraphic. ! 60: */ ! 61: { ! 62: [self init]; ! 63: return [self finishedWithInit]; ! 64: } ! 65: ! 66: - finishedWithInit ! 67: /* ! 68: * Override this if you want to know when a newly ! 69: * initialized TextGraphics is fully init'ed. ! 70: */ ! 71: { ! 72: return self; ! 73: } ! 74: ! 75: - doInitFromStream:(NXStream *)stream ! 76: /* ! 77: * Common code for initFromStream: and reinitFromStream:. ! 78: * Looks at the first 5 characters of the stream and if it ! 79: * looks like an RTF file, then the contents of the stream ! 80: * are parsed as RTF, otherwise, the contents of the stream ! 81: * are assumed to be ASCII text and is passed through the ! 82: * drawText object and turned into RTF (using the method ! 83: * (writeRichText:). ! 84: */ ! 85: { ! 86: int maxlen; ! 87: char *buffer; ! 88: ! 89: if (stream) { ! 90: NXGetMemoryBuffer(stream, &buffer, &length, &maxlen); ! 91: if (!strncmp(buffer, "{\\rtf", 5)) { ! 92: NX_ZONEMALLOC([self zone], data, char, length); ! 93: bcopy(buffer, data, length); ! 94: [drawText readRichText:stream]; ! 95: } else { ! 96: [drawText selectAll:self]; ! 97: [drawText setFont:[Font userFontOfSize:-1.0 matrix:NX_FLIPPEDMATRIX]]; ! 98: [drawText readText:stream]; ! 99: stream = NXOpenMemory(NULL, 0, NX_WRITEONLY); ! 100: [drawText writeRichText:stream]; ! 101: NXGetMemoryBuffer(stream, &buffer, &length, &maxlen); ! 102: NX_ZONEMALLOC([self zone], data, char, length); ! 103: bcopy(buffer, data, length); ! 104: NXCloseMemory(stream, NX_FREEBUFFER); ! 105: } ! 106: [drawText setSel:0 :0]; ! 107: font = [drawText font]; ! 108: } ! 109: ! 110: return self; ! 111: } ! 112: ! 113: - initFromStream:(NXStream *)stream ! 114: /* ! 115: * Initializes the TextGraphic using data from the passed stream. ! 116: */ ! 117: { ! 118: [self init]; ! 119: ! 120: if (stream) { ! 121: [self doInitFromStream:stream]; ! 122: [drawText setHorizResizable:YES]; ! 123: [drawText setVertResizable:YES]; ! 124: bounds.size.width = bounds.size.height = 10000.0; ! 125: [drawText setMaxSize:&bounds.size]; ! 126: [drawText calcLine]; ! 127: [drawText getMinWidth:&bounds.size.width minHeight:&bounds.size.height maxWidth:10000.0 maxHeight:10000.0]; ! 128: bounds.origin.x = bounds.origin.y = 0.0; ! 129: } ! 130: ! 131: return [self finishedWithInit]; ! 132: } ! 133: ! 134: - initFromFile:(const char *)file ! 135: /* ! 136: * Initializes the TextGraphic using data from the passed file. ! 137: */ ! 138: { ! 139: TextGraphic *retval = nil; ! 140: NXStream *stream = NXMapFile(file, NX_READONLY); ! 141: retval = [self initFromStream:stream]; ! 142: NXCloseMemory(stream, NX_FREEBUFFER); ! 143: return [retval finishedWithInit]; ! 144: } ! 145: ! 146: ! 147: - initFromPasteboard:(Pasteboard *)pboard ! 148: /* ! 149: * Initializes the TextGraphic using data from the passed Pasteboard. ! 150: */ ! 151: { ! 152: NXStream *stream; ! 153: ! 154: if (IncludesType([pboard types], NXRTFPboardType)) { ! 155: stream = [pboard readTypeToStream:NXRTFPboardType]; ! 156: [self initFromStream:stream]; ! 157: NXCloseMemory(stream, NX_FREEBUFFER); ! 158: } else if (IncludesType([pboard types], NXAsciiPboardType)) { ! 159: stream = [pboard readTypeToStream:NXAsciiPboardType]; ! 160: [self initFromStream:stream]; ! 161: NXCloseMemory(stream, NX_FREEBUFFER); ! 162: } else { ! 163: [self free]; ! 164: return nil; ! 165: } ! 166: ! 167: return [self finishedWithInit]; ! 168: } ! 169: ! 170: - (NXRect)reinitFromStream:(NXStream *)stream ! 171: /* ! 172: * Reinitializes the TextGraphic from the data in the passed stream. ! 173: */ ! 174: { ! 175: NXRect ebounds; ! 176: [self doInitFromStream:stream]; ! 177: [self getExtendedBounds:&ebounds]; ! 178: return ebounds; ! 179: } ! 180: ! 181: - (NXRect)reinitFromFile:(const char *)file ! 182: /* ! 183: * Reinitializes the TextGraphic from the data in the passed file. ! 184: */ ! 185: { ! 186: NXRect ebounds; ! 187: NXStream *stream = NXMapFile(file, NX_READONLY); ! 188: [self doInitFromStream:stream]; ! 189: NXCloseMemory(stream, NX_FREEBUFFER); ! 190: [self getExtendedBounds:&ebounds]; ! 191: return ebounds; ! 192: } ! 193: ! 194: - (NXRect)reinitFromPasteboard:(Pasteboard *)pboard ! 195: /* ! 196: * Reinitializes the TextGraphic from the data in the passed Pasteboard. ! 197: */ ! 198: { ! 199: NXRect ebounds; ! 200: NXStream *stream; ! 201: ! 202: if (IncludesType([pboard types], NXRTFPboardType)) { ! 203: stream = [pboard readTypeToStream:NXRTFPboardType]; ! 204: [self doInitFromStream:stream]; ! 205: [self getExtendedBounds:&ebounds]; ! 206: NXCloseMemory(stream, NX_FREEBUFFER); ! 207: } else if (IncludesType([pboard types], NXAsciiPboardType)) { ! 208: stream = [pboard readTypeToStream:NXAsciiPboardType]; ! 209: [self doInitFromStream:stream]; ! 210: [self getExtendedBounds:&ebounds]; ! 211: NXCloseMemory(stream, NX_FREEBUFFER); ! 212: } else { ! 213: ebounds.origin.x = ebounds.origin.y = 0.0; ! 214: ebounds.size.width = ebounds.size.height = 0.0; ! 215: } ! 216: ! 217: return ebounds; ! 218: } ! 219: ! 220: - free ! 221: { ! 222: free(data); ! 223: return [super free]; ! 224: } ! 225: ! 226: /* Link methods */ ! 227: ! 228: - setLink:(NXDataLink *)aLink ! 229: /* ! 230: * Note that we "might" be linked because even though we obviously ! 231: * ARE linked now, that might change in the future and the mightBeLinked ! 232: * flag is only advisory and is never cleared. This is because during ! 233: * cutting and pasting, the TextGraphic might be linked, then unlinked, ! 234: * then linked, then unlinked and we have to know to keep trying to ! 235: * reestablish the link. See readLinkForGraphic:... in gvLinks.m. ! 236: */ ! 237: { ! 238: NXDataLink *oldLink = link; ! 239: link = aLink; ! 240: gFlags.mightBeLinked = YES; ! 241: return oldLink; ! 242: } ! 243: ! 244: - (NXDataLink *)link ! 245: { ! 246: return link; ! 247: } ! 248: ! 249: /* Form entry methods. */ ! 250: ! 251: /* ! 252: * Form Entries are essentially text items whose location, font, etc., are ! 253: * written out separately in an ASCII file when a Draw document is saved. ! 254: * When this is done, an EPS image of the Draw view is also written out ! 255: * (both of these files are place along with the document in the file package). ! 256: * These ASCII descriptions can then be used by other applications to overlay ! 257: * fields on top of a background of what is created by Draw. ! 258: * ! 259: * The most notable client of this right now is the Fax stuff. ! 260: */ ! 261: ! 262: - initFormEntry:(const char *)entryName localizable:(BOOL)isLocalizable ! 263: /* ! 264: * The localizeFormEntry stuff is used by the Fax stuff in the following manner: ! 265: * If a form entry is localizable, then it appears in Draw in whatever the local ! 266: * language is, but, when written to the ASCII form.info file, it is written out ! 267: * not-localized. Then, when the entity that reads the form.info file reads it, ! 268: * it is responsible for localizing it. This enables the entity reading the ! 269: * form to actually semantically understand what a given form entry is (e.g. it ! 270: * is the To: field in a Fax Cover Sheet). ! 271: */ ! 272: { ! 273: char *buffer; ! 274: int maxlen; ! 275: NXStream *stream; ! 276: ! 277: [self init]; ! 278: gFlags.isFormEntry = YES; ! 279: gFlags.localizeFormEntry = isLocalizable ? YES : NO; ! 280: bounds.size.width = 300.0; ! 281: bounds.size.height = 30.0; ! 282: [drawText setText:entryName]; ! 283: [drawText setSel:0:100000]; ! 284: [drawText setSelColor:NX_COLORBLACK]; ! 285: [drawText setFont:[Font userFontOfSize:24.0 matrix:NX_FLIPPEDMATRIX]]; ! 286: [drawText setHorizResizable:YES]; ! 287: [drawText setVertResizable:YES]; ! 288: bounds.size.width = bounds.size.height = 10000.0; ! 289: [drawText setMaxSize:&bounds.size]; ! 290: [drawText calcLine]; ! 291: [drawText getMinWidth:&bounds.size.width minHeight:&bounds.size.height maxWidth:10000.0 maxHeight:10000.0]; ! 292: bounds.origin.x = bounds.origin.y = 0.0; ! 293: bounds.size.width = 300.0; ! 294: stream = NXOpenMemory(NULL, 0, NX_WRITEONLY); ! 295: [drawText writeRichText:stream]; ! 296: NXGetMemoryBuffer(stream, &buffer, &length, &maxlen); ! 297: NX_ZONEMALLOC([self zone], data, char, length); ! 298: bcopy(buffer, data, length); ! 299: NXCloseMemory(stream, NX_FREEBUFFER); ! 300: ! 301: return [self finishedWithInit]; ! 302: } ! 303: ! 304: #define LOCAL_FORM_ENTRY(s) \ ! 305: NXLoadLocalStringFromTableInBundle("CoverSheet", [NXBundle mainBundle], s, NULL) ! 306: #define FORM_ENTRY_BUF_SIZE 100 ! 307: ! 308: - prepareFormEntry ! 309: /* ! 310: * Loads up the drawText with all the right attributes to ! 311: * display a form entry. Called from draw. ! 312: */ ! 313: { ! 314: NXCoord width, height; ! 315: char *s, buffer[FORM_ENTRY_BUF_SIZE]; ! 316: ! 317: [drawText setTextGray:NX_LTGRAY]; ! 318: [drawText setFont:[drawText font]]; ! 319: [drawText setAlignment:NX_LEFTALIGNED]; ! 320: [drawText getSubstring:buffer start:0 length:FORM_ENTRY_BUF_SIZE]; ! 321: buffer[FORM_ENTRY_BUF_SIZE-1] = '\0'; ! 322: if ((s = strchr(buffer, '\n')) || gFlags.localizeFormEntry) { ! 323: if (s) *s = '\0'; ! 324: if (gFlags.localizeFormEntry) { ! 325: [drawText setText:LOCAL_FORM_ENTRY(buffer)]; ! 326: } else { ! 327: [drawText setText:buffer]; ! 328: } ! 329: } ! 330: [drawText setHorizResizable:YES]; ! 331: [drawText setVertResizable:YES]; ! 332: [drawText setMaxSize:&bounds.size]; ! 333: [drawText calcLine]; ! 334: [drawText getMinWidth:&width minHeight:&height maxWidth:10000.0 maxHeight:10000.0]; ! 335: if (width > bounds.size.width) width = bounds.size.width; ! 336: if (height > bounds.size.height) height = bounds.size.height; ! 337: [drawText sizeTo:width :height]; ! 338: [drawText moveTo:bounds.origin.x + floor((bounds.size.width - width) / 2.0) ! 339: :bounds.origin.y + floor((bounds.size.height - height) / 2.0)]; ! 340: ! 341: return self; ! 342: } ! 343: ! 344: - (BOOL)isFormEntry ! 345: { ! 346: return gFlags.isFormEntry; ! 347: } ! 348: ! 349: - setFormEntry:(int)flag ! 350: { ! 351: gFlags.isFormEntry = flag ? YES : NO; ! 352: return self; ! 353: } ! 354: ! 355: - (Font *)getFormEntry:(char *)buffer andGray:(float *)gray ! 356: /* ! 357: * Gets the information which will be written out into the ! 358: * form.info ASCII form entry description file. Specifically, ! 359: * it gets the gray value, the actually name of the entry, and ! 360: * the Font of the entry. ! 361: */ ! 362: { ! 363: char *s; ! 364: NXStream *stream; ! 365: ! 366: if (gFlags.isFormEntry) { ! 367: stream = NXOpenMemory(data, length, NX_READONLY); ! 368: [drawText readRichText:stream]; ! 369: [drawText setSel:0 :0]; ! 370: if (gray) *gray = [drawText selGray]; ! 371: NXCloseMemory(stream, NX_SAVEBUFFER); ! 372: [drawText getSubstring:buffer start:0 length:FORM_ENTRY_BUF_SIZE]; ! 373: buffer[FORM_ENTRY_BUF_SIZE-1] = '\0'; ! 374: if (s = strchr(buffer, '\n')) *s = '\0'; ! 375: return [drawText font]; ! 376: } ! 377: ! 378: return nil; ! 379: } ! 380: ! 381: - (BOOL)writeFormEntryToStream:(NXStream *)stream ! 382: /* ! 383: * Writes out the ASCII representation of the location, gray, ! 384: * etc., of this form entry. This is called only during ! 385: * the saving of a Draw document. ! 386: */ ! 387: { ! 388: Font *myFont; ! 389: float gray; ! 390: char buffer[FORM_ENTRY_BUF_SIZE]; ! 391: ! 392: if (myFont = [self getFormEntry:buffer andGray:&gray]) { ! 393: NXPrintf(stream, "Entry: %s\n", buffer); ! 394: NXPrintf(stream, "Font: %s\n", [myFont name]); ! 395: NXPrintf(stream, "Font Size: %f\n", [myFont pointSize]); ! 396: NXPrintf(stream, "Text Gray: %f\n", gray); ! 397: NXPrintf(stream, "Location: x = %d, y = %d, w = %d, h = %d\n", ! 398: (int)bounds.origin.x, (int)bounds.origin.y, ! 399: (int)bounds.size.width, (int)bounds.size.height); ! 400: return YES; ! 401: } ! 402: ! 403: return NO; ! 404: } ! 405: ! 406: /* Factory methods overridden from superclass */ ! 407: ! 408: + (BOOL)isEditable ! 409: { ! 410: return YES; ! 411: } ! 412: ! 413: + cursor ! 414: { ! 415: return NXIBeam; ! 416: } ! 417: ! 418: /* Instance methods overridden from superclass */ ! 419: ! 420: - (const char *)title ! 421: { ! 422: return NXLocalStringFromTable("Operations", "Text", NULL, "The %s of the `New %s' operation corresponding to creating an area for the user to type into."); ! 423: } ! 424: ! 425: - (BOOL)create:(NXEvent *)event in:(GraphicView *)view ! 426: /* ! 427: * We are only interested in where the mouse goes up, that's ! 428: * where we'll start editing. ! 429: */ ! 430: { ! 431: NXRect viewBounds; ! 432: ! 433: event = [NXApp getNextEvent:NX_MOUSEUPMASK]; ! 434: bounds.size.width = bounds.size.height = 0.0; ! 435: bounds.origin = event->location; ! 436: [view convertPoint:&bounds.origin fromView:nil]; ! 437: [view getBounds:&viewBounds]; ! 438: gFlags.selected = NO; ! 439: ! 440: return NXMouseInRect(&bounds.origin, &viewBounds, NO); ! 441: } ! 442: ! 443: - (BOOL)edit:(NXEvent *)event in:(View *)view ! 444: { ! 445: id change; ! 446: NXRect eb; ! 447: ! 448: if (gFlags.isFormEntry && gFlags.localizeFormEntry) return NO; ! 449: if ([self link]) return NO; ! 450: ! 451: editView = view; ! 452: graphicView = [editView superview]; ! 453: ! 454: /* Get the field editor in this window. */ ! 455: ! 456: if (gFlags.isFormEntry) { ! 457: gFlags.isFormEntry = NO; ! 458: [[view superview] cache:[self getExtendedBounds:&eb]]; // gFlags.isFormEntry starts editing ! 459: [[view window] flushWindow]; ! 460: gFlags.isFormEntry = YES; ! 461: } ! 462: ! 463: change = [[StartEditingGraphicsChange alloc] initGraphic:self]; ! 464: [change startChange]; ! 465: [self prepareFieldEditor]; ! 466: if (event) { ! 467: [fe selectNull]; /* eliminates any existing selection */ ! 468: [fe mouseDown:event]; /* Pass the event on to the Text object */ ! 469: } ! 470: [change endChange]; ! 471: ! 472: return YES; ! 473: } ! 474: ! 475: - draw ! 476: /* ! 477: * If the region has already been created, then we must draw the text. ! 478: * To do this, we first load up the shared drawText Text object with ! 479: * our rich text. We then set the frame of the drawText object ! 480: * to be our bounds. Finally, we add the Text object as a subview of ! 481: * the view that is currently being drawn in ([NXApp focusView]) ! 482: * and tell the Text object to draw itself. We then remove the Text ! 483: * object view from the view heirarchy. ! 484: */ ! 485: { ! 486: NXStream *stream; ! 487: ! 488: if (data && (!gFlags.isFormEntry || NXDrawingStatus == NX_DRAWING)) { ! 489: stream = NXOpenMemory(data, length, NX_READONLY); ! 490: [drawText readRichText:stream]; ! 491: NXCloseMemory(stream, NX_SAVEBUFFER); ! 492: if (gFlags.isFormEntry) { ! 493: [self prepareFormEntry]; ! 494: } else { ! 495: [drawText setFrame:&bounds]; ! 496: } ! 497: [[NXApp focusView] addSubview:drawText]; ! 498: [drawText display]; ! 499: [drawText removeFromSuperview]; ! 500: if (DrawStatus == Resizing || gFlags.isFormEntry) { ! 501: PSsetgray(NX_LTGRAY); ! 502: NXFrameRect(&bounds); ! 503: } ! 504: } ! 505: ! 506: return self; ! 507: } ! 508: ! 509: - performTextMethod:(SEL)aSelector with:(void *)anArgument ! 510: /* ! 511: * This performs the given aSelector on the text by loading up ! 512: * a Text object and applying aSelector to it (with selectAll: ! 513: * having been done first). See PerformTextGraphicsChange.m ! 514: * in graphicsUndo.subproj. ! 515: */ ! 516: { ! 517: id change; ! 518: ! 519: if (data) { ! 520: change = [PerformTextGraphicsChange alloc]; ! 521: [change initGraphic:self view:graphicView]; ! 522: [change startChangeIn:graphicView]; ! 523: [change loadGraphic]; ! 524: [[change editText] perform:aSelector with:anArgument]; ! 525: [change unloadGraphic]; ! 526: [change endChange]; ! 527: } ! 528: ! 529: return self; ! 530: } ! 531: ! 532: - setFont:aFont ! 533: { ! 534: font = aFont; ! 535: return self; ! 536: } ! 537: ! 538: - (char *)data ! 539: { ! 540: return data; ! 541: } ! 542: ! 543: - setData:(char *)newData ! 544: { ! 545: if (data) NX_FREE(data); ! 546: data = newData; ! 547: return self; ! 548: } ! 549: ! 550: - (int)length ! 551: { ! 552: return length; ! 553: } ! 554: ! 555: - setLength:(int)newLength ! 556: { ! 557: length = newLength; ! 558: return self; ! 559: } ! 560: ! 561: - changeFont:sender ! 562: { ! 563: [self performTextMethod:@selector(changeFont:) with:sender]; ! 564: return self; ! 565: } ! 566: ! 567: - (Font *)font ! 568: { ! 569: NXStream *stream; ! 570: ! 571: if (!font && data) { ! 572: stream = NXOpenMemory(data, length, NX_READONLY); ! 573: [drawText readRichText:stream]; ! 574: NXCloseMemory(stream, NX_SAVEBUFFER); ! 575: [drawText setSel:0 :0]; ! 576: font = [drawText font]; ! 577: } ! 578: ! 579: return font; ! 580: } ! 581: ! 582: - (BOOL)isOpaque ! 583: /* ! 584: * We are never opaque. ! 585: */ ! 586: { ! 587: return NO; ! 588: } ! 589: ! 590: - (BOOL)isValid ! 591: /* ! 592: * Any size TextGraphic is valid (since we fix up the size if it is ! 593: * too small in our override of create:in:). ! 594: */ ! 595: { ! 596: return YES; ! 597: } ! 598: ! 599: - (NXColor)lineColor ! 600: { ! 601: return NX_COLORBLACK; ! 602: } ! 603: ! 604: - (NXColor)fillColor ! 605: { ! 606: return NX_COLORWHITE; ! 607: } ! 608: ! 609: - (NXCoord)baseline ! 610: { ! 611: NXCoord ascender, descender, lineHeight; ! 612: ! 613: if (!font) [self font]; ! 614: if (font) { ! 615: NXTextFontInfo(font, &ascender, &descender, &lineHeight); ! 616: return bounds.origin.y + bounds.size.height + ascender; ! 617: } ! 618: ! 619: return 0; ! 620: } ! 621: ! 622: - moveBaselineTo:(NXCoord *)y ! 623: { ! 624: NXCoord ascender, descender, lineHeight; ! 625: ! 626: if (y && !font) [self font]; ! 627: if (y && font) { ! 628: NXTextFontInfo(font, &ascender, &descender, &lineHeight); ! 629: bounds.origin.y = *y - ascender - bounds.size.height; ! 630: } ! 631: ! 632: return self; ! 633: } ! 634: ! 635: /* Public methods */ ! 636: ! 637: - prepareFieldEditor ! 638: /* ! 639: * Here we are going to use the shared field editor for the window to ! 640: * edit the text in the TextGraphic. First, we must end any other editing ! 641: * that is going on with the field editor in this window using endEditingFor:. ! 642: * Next, we get the field editor from the window. Normally, the field ! 643: * editor ends editing when carriage return is pressed. This is due to ! 644: * the fact that its character filter is NXFieldFilter. Since we want our ! 645: * editing to be more like an editor (and less like a Form or TextField), ! 646: * we set the character filter to be NXEditorFilter. What is more, normally, ! 647: * you can't change the font of a TextField or Form with the FontPanel ! 648: * (since that might interfere with any real editable Text objects), but ! 649: * in our case, we do want to be able to do that. We also want to be ! 650: * able to edit rich text, so we issue a setMonoFont:NO. Editing is a bit ! 651: * more efficient if we set the Text object to be opaque. Note that ! 652: * in textDidEnd:endChar: we will have to set the character filter, ! 653: * FontPanelEnabled and mono-font back so that if there were any forms ! 654: * or TextFields in the window, they would have a correctly configured ! 655: * field editor. ! 656: * ! 657: * To let the field editor know exactly where editing is occurring and how ! 658: * large the editable area may grow to, we must calculate and set the frame ! 659: * of the field editor as well as its minimum and maximum size. ! 660: * ! 661: * We load up the field editor with our rich text (if any). ! 662: * ! 663: * Finally, we set self as the delegate (so that it will receive the ! 664: * textDidEnd:endChar: message when editing is completed) and either ! 665: * pass the mouse-down event onto the Text object, or, if a mouse-down ! 666: * didn't cause editing to occur (i.e. we just created it), then we ! 667: * simply put the blinking caret at the beginning of the editable area. ! 668: * ! 669: * The line marked with the "ack!" is kind of strange, but is necessary ! 670: * since growable Text objects only work when they are subviews of a flipped ! 671: * view. ! 672: * ! 673: * This is why GraphicView has an "editView" which is a flipped view that it ! 674: * inserts as a subview of itself for the purposes of providing a superview ! 675: * for the Text object. The "ack!" line converts the bounds of the TextGraphic ! 676: * (which are in GraphicView coordinates) to the coordinates of the Text ! 677: * object's superview (the editView). This limitation of the Text object ! 678: * will be fixed post-1.0. Note that the "ack!" line is the only one ! 679: * concession we need to make to this limitation in this method (there is ! 680: * another such line in resignFieldEditor). ! 681: */ ! 682: { ! 683: NXSize maxSize; ! 684: NXStream *stream; ! 685: NXRect viewBounds, frame, eb; ! 686: ! 687: [NXApp sendAction:@selector(disableChanges:) to:nil from:self]; ! 688: [[graphicView window] endEditingFor:self]; ! 689: fe = [[graphicView window] getFieldEditor:YES for:self]; ! 690: ! 691: if ([self isSelected]) { ! 692: [self deselect]; ! 693: [graphicView cache:[self getExtendedBounds:&eb] andUpdateLinks:NO]; ! 694: [[graphicView selectedGraphics] removeObject:self]; ! 695: } ! 696: ! 697: [fe setFont:[[FontManager new] selFont]]; ! 698: ! 699: /* Modify it so that it will edit Rich Text and use the FontPanel. */ ! 700: ! 701: [fe setCharFilter:NXEditorFilter]; ! 702: [fe setFontPanelEnabled:YES]; ! 703: [fe setMonoFont:NO]; ! 704: [fe setOpaque:YES]; ! 705: ! 706: /* ! 707: * Determine the minimum and maximum size that the Text object can be. ! 708: * We let the Text object grow out to the edges of the GraphicView, ! 709: * but no further. ! 710: */ ! 711: ! 712: [editView getBounds:&viewBounds]; ! 713: maxSize.width = viewBounds.origin.x+viewBounds.size.width-bounds.origin.x; ! 714: maxSize.height = bounds.origin.y+bounds.size.height-viewBounds.origin.y; ! 715: if (!bounds.size.height && !bounds.size.width) { ! 716: bounds.origin.y -= floor([fe lineHeight] / 2.0); ! 717: bounds.size.height = [fe lineHeight]; ! 718: bounds.size.width = 5.0; ! 719: } ! 720: frame = bounds; ! 721: [editView convertRect:&frame fromView:graphicView]; // ack! ! 722: [fe setMinSize:&bounds.size]; ! 723: [fe setMaxSize:&maxSize]; ! 724: [fe setFrame:&frame]; ! 725: [fe setVertResizable:YES]; ! 726: ! 727: /* ! 728: * If we already have text, then put it in the Text object (allowing ! 729: * the Text object to grow downward if necessary), otherwise, put ! 730: * no text in, set some initial parameters, and allow the Text object ! 731: * to grow horizontally as well as vertically ! 732: */ ! 733: ! 734: if (data) { ! 735: [fe setHorizResizable:NO]; ! 736: stream = NXOpenMemory(data, length, NX_READONLY); ! 737: [fe readRichText:stream]; ! 738: NXCloseMemory(stream, NX_SAVEBUFFER); ! 739: } else { ! 740: [fe setHorizResizable:YES]; ! 741: [fe setText:""]; ! 742: [fe setAlignment:NX_LEFTALIGNED]; ! 743: [fe setSelColor:NX_COLORBLACK]; ! 744: [fe unscript:self]; ! 745: } ! 746: ! 747: /* ! 748: * Add the Text object to the view heirarchy and set self as its delegate ! 749: * so that we will receive the textDidEnd:endChar: message when editing ! 750: * is finished. ! 751: */ ! 752: ! 753: [fe setDelegate:self]; ! 754: [editView addSubview:fe]; ! 755: ! 756: /* ! 757: * Make it the first responder. ! 758: */ ! 759: ! 760: [[graphicView window] makeFirstResponder:fe]; ! 761: ! 762: /* Change the ruler to be a text ruler. */ ! 763: ! 764: [fe tryToPerform:@selector(showTextRuler:) with:fe]; ! 765: ! 766: [fe setSel:0:0]; ! 767: [NXApp sendAction:@selector(enableChanges:) to:nil from:self]; ! 768: ! 769: return self; ! 770: } ! 771: ! 772: - resignFieldEditor ! 773: /* ! 774: * We must extract the rich text the user has typed from the Text object, ! 775: * and store it away. We also need to get the frame of the Text object ! 776: * and make that our bounds (but, remember, since the Text object must ! 777: * be a subview of a flipped view, we need to convert the bounds rectangle ! 778: * to the coordinates of the unflipped GraphicView). If the Text object ! 779: * is empty, then we remove this TextGraphic from the GraphicView. ! 780: * We must remove the Text object from the view heirarchy and, since ! 781: * this Text object is going to be reused, we must set its delegate ! 782: * back to nil. ! 783: * ! 784: * For further explanation of the "ack!" line, see edit:in: above. ! 785: */ ! 786: { ! 787: int maxlen; ! 788: char *buffer; ! 789: NXStream *stream; ! 790: NXRect oldBounds, *redrawRect = NULL; ! 791: ! 792: [NXApp sendAction:@selector(disableChanges:) to:nil from:self]; ! 793: if (data) { ! 794: NX_FREE(data); ! 795: data = NULL; ! 796: length = 0; ! 797: } ! 798: ! 799: NX_ASSERT(editView == [fe superview], "Fault in Text Graphic: Code 2"); ! 800: NX_ASSERT(graphicView == [editView superview], "Fault in Text Graphic: Code 3"); ! 801: ! 802: if ([fe textLength]) { ! 803: stream = NXOpenMemory(NULL, 0, NX_WRITEONLY); ! 804: [fe writeRichText:stream]; ! 805: NXGetMemoryBuffer(stream, &buffer, &length, &maxlen); ! 806: NX_ZONEMALLOC([self zone], data, char, length); ! 807: bcopy(buffer, data, length); ! 808: NXCloseMemory(stream, NX_FREEBUFFER); ! 809: oldBounds = bounds; ! 810: [fe getFrame:&bounds]; ! 811: [editView convertRect:&bounds toView:graphicView]; // ack! ! 812: NXUnionRect(&bounds, &oldBounds); ! 813: redrawRect = &oldBounds; ! 814: } ! 815: ! 816: if (redrawRect) [[graphicView window] disableFlushWindow]; ! 817: ! 818: [graphicView tryToPerform:@selector(hideRuler:) with:nil]; ! 819: [fe removeFromSuperview]; ! 820: [fe setDelegate:nil]; ! 821: [fe setSel:0 :0]; ! 822: font = [fe font]; ! 823: ! 824: if (redrawRect) { ! 825: [graphicView cache:redrawRect]; ! 826: [[graphicView window] reenableFlushWindow]; ! 827: [[graphicView window] flushWindow]; ! 828: } ! 829: ! 830: fe = nil; ! 831: [NXApp sendAction:@selector(enableChanges:) to:nil from:self]; ! 832: ! 833: return self; ! 834: } ! 835: ! 836: - (BOOL)isEmpty ! 837: { ! 838: return data ? NO : YES; ! 839: } ! 840: ! 841: /* Text object delegate methods */ ! 842: ! 843: /* ! 844: * If we have more than one line, turn off horizontal resizing. ! 845: */ ! 846: - textDidResize:textObject oldBounds:(const NXRect *)oldBounds invalid:(NXRect *)invalidRect ! 847: { ! 848: NXSelPt start,end; ! 849: ! 850: [textObject getSel:&start :&end]; ! 851: if (start.line || end.line) ! 852: [textObject setHorizResizable:NO]; ! 853: return self; ! 854: } ! 855: ! 856: - textDidEnd:textObject endChar:(unsigned short)endChar ! 857: /* ! 858: * This method is called when ever first responder is taken away from a ! 859: * currently editing TextGraphic (i.e. when the user is done editing and ! 860: * chooses to go do something else). ! 861: */ ! 862: { ! 863: id change; ! 864: ! 865: NX_ASSERT(fe == textObject, "Fault in Text Graphic: Code 1") ! 866: ! 867: change = [[EndEditingGraphicsChange alloc] initGraphicView:graphicView graphic:self]; ! 868: [change startChange]; ! 869: [self resignFieldEditor]; ! 870: if ([self isEmpty]) ! 871: [graphicView removeGraphic:self]; ! 872: [change endChange]; ! 873: ! 874: return self; ! 875: } ! 876: ! 877: /* Archiving methods */ ! 878: ! 879: - awake ! 880: { ! 881: initClassVars(); ! 882: return [super awake]; ! 883: } ! 884: ! 885: - write:(NXTypedStream *)stream ! 886: /* ! 887: * Writes the TextGraphic out to the typed stream. ! 888: */ ! 889: { ! 890: [super write:stream]; ! 891: NXWriteTypes(stream, "i", &length); ! 892: NXWriteArray(stream, "c", length, data); ! 893: return self; ! 894: } ! 895: ! 896: - read:(NXTypedStream *)stream ! 897: /* ! 898: * Reads the TextGraphic in from the typed stream. ! 899: * This is versioned. The old way we used to implement ! 900: * this class included using a Cell object. Now we ! 901: * use the Text object directly. ! 902: */ ! 903: { ! 904: int version; ! 905: ! 906: version = NXTypedStreamClassVersion(stream, "TextGraphic"); ! 907: [super read:stream]; ! 908: ! 909: if (version < 1) { ! 910: Cell *cell; ! 911: int maxlen; ! 912: NXStream *s; ! 913: char *buffer; ! 914: NXReadTypes(stream, "@", &cell); ! 915: [drawText setText:[cell stringValue]]; ! 916: font = [cell font]; ! 917: [drawText setFont:[cell font]]; ! 918: [drawText setTextColor:[self lineColor]]; ! 919: s = NXOpenMemory(NULL, 0, NX_WRITEONLY); ! 920: [drawText writeRichText:s]; ! 921: NXGetMemoryBuffer(s, &buffer, &length, &maxlen); ! 922: NX_ZONEMALLOC([self zone], data, char, length); ! 923: bcopy(buffer, data, length); ! 924: NXCloseMemory(s, NX_FREEBUFFER); ! 925: } else { ! 926: NXReadTypes(stream, "i", &length); ! 927: NX_ZONEMALLOC([self zone], data, char, length); ! 928: NXReadArray(stream, "c", length, data); ! 929: } ! 930: ! 931: if (version > 2 && version < 5) { ! 932: int linkNumber; ! 933: NXReadTypes(stream, "i", &linkNumber); ! 934: } else if (version == 2) { ! 935: int linkNumber; ! 936: link = NXReadObject(stream); ! 937: linkNumber = [link linkNumber]; ! 938: link = nil; ! 939: } ! 940: ! 941: if (version > 3 && version < 6) { ! 942: BOOL isFormEntry; ! 943: NXReadTypes(stream, "c", &isFormEntry); ! 944: gFlags.isFormEntry = isFormEntry ? YES : NO; ! 945: } ! 946: ! 947: return self; ! 948: } ! 949: ! 950: @end
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.