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