|
|
1.1 root 1: #import "draw.h"
2:
3: extern int errno;
4:
5: #define DRAW_VERSION_2_0 184
6: #define DRAW_VERSION_3_0_PRERELEASE 234
7: #define DRAW_VERSION_3_0 245
8: #define NEW_DRAW_VERSION DRAW_VERSION_3_0
9:
10: /* Localization strings */
11:
12: #define SAVE NXLocalString("Save", NULL, "Button choice which allows the user to save his document.")
13:
14: #define DONT_SAVE NXLocalString("Don't Save", NULL, "Button choice which allows the user to abort the saving of his document when the user has asked him if he'd like to do so.")
15:
16: #define REVERT NXLocalString("REVERT_BUTTON", "Revert", "Button choice which allows the user to revert his changes to what was last saved on disk.")
17:
18: @implementation DrawDocument
19: /*
20: * This class is used to keep track of a Draw document.
21: *
22: * Its view and window instance variables keep track of the GraphicView
23: * comprising the document as well as the window it is in.
24: * The printInfo instance variable is used to allow the user to control
25: * how the printed page is printed. It is an instance of a PrintInfo
26: * object (which is edited via the PageLayout and PrintPanels).
27: * The listener is used to allow the user to drag an icon representing
28: * a PostScript or TIFF file into the document. The iconPathList is the
29: * list of files which was last dragged into the document.
30: * The name and directory specify where the document is to be saved.
31: * haveSavedDocument keeps track of whether a disk file is associated
32: * with the document yet (i.e. if it has ever been saved).
33: *
34: * The DrawDocument class's responsibilities:
35: *
36: * 1. Manage the window (including the scrolling view) which holds the
37: * document's GraphicView. This includes constraining the resizing of
38: * the window so that it never becomes larger than the GraphicView, and
39: * ensuring that if the window contains an unsaved document and the user
40: * tries to close it, the user gets an opportunity to save her changes.
41: * 2. Handle communication with the Workspace Manager which allows icons
42: * for PostScript and TIFF files to be dragged into the document window
43: * and be assimilated into the document.
44: * 3. Saving the document to a disk file.
45: * 4. Provide an external interface to saving the contents of the GraphicView
46: * as a PostScript or TIFF file.
47: */
48:
49: #define MIN_WINDOW_WIDTH 50.0
50: #define MIN_WINDOW_HEIGHT 75.0
51: #define SCROLLVIEW_BORDER NX_NOBORDER
52:
53: static NXRect *calcFrame(PrintInfo *printInfo, NXRect *viewRect)
54: /*
55: * Calculates the size of the page the user has chosen minus its margins.
56: */
57: {
58: float lm, rm, bm, tm;
59: const NXRect *paperRect;
60:
61: viewRect->origin.x = viewRect->origin.y = 0.0;
62: paperRect = [printInfo paperRect];
63: [printInfo getMarginLeft:&lm right:&rm top:&tm bottom:&bm];
64: viewRect->size = paperRect->size;
65: viewRect->size.width -= lm + rm;
66: viewRect->size.height -= tm + bm;
67:
68: return viewRect;
69: }
70:
71: static void getContentSizeForView(View *view, NXSize *contentSize)
72: /*
73: * Calculates the size of the window's contentView by accounting for the
74: * existence of the ScrollView around the GraphicView. No scrollers are
75: * assumed since we are interested in the minimum size need to enclose
76: * the entire view and, if the entire view is visible, we don't need
77: * scroll bars!
78: */
79: {
80: NXRect viewFrame;
81:
82: [view getFrame:&viewFrame];
83: [SyncScrollView getFrameSize:contentSize
84: forContentSize:&viewFrame.size
85: horizScroller:YES vertScroller:YES
86: borderType:SCROLLVIEW_BORDER];
87: }
88:
89: #define WINDOW_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_FLAGSCHANGEDMASK)
90:
91: static Window *createWindowFor(View *view, NXRect *windowContentRect, const char *frameString)
92: /*
93: * Creates a window for the specified view.
94: * If windowContentRect is NULL, then a window big enough to fit the whole
95: * view is created (unless that would be too big to comfortably fit on the
96: * screen, in which case a smaller window may be allocated).
97: * If windowContentRect is not NULL, then it is used as the contentView of
98: * the newly created window.
99: *
100: * setMiniwindowIcon: sets the name of the bitmap which will be used in
101: * the miniwindow of the window (i.e. when the window is miniaturized).
102: * The icon "drawdoc" was defined in InterfaceBuilder (take a look in
103: * the icon suitcase).
104: */
105: {
106: Window *window;
107: NXSize screenSize;
108: SyncScrollView *scrollView;
109: NXRect defaultWindowContentRect;
110:
111: if (!windowContentRect) {
112: windowContentRect = &defaultWindowContentRect;
113: getContentSizeForView(view, &windowContentRect->size);
114: [NXApp getScreenSize:&screenSize];
115: if (windowContentRect->size.width > screenSize.width / 2.0) {
116: windowContentRect->size.width = floor(screenSize.width / 2.0);
117: }
118: if (windowContentRect->size.height > screenSize.height - 20.0) {
119: windowContentRect->size.height = screenSize.height - 20.0;
120: }
121: windowContentRect->origin.x = screenSize.width - 85.0 - windowContentRect->size.width;
122: windowContentRect->origin.y = floor((screenSize.height - windowContentRect->size.height) / 2.0);
123: }
124:
125: window = [[Window allocFromZone:[view zone]] initContent:windowContentRect
126: style:NX_RESIZEBARSTYLE
127: backing:(InMsgPrint ? NX_NONRETAINED : NX_BUFFERED)
128: buttonMask:NX_CLOSEBUTTONMASK|NX_MINIATURIZEBUTTONMASK
129: defer:(InMsgPrint ? NO : YES)];
130:
131: if (frameString) [window setFrameFromString:frameString];
132: scrollView = [[SyncScrollView allocFromZone:[view zone]] initFrame:windowContentRect];
133: [scrollView setRulerClass:[Ruler class]];
134: [scrollView setRulerOrigin:UpperLeft];
135: [scrollView setRulerWidths:[Ruler width] :[Ruler width]];
136: [scrollView setVertScrollerRequired:YES];
137: [scrollView setHorizScrollerRequired:YES];
138: [scrollView setBorderType:SCROLLVIEW_BORDER];
139: [scrollView setDocView:view];
140: [[window setContentView:scrollView] free];
141: [window addToEventMask:WINDOW_MASK];
142: [window makeFirstResponder:view];
143: [window setMiniwindowIcon:"drawdoc"];
144: [window setFreeWhenClosed:YES];
145:
146: return window;
147: }
148:
149: static int removeFile(const char *file)
150: {
151: DIR *dirp;
152: struct stat st;
153: struct direct *dp;
154: char *leaf = NULL;
155: char path[MAXPATHLEN+1];
156:
157: if (!stat(file, &st)) {
158: if ((st.st_mode & S_IFMT) == S_IFDIR) {
159: dirp = opendir(file);
160: for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
161: if (strcmp(dp->d_name, ".") && strcmp(dp->d_name, "..")) {
162: if (!leaf) {
163: strcpy(path, file);
164: strcat(path, "/");
165: leaf = path + strlen(path);
166: }
167: strcpy(leaf, dp->d_name);
168: if (unlink(path)) {
169: closedir(dirp);
170: return -1;
171: }
172: }
173: }
174: return rmdir(file);
175: } else {
176: return unlink(file);
177: }
178: }
179:
180: return -1;
181: }
182:
183: /* Very private methods needed by factory methods */
184:
185: + backupOldDrawDocument:(const char *)file
186: /*
187: * We do this because this is not a fully-supported application.
188: */
189: {
190: NXStream *volatile stream;
191: char *extension, *dash;
192: NXTypedStream *volatile ts = NULL;
193: volatile int version = NEW_DRAW_VERSION;
194: char buffer[MAXPATHLEN+1];
195:
196: NX_DURING
197: stream = NXMapFile(file, NX_READONLY);
198: if (stream) ts = NXOpenTypedStream(stream, NX_READONLY);
199: if (ts) NXReadType(ts, "i", (int *)&version);
200: NX_HANDLER
201: NX_ENDHANDLER
202:
203: if (ts) NXCloseTypedStream(ts);
204: if (stream) NXCloseMemory(stream, NX_FREEBUFFER);
205:
206: if (version < DRAW_VERSION_3_0_PRERELEASE) {
207: strcpy(buffer, file);
208: extension = strrchr(buffer, '.');
209: if (extension && !strcmp(extension, ".draw")) {
210: dash = strrchr(buffer, '-');
211: if (version == DRAW_VERSION_2_0) {
212: if (!dash || strcmp(dash, "-2.0.draw")) strcpy(extension, "-2.0.draw");
213: } else {
214: if (!dash || strcmp(dash, "-1.0.draw")) strcpy(extension, "-1.0.draw");
215: }
216: } else {
217: if (version == DRAW_VERSION_2_0) {
218: strcat(buffer, "-2.0");
219: } else {
220: strcat(buffer, "-1.0");
221: }
222: }
223: link(file, buffer);
224: }
225:
226: return self;
227: }
228:
229: - (BOOL)loadDocument:(NXStream *)stream frameSize:(NXRect *)frame frameString:(char *)frameString
230: /*
231: * Loads an archived document from the specified filename.
232: * Loads the window frame specified in the archived document into the
233: * frame argument (if the frame argument is NULL, then the frame in
234: * the archived document is ignored). Returns YES if the document
235: * has been successfully loaded, NO otherwise. Note that this method
236: * destroys the receiving document, so use with extreme care
237: * (essentially, this should only be called when a new document is
238: * being created or an existing one is being reverted to its form
239: * on disk).
240: *
241: * An NX_DURING handler is needed around the NXTypedStream operations because
242: * if the user has asked that a bogus file be opened, the NXTypedStream will
243: * raise an error. To handle the error, the NXTypedStream must be closed.
244: */
245: {
246: char *s;
247: int cgi, version;
248: volatile NXRect docFrame;
249: volatile BOOL retval = YES;
250: NXTypedStream *volatile ts = NULL;
251:
252: NX_DURING
253: ts = NXOpenTypedStream(stream, NX_READONLY);
254: if (ts) {
255: NXSetTypedStreamZone(ts, [self zone]);
256: NXReadType(ts, "i", &version);
257: printInfo = NXReadObject(ts);
258: if (version >= DRAW_VERSION_3_0_PRERELEASE) {
259: NXReadType(ts, "*", &s);
260: if (frameString) strcpy(frameString, s);
261: free(s);
262: } else {
263: NXReadRect(ts, (NXRect *)&docFrame);
264: }
265: if (version >= DRAW_VERSION_3_0) {
266: NXReadType(ts, "i", &cgi);
267: [Graphic updateCurrentGraphicIdentifier:cgi];
268: }
269: view = NXReadObject(ts);
270: } else {
271: retval = NO;
272: }
273: NX_HANDLER
274: retval = NO;
275: NX_ENDHANDLER
276:
277: if (ts) NXCloseTypedStream(ts);
278: if (retval && frame) *frame = docFrame;
279:
280: return retval;
281: }
282:
283: /* Factory methods */
284:
285: /*
286: * We reuse zones since it doesn't cost us anything to have a
287: * zone lying around (e.g. if we open ten documents at the start
288: * then don't use 8 of them for the rest of the session, it doesn't
289: * cost us anything except VM (no real memory cost)), and it is
290: * risky business to go around NXDestroy()'ing zones since if
291: * your application accidentally allocates some piece of global
292: * data into a zone that gets destroyed, you could have a pointer
293: * to freed data on your hands! We use the List object since it
294: * is so easy to use (which is okay as long as 'id' remains a
295: * pointer just like (NXZone *) is a pointer!).
296: *
297: * Note that we don't implement alloc and allocFromZone: because
298: * we create our own zone to put ourselves in. It is generally a
299: * good idea to "notImplemented:" those methods if you do not allow
300: * an object to be alloc'ed from an arbitrary zone (other examples
301: * include Application and all of the Application Kit panels
302: * (which allocate themselves into their own zone).
303: */
304:
305: static List *zoneList = nil;
306:
307: + (NXZone *)newZone
308: {
309: if (!zoneList || ![zoneList count]) {
310: return NXCreateZone(vm_page_size, vm_page_size, YES);
311: } else {
312: return (NXZone *)[zoneList removeLastObject];
313: }
314: }
315:
316: + (void)reuseZone:(NXZone *)aZone
317: {
318: if (!zoneList) zoneList = [List new];
319: [zoneList addObject:(id)aZone];
320: NXNameZone(aZone, "Unused");
321: }
322:
323: + allocFromZone:(NXZone *)aZone
324: {
325: return [self notImplemented:@selector(allocFromZone:)];
326: }
327:
328: + alloc
329: {
330: return [self notImplemented:@selector(alloc)];
331: }
332:
333: /* Creation methods */
334:
335: + new
336: /*
337: * Creates a new, empty, document.
338: *
339: * Creates a PrintInfo object; creates a view whose size depends on the
340: * default PrintInfo created; creates a window for that view; sets self
341: * as the window's delegate; orders the window front; registers the window
342: * with the Workspace Manager. Note that the default margins are set
343: * to 1/2 inch--that's more appropriate for a draw program than 1 or 1.25
344: * inches.
345: */
346: {
347: NXZone *zone;
348: NXRect frameRect;
349:
350: zone = [self newZone];
351: self = [super allocFromZone:zone];
352: [self init];
353: printInfo = [[PrintInfo allocFromZone:zone] init];
354: [printInfo setMarginLeft:36.0 right:36.0 top:36.0 bottom:36.0];
355: calcFrame(printInfo, &frameRect);
356: view = [[GraphicView allocFromZone:zone] initFrame:&frameRect];
357: [view setClipping:NO]; /* since it is in a ClipView */
358: window = createWindowFor(view, NULL, NULL);
359: [window setDelegate:self];
360: [self resetScrollers];
361: [self setName:NULL andDirectory:NULL];
362: [self setLinkManager:[[NXDataLinkManager allocFromZone:[self zone]] initWithDelegate:self]];
363: [window makeKeyAndOrderFront:self];
364:
365: return self;
366: }
367:
368: + newFromStream:(NXStream *)stream
369: /*
370: * Creates a new document from what is in the passed stream.
371: */
372: {
373: NXZone *zone;
374: NXRect contentViewFrame;
375: char frameString[NX_MAXFRAMESTRINGLENGTH];
376:
377: zone = [self newZone];
378: self = [super allocFromZone:zone];
379: [self init];
380: *frameString = '\0'; // will come back still "" if an old file is read
381: if (stream && [self loadDocument:stream frameSize:&contentViewFrame frameString:frameString]) {
382: window = createWindowFor(view, &contentViewFrame, *frameString ? frameString : NULL);
383: [window setDelegate:self];
384: [self resetScrollers];
385: haveSavedDocument = YES;
386: return self;
387: } else {
388: NXRunLocalizedAlertPanel(NULL, "Open Draw Document", "I/O error. Can't open file.", NULL, NULL, NULL, "Alert given to user when he tries to open a draw document and there is an I/O error. This usually happens when the document being opened is not really a draw document but somehow got renamed to have the .draw extension.");
389: [self free];
390: return nil;
391: }
392: }
393:
394: + newFromFile:(const char *)file andDisplay:(BOOL)display
395: /*
396: * Opens an existing document from the specified file.
397: */
398: {
399: struct stat st;
400: NXStream *stream = NULL;
401: DrawDocument *newDocument;
402: char cwd[MAXPATHLEN+1], path[MAXPATHLEN+1];
403:
404: [self backupOldDrawDocument:file];
405:
406: if (!stat(file, &st)) {
407: if ((st.st_mode & S_IFMT) == S_IFDIR) {
408: getwd(cwd);
409: if (!chdir(file) && getwd(path)) {
410: stream = NXMapFile("document.draw", NX_READONLY);
411: } else {
412: strcpy(path, file);
413: strcat(path, "/document.draw");
414: stream = NXMapFile(path, NX_READONLY);
415: strcpy(path, file);
416: }
417: chdir(cwd);
418: } else {
419: stream = NXMapFile(file, NX_READONLY);
420: strcpy(path, file);
421: }
422: }
423:
424: if (stream) {
425: if (newDocument = [self newFromStream:stream]) {
426: [newDocument setName:path];
427: [newDocument setLinkManager:[[NXDataLinkManager allocFromZone:[newDocument zone]] initWithDelegate:newDocument fromFile:path]];
428: if ([newDocument isDirty]) [newDocument dirty:nil]; // initWithDelegate:fromFile: might dirty our document but the linkManager is obviously not set yet, so let it know now that it is set, catch-22!
429: if (display) [newDocument->window makeKeyAndOrderFront:newDocument];
430: }
431: NXCloseMemory(stream, NX_FREEBUFFER);
432: return newDocument;
433: } else {
434: NXRunLocalizedAlertPanel(NULL, "Open Draw Document", "I/O error. Can't open file.", NULL, NULL, NULL, "Alert given to user when he tries to open a draw document and there is an I/O error. This usually happens when the document being opened is not really a draw document but somehow got renamed to have the .draw extension.");
435: return nil;
436: }
437: }
438:
439: + newFromFile:(const char *)file
440: {
441: return [self newFromFile:file andDisplay:YES];
442: }
443:
444: - init
445: {
446: [super init];
447: [self registerForServicesMenu];
448: return self;
449: }
450:
451: - free
452: {
453: [self reset:self];
454: if ([NXApp printInfo] == printInfo) [NXApp setPrintInfo:nil];
455: [printInfo free];
456: [linkManager free];
457: NX_FREE(name);
458: NX_FREE(directory);
459: NX_FREE(iconPathList);
460: [[self class] reuseZone:[self zone]];
461: return [super free];
462: }
463:
464: /* Data link methods -- see gvLinks.m and Links.rtf for more info. */
465:
466: - setLinkManager:(NXDataLinkManager *)aLinkManager
467: {
468: linkManager = aLinkManager;
469: [view setLinkManager:aLinkManager];
470: return self;
471: }
472:
473: - showSelection:(NXSelection *)selection
474: {
475: return [view showSelection:selection];
476: }
477:
478: - copyToPasteboard:(Pasteboard *)pasteboard at:(NXSelection *)selection cheapCopyAllowed:(BOOL)flag
479: {
480: return [view copyToPasteboard:pasteboard at:selection cheapCopyAllowed:flag];
481: }
482:
483: - pasteFromPasteboard:(Pasteboard *)pasteboard at:(NXSelection *)selection
484: {
485: return [view pasteFromPasteboard:pasteboard at:selection];
486: }
487:
488: - importFile:(const char *)filename at:(NXSelection *)selection
489: {
490: return [view importFile:filename at:selection];
491: }
492:
493: - windowForSelection:(NXSelection *)selection
494: {
495: return window;
496: }
497:
498: - dataLinkManager:linkManager didBreakLink:(NXDataLink *)aLink
499: {
500: return [view breakLinkAndRedrawOutlines:aLink];
501: }
502:
503: - dataLinkManagerRedrawLinkOutlines:(NXDataLinkManager *)sender
504: {
505: return [view breakLinkAndRedrawOutlines:nil];
506: }
507:
508: - (BOOL)dataLinkManagerTracksLinksIndividually:(NXDataLinkManager *)sender
509: {
510: return YES;
511: }
512:
513: - dataLinkManager:(NXDataLinkManager *)sender startTrackingLink:(NXDataLink *)link
514: {
515: [view startTrackingLink:link];
516: return self;
517: }
518:
519: - dataLinkManager:(NXDataLinkManager *)sender stopTrackingLink:(NXDataLink *)link
520: {
521: [view stopTrackingLink:link];
522: return self;
523: }
524:
525: - dataLinkManagerDidEditLinks:(NXDataLinkManager *)sender
526: {
527: [self dirty:self];
528: [view updateLinksPanel];
529: return self;
530: }
531:
532: - saveLink:sender
533: {
534: NXSelection *selection;
535: NXDataLink *link;
536:
537: selection = [view currentSelection];
538: link = [[NXDataLink alloc] initLinkedToSourceSelection:selection managedBy:linkManager supportingTypes:TypesDrawExports() count:NUM_TYPES_DRAW_EXPORTS];
539: [link saveLinkIn:[self filename]];
540: [link free];
541:
542: return self;
543: }
544:
545: /*
546: * Overridden from ChangeManager (Undo stuff)
547: */
548:
549: - changeWasDone
550: {
551: [super changeWasDone];
552: [window setDocEdited:[self isDirty]];
553: [linkManager documentEdited];
554: return self;
555: }
556:
557: - changeWasUndone
558: {
559: [super changeWasUndone];
560: [window setDocEdited:[self isDirty]];
561: [linkManager documentEdited];
562: return self;
563: }
564:
565: - changeWasRedone
566: {
567: [super changeWasRedone];
568: [window setDocEdited:[self isDirty]];
569: [linkManager documentEdited];
570: return self;
571: }
572:
573: - clean:sender
574: {
575: [super clean:sender];
576: [window setDocEdited:NO];
577: return self;
578: }
579:
580: - dirty:sender
581: {
582: [super dirty:sender];
583: [window setDocEdited:YES];
584: [linkManager documentEdited];
585: return self;
586: }
587:
588: /* Services menu support methods. */
589:
590: /* Services menu registrar */
591:
592: - registerForServicesMenu
593: {
594: static BOOL registered = NO;
595: const char *validSendTypes[2];
596:
597: if (!registered) {
598: registered = YES;
599: validSendTypes[0] = NXFilenamePboardType;
600: validSendTypes[1] = NULL;
601: [NXApp registerServicesMenuSendTypes:validSendTypes andReturnTypes:NULL];
602: }
603:
604: return self;
605: }
606:
607: - validRequestorForSendType:(NXAtom)sendType andReturnType:(NXAtom)returnType
608: /*
609: * Services menu support.
610: * We are a valid requestor if the send type is filename
611: * and there is no return data from the request.
612: */
613: {
614: return (haveSavedDocument && sendType == NXFilenamePboardType && (!returnType || !*returnType)) ? self : nil;
615: }
616:
617: #define SERVICE NXLocalString("Service", NULL, "This is the the title of an alert which comes up when the user requests a service from the Services menu.")
618: #define SAVE_FOR_SERVICE NXLocalString("Do you wish to save this document before your request is serviced?", NULL, "This question appears in an alert when the user requests a service from the Services menu which operates on the entire draw document, but the draw document has been edited since it was last saved. Draw is just asking if the user would like to save the document first so that the service will operate on the current state of the document rather than on the last saved state.")
619:
620: - writeSelectionToPasteboard:pboard types:(NXAtom *)types
621: /*
622: * Services menu support.
623: * Here we are asked by the Services menu mechanism to supply
624: * the filename (which we said we were a valid requestor for
625: * in the above method).
626: */
627: {
628: int save;
629:
630: if (haveSavedDocument) {
631: while (types && *types) if (*types == NXFilenamePboardType) break; else types++;
632: if (types && *types) {
633: if ([self isDirty]) {
634: save = NXRunAlertPanel(SERVICE, SAVE_FOR_SERVICE, SAVE, DONT_SAVE, NULL);
635: if (save == NX_ALERTDEFAULT) {
636: if ([self save]) [linkManager documentSaved];
637: }
638: }
639: [pboard declareTypes:&NXFilenamePboardType num:1 owner:self];
640: [pboard writeType:NXFilenamePboardType data:[self filename] length:strlen([self filename])+1];
641: return self;
642: }
643: }
644:
645: return nil;
646: }
647:
648: /* Other methods. */
649:
650: - resetScrollers
651: /*
652: * Checks to see if the new window size is too large.
653: * Called whenever the page layout (either by user action or
654: * by the opening or reverting of a file) is changed or
655: * the user resizes the window.
656: */
657: {
658: SyncScrollView *scrollView;
659: NXSize contentSize;
660: NXRect contentRect, windowFrame;
661: BOOL updateRuler = NO;
662:
663: if (window) {
664: [window getFrame:&windowFrame];
665: [[window class] getContentRect:&contentRect
666: forFrameRect:&windowFrame
667: style:[window style]];
668: scrollView = [window contentView];
669: getContentSizeForView(view, &contentSize);
670: if ([scrollView horizontalRulerIsVisible]) {
671: contentSize.height += [Ruler width];
672: updateRuler = YES;
673: }
674: if ([scrollView verticalRulerIsVisible]) {
675: contentSize.width += [Ruler width];
676: updateRuler = YES;
677: }
678: if (contentRect.size.width >= contentSize.width || contentRect.size.height >= contentSize.height) {
679: contentSize.width = MIN(contentRect.size.width, contentSize.width);
680: contentSize.height = MIN(contentRect.size.height, contentSize.height);
681: [window sizeWindow:contentSize.width :contentSize.height];
682: }
683: if (updateRuler) [scrollView updateRuler];
684: }
685:
686: return self;
687: }
688:
689: - view
690: /*
691: * Returns the GraphicView associated with this document.
692: */
693: {
694: return view;
695: }
696:
697: - printInfo
698: /*
699: * Returns the PrintInfo object associated with this document.
700: */
701: {
702: return printInfo;
703: }
704:
705: /* Target/Action methods */
706:
707: #define BAD_MARGINS NXLocalString("The margins or paper size specified are invalid.", NULL, NULL)
708:
709: - changeLayout:sender
710: /*
711: * Puts up a PageLayout panel and allows the user to pick a different
712: * size paper to work on. After she does so, the view is resized to the
713: * new paper size.
714: * Since the PrintInfo is effectively part of the document, we note that
715: * the document is now dirty (by performing the dirty method).
716: */
717: {
718: NXRect frame;
719: float lm, rm, tm, bm;
720: float savedlm, savedrm, savedtm, savedbm;
721: const NXRect *paperRect;
722:
723: [printInfo getMarginLeft:&savedlm right:&savedrm top:&savedtm bottom:&savedbm];
724: if ([[NXApp pageLayout] runModal] == NX_OKTAG) {
725: paperRect = [printInfo paperRect];
726: [printInfo getMarginLeft:&lm right:&rm top:&tm bottom:&bm];
727: if (lm < 0.0 || rm < 0.0 || tm < 0.0 || bm < 0.0 ||
728: paperRect->size.width - lm - rm < 0.0 || paperRect->size.height - tm - bm < 0.0) {
729: [printInfo setMarginLeft:savedlm right:savedrm top:savedtm bottom:savedbm];
730: NXRunAlertPanel(NULL, BAD_MARGINS, NULL, NULL, NULL);
731: return self;
732: }
733: calcFrame(printInfo, &frame);
734: [view sizeTo:frame.size.width :frame.size.height];
735: [self resetScrollers];
736: [view display];
737: [self dirty:self];
738: }
739:
740: return self;
741: }
742:
743: - changeGrid:sender
744: /*
745: * Changes the grid by putting up a modal panel asking the user what
746: * she wants the grid to look like.
747: */
748: {
749: [[NXApp gridInspector] runModalForGraphicView:view];
750: return self;
751: }
752:
753: - close:sender
754: {
755: [window performClose:self];
756: return self;
757: }
758:
759: - save:sender
760: /*
761: * Saves the file. If this document has never been saved to disk,
762: * then a SavePanel is put up to ask the user what file name she
763: * wishes to use to save the document.
764: */
765: {
766: if (haveSavedDocument) {
767: if ([self save]) {
768: [linkManager documentSaved];
769: [self clean:self];
770: }
771: return self;
772: } else {
773: return [self saveAs:sender];
774: }
775: }
776:
777: - saveAs:sender
778: {
779: struct stat st;
780: const char *spfname;
781: SavePanel *savepanel;
782: char cwd[MAXPATHLEN+1], path[MAXPATHLEN+1];
783:
784: savepanel = [NXApp saveAsPanel:sender];
785: if ([savepanel runModalForDirectory:directory file:name]) {
786: getwd(cwd);
787: spfname = [savepanel filename];
788: if (stat(spfname, &st) && errno == ENOENT) mkdir(spfname, 0755);
789: if (chdir(spfname) || !getwd(path)) strcpy(path, spfname);
790: chdir(cwd);
791: [self setName:path];
792: if ([self save]) {
793: [linkManager documentSavedAs:path];
794: [self clean:self];
795: }
796: return self;
797: }
798:
799: return nil;
800: }
801:
802: - changeSaveType:sender
803: /*
804: * Called by the SavePanel accessory view whenever the user chooses
805: * a different type of file to save to. The window of the sender
806: * is, of course, the SavePanel itself. setRequiredFileType: does
807: * not affect the SavePanel while it is running. It only has effect
808: * when the user has chosen a file, and the SavePanel ensures that it
809: * has the correct extension by adding it if it doesn't have it already.
810: * This message gets here via the Responder chain from the SavePanel.
811: */
812: {
813: switch ([sender selectedRow]) {
814: case 0: [[sender window] setRequiredFileType:"draw"]; break;
815: case 1: [[sender window] setRequiredFileType:"eps"]; break;
816: case 2: [[sender window] setRequiredFileType:"tiff"]; break;
817: }
818: return self;
819: }
820:
821: - saveTo:sender
822: /*
823: * This takes the document and saves it as a Draw document file, PostScript
824: * file, or TIFF file. If the document type chosen is Draw document, then
825: * this saves the file, but DOES NOT make that file the currently edited
826: * file (this makes it easy to save your document elsewhere as a backup
827: * and keep on going in the current document).
828: *
829: * If PostScript or TIFF is selected, then the document is written out
830: * in the appropriate format. In the case of PostScript and TIFF, the
831: * actual saving is done using the more general method saveAs:using:.
832: */
833: {
834: const char *file;
835: SavePanel *savepanel;
836: char *type, *savedName, *savedDirectory;
837: BOOL reallyHaveSavedDocument;
838: char buffer[MAXPATHLEN+1];
839:
840: strcpy(buffer, name);
841: type = strrchr(buffer, '.');
842: if (type) *type = '\0';
843: savepanel = [NXApp saveToPanel:sender];
844: if (![savepanel runModalForDirectory:directory file:buffer]) return self;
845:
846: file = [savepanel filename];
847: type = strrchr(file, '.');
848: if (type) {
849: if (!strcmp(type, ".eps")) {
850: [self saveTo:file using:@selector(writePSToStream:)];
851: } else if (!strcmp(type, ".tiff")) {
852: [self saveTo:file using:@selector(writeTIFFToStream:)];
853: } else if (!strcmp(type, ".draw")) {
854: reallyHaveSavedDocument = haveSavedDocument;
855: savedName = name; /* save current name */
856: savedDirectory = directory; /* save current directory */
857: name = NULL; directory = NULL; /* clear current filename */
858: [self setName:file]; /* temporarily change name */
859: if ([self save]) { /* save, then restore name */
860: [linkManager documentSavedTo:[self filename]];
861: }
862: [self setName:savedName andDirectory:savedDirectory];
863: haveSavedDocument = reallyHaveSavedDocument;
864: }
865: }
866:
867: return self;
868: }
869:
870: #define REVERT_TITLE NXLocalString("Revert", NULL, "This is the title of the alert which asks the user if he is sure he wants to revert a document he has edited back to its last-saved state.")
871: #define SURE_TO_REVERT NXLocalString("%s has been edited. Are you sure you want to undo changes?", NULL, "This question is asked of the user when he asks to revert the state of his document to the version last saved on disk, but he has made changes in the interim which would be lost if he did actually revert. The %s is the name of the document.")
872:
873: - revertToSaved:sender
874: /*
875: * Revert the document back to what is on the disk.
876: */
877: {
878: struct stat st;
879: const char *file;
880: NXStream *stream = NULL;
881: NXRect viewFrame, visibleRect;
882: char path[MAXPATHLEN+1];
883:
884: if (!haveSavedDocument || ![self isDirty] || (NXRunAlertPanel(REVERT_TITLE, SURE_TO_REVERT, REVERT, CANCEL, NULL, name) != NX_ALERTDEFAULT)) {
885: return self;
886: }
887:
888: [view getVisibleRect:&visibleRect];
889: [window endEditingFor:nil];
890:
891: file = [self filename];
892: if (!stat(file, &st)) {
893: if ((st.st_mode & S_IFMT) == S_IFDIR) {
894: strcpy(path, file);
895: strcat(path, "/document.draw");
896: stream = NXMapFile(path, NX_READONLY);
897: } else {
898: stream = NXMapFile(file, NX_READONLY);
899: }
900: }
901:
902: if (stream && [self loadDocument:stream frameSize:NULL frameString:NULL]) {
903: [self reset:self];
904: [[[window contentView] setDocView:view] free];
905: calcFrame(printInfo, &viewFrame);
906: [window disableFlushWindow];
907: [view sizeTo:viewFrame.size.width :viewFrame.size.height];
908: [self resetScrollers];
909: [view scrollRectToVisible:&visibleRect];
910: [view display];
911: [window reenableFlushWindow];
912: [window flushWindow];
913: [window makeFirstResponder:view];
914: [self reset:self];
915: [window setDocEdited:NO];
916: [view setLinkManager:linkManager];
917: [linkManager documentReverted];
918: [view updateLinksPanel];
919: NXCloseMemory(stream, NX_FREEBUFFER);
920: } else {
921: if (stream) NXCloseMemory(stream, NX_FREEBUFFER);
922: NXRunLocalizedAlertPanel(NULL, "Revert", "I/O error. Can't revert.", NULL, NULL, NULL, "This very rare alert shows up when the user tries to revert the state of his document to whatever it was when he opened it up. Some unexpected I/O error occurs, however, and he is unable to do so.");
923: }
924:
925: return self;
926: }
927:
928: - showTextRuler:sender
929: /*
930: * Sent to cause the Text object ruler to be displayed.
931: * Only does anything if the rulers are already visible.
932: */
933: {
934: SyncScrollView *scrollView = [window contentView];
935:
936: if ([scrollView verticalRulerIsVisible] && [scrollView horizontalRulerIsVisible]) {
937: [scrollView showHorizontalRuler:NO];
938: [sender toggleRuler:sender];
939: }
940:
941: return self;
942: }
943:
944: - hideRuler:sender
945: /*
946: * If sender is nil, we assume the sender wants the
947: * ruler hidden, otherwise, we toggle the ruler.
948: * If sender is the field editor itself, we do nothing
949: * (this allows the field editor to demand that the
950: * ruler stay up).
951: */
952: {
953: SyncScrollView *scrollView = [window contentView];
954: Text *fe = [window getFieldEditor:NO for:NXApp];
955:
956: if (!sender && [scrollView verticalRulerIsVisible]) {
957: [fe toggleRuler:sender];
958: [window disableDisplay];
959: [scrollView toggleRuler:nil];
960: if ([scrollView verticalRulerIsVisible]) [scrollView showHorizontalRuler:YES];
961: [window reenableDisplay];
962: [scrollView resizeSubviews:(NXSize *)0];
963: } else if (sender) {
964: if ([scrollView verticalRulerIsVisible]) {
965: [scrollView showVerticalRuler:NO];
966: [scrollView showHorizontalRuler:NO];
967: if (![fe window]) [scrollView toggleRuler:nil];
968: } else {
969: [scrollView showVerticalRuler:YES];
970: if ([fe window]) {
971: [scrollView showHorizontalRuler:NO];
972: } else {
973: [scrollView showHorizontalRuler:YES];
974: [scrollView toggleRuler:nil];
975: }
976: }
977: if ([fe superview] != nil)
978: [fe toggleRuler:sender];
979: }
980:
981: return self;
982: }
983:
984: /* Methods related to naming/saving this document. */
985:
986: - (const char *)filename
987: /*
988: * Gets the fully specified file name of the document.
989: * If directory is NULL, then the currentDirectory is used.
990: * If name is NULL, then the default title is used.
991: */
992: {
993: static char filenamebuf[MAXPATHLEN+1];
994:
995: if (!directory && !name) {
996: [self setName:NULL andDirectory:NULL];
997: }
998: if (directory) {
999: strcpy(filenamebuf, directory);
1000: strcat(filenamebuf, "/");
1001: } else {
1002: filenamebuf[0] = '\0';
1003: }
1004: if (name) {
1005: strcat(filenamebuf, name);
1006: }
1007:
1008: return filenamebuf;
1009: }
1010:
1011: - (const char *)directory
1012: {
1013: return directory;
1014: }
1015:
1016: - (const char *)name
1017: {
1018: return name;
1019: }
1020:
1021: - setName:(const char *)newName andDirectory:(const char *)newDirectory
1022: /*
1023: * Updates the name and directory of the document.
1024: * newName or newDirectory can be NULL, in which case the name or directory
1025: * will not be changed (unless one is currently not set, in which case
1026: * a default name will be used).
1027: */
1028: {
1029: char oldName[MAXPATHLEN+1];
1030:
1031: if (directory && name) {
1032: strcpy(oldName, [self filename]);
1033: } else {
1034: oldName[0] = '\0';
1035: }
1036:
1037: if ((newName && *newName) || !name) {
1038: if (!newName || !*newName) newName = NXLocalString("UNTITLED", NULL, "The name of a document which the user has not yet given a name to.");
1039: NX_FREE(name);
1040: name = NXCopyStringBufferFromZone(newName, [self zone]);
1041: }
1042:
1043: if ((newDirectory && (*newDirectory == '/')) || !directory) {
1044: if (!newDirectory || (*newDirectory != '/')) {
1045: newDirectory = [NXApp currentDirectory];
1046: }
1047: NX_FREE(directory);
1048: directory = NXCopyStringBufferFromZone(newDirectory, [self zone]);
1049: }
1050:
1051: [window setTitleAsFilename:[self filename]];
1052: NXNameZone([self zone], [self filename]);
1053:
1054: return self;
1055: }
1056:
1057: - (BOOL)setName:(const char *)file
1058: /*
1059: * If file is a full path name, then both the name and directory of the
1060: * document is updated appropriately, otherwise, only the name is changed.
1061: */
1062: {
1063: char *lastComponent;
1064: char path[MAXPATHLEN+1];
1065:
1066: if (file) {
1067: strcpy(path, file);
1068: lastComponent = strrchr(path, '/');
1069: if (lastComponent) {
1070: *lastComponent++ = '\0';
1071: [self setName:lastComponent andDirectory:path];
1072: return YES;
1073: } else {
1074: [self setName:file andDirectory:NULL];
1075: return YES;
1076: }
1077: }
1078:
1079: return NO;
1080: }
1081:
1082: - setTemporaryTitle:(const char *)title
1083: {
1084: [window setTitle:title];
1085: haveSavedDocument = NO;
1086: NX_FREE(directory);
1087: NX_FREE(name);
1088: directory = NXCopyStringBufferFromZone(NXHomeDirectory(), [self zone]);
1089: name = NXCopyStringBufferFromZone(title, [self zone]);
1090: return self;
1091: }
1092:
1093: - saveTo:(const char *)file using:(SEL)streamWriter
1094: /*
1095: * Performed by the saveTo: method, this method uses the streamWriter method
1096: * to have the GraphicView write itself in some foreign format (i.e., not
1097: * in Draw archive format). It does some work to make the default name
1098: * of the file being saved to be the name of the document with the appropriate
1099: * extension. It brings up the SavePanel so the user can specify the name
1100: * of the file to save to.
1101: */
1102: {
1103: NXStream *stream;
1104:
1105: if (!file || !streamWriter) return self;
1106:
1107: stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
1108: if (stream) {
1109: [view perform:streamWriter with:(id)stream];
1110: NXSaveToFile(stream, file);
1111: NXCloseMemory(stream, NX_FREEBUFFER);
1112: }
1113:
1114: return self;
1115: }
1116:
1117: - save
1118: /*
1119: * Writes out the document in three steps:
1120: *
1121: * 1. Write the printInfo object
1122: * 2. Write the frame of the window
1123: * 3. Write the GraphicView itself.
1124: * 4. Write form information if necessary
1125: *
1126: * See GraphicView's write: method for more details on how the GraphicView
1127: * is archived.
1128: *
1129: * Note the "This is questionable" line. It's questionable because it means
1130: * that Draw documents saved under a particular SYSTEM version can really only
1131: * be opened under the same SYSTEM version no matter which version of DRAW
1132: * writes the file. In other words, if we take 2.0 Draw on a 3.0 system and
1133: * save a file, that file will NOT be able to be opened with the same version
1134: * of Draw (2.0) on a 2.0 system. The bottom line is that archiving Application
1135: * Kit objects as part of the file format of your documents ties the user forever
1136: * not only to the version of Draw that saved the file (obviously you cannot open
1137: * a file saved with Draw version 3.0 with Draw version 2.0--that's okay, we'll
1138: * accept that restriction), but also to the version of the system (meaning a file
1139: * saved with Draw 2.0 may not be openable by Draw 2.0 if the save and read don't
1140: * occur on the same version of NeXTSTEP). Since Draw is always "re-released"
1141: * (for free, no less) whenever new versions of NeXTSTEP are released, this is
1142: * probably okay for Draw, but probably IS NOT OKAY for 3rd party applications!
1143: *
1144: * The right way to do it is just to have the GraphicView write out its data
1145: * directly to the typedstream (i.e. don't call NXWriteObject on the GraphicView,
1146: * just call a method you invent called writeToTypedStream: in GraphicView--your
1147: * implementation of writeToTypedStream: in GraphicView must remember to explicitly
1148: * write out any instance variables of View that need to be saved and restored
1149: * when the document is saved and reopened).
1150: *
1151: * The writing out of the PrintInfo object is more problematic. There is no real
1152: * way for you to know how to write that out yourself. NeXT will have to be careful
1153: * not to change the archiving of this object in an incompatible way (backwards or
1154: * forwards).
1155: */
1156: {
1157: char *s;
1158: struct stat st;
1159: int cgi, version;
1160: volatile BOOL savedOk = NO;
1161: NXTypedStream *ts = NULL;
1162: const char *saveFile = [self filename];
1163: BOOL alreadyHasFormEntries = NO;
1164: char buffer[MAXPATHLEN+1], cwd[MAXPATHLEN+1];
1165:
1166: if (!stat(saveFile, &st) && (st.st_mode & S_IFMT) != S_IFDIR) unlink(saveFile); // remove 1.0/2.0 files
1167: errno = 0;
1168: if (getwd(cwd) && (!chdir(saveFile) || errno == ENOENT)) {
1169: if (errno != ENOENT) {
1170: alreadyHasFormEntries = !stat("form.info", &st);
1171: getwd(buffer);
1172: } else {
1173: strcpy(buffer, saveFile);
1174: }
1175: if (s = strrchr(buffer, '/')) {
1176: *s++ = '\0';
1177: chdir(buffer);
1178: s = NXCopyStringBufferFromZone(s, [self zone]);
1179: } else {
1180: s = NXCopyStringBufferFromZone(saveFile, [self zone]);
1181: }
1182: strcpy(buffer, s);
1183: strcat(buffer, "~");
1184: if ((removeFile(buffer) && errno != ENOENT) || (rename(s, buffer) && errno != ENOENT)) {
1185: NXRunLocalizedAlertPanel(NULL, "Save", "Can't create backup file.", NULL, NULL, NULL);
1186: } else if (!mkdir(s, 0755)) {
1187: chdir(s);
1188: ts = NXOpenTypedStreamForFile("document.draw", NX_WRITEONLY);
1189: }
1190: free(s);
1191: if (ts) {
1192: NX_DURING
1193: version = NEW_DRAW_VERSION;
1194: [window makeFirstResponder:view];
1195: NXWriteType(ts, "i", &version);
1196: NXWriteRootObject(ts, printInfo);
1197: [window saveFrameToString:(s = buffer)];
1198: NXWriteType(ts, "*", &s);
1199: cgi = [Graphic currentGraphicIdentifier];
1200: NXWriteType(ts, "i", &cgi);
1201: NXWriteRootObject(ts, view); // This is questionable. See above.
1202: NXCloseTypedStream(ts);
1203: savedOk = YES;
1204: haveSavedDocument = YES;
1205: if ([view hasFormEntries]) {
1206: if (alreadyHasFormEntries || NXRunLocalizedAlertPanel(NULL, "Save", "This document has Form Entries in it. Do you wish to save them with the document?", "Yes", "No", NULL)) {
1207: [view writeFormEntriesToFile:"form.info"];
1208: strcpy(buffer, saveFile);
1209: strcat(buffer, "/form.eps");
1210: [self saveTo:buffer using:@selector(writePSToStream:)];
1211: }
1212: }
1213: NX_HANDLER
1214: NX_ENDHANDLER
1215: }
1216: }
1217:
1218: chdir(cwd);
1219:
1220: if (!savedOk) NXRunLocalizedAlertPanel(NULL, "Save", "Can't save file.", NULL, NULL, NULL, "This alert appears when the user has asked to save his file somewhere, but Draw was unable to create that file. This can occur for many reasons, the most common of which is that the file or directory is not writable.");
1221:
1222: return savedOk ? self : nil;
1223: }
1224:
1225: - (BOOL)isSameAs:(const char *)filename
1226: {
1227: struct stat me, other;
1228: if (!stat([self filename], &me) && !stat(filename, &other) &&
1229: me.st_dev == other.st_dev && me.st_ino == other.st_ino) return YES;
1230: return NO;
1231: }
1232:
1233: /* Window delegate methods. */
1234:
1235: #define SAVE_CHANGES NXLocalString("%s has changes. Save them?", NULL, "Question asked of user when he tries to close a window, either by choosing close from the menu or clicking the close box or quitting the application, and the contents of the window have not been saved. The %s is the name of the document.")
1236:
1237: - windowWillClose:sender cancellable:(BOOL)cancellable
1238: /*
1239: * If the GraphicView has been edited, then this asks the user if she
1240: * wants to save the changes before closing the window.
1241: *
1242: * Returning nil from this method informs the caller that the window should
1243: * NOT be closed. Anything else implies it should be closed.
1244: */
1245: {
1246: int save;
1247: const char *action = [[sender selectedCell] title];
1248:
1249: if (!action || !*action) action = NXLocalString("Close", NULL, "The operation of closing a window usually generated by choosing the Close Window option in the Windows menu.");
1250:
1251: if ([self isDirty]) {
1252: if (cancellable) {
1253: save = NXRunAlertPanel(action, SAVE_CHANGES, SAVE, DONT_SAVE, CANCEL, name);
1254: } else {
1255: save = NXRunAlertPanel(action, SAVE_CHANGES, SAVE, DONT_SAVE, NULL, name);
1256: }
1257: if (save != NX_ALERTDEFAULT && save != NX_ALERTALTERNATE) {
1258: return nil;
1259: } else {
1260: [window endEditingFor:self]; /* terminate any editing */
1261: if ((save == NX_ALERTDEFAULT) && ![self save:sender]) return nil;
1262: }
1263: }
1264:
1265: return self;
1266: }
1267:
1268: - close
1269: {
1270: [linkManager documentClosed];
1271: [linkManager free];
1272: linkManager = nil;
1273: [self reset:self];
1274: [NXApp delayedFree:self];
1275: return self;
1276: }
1277:
1278: - windowWillClose:(Window *)sender
1279: {
1280: if ([self windowWillClose:nil cancellable:YES]) {
1281: return [self close];
1282: } else {
1283: return nil;
1284: }
1285: }
1286:
1287: - windowDidBecomeMain:(Window *)sender
1288: /*
1289: * Switch the Application's PrintInfo to the document's when the document
1290: * window becomes the main window. Also set the cursor appropriately
1291: * depending on which tool is currently selected.
1292: */
1293: {
1294: [NXApp setPrintInfo:printInfo];
1295: [self resetCursor];
1296: return self;
1297: }
1298:
1299: - windowDidUpdate:(Window *)sender
1300: {
1301: if ([window isMainWindow]) [view updateLinksPanel];
1302: return self;
1303: }
1304:
1305: - windowWillResize:(Window *)sender toSize:(NXSize *)size
1306: /*
1307: * Constrains the size of the window to never be larger than the
1308: * GraphicView inside it (including the ScrollView around it).
1309: */
1310: {
1311: NXRect fRect, cRect;
1312:
1313: getContentSizeForView(view, &cRect.size);
1314: [[window class] getFrameRect:&fRect forContentRect:&cRect style:[window style]];
1315: if ([[window contentView] horizontalRulerIsVisible]) fRect.size.height += [Ruler width];
1316: if ([[window contentView] verticalRulerIsVisible]) fRect.size.width += [Ruler width];
1317: size->width = MIN(fRect.size.width, size->width);
1318: size->height = MIN(fRect.size.height, size->height);
1319: size->width = MAX(MIN_WINDOW_WIDTH, size->width);
1320: size->height = MAX(MIN_WINDOW_HEIGHT, size->height);
1321:
1322: return self;
1323: }
1324:
1325: - windowDidResize:(Window *)sender
1326: /*
1327: * Just makes sure the selection is visible after resizing.
1328: */
1329: {
1330: [view scrollSelectionToVisible];
1331: return self;
1332: }
1333:
1334: - windowWillMiniaturize:(Window *)sender toMiniwindow:counterpart
1335: {
1336: char *dot;
1337: char title[MAXPATHLEN+1];
1338:
1339: strcpy(title, [self name]);
1340: dot = strrchr(title, '.');
1341: if (dot && !strcmp(dot, ".draw")) *dot = '\0';
1342: [counterpart setTitle:title];
1343: return self;
1344: }
1345:
1346: - windowWillReturnFieldEditor:(Window *)sender toObject:client
1347: {
1348: if (!undoFieldEditor) undoFieldEditor = [[UndoText alloc] initFrame:NULL];
1349: return undoFieldEditor;
1350: }
1351:
1352: /* Validates whether a menu command makes sense now */
1353:
1354: #define HIDE_RULER NXLocalString("Hide Ruler", NULL, "Menu item which hides the ruler.")
1355: #define SHOW_RULER NXLocalString("Show Ruler", NULL, "Menu item which unhides the ruler.")
1356:
1357: - (BOOL)validateCommand:(MenuCell *)menuCell
1358: /*
1359: * Validates whether a menu command that DrawDocument responds to
1360: * is valid at the current time.
1361: */
1362: {
1363: SEL action = [menuCell action];
1364:
1365: if (action == @selector(save:)) {
1366: return YES;
1367: } else if (action == @selector(revertToSaved:)) {
1368: return ([self isDirty] && haveSavedDocument);
1369: } else if (action == @selector(saveAs:)) {
1370: return (haveSavedDocument || ![view isEmpty]);
1371: } else if (action == @selector(saveTo:)) {
1372: return ![view isEmpty];
1373: } else if (action == @selector(saveLink:)) {
1374: return (haveSavedDocument && ![view hasEmptySelection]);
1375: } else if (action == @selector(close:)) {
1376: return YES;
1377: } else if (action == @selector(hideRuler:)) {
1378: if ([[window contentView] eitherRulerIsVisible] && strcmp([menuCell title], HIDE_RULER)) {
1379: [menuCell setTitle:HIDE_RULER];
1380: [menuCell setEnabled:NO];
1381: } else if (![[window contentView] eitherRulerIsVisible] && strcmp([menuCell title], SHOW_RULER)) {
1382: [menuCell setTitle:SHOW_RULER];
1383: [menuCell setEnabled:NO];
1384: }
1385: } else if (action == @selector(alignSelLeft:) ||
1386: action == @selector(alignSelRight:) ||
1387: action == @selector(alignSelCenter:) ||
1388: action == @selector(checkSpelling:) ||
1389: action == @selector(showGuessPanel:)) {
1390: return [[window getFieldEditor:NO for:NXApp] superview] ? YES : NO;
1391: }
1392:
1393: return [super validateCommand:menuCell];
1394: }
1395:
1396: /* Cursor-setting method */
1397:
1398: - resetCursor
1399: /*
1400: * Sets the document's cursor according to whatever the current graphic is.
1401: * Makes the graphic view the first responder if there isn't one or if
1402: * no tool is selected (the cursor is the normal one).
1403: */
1404: {
1405: id fr, cursor = [NXApp cursor];
1406: ScrollView *scrollview = [window contentView];
1407:
1408: [scrollview setDocCursor:cursor];
1409: fr = [window firstResponder];
1410: if (!fr || fr == window || cursor == NXArrow) {
1411: [window makeFirstResponder:view];
1412: }
1413:
1414: return self;
1415: }
1416:
1417: @end
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.