home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.0 / NeXTSTEP3.0.iso / NextDeveloper / Examples / AppKit / Draw / TextGraphic.m < prev    next >
Encoding:
Text File  |  1992-07-20  |  25.1 KB  |  925 lines

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