home *** CD-ROM | disk | FTP | other *** search
/ OpenStep 4.2J (Developer) / os42jdev.iso / NextDeveloper / Examples / AppKit / Draw / TextGraphic.m < prev    next >
Text File  |  1996-04-18  |  29KB  |  916 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 (this is a bug and will
  9.  * be fixed in 4.0).  Since a GraphicView is not flipped, we must have a
  10.  * flipped view into the view heirarchy when we edit (this editing view
  11.  * is permanently installed as a subview of the GraphicView--see
  12.  * GraphicView's newFrame: method).
  13.  */
  14.  
  15. static NSTextView *drawText = nil;    /* shared Text object used for drawing */
  16.  
  17. + (void)initClassVars
  18. /*
  19.  * Create the class variable drawText here.
  20.  */
  21. {
  22.     if (!drawText) {
  23.         drawText = [[NSTextView alloc] init];
  24.         [[drawText textContainer] setWidthTracksTextView:YES];
  25.         [[drawText textContainer] setHeightTracksTextView:YES];
  26.         [drawText setHorizontallyResizable:NO];
  27.         [drawText setVerticallyResizable:NO];
  28.         [drawText setDrawsBackground:NO];
  29.         [drawText setRichText:YES];
  30.         [drawText setEditable:NO];
  31.         [drawText setSelectable:NO];
  32.     }
  33.     [super initClassVars];
  34. }
  35.  
  36. + (NSTextView *)drawText
  37. {
  38.     if (!drawText) [self initClassVars];
  39.     return drawText;
  40. }
  41.  
  42. + (BOOL)canInitWithPasteboard:(NSPasteboard *)pboard
  43. {
  44.     return IncludesType([pboard types], NSRTFPboardType) ||
  45.        IncludesType([pboard types], NSStringPboardType);
  46. }
  47.  
  48. - (id)init
  49. /*
  50.  * Creates a "blank" TextGraphic.
  51.  * This is TextGraphic's designated initializer,
  52.  * but be wary because by the time this returns, the
  53.  * TextGraphic may not be full initialized (it'll be
  54.  * valid, just perhaps not fully initialized).
  55.  * Override finishedWithInit if you want that.
  56.  */
  57. {
  58.     [super init];
  59.     [[self class] initClassVars];
  60.     return self;
  61. }
  62.  
  63. - initEmpty
  64. /*
  65.  * Creates an empty TextGraphic.
  66.  */
  67. {
  68.     [self init];
  69.     return [self finishedWithInit];
  70. }
  71.  
  72. - finishedWithInit
  73. /*
  74.  * Override this if you want to know when a newly
  75.  * initialized TextGraphics is fully init'ed.
  76.  */
  77. {
  78.      return self;
  79. }
  80.  
  81. - doInitFromData:(NSData *)data
  82. /*
  83.  * Common code for initFromData: and reinitFromData:.
  84.  * Looks at the first 5 characters of the data and if it
  85.  * looks like an RTF file, then the contents of the stream
  86.  * are parsed as RTF, otherwise, the contents of the stream
  87.  * are assumed to be ASCII text and is passed through the
  88.  * drawText object and turned into RTF (using the method
  89.  * (writeRichText:).
  90.  */
  91. {
  92.     if (data) {
  93.     if (!strncmp([data bytes], "{\\rtf", 5)) {
  94.         [self setRichTextData:data];
  95.         [drawText replaceCharactersInRange:(NSRange){0, [[drawText string] length]} withRTF:data];
  96.     } else {
  97.         [drawText selectAll:self];
  98.         [drawText setFont:[NSFont userFontOfSize:-1.0]];
  99.         [drawText setString:[NSString stringWithCString:[data bytes] length:[data length]]];
  100.         [self setRichTextData:[drawText RTFFromRange:(NSRange){0, [[drawText string] length]}]];
  101.     }
  102.     [drawText setSelectedRange:(NSRange){0,0}];
  103.     font = [drawText font];
  104.     }
  105.  
  106.     return self;
  107. }
  108.  
  109. - (id)initFromData:(NSData *)data
  110. /*
  111.  * Initializes the TextGraphic using data from the passed data.
  112.  */
  113. {
  114.     [self init];
  115.  
  116.     if (data) {
  117.     [self doInitFromData:data];
  118.     [drawText setHorizontallyResizable:YES];
  119.     [drawText setVerticallyResizable:YES];
  120.     bounds.size.width = bounds.size.height = 10000.0;
  121.     [drawText setMaxSize:bounds.size];
  122.     [drawText sizeToFit];
  123.     bounds.size = [drawText bounds].size;
  124.     bounds.origin.x = bounds.origin.y = 0.0;
  125.     }
  126.  
  127.     return [self finishedWithInit];
  128. }
  129.  
  130. - (id)initFromFile:(NSString *)file
  131. /*
  132.  * Initializes the TextGraphic using data from the passed file.
  133.  */
  134. {
  135.     return [self initFromData:[NSData dataWithContentsOfMappedFile:file]];
  136. }
  137.  
  138.  
  139. - (id)initWithPasteboard:(NSPasteboard *)pboard
  140. /*
  141.  * Initializes the TextGraphic using data from the passed Pasteboard.
  142.  */
  143. {
  144.     if (IncludesType([pboard types], NSRTFPboardType)) {
  145.     return [self initFromData:[pboard dataForType:NSRTFPboardType]];
  146.     } else if (IncludesType([pboard types], NSStringPboardType)) {
  147.     return [self initFromData:[pboard dataForType:NSStringPboardType]];
  148.     } else {
  149.     [self release];
  150.     return nil;
  151.     }
  152. }
  153.  
  154. - (NSRect)reinitFromData:(NSData *)data
  155. /*
  156.  * Reinitializes the TextGraphic from the data in the passed data.
  157.  */
  158. {
  159.     [self doInitFromData:data];
  160.     return [self extendedBounds];
  161. }
  162.  
  163. - (NSRect)reinitFromFile:(NSString *)file
  164. /*
  165.  * Reinitializes the TextGraphic from the data in the passed file.
  166.  */
  167. {
  168.     [self doInitFromData:[NSData dataWithContentsOfMappedFile:file]];
  169.     return [self extendedBounds];
  170. }
  171.  
  172. - (NSRect)reinitWithPasteboard:(NSPasteboard *)pboard
  173. /*
  174.  * Reinitializes the TextGraphic from the data in the passed Pasteboard.
  175.  */
  176. {
  177.     NSRect ebounds;
  178.  
  179.     if (IncludesType([pboard types], NSRTFPboardType)) {
  180.     [self doInitFromData:[pboard dataForType:NSRTFPboardType]];
  181.     ebounds = [self extendedBounds];
  182.     } else if (IncludesType([pboard types], NSStringPboardType)) {
  183.     [self doInitFromData:[pboard dataForType:NSStringPboardType]];
  184.     ebounds = [self extendedBounds];
  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. - (void)dealloc
  194. {
  195.     [richTextData release];
  196.     [super dealloc];
  197. }
  198.  
  199. /* Link methods */
  200.  
  201. - (void)setLink:(NSDataLink *)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.     link = [aLink retain];
  212.     gFlags.mightBeLinked = YES;
  213. }
  214.  
  215. - (NSDataLink *)link
  216. {
  217.     return link;
  218. }
  219.  
  220. /* Form entry methods. */
  221.  
  222. /*
  223.  * Form Entries are essentially text items whose location, font, etc., are
  224.  * written out separately in an ASCII file when a Draw document is saved.
  225.  * When this is done, an EPS image of the Draw view is also written out
  226.  * (both of these files are place along with the document in the file package).
  227.  * These ASCII descriptions can then be used by other applications to overlay
  228.  * fields on top of a background of what is created by Draw.
  229.  *
  230.  * The most notable client of this right now is the Fax stuff.
  231.  */
  232.  
  233. - initFormEntry:(NSString *)entryName localizable:(BOOL)isLocalizable
  234. /*
  235.  * The localizeFormEntry stuff is used by the Fax stuff in the following manner:
  236.  * If a form entry is localizable, then it appears in Draw in whatever the local
  237.  * language is, but, when written to the ASCII form.info file, it is written out
  238.  * not-localized.  Then, when the entity that reads the form.info file reads it,
  239.  * it is responsible for localizing it.  This enables the entity reading the
  240.  * form to actually semantically understand what a given form entry is (e.g. it
  241.  * is the To: field in a Fax Cover Sheet).
  242.  */ 
  243. {
  244.     [self init];
  245.     gFlags.isFormEntry = YES;
  246.     gFlags.localizeFormEntry = isLocalizable ? YES : NO;
  247.     bounds.size.width = 300.0;
  248.     bounds.size.height = 30.0;
  249.     [drawText setString:entryName];
  250.     [drawText setSelectedRange:(NSRange){0, [[drawText string] length]}];
  251.     [drawText setTextColor:[NSColor blackColor] range:[drawText selectedRange]];
  252.     [drawText setFont:[NSFont userFontOfSize:24.0]];
  253.     [drawText setHorizontallyResizable:YES];
  254.     [drawText setVerticallyResizable:YES];
  255.     bounds.size.width = bounds.size.height = 10000.0;
  256.     [drawText setMaxSize:bounds.size];
  257.     [drawText sizeToFit];
  258.     bounds.size = [drawText bounds].size;
  259.     bounds.origin.x = bounds.origin.y = 0.0;
  260.     bounds.size.width = 300.0;
  261.     [self setRichTextData:[drawText RTFFromRange:(NSRange){0, [[drawText string] length]}]];
  262.     return [self finishedWithInit];
  263. }
  264.    
  265. #define LOCAL_FORM_ENTRY(s) NSLocalizedStringFromTable(s, @"CoverSheet", nil)
  266.  
  267. - prepareFormEntry
  268. /*
  269.  * Loads up the drawText with all the right attributes to
  270.  * display a form entry.  Called from draw.
  271.  */
  272. {
  273.     float width, height;
  274.     NSString *formText;
  275.     int crLocation;
  276.     
  277.     [drawText setTextColor:[NSColor lightGrayColor]];
  278.     [drawText setFont:[drawText font]];
  279.     [drawText setAlignment:NSLeftTextAlignment];
  280.     formText = [[drawText string] substringWithRange:(NSRange){0, [[drawText string] length]}];
  281.     if (((crLocation = [formText rangeOfString:@"\n"].location) != NSNotFound) || gFlags.localizeFormEntry) {
  282.     if (crLocation != NSNotFound) {
  283.         // Then only use string up to carriage return...
  284.         formText = [formText substringToIndex:crLocation];
  285.     }
  286.     if (gFlags.localizeFormEntry) {
  287.         [drawText setString:LOCAL_FORM_ENTRY(formText)];
  288.     } else {
  289.         [drawText setString:formText];
  290.     }
  291.     }
  292.     [drawText setHorizontallyResizable:YES];
  293.     [drawText setVerticallyResizable:YES];
  294.     [drawText setMaxSize:bounds.size];
  295.     [drawText sizeToFit];
  296.     width = [drawText bounds].size.width;
  297.     height = [drawText bounds].size.height;
  298.     if (width > bounds.size.width) width = bounds.size.width;
  299.     if (height > bounds.size.height) height = bounds.size.height;
  300.     [drawText setFrameSize:(NSSize){ width, height }];
  301.     [drawText setFrameOrigin:(NSPoint){ bounds.origin.x+floor((bounds.size.width-width)/2.0), bounds.origin.y+floor((bounds.size.height-height)/2.0) }];
  302.  
  303.     return self;
  304. }
  305.  
  306. - (BOOL)isFormEntry
  307. {
  308.     return gFlags.isFormEntry;
  309. }
  310.  
  311. - (void)setFormEntry:(int)flag
  312. {
  313.     gFlags.isFormEntry = flag ? YES : NO; 
  314. }
  315.  
  316. - (NSFont *)getFormEntry:(NSString **)stringPointer andColor:(NSColor **)color
  317. /*
  318.  * Gets the information which will be written out into the
  319.  * form.info ASCII form entry description file.  Specifically,
  320.  * it gets the color value, the actual name of the entry, and
  321.  * the Font of the entry.
  322.  */
  323. {
  324.     int crLocation;
  325.     NSString *formText;
  326.  
  327.     if (gFlags.isFormEntry) {
  328.     [drawText replaceCharactersInRange:(NSRange){0, [[drawText string] length]} withRTF:richTextData];
  329.     [drawText setSelectedRange:(NSRange){0,0}];
  330.         if (color) {
  331.             if ([drawText respondsToSelector:@selector(selColor)]) {
  332.                 // Old text object...
  333.                 *color = [(id)drawText selColor];
  334.             } else {
  335.                 *color = [drawText textColor];
  336.             }
  337.         }
  338.     formText = [[drawText string] substringWithRange:(NSRange){0, [[drawText string] length]}];
  339.     if ((crLocation = [formText rangeOfString:@"\n"].location) != NSNotFound) {
  340.         // Then only use string up to carriage return...
  341.         formText = [formText substringToIndex:crLocation];
  342.     }
  343.     *stringPointer = formText;
  344.     return [drawText font];
  345.     }
  346.  
  347.     return nil;
  348. }
  349.  
  350. - (BOOL)writeFormEntryToMutableString:(NSMutableString *)string
  351. /*
  352.  * Writes out the ASCII representation of the location, color,
  353.  * etc., of this form entry.  This is called only during
  354.  * the saving of a Draw document.
  355.  *
  356.  * We MUST write the same format as always because this file is used by the fax
  357.  * system and we don't really need/want two different formats.  Also, it would
  358.  * be nice if we could have forward compatibility such that cover sheets created
  359.  * in 4.0 can be user in 3.x.
  360.  */
  361. {
  362.     NSFont *myFont = nil;
  363.     NSColor *color = nil;
  364.     NSString *formText = nil;
  365.     NSColor *convertedColor;
  366.     float whiteness;
  367.  
  368.     if ((myFont = [self getFormEntry:&formText andColor:&color])) {
  369.         [string appendFormat:@"Entry: %@\n", formText];
  370.         [string appendFormat:@"Font: %@\n", [myFont fontName]];
  371.         [string appendFormat:@"Font Size: %f\n", [myFont pointSize]];
  372.         convertedColor = [color colorUsingColorSpaceName:NSCalibratedWhiteColorSpace];
  373.         if (convertedColor) {
  374.             whiteness = [convertedColor whiteComponent];
  375.         } else {
  376.             whiteness = 0.0;
  377.         }
  378.         [string appendFormat:@"Text Gray: %f\n", whiteness];
  379.         [string appendFormat:@"Location: x = %d, y = %d, w = %d, h = %d\n",
  380.         (int)bounds.origin.x, (int)bounds.origin.y, (int)bounds.size.width, (int)bounds.size.height];
  381.     return YES;
  382.     }
  383.  
  384.     return NO;
  385. }
  386.  
  387. /* Factory methods overridden from superclass */
  388.  
  389. + (BOOL)isEditable
  390. {
  391.     return YES;
  392. }
  393.  
  394. + (NSCursor *)cursor
  395. {
  396.     return [NSCursor IBeamCursor];
  397. }
  398.  
  399. /* Instance methods overridden from superclass */
  400.  
  401. - (NSString *)title
  402. {
  403.     return TEXT_OP;
  404. }
  405.  
  406. - (BOOL)create:(NSEvent *)event in:(GraphicView *)view
  407.  /*
  408.   * We are only interested in where the mouse goes up, that's
  409.   * where we'll start editing.
  410.   */
  411. {
  412.     NSRect viewBounds;
  413.  
  414.     event = [[view window] nextEventMatchingMask:NSLeftMouseUpMask];
  415.     bounds.size.width = bounds.size.height = 0.0;
  416.     bounds.origin = [event locationInWindow];
  417.     bounds.origin = [view convertPoint:bounds.origin fromView:nil];
  418.     viewBounds = [view bounds];
  419.     gFlags.selected = NO;
  420.  
  421.     return NSMouseInRect(bounds.origin, viewBounds, NO);
  422. }
  423.  
  424. - (BOOL)edit:(NSEvent *)event in:(NSView *)view
  425. {
  426.     id change;
  427.  
  428.     if (gFlags.isFormEntry && gFlags.localizeFormEntry) return NO;
  429.     if ([self link]) return NO;
  430.  
  431.     editView = view;
  432.     graphicView = (GraphicView *)[editView superview];
  433.     
  434.     /* Get the field editor in this window. */
  435.  
  436.     if (gFlags.isFormEntry) {
  437.     gFlags.isFormEntry = NO;
  438.         [(GraphicView *)[view superview] cache:[self extendedBounds]];    // gFlags.isFormEntry starts editing
  439.     [[view window] flushWindow];
  440.     gFlags.isFormEntry = YES;
  441.     }
  442.  
  443.     change = [[StartEditingGraphicsChange alloc] initGraphic:self];
  444.     [change startChange];
  445.     [self prepareFieldEditor];
  446.     if (event) {  
  447.         [fe setSelectedRange:NSMakeRange(0, 0)];    /* eliminates any existing selection */
  448.         [fe mouseDown:event]; /* Pass the event on to the Text object */
  449.     }
  450.     [change endChange];
  451.  
  452.     return YES;
  453. }
  454.  
  455. - draw
  456.  /*
  457.   * If the region has already been created, then we must draw the text.
  458.   * To do this, we first load up the shared drawText Text object with
  459.   * our rich text.  We then set the frame of the drawText object
  460.   * to be our bounds.  Finally, we add the Text object as a subview of
  461.   * the view that is currently being drawn in ([NSApp focusView])
  462.   * and tell the Text object to draw itself.  We then remove the Text
  463.   * object view from the view heirarchy.
  464.   */
  465. {
  466.     if (!fe && richTextData && (!gFlags.isFormEntry || [(NSDPSContext *)[NSDPSContext currentContext] isDrawingToScreen])) {
  467.     [drawText replaceCharactersInRange:(NSRange){0, [[drawText string] length]} withRTF:richTextData];
  468.     if (gFlags.isFormEntry) {
  469.         [self prepareFormEntry];
  470.     } else {
  471.         [drawText setFrame:bounds];
  472.     }
  473.         [[graphicView window] setAutodisplay:NO]; // don't let addSubview: cause redisplay
  474.     [[NSView focusView] addSubview:drawText];
  475.         [drawText lockFocus];
  476.         [drawText drawRect:[drawText bounds]];
  477.         [drawText unlockFocus];
  478.     [drawText removeFromSuperview];
  479.         [[graphicView window] setAutodisplay:YES];
  480.     if (DrawStatus == Resizing || gFlags.isFormEntry) {
  481.         PSsetgray(NSLightGray);
  482.         NSFrameRect(bounds);
  483.     }
  484.     }
  485.  
  486.     return self;
  487. }
  488.  
  489. #if TEXT_UNDO_ENABLED
  490. // This version of performTextMethod: is used when text undo is supported.
  491. - (void)performTextMethod:(SEL)aSelector with:(void *)anArgument
  492. /*
  493.  * This performs the given aSelector on the text by loading up
  494.  * a Text object and applying aSelector to it (with selectAll:
  495.  * having been done first).  See PerformTextGraphicsChange.m
  496.  * in graphicsUndo.subproj.
  497.  */
  498. {
  499.     id change;
  500.  
  501.     if (richTextData) {
  502.     change = [PerformTextGraphicsChange alloc];
  503.     [change initGraphic:self view:graphicView];
  504.     [change startChangeIn:graphicView];
  505.         [change loadGraphic];
  506.         [[change editText] performSelector:aSelector withObject:anArgument];
  507.         [change unloadGraphic];
  508.     [change endChange];
  509.     } 
  510. }
  511. #else
  512. - (void)performTextMethod:(SEL)aSelector with:(void *)anArgument
  513. /*
  514.  * This performs the given aSelector on the text by loading up
  515.  * a Text object and applying aSelector to it (with selectAll:
  516.  * having been done first).
  517.  */
  518. {
  519.     static id tempText = nil;
  520.     static id tempWindow = nil;
  521.     
  522.     if (richTextData)  {
  523.     NSRect graphicBounds;
  524.     
  525.     // Equivalent to [change initGraphic:self inView graphicView] above.
  526.     if (!tempText)  {
  527.         tempText = [[DrawSpellText alloc] initWithFrame:(NSRect){{0,0},{0,0}}];
  528.         [tempText setRichText:YES];
  529.     }
  530.     if (!tempWindow)  {
  531.         tempWindow = [[NSWindow alloc] init];
  532.     }
  533.     
  534.     // Equivalent to [change loadGraphic] above.
  535.     [tempText replaceCharactersInRange:(NSRange){0, [[tempText string] length]} withRTF:richTextData];
  536.     graphicBounds = [self bounds];
  537.     [tempText setFrame:graphicBounds];
  538.     [tempWindow setNextResponder:graphicView];
  539.     [[tempWindow contentView] addSubview:tempText];
  540.     [tempText selectAll:self];
  541.     
  542.     [tempText performSelector:aSelector withObject:anArgument];
  543.     
  544.     // Equivalent to [change unloadGraphic] above.
  545.     [tempWindow setNextResponder:nil];
  546.     [tempText removeFromSuperview];
  547.     [tempText setSelectedRange:(NSRange){0,0}];
  548.     [self setFont:[tempText font]];
  549.     [self setRichTextData:[tempText RTFFromRange:(NSRange){0, [[tempText string] length]}]];
  550.     } 
  551. }
  552. #endif
  553.  
  554. - (void)setFont:(NSFont *)aFont
  555. {
  556.     font = aFont;
  557. }
  558.  
  559. - (NSData *)richTextData
  560. {
  561.     return richTextData;
  562. }
  563.  
  564. - (void)setRichTextData:(NSData *)data
  565. {
  566.     if (richTextData) [richTextData autorelease];
  567.     richTextData = [data copy];
  568. }
  569.  
  570. - (void)changeFont:(id)sender
  571. {
  572.     [self performTextMethod:@selector(changeFont:) with:sender];
  573. }
  574.  
  575. - (NSFont *)font
  576. {
  577.     if (!font && richTextData) {
  578.     [drawText replaceCharactersInRange:(NSRange){0, [[drawText string] length]} withRTF:richTextData];
  579.     [drawText setSelectedRange:(NSRange){0,0}];
  580.     font = [drawText font];
  581.     }
  582.  
  583.     return font;
  584. }
  585.  
  586. - (BOOL)isOpaque
  587. /*
  588.  * We are never opaque.
  589.  */
  590. {
  591.     return NO;
  592. }
  593.  
  594. - (BOOL)isValid
  595. /*
  596.  * Any size TextGraphic is valid (since we fix up the size if it is
  597.  * too small in our override of create:in:).
  598.  */
  599. {
  600.     return YES;
  601. }
  602.  
  603. - (NSColor *)lineColor
  604. {
  605.     return [NSColor blackColor];
  606. }
  607.  
  608. - (NSColor *)fillColor
  609. {
  610.     return [NSColor whiteColor];
  611. }
  612.  
  613. - (float)baseline
  614. {
  615.     float ascender, descender, lineHeight;
  616.  
  617.     if (!font) [self font];
  618.     if (font) {
  619.     // This function is defined in the old NSCStringText class,
  620.     // but it doesn't actually have anything to do with the text object.
  621.     // It uses only the font info.
  622.     NSTextFontInfo(font, &ascender, &descender, &lineHeight);
  623.     return bounds.origin.y + bounds.size.height + ascender;
  624.     }
  625.  
  626.     return 0;
  627. }
  628.  
  629. - (void)moveBaselineTo:(const float *)y
  630. {
  631.     float ascender, descender, lineHeight;
  632.  
  633.     if (y && !font) [self font];
  634.     if (y && font) {
  635.         // This function is defined in the old NSCStringText class,
  636.         // but it doesn't actually have anything to do with the text object.
  637.         // It uses only the font info.
  638.     NSTextFontInfo(font, &ascender, &descender, &lineHeight);
  639.     bounds.origin.y = *y - ascender - bounds.size.height;
  640.     } 
  641. }
  642.  
  643. - (void)updateEditingViewRect:(NSRect)updateRect
  644. {
  645.     updateRect = [graphicView convertRect:updateRect fromView:editView];
  646.     [graphicView lockFocus];
  647.     [graphicView drawRect:updateRect];
  648.     [graphicView unlockFocus];
  649.     [[graphicView window] flushWindow];
  650. }
  651.  
  652. - (void)editorFrameChanged:(NSNotification *)arg
  653. {
  654.     NSRect currentEditingFrame = [[arg object] frame];
  655.     if (!NSEqualRects(lastEditingFrame, NSZeroRect)) {
  656.         if (lastEditingFrame.size.width > currentEditingFrame.size.width) {
  657.             NSRect updateRect = lastEditingFrame;
  658.             updateRect.origin.x = currentEditingFrame.origin.x + currentEditingFrame.size.width;
  659.             [self updateEditingViewRect:updateRect];
  660.         }
  661.         if (lastEditingFrame.size.height > currentEditingFrame.size.height) {
  662.             NSRect updateRect = lastEditingFrame;
  663.             updateRect.origin.y = currentEditingFrame.origin.y + currentEditingFrame.size.height;
  664.             [self updateEditingViewRect:updateRect];
  665.         }
  666.     }
  667.     lastEditingFrame = currentEditingFrame;
  668. }
  669.  
  670. /* Public methods */
  671.  
  672. - (void)prepareFieldEditor
  673. /*
  674.  * Here we are going to use the shared field editor for the window to
  675.  * edit the text in the TextGraphic.  First, we must end any other editing
  676.  * that is going on with the field editor in this window using endEditingFor:.
  677.  * Next, we get the field editor from the window.  Normally, the field
  678.  * editor ends editing when carriage return is pressed.  This is due to
  679.  * the fact that its character filter is NSFieldFilter.  Since we want our
  680.  * editing to be more like an editor (and less like a Form or TextField),
  681.  * we set the character filter to be NSEditorFilter.  What is more, normally,
  682.  * you can't change the font of a TextField or Form with the FontPanel
  683.  * (since that might interfere with any real editable Text objects), but
  684.  * in our case, we do want to be able to do that.  We also want to be
  685.  * able to edit rich text, so we issue a setMonoFont:NO.  Editing is a bit
  686.  * more efficient if we set the Text object to be opaque.  Note that
  687.  * in textDidEnd:endChar: we will have to set the character filter,
  688.  * FontPanelEnabled and mono-font back so that if there were any forms
  689.  * or TextFields in the window, they would have a correctly configured
  690.  * field editor.
  691.  *
  692.  * To let the field editor know exactly where editing is occurring and how
  693.  * large the editable area may grow to, we must calculate and set the frame
  694.  * of the field editor as well as its minimum and maximum size.
  695.  *
  696.  * We load up the field editor with our rich text (if any).
  697.  *
  698.  * Finally, we set self as the delegate (so that it will receive the
  699.  * textDidEnd:endChar: message when editing is completed) and either
  700.  * pass the mouse-down event onto the Text object, or, if a mouse-down
  701.  * didn't cause editing to occur (i.e. we just created it), then we
  702.  * simply put the blinking caret at the beginning of the editable area.
  703.  *
  704.  * The line marked with the "ack!" is kind of strange, but is necessary
  705.  * since growable Text objects only work when they are subviews of a flipped
  706.  * view.
  707.  *
  708.  * This is why GraphicView has an "editView" which is a flipped view that it
  709.  * inserts as a subview of itself for the purposes of providing a superview
  710.  * for the Text object.  The "ack!" line converts the bounds of the TextGraphic
  711.  * (which are in GraphicView coordinates) to the coordinates of the Text
  712.  * object's superview (the editView).  This limitation of the Text object
  713.  * will be fixed post-1.0.  Note that the "ack!" line is the only one
  714.  * concession we need to make to this limitation in this method (there is
  715.  * another such line in resignFieldEditor).
  716.  */
  717. {
  718.     NSSize maxSize, containerSize;
  719.     NSRect viewBounds, frame;
  720.  
  721.     [NSApp sendAction:@selector(disableChanges:) to:nil from:self];
  722.     [[graphicView window] endEditingFor:self];
  723.     fe = (NSTextView *)[[graphicView window] fieldEditor:YES forObject:self];
  724.     
  725.     if ([self isSelected]) {
  726.         [self deselect];
  727.         [graphicView cache:[self extendedBounds] andUpdateLinks:NO];
  728.         [[graphicView selectedGraphics] removeObject:self];
  729.     }
  730.     
  731.     [fe setFont:[[NSFontManager sharedFontManager] selectedFont]];
  732.     
  733.     /* Modify it so that it will edit Rich Text and use the FontPanel. */
  734.     
  735.     [fe setFieldEditor:NO];
  736.     [fe setUsesFontPanel:YES];
  737.     [fe setRichText:YES];
  738.     [fe setDrawsBackground:NO];
  739.     
  740.     /*
  741.         * Determine the minimum and maximum size that the Text object can be.
  742.         * We let the Text object grow out to the edges of the GraphicView,
  743.         * but no further.
  744.         */
  745.     
  746.     viewBounds = [editView bounds];
  747.     maxSize.width = viewBounds.origin.x+viewBounds.size.width- bounds.origin.x;
  748.     maxSize.height = bounds.origin.y+bounds.size.height- viewBounds.origin.y;
  749.     if (!bounds.size.height && !bounds.size.width) {
  750.         // These calls to pointSize in NSFont used to be calls to lineHeight in NSCStringText which was more accurate.
  751.         bounds.origin.y -= floor([[fe font] pointSize] / 2.0);
  752.         bounds.size.height = [[fe font] pointSize];
  753.         bounds.size.width = 5.0;
  754.     }
  755.     frame = bounds;
  756.     frame = [editView convertRect:frame fromView:graphicView];    // ack!
  757.     [fe setMinSize:bounds.size];
  758.     [fe setMaxSize:maxSize];
  759.     [fe setFrame:frame];
  760.         [fe setVerticallyResizable:YES];
  761.         lastEditingFrame = NSZeroRect;
  762.         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(editorFrameChanged:) name:NSViewFrameDidChangeNotification object:fe];
  763.     
  764.     /*
  765.     * If we already have text, then put it in the Text object (allowing
  766.         * the Text object to grow downward if necessary), otherwise, put
  767.     * no text in, set some initial parameters, and allow the Text object
  768.     * to grow horizontally as well as vertically
  769.         */
  770.     
  771.     if (richTextData) {
  772.         [fe setHorizontallyResizable:NO];
  773.             [[fe textContainer] setWidthTracksTextView:YES];
  774.             containerSize.width = bounds.size.width;
  775.             containerSize.height = [[fe textContainer] containerSize].height;
  776.             [[fe textContainer] setContainerSize:containerSize];
  777.         [fe replaceCharactersInRange:(NSRange){0, [[fe string] length]} withRTF:richTextData];
  778.     } else {
  779.         [fe setHorizontallyResizable:YES];
  780.             [[fe textContainer] setWidthTracksTextView:NO];
  781.             containerSize.width = NSMaxX(viewBounds) - bounds.origin.x;
  782.             containerSize.height = [[fe textContainer] containerSize].height;
  783.             [[fe textContainer] setContainerSize:containerSize];
  784.         [fe setString:@""];
  785.         [fe setAlignment:NSLeftTextAlignment];
  786.         [fe setTextColor:[NSColor blackColor] range:[fe selectedRange]];
  787.         [fe unscript:self];
  788.     }
  789.     
  790.     /*
  791.         * Add the Text object to the view heirarchy and set self as its delegate
  792.         * so that we will receive the textDidEnd:endChar: message when editing
  793.         * is finished.
  794.         */
  795.     
  796.     [fe setDelegate:self];
  797.     [editView addSubview:fe];
  798.     
  799.     /*
  800.      * Make it the first responder.
  801.      */
  802.     
  803.     [[graphicView window] makeFirstResponder:fe];
  804.     
  805.     /* Change the ruler to be a text ruler. */
  806.     
  807.     [fe tryToPerform:@selector(showTextRuler:) with:fe];
  808.  
  809.     [fe setSelectedRange:(NSRange){0,0}];
  810.  
  811.         [graphicView cache:bounds];
  812.     [NSApp sendAction:@selector(enableChanges:) to:nil from:self]; 
  813. }
  814.  
  815. - (void)resignFieldEditor
  816. /* 
  817.  * We must extract the rich text the user has typed from the Text object,
  818.  * and store it away. We also need to get the frame of the Text object
  819.  * and make that our bounds (but, remember, since the Text object must
  820.  * be a subview of a flipped view, we need to convert the bounds rectangle
  821.  * to the coordinates of the unflipped GraphicView).  If the Text object
  822.  * is empty, then we remove this TextGraphic from the GraphicView.
  823.  * We must remove the Text object from the view heirarchy and, since
  824.  * this Text object is going to be reused, we must set its delegate
  825.  * back to nil.
  826.  *
  827.  * For further explanation of the "ack!" line, see edit:in: above.
  828.  */
  829. {
  830.     NSRect redrawRect;
  831.     int len;
  832.  
  833.     if (fe) {
  834.         [NSApp sendAction:@selector(disableChanges:) to:nil from:self];
  835.             if (richTextData) {
  836.                 [richTextData release];
  837.                 richTextData = NULL;
  838.             }
  839.  
  840.             NSAssert1(editView == [fe superview], @"%@", "Fault in Text Graphic: Code 2");
  841.             NSAssert1(graphicView == (GraphicView *)[editView superview], @"%@", "Fault in Text Graphic: Code 3");
  842.  
  843.             redrawRect = bounds;
  844.             if ((len = [[fe string] length]) != 0) {
  845.                 [self setRichTextData: [fe RTFFromRange:(NSRange){0, len}]];
  846.                 bounds = [fe frame];
  847.                 bounds = [editView convertRect:bounds toView:graphicView];    // ack!
  848.                 redrawRect = NSUnionRect(bounds, redrawRect);
  849.             }
  850.  
  851.             [[graphicView window] disableFlushWindow];
  852.             [graphicView tryToPerform:@selector(hideRuler:) with:nil];
  853.             [fe removeFromSuperview];
  854.             [[NSNotificationCenter defaultCenter] removeObserver:self name:NSViewFrameDidChangeNotification object:fe];
  855.             [fe setDelegate:nil];
  856.             [fe setSelectedRange:(NSRange){0, 0}];
  857.             font = [fe font];
  858.             fe = nil;
  859.             [graphicView cache:redrawRect];
  860.             [[graphicView window] enableFlushWindow];
  861.             [[graphicView window] flushWindow];
  862.         [NSApp sendAction:@selector(enableChanges:) to:nil from:self];
  863.     }
  864. }
  865.  
  866. - (BOOL)isEmpty
  867. {
  868.     return richTextData ? NO : YES;
  869. }
  870.  
  871. /* Text object delegate methods */
  872.  
  873. - (void)textDidEndEditing:(NSNotification *)notification
  874. /*
  875.  * This method is called when ever first responder is taken away from a
  876.  * currently editing TextGraphic (i.e. when the user is done editing and
  877.  * chooses to go do something else).  
  878.  */
  879. {
  880.     id change;
  881.     NSTextView *textObject = [notification object];
  882.  
  883.     NSAssert(fe == textObject, @"Fault in Text Graphic: Code 1");
  884.     change = [[EndEditingGraphicsChange alloc] initGraphicView:graphicView  graphic:self];
  885.     [change startChange];
  886.         [self resignFieldEditor];
  887.     if ([self isEmpty]) [graphicView removeGraphic:self];
  888.     [change endChange];
  889. }
  890.  
  891. /* Archiving methods */
  892.  
  893. #define RICH_TEXT_KEY @"TheText"
  894.  
  895. - (id)propertyList
  896. {
  897.     NSMutableDictionary *plist = [super propertyList];
  898.     [plist setObject:richTextData forKey:RICH_TEXT_KEY];
  899.     return plist;
  900. }
  901.  
  902. - (NSString *)description
  903. {
  904.     return [(NSObject *)[self propertyList] description];
  905. }
  906.  
  907. - initFromPropertyList:(id)plist inDirectory:(NSString *)directory
  908. {
  909.     [super initFromPropertyList:plist inDirectory:directory];
  910.     richTextData = [[plist objectForKey:RICH_TEXT_KEY] retain];
  911.     [[self class] initClassVars];
  912.     return self;
  913. }
  914.  
  915. @end
  916.