home *** CD-ROM | disk | FTP | other *** search
/ OpenStep 4.2J (Developer) / os42jdev.iso / NextDeveloper / Examples / AppKit / Draw / DrawDocument.m < prev    next >
Text File  |  1996-08-12  |  41KB  |  1,227 lines

  1. #import "draw.h"
  2. #import "compatibility.h"
  3.  
  4. #define DRAW_VERSION_2_0 184
  5. #define NEW_DRAW_VERSION FIRST_OPENSTEP_VERSION
  6.  
  7. #define DRAW_DOCUMENT_NAME @"document.draw"
  8.  
  9. /* Keys used in the property list draw documents are stored as. */
  10.  
  11. #define VERSION_KEY @"Version"
  12. #define PRINTINFO_KEY @"PrintInfo"
  13. #define FRAME_KEY @"WindowFrame"
  14. #define SIZE_KEY @"ViewSize"
  15. #define GRAPHIC_IDENT_KEY @"GraphicIdentifier"
  16. #define VIEW_KEY @"View"
  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 object.
  26.  * The listener is used to allow the user to drag an icon representing
  27.  * a PostScript or TIFF file into the document.  The iconPathList is the
  28.  * list of files which was last dragged into the document.
  29.  * The name and directory specify where the document is to be saved.
  30.  * haveSavedDocument keeps track of whether a disk file is associated
  31.  * with the document yet (i.e. if it has ever been saved).
  32.  *
  33.  * The DrawDocument class's responsibilities:
  34.  *
  35.  * 1. Manage the window (including the scrolling view) which holds the
  36.  *    document's GraphicView.  This includes constraining the resizing of
  37.  *    the window so that it never becomes larger than the GraphicView, and
  38.  *    ensuring that if the window contains an unsaved document and the user
  39.  *    tries to close it, the user gets an opportunity to save her changes.
  40.  * 2. Handle communication with the Workspace Manager which allows icons
  41.  *    for PostScript and TIFF files to be dragged into the document window
  42.  *    and be assimilated into the document.
  43.  * 3. Saving the document to a disk file.
  44.  * 4. Provide an external interface to saving the contents of the GraphicView
  45.  *    as a PostScript or TIFF file.
  46.  */
  47.  
  48. #define MIN_WINDOW_WIDTH 50.0
  49. #define MIN_WINDOW_HEIGHT 75.0
  50. #define SCROLLVIEW_BORDER NSNoBorder
  51.  
  52. static NSRect calcFrame(NSPrintInfo *printInfo)
  53. /*
  54.  * Calculates the size of the page the user has chosen minus its margins.
  55.  */
  56. {
  57.     NSRect viewRect;
  58.     viewRect.origin = NSZeroPoint;
  59.     viewRect.size = [printInfo paperSize];
  60.     viewRect.size.width -= [printInfo leftMargin] + [printInfo rightMargin];
  61.     viewRect.size.height -= [printInfo topMargin] + [printInfo bottomMargin];
  62.     return viewRect;
  63. }
  64.  
  65. static NSSize contentSizeForView(NSView *view)
  66. /*
  67.  * Calculates the size of the window's contentView by accounting for the
  68.  * existence of the ScrollView around the GraphicView.  No scrollers are
  69.  * assumed since we are interested in the minimum size need to enclose
  70.  * the entire view and, if the entire view is visible, we don't need
  71.  * scroll bars!
  72.  */
  73. {
  74.     return [SyncScrollView frameSizeForContentSize:[view frame].size hasHorizontalScroller:YES hasVerticalScroller:YES borderType:SCROLLVIEW_BORDER];
  75. }
  76.  
  77. static NSWindow *createWindowForView(NSView *view, NSRect *windowContentRect, NSString *frameString)
  78. /*
  79.  * Creates a window for the specified view.
  80.  *
  81.  * If windowContentRect is NULL, then a window big enough to fit the whole
  82.  * view is created (unless that would be too big to comfortably fit on the
  83.  * screen, in which case a smaller window may be allocated).
  84.  * If windowContentRect is not NULL, then it is used as the contentView of
  85.  * the newly created window.
  86.  *
  87.  * setMiniwindowIcon: sets the name of the bitmap which will be used in
  88.  * the miniwindow of the window (i.e. when the window is miniaturized).
  89.  * The icon "drawdoc" was defined in InterfaceBuilder (take a look in
  90.  * the icon suitcase).
  91.  */
  92. {
  93.     NSWindow *window;
  94.     NSSize screenSize;
  95.     SyncScrollView *scrollView;
  96.     NSRect defaultWindowContentRect;
  97.  
  98.     if (!windowContentRect) {
  99.     windowContentRect = &defaultWindowContentRect;
  100.     windowContentRect->size = contentSizeForView(view);
  101.     screenSize = [[NSScreen mainScreen] frame].size;
  102. #ifndef WIN32
  103.     if (windowContentRect->size.width > screenSize.width / 2.0) {
  104.         windowContentRect->size.width = floor(screenSize.width / 2.0);
  105.     }
  106. #endif WIN32
  107.     if (windowContentRect->size.height > screenSize.height - 20.0) {
  108.         windowContentRect->size.height = screenSize.height - 20.0;
  109.     }
  110.     windowContentRect->origin.x = screenSize.width - 85.0 - windowContentRect->size.width;
  111.     windowContentRect->origin.y = floor((screenSize.height - windowContentRect->size.height) / 2.0);
  112.     }
  113.  
  114.     window = [[NSWindow allocWithZone:(NSZone *)[view zone]] initWithContentRect:*windowContentRect
  115.                 styleMask:NSResizableWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask
  116.                   backing:(InMsgPrint ? NSBackingStoreNonretained : NSBackingStoreBuffered)
  117.                 defer:(InMsgPrint ? NO : YES)];
  118.  
  119.     if (frameString) [window setFrameFromString:frameString];
  120.     scrollView = [[SyncScrollView allocWithZone:(NSZone *)[view zone]] initWithFrame:*windowContentRect];
  121.     [scrollView setRulerClass:[Ruler class]];
  122.     [scrollView setRulerOrigin:UpperLeft];
  123.     [scrollView setRulerWidths:[Ruler width] :[Ruler width]];
  124.     [scrollView setHasVerticalScroller:YES];
  125.     [scrollView setHasHorizontalScroller:YES];
  126.     [scrollView setBorderType:SCROLLVIEW_BORDER];
  127.     [scrollView setDocumentView:view];
  128.     [window setContentView:scrollView];
  129.     [window makeFirstResponder:view];
  130.     [window setMiniwindowImage:[[NSWorkspace sharedWorkspace] iconForFileType:DRAW_EXTENSION]];
  131.     [window setReleasedWhenClosed:YES];
  132.  
  133.     return window;
  134. }
  135.  
  136. + (NSWindow *)createWindowForView:(NSView *)gview windowRect:(NSRect *)windowContentRect frameString:(NSString *)frameString
  137. {
  138.     return createWindowForView(gview, windowContentRect, frameString);
  139. }
  140.  
  141. /* Factory methods */
  142.  
  143. /*
  144.  * We reuse zones since it doesn't cost us anything to have a
  145.  * zone lying around (e.g. if we open ten documents at the start
  146.  * then don't use 8 of them for the rest of the session, it doesn't
  147.  * cost us anything except VM (no real memory cost)), and it is
  148.  * risky business to go around NSDestroy()'ing zones since if
  149.  * your application accidentally allocates some piece of global
  150.  * data into a zone that gets destroyed, you could have a pointer
  151.  * to freed data on your hands!  We use the List object since it
  152.  * is so easy to use (which is okay as long as 'id' remains a
  153.  * pointer just like (NSZone *) is a pointer!).
  154.  *
  155.  * Note that we don't implement alloc and allocFromZone: because
  156.  * we create our own zone to put ourselves in.  It is generally a
  157.  * good idea to "notImplemented:" those methods if you do not allow
  158.  * an object to be alloc'ed from an arbitrary zone (other examples
  159.  * include Application and all of the Application Kit panels
  160.  * (which allocate themselves into their own zone).
  161.  */
  162.  
  163. static List *zoneList = nil;
  164.  
  165. + (NSZone *)newZone
  166. {
  167.     if (!zoneList || ![zoneList count]) {
  168.     return NSCreateZone(NSPageSize(), NSPageSize(), YES);
  169.     } else {
  170.     return (NSZone *)[zoneList removeLastObject];
  171.     }
  172. }
  173.  
  174. + (void)reuseZone:(NSZone *)aZone
  175. {
  176.     if (!zoneList) zoneList = [List new];
  177.     [zoneList addObject:(id)aZone];
  178.     NSSetZoneName(aZone, @"Unused");
  179. }
  180.  
  181. + (id)allocWithZone:(NSZone *)aZone
  182. {
  183.     [NSException raise:@"NSInvalidArgumentException" format:@"*** Method: %@ not implemented by %@", sel_getName(_cmd), [self class]];
  184.     return nil;
  185. }
  186.  
  187. + (id)alloc
  188. {
  189.     [NSException raise:@"NSInvalidArgumentException" format:@"*** Method: %@ not implemented by %@", sel_getName(_cmd), [self class]];
  190.     return nil;
  191. }
  192.  
  193. /* Handles errors encountered by NSFileManager */
  194.  
  195. + (BOOL)fileManagerShouldProceedAfterError:(NSDictionary *)info
  196. {
  197.     return NSRunAlertPanel(FILE_ERROR, [info objectForKey:@"Error"], PROCEED_AFTER_FILE_ERROR, ABORT_AFTER_FILE_ERROR, nil);
  198. }
  199.  
  200. /* Creation methods */
  201.  
  202. + new
  203. /*
  204.  * Creates a new, empty, document.
  205.  *
  206.  * Creates a PrintInfo object; creates a view whose size depends on the
  207.  * default PrintInfo created; creates a window for that view; sets self
  208.  * as the window's delegate; orders the window front; registers the window
  209.  * with the Workspace Manager.  Note that the default margins are set
  210.  * to 1/2 inch--that's more appropriate for a draw program than 1 or 1.25
  211.  * inches.
  212.  */
  213. {
  214.     NSZone *zone;
  215.     NSRect frameRect;
  216.     DrawDocument *newDocument = nil;
  217.     zone = [self newZone];
  218.     newDocument = [super allocWithZone:zone];
  219.     [newDocument init];
  220.     newDocument->printInfo = [[NSPrintInfo allocWithZone:zone] init];
  221.     [newDocument->printInfo setLeftMargin:36.0];
  222.     [newDocument->printInfo setRightMargin:36.0];
  223.     [newDocument->printInfo setTopMargin:36.0];
  224.     [newDocument->printInfo setBottomMargin:36.0];
  225.     frameRect = calcFrame(newDocument->printInfo);
  226.     newDocument->view = [[GraphicView allocWithZone:zone] initWithFrame:frameRect];
  227.     newDocument->window = [self createWindowForView:newDocument->view windowRect:NULL frameString:nil];
  228.     [newDocument->window setDelegate:newDocument];
  229.     [newDocument resetScrollers];
  230.     [newDocument setName:nil andDirectory:nil];
  231. #ifndef OBJECT_LINKS_BROKEN
  232.     [newDocument setLinkManager:[[NSDataLinkManager allocWithZone:(NSZone *)[newDocument zone]] initWithDelegate:newDocument]];
  233. #endif
  234.     [newDocument->window makeKeyAndOrderFront:newDocument];
  235.  
  236.     return newDocument;
  237. }
  238.  
  239. + newFromFile:(NSString *)file andDisplay:(BOOL)display
  240. /*
  241.  * Opens an existing document from the specified file.
  242.  */
  243. {
  244.     DrawDocument *newDocument = nil;
  245.     NSDictionary *plist = nil;
  246.     NSString *fileDirectory = nil;
  247.     NSFileManager *fileManager = [NSFileManager defaultManager];
  248.     NSString *directoryContainingDocument = nil;
  249.     BOOL isDirectory = NO;
  250.  
  251.     if ([fileManager fileExistsAtPath:file isDirectory:&isDirectory]) {
  252.         if (isDirectory) {
  253.             fileDirectory = file;
  254.             file = [file stringByAppendingPathComponent:DRAW_DOCUMENT_NAME];
  255.         } else if ([[file lastPathComponent] isEqual:DRAW_DOCUMENT_NAME]) {
  256.             fileDirectory = [file stringByDeletingLastPathComponent];
  257.             if (![[fileDirectory pathExtension] isEqual:DRAW_EXTENSION]) {
  258.                 fileDirectory = nil;
  259.             }
  260.         }
  261.         if ([self isPreOpenStepFile:file]) {
  262.             [fileManager copyPath:file
  263.                         toPath:[[[file stringByDeletingPathExtension] stringByAppendingPathComponent:@"-pre-OpenStep"] stringByAppendingPathExtension:DRAW_EXTENSION]
  264.                         handler:self];
  265.             newDocument = [self openPreOpenStepFile:file];
  266.         } else {
  267.             if ([fileManager isReadableFileAtPath:file]) {
  268.                 newDocument = [super allocWithZone:[self newZone]];
  269.                 [newDocument init];
  270.                 plist = [NSDictionary dictionaryWithContentsOfFile:file];
  271.                 newDocument->printInfo = [[NSUnarchiver unarchiveObjectWithData:[plist objectForKey:PRINTINFO_KEY]] retain];
  272.                 [Graphic updateCurrentGraphicIdentifier:[[plist objectForKey:GRAPHIC_IDENT_KEY] intValue]];
  273.                  directoryContainingDocument = fileDirectory ? fileDirectory : [file stringByDeletingLastPathComponent];
  274.                 newDocument->view = [[GraphicView allocWithZone:[newDocument zone]] initWithFrame:rectFromPropertyList([plist objectForKey:SIZE_KEY])
  275.                                                                                  fromPropertyList:[plist objectForKey:VIEW_KEY]
  276.                                                                                       inDirectory:directoryContainingDocument];
  277.                 newDocument->window = [self createWindowForView:newDocument->view windowRect:NULL frameString:[plist objectForKey:FRAME_KEY]];
  278.             }
  279.         }
  280.     }
  281.  
  282.     if (!newDocument) {
  283.         NSRunAlertPanel(OPEN_TITLE, OPEN_ERROR, nil, nil, nil, file);
  284.     } else {
  285.         [newDocument->window setDelegate:newDocument];
  286.         [newDocument resetScrollers];
  287.         newDocument->haveSavedDocument = YES;
  288.         [newDocument setName:fileDirectory ? fileDirectory : file];
  289. #ifndef OBJECT_LINKS_BROKEN
  290.         [newDocument setLinkManager:[[NSDataLinkManager allocWithZone:(NSZone *)[newDocument zone]] initWithDelegate:newDocument fromFile:fileDirectory ? fileDirectory : file]];
  291.     // 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!
  292.     if ([newDocument isDirty]) [newDocument dirty:nil];
  293. #endif
  294.         if (display) [newDocument->window makeKeyAndOrderFront:newDocument];
  295.     }
  296.  
  297.     return newDocument;
  298. }
  299.  
  300. + newFromFile:(NSString *)file
  301. {
  302.     return [self newFromFile:file andDisplay:YES];
  303. }
  304.  
  305. - (id)init
  306. {
  307.     [super init];
  308.     [self registerForServicesMenu];
  309.     return self;
  310. }
  311.  
  312. - (void)dealloc
  313. {
  314.     [self reset:self];
  315.     [printInfo release];
  316.     [linkManager release];
  317.     [name autorelease];
  318.     [directory autorelease];
  319.     [iconPathList autorelease];
  320.     [[self class] reuseZone:(NSZone *)[self zone]];
  321.     [super dealloc];
  322.  
  323. }
  324.  
  325. /* Data link methods -- see gvLinks.m and Links.rtf for more info. */
  326.  
  327. - (void)setLinkManager:(NSDataLinkManager *)aLinkManager
  328. {
  329.     linkManager = aLinkManager;
  330.     [view setLinkManager:aLinkManager]; 
  331. }
  332.  
  333. - (BOOL)showSelection:(NSSelection *)selection
  334. {
  335.     return [view showSelection:selection];
  336. }
  337.  
  338. - copyToPasteboard:(NSPasteboard *)pasteboard at:(NSSelection *)selection cheapCopyAllowed:(BOOL)flag
  339. {
  340.     return [view copyToPasteboard:pasteboard at:selection cheapCopyAllowed:flag];
  341. }
  342.  
  343. - (BOOL)pasteFromPasteboard:(NSPasteboard *)pasteboard at:(NSSelection *)selection
  344. {
  345.     return [view pasteFromPasteboard:pasteboard at:selection];
  346. }
  347.  
  348. - (BOOL)importFile:(NSString *)filename at:(NSSelection *)selection
  349. {
  350.     return [view importFile:filename at:selection];
  351. }
  352.  
  353. - (NSWindow *)windowForSelection:(NSSelection *)selection
  354. {
  355.     return window;
  356. }
  357.  
  358. - (void)dataLinkManager:linkManager didBreakLink:(NSDataLink *)aLink
  359. {
  360.     [view breakLinkAndRedrawOutlines:aLink];
  361. }
  362.  
  363. - (void)dataLinkManagerRedrawLinkOutlines:(NSDataLinkManager *)sender
  364. {
  365.     [view breakLinkAndRedrawOutlines:nil];
  366. }
  367.  
  368. - (BOOL)dataLinkManagerTracksLinksIndividually:(NSDataLinkManager *)sender
  369. {
  370.     return YES;
  371. }
  372.  
  373. - (void)dataLinkManager:(NSDataLinkManager *)sender startTrackingLink:(NSDataLink *)link
  374. {
  375.     [view startTrackingLink:link];
  376. }
  377.  
  378. - (void)dataLinkManager:(NSDataLinkManager *)sender stopTrackingLink:(NSDataLink *)link
  379. {
  380.     [view stopTrackingLink:link];
  381. }
  382.  
  383. - (void)dataLinkManagerDidEditLinks:(NSDataLinkManager *)sender
  384. {
  385.     [self dirty:self];
  386.     [view updateLinksPanel];
  387. }
  388.  
  389. - saveLink:sender
  390. {
  391.     NSSelection *selection;
  392.     NSDataLink *link;
  393.     NSArray *typesDrawExports = TypesDrawExports();
  394.  
  395.     selection = [view currentSelection];
  396.     link = [[NSDataLink alloc] initLinkedToSourceSelection:selection managedBy:linkManager supportingTypes:typesDrawExports];
  397.     [link saveLinkIn:[self filename]];
  398.     [link release];
  399.  
  400.     return self;
  401. }
  402.  
  403. /*
  404.  * Overridden from ChangeManager (Undo stuff)
  405.  */
  406.  
  407. - (void)changeWasDone
  408. {
  409.     [super changeWasDone];
  410.     [window setDocumentEdited:[self isDirty]];
  411.     [linkManager noteDocumentEdited];
  412. }
  413.  
  414. - (void)changeWasUndone
  415. {
  416.     [super changeWasUndone];
  417.     [window setDocumentEdited:[self isDirty]];
  418.     [linkManager noteDocumentEdited];
  419. }
  420.  
  421. - (void)changeWasRedone
  422. {
  423.     [super changeWasRedone];
  424.     [window setDocumentEdited:[self isDirty]];
  425.     [linkManager noteDocumentEdited];
  426. }
  427.  
  428. - (void)clean:sender
  429. {
  430.     [super clean:sender];
  431.     [window setDocumentEdited:NO]; 
  432. }
  433.  
  434. - (void)dirty:sender
  435. {
  436.     [super dirty:sender];
  437.     [window setDocumentEdited:YES];
  438.     [linkManager noteDocumentEdited];
  439. }
  440.  
  441. /* Services menu support methods. */
  442.  
  443. /* Services menu registrar */
  444.  
  445. - (void)registerForServicesMenu
  446. {
  447.     static BOOL registered = NO;
  448.     NSArray * validSendTypes = [[[NSArray alloc] initWithObjects:NSFilenamesPboardType, nil] autorelease];
  449.  
  450.     if (!registered) {
  451.     registered = YES;
  452.     [NSApp registerServicesMenuSendTypes:validSendTypes returnTypes:nil];
  453.     } 
  454. }
  455.  
  456. - (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType
  457. /*
  458.  * Services menu support.
  459.  * We are a valid requestor if the send type is filename
  460.  * and there is no return data from the request.
  461.  */
  462. {
  463.     return (haveSavedDocument && [sendType isEqual:NSFilenamesPboardType] && (!returnType || [returnType isEqual:@""])) ? self : nil;
  464. }
  465.  
  466. - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard types:(NSArray *)types;
  467. /*
  468.  * Services menu support.
  469.  * Here we are asked by the Services menu mechanism to supply
  470.  * the filename (which we said we were a valid requestor for
  471.  * in the above method).
  472.  */
  473. {
  474.     int save;
  475.  
  476.     if (haveSavedDocument) {
  477.     int typeCount = [types count];
  478.     int index = 0;
  479.     
  480.     while (index < typeCount) {
  481.         if ([[types objectAtIndex:index] isEqual:NSFilenamesPboardType]) {
  482.             break;
  483.         }else {
  484.             index++;
  485.         }
  486.     }
  487.     if (index < typeCount) {
  488.         NSString *filename = [self filename];
  489.         
  490.         if ([self isDirty]) {
  491.         save = NSRunAlertPanel(SERVICE, SAVE_FOR_SERVICE, SAVE, DONT_SAVE, nil);
  492.         if (save == NSAlertDefaultReturn) {
  493.             if ([self saveDocument]) [linkManager noteDocumentSaved];
  494.         }
  495.         }
  496.  
  497.         [pboard declareTypes:[[[NSArray alloc] initWithObjects:NSFilenamesPboardType, nil] autorelease] owner:self];
  498.         [pboard setData:[filename dataUsingEncoding:NSNonLossyASCIIStringEncoding] forType:NSFilenamesPboardType];
  499.  
  500.         return YES;
  501.     }
  502.     }
  503.  
  504.     return NO;
  505. }
  506.  
  507. /* Other methods. */
  508.  
  509. - (void)resetScrollers
  510. /*
  511.  * Checks to see if the new window size is too large.
  512.  * Called whenever the page layout (either by user action or
  513.  * by the opening or reverting of a file) is changed or
  514.  * the user resizes the window.
  515.  */
  516. {
  517.     SyncScrollView *scrollView;
  518.     NSSize contentSize;
  519.     NSRect contentRect, windowFrame;
  520.     BOOL updateRuler = NO;
  521.  
  522.     if (window) {
  523.     windowFrame = [window frame];
  524.     contentRect = [[window class] contentRectForFrameRect:windowFrame styleMask:[window styleMask]];
  525.     scrollView = [window contentView];
  526.     contentSize = contentSizeForView(view);
  527.     if ([scrollView horizontalRulerIsVisible]) {
  528.         contentSize.height += [Ruler width];
  529.         updateRuler = YES;
  530.     }
  531.     if ([scrollView verticalRulerIsVisible]) {
  532.         contentSize.width += [Ruler width];
  533.         updateRuler = YES;
  534.     }
  535.     if (contentRect.size.width >= contentSize.width || contentRect.size.height >= contentSize.height) {
  536.         contentSize.width = MIN(contentRect.size.width, contentSize.width);
  537.         contentSize.height = MIN(contentRect.size.height, contentSize.height);
  538.         [window setContentSize:(NSSize){contentSize.width, contentSize.height}];
  539.     }
  540.     if (updateRuler) [scrollView updateRuler];
  541.     } 
  542. }
  543.  
  544. - (GraphicView *)view
  545. /*
  546.  * Returns the GraphicView associated with this document.
  547.  */
  548. {
  549.     return view;
  550. }
  551.  
  552. - (NSPrintInfo *)printInfo
  553. /*
  554.  * Returns the PrintInfo object associated with this document.
  555.  */
  556. {
  557.     return printInfo;
  558. }
  559.  
  560. /* Target/Action methods */
  561.  
  562.  
  563. - (void)changeLayout:sender
  564. /*
  565.  * Puts up a PageLayout panel and allows the user to pick a different
  566.  * size paper to work on.  After she does so, the view is resized to the
  567.  * new paper size.
  568.  * Since the PrintInfo is effectively part of the document, we note that
  569.  * the document is now dirty (by performing the dirty method).
  570.  */
  571. {
  572.     NSRect frame;
  573.     float lm, rm, tm, bm;
  574.     NSSize paperSize;
  575.     NSPrintInfo *tempPrintInfo;
  576.     
  577.     tempPrintInfo = [[printInfo copy] autorelease];
  578.  
  579.     if ([[NSApp pageLayout] runModalWithPrintInfo:tempPrintInfo] == NSOKButton) {
  580.     paperSize = [printInfo paperSize];
  581.     lm = [printInfo leftMargin];
  582.     rm = [printInfo rightMargin];
  583.     tm = [printInfo topMargin];
  584.     bm = [printInfo bottomMargin];
  585.     if (lm < 0.0 || rm < 0.0 || tm < 0.0 || bm < 0.0 ||
  586.         paperSize.width - lm - rm < 0.0 || paperSize.height - tm - bm < 0.0) {
  587.         NSRunAlertPanel(nil, BAD_MARGINS, nil, nil, nil);
  588.     } else {
  589.             [printInfo release];            /* Keep the changed (new) printInfo */
  590.             printInfo = [tempPrintInfo retain];
  591.             frame = calcFrame(printInfo);
  592.             [view setFrameSize:(NSSize){ frame.size.width, frame.size.height }];
  593.             [self resetScrollers];
  594.             [view display];
  595.             [self dirty:self];
  596.     }
  597.     } 
  598. }
  599.  
  600. - (void)printDocumentWithPanels:(BOOL)panelFlag
  601. /*
  602.  * This is the "designated method" for printing.
  603.  */
  604. {
  605.     NSPrintOperation *op;
  606.     
  607.     op = [NSPrintOperation printOperationWithView:[self view] printInfo:[self printInfo]];
  608.     [op setShowPanels:panelFlag];
  609.     [op runOperation]; 
  610. }
  611.  
  612. - (void)printDocument:sender
  613. /*
  614.  * Print the document with UI, etc.  The default Print command.
  615.  */
  616. {
  617.     [self printDocumentWithPanels:YES]; 
  618. }
  619.  
  620. - (void)changeGrid:sender
  621. /*
  622.  * Changes the grid by putting up a modal panel asking the user what
  623.  * she wants the grid to look like.
  624.  */
  625. {
  626.     [[NSApp gridInspector] runModalForGraphicView:view]; 
  627. }
  628.  
  629. - close:sender
  630. {
  631.     [window performClose:self];
  632.     return self;
  633. }
  634.  
  635. - (BOOL)save:(id <NSMenuItem>)invokingMenuItem
  636. /*
  637.  * Saves the file.  If this document has never been saved to disk,
  638.  * then a SavePanel is put up to ask the user what file name she
  639.  * wishes to use to save the document.
  640.  */
  641. {
  642.     if (haveSavedDocument) {
  643.         if ([self saveDocument]) {
  644.         [linkManager noteDocumentSaved];
  645.         [self clean:self];
  646.     }
  647.         return YES;
  648.     } else {
  649.         return [self saveAs:invokingMenuItem];
  650.     }
  651. }
  652.  
  653. - (BOOL)saveAs:(id <NSMenuItem>)invokingMenuItem
  654. {
  655.     NSSavePanel *savepanel;
  656.  
  657.     savepanel = [NSApp saveAsPanel:invokingMenuItem];
  658.     if ([savepanel runModalForDirectory:directory file:name]) {
  659.     NSString *path = [savepanel filename];
  660.     [self setName:path];
  661.     if ([self saveDocument]) {
  662.         [linkManager noteDocumentSavedAs:path];
  663.         [self clean:self];
  664.     }
  665.     return YES;
  666.     }
  667.  
  668.     return NO;
  669. }
  670.  
  671. - (void)saveTo:(id <NSMenuItem>)invokingMenuItem
  672. /*
  673.  * This takes the document and saves it as a Draw document file, PostScript
  674.  * file, or TIFF file.  If the document type chosen is Draw document, then
  675.  * this saves the file, but DOES NOT make that file the currently edited
  676.  * file (this makes it easy to save your document elsewhere as a backup
  677.  * and keep on going in the current document).
  678.  *
  679.  * If PostScript or TIFF is selected, then the document is written out
  680.  * in the appropriate format.  In the case of PostScript and TIFF, the
  681.  * actual saving is done using the more general method saveAs:using:.
  682.  */
  683. {
  684.     NSSavePanel *savepanel = [NSApp saveToPanel:invokingMenuItem];
  685.  
  686.     if ([savepanel runModalForDirectory:directory file:[name stringByDeletingPathExtension]]) {
  687.         NSString *fileWithExtension = [savepanel filename];
  688.         NSString *fileWithoutExtension = [fileWithExtension stringByDeletingPathExtension];
  689.         if ([[savepanel requiredFileType] isEqual:@"eps"]) {
  690.             [self saveToEPSFile:fileWithoutExtension];
  691.         } else if ([[savepanel requiredFileType] isEqual:@"tiff"]) {
  692.             [self saveToTIFFFile:fileWithoutExtension];
  693.         } else {
  694.             BOOL reallyHaveSavedDocument = haveSavedDocument;
  695.             NSString *savedName = name;                /* save current name */
  696.             NSString *savedDirectory = directory;    /* save current directory */
  697.             name = nil; directory = nil;        /* clear current filename */
  698.             [self setName:fileWithExtension];        /* temporarily change name */
  699.             if ([self saveDocument]) {            /* save, then restore name */
  700.                 [linkManager noteDocumentSavedTo:[self filename]];
  701.             }
  702.             [self setName:savedName andDirectory:savedDirectory];
  703.             haveSavedDocument = reallyHaveSavedDocument;
  704.         }
  705.     }
  706. }
  707.  
  708. - (void)revertToSaved:sender
  709. /*
  710.  * Revert the document back to what is on the disk.
  711.  */ 
  712. {
  713.     NSDictionary *plist;
  714.     GraphicView *newView;
  715.     NSPrintInfo *newPrintInfo;
  716.     NSRect viewFrame, visibleRect;
  717.     NSView *oldDocView;
  718.     NSString *file;
  719.     BOOL isDirectory = NO;
  720.     
  721.     if (!haveSavedDocument
  722.     || ![self isDirty]
  723.     || (NSRunAlertPanel(REVERT_TITLE, SURE_TO_REVERT, REVERT, CANCEL, nil, name) != NSAlertDefaultReturn)) {
  724.     return;
  725.     }
  726.  
  727.     visibleRect = [view visibleRect];
  728.     [window endEditingFor:nil];
  729.  
  730.     file = [self filename];
  731.     if ([[NSFileManager defaultManager] fileExistsAtPath:file isDirectory:&isDirectory] && isDirectory) {
  732.         file = [file stringByAppendingPathComponent:DRAW_DOCUMENT_NAME];
  733.     }
  734.  
  735.     if ((plist = [NSDictionary dictionaryWithContentsOfFile:file])) {
  736.         newPrintInfo = [[NSUnarchiver unarchiveObjectWithData:[plist objectForKey:PRINTINFO_KEY]] retain];
  737.         newView = [[GraphicView allocWithZone:[self zone]] initWithFrame:rectFromPropertyList([plist objectForKey:SIZE_KEY])
  738.                                                         fromPropertyList:[plist objectForKey:VIEW_KEY]
  739.                                                              inDirectory:[file stringByDeletingLastPathComponent]];
  740.         if (newPrintInfo && newView) {
  741.             [Graphic updateCurrentGraphicIdentifier:[[plist objectForKey:GRAPHIC_IDENT_KEY] intValue]];
  742.             [self reset:self];
  743.             printInfo = newPrintInfo;
  744.             view = newView;
  745.             oldDocView = [[window contentView] documentView];
  746.             [[window contentView] setDocumentView:newView];
  747.             [oldDocView release];
  748.             viewFrame = calcFrame(printInfo);
  749.             [window disableFlushWindow];
  750.             [newView setFrameSize:(NSSize){ viewFrame.size.width, viewFrame.size.height }];
  751.             [self resetScrollers];
  752.             [newView scrollRectToVisible:visibleRect];
  753.             [newView display];
  754.             [window enableFlushWindow];
  755.             [window flushWindow];
  756.             [window makeFirstResponder:view];
  757.             [self reset:self];
  758.             [window setDocumentEdited:NO];
  759.         [view setLinkManager:linkManager];
  760.         [linkManager noteDocumentReverted];
  761.             [view updateLinksPanel];
  762.         }
  763.     }
  764. }
  765.  
  766. - (void)showTextRuler:sender
  767. /*
  768.  * Sent to cause the Text object ruler to be displayed.
  769.  * Only does anything if the rulers are already visible.
  770.  */
  771. {
  772.     SyncScrollView *scrollView = [window contentView];
  773.  
  774.     if ([scrollView verticalRulerIsVisible] && [scrollView horizontalRulerIsVisible]) {
  775.     [scrollView showHorizontalRuler:NO];
  776.     [sender toggleRuler:sender];
  777.     } 
  778. }
  779.  
  780. - (void)hideRuler:sender
  781. /*
  782.  * If sender is nil, we assume the sender wants the
  783.  * ruler hidden, otherwise, we toggle the ruler.
  784.  * If sender is the field editor itself, we do nothing
  785.  * (this allows the field editor to demand that the
  786.  * ruler stay up).
  787.  */
  788. {
  789.     SyncScrollView *scrollView = [window contentView];
  790.     NSText *fe = [window fieldEditor:NO forObject:NSApp];
  791.  
  792.     if (!sender && [scrollView verticalRulerIsVisible]) {
  793.     [fe toggleRuler:sender];
  794.     [scrollView toggleRuler:nil];
  795.     if ([scrollView verticalRulerIsVisible]) [scrollView showHorizontalRuler:YES];
  796.     [scrollView resizeSubviewsWithOldSize:NSZeroSize];
  797.     } else if (sender) {
  798.     if ([scrollView verticalRulerIsVisible]) {
  799.         [scrollView showVerticalRuler:NO];
  800.         [scrollView showHorizontalRuler:NO];
  801.         if (![fe window]) [scrollView toggleRuler:nil];
  802.     } else {
  803.         [scrollView showVerticalRuler:YES];
  804.         if ([fe window]) {
  805.         [scrollView showHorizontalRuler:NO];
  806.         } else {
  807.         [scrollView showHorizontalRuler:YES];
  808.         [scrollView toggleRuler:nil];
  809.         }
  810.     }
  811.     if ([fe superview] != nil)
  812.         [fe toggleRuler:sender];
  813.     } 
  814. }
  815.  
  816. /* Methods related to naming/saving this document. */
  817.  
  818. - (NSString *)filename
  819. /*
  820.  * Gets the fully specified file name of the document.
  821.  * If directory is NULL, then the currentDirectory is used.
  822.  * If name is NULL, then the default title is used.
  823.  * ???kb NSString - This now returns an autorelease... I tried to fix up
  824.  * all users of this, but it may kill something!
  825.  */
  826. {
  827.     NSString *returnString = @"";
  828.     if (!directory && !name) [self setName:nil andDirectory:nil];
  829.     if (name) returnString = [directory stringByAppendingPathComponent:name];
  830.     return returnString;
  831. }
  832.  
  833. - (NSString *)directory
  834. {
  835.     return directory;
  836. }
  837.  
  838. - (NSString *)name
  839. {
  840.     return name;
  841. }
  842.  
  843. - (void)setName:(NSString *)newName andDirectory:(NSString *)newDirectory
  844. /*
  845.  * Updates the name and directory of the document.
  846.  * newName or newDirectory can be nil, in which case the name or directory
  847.  * will not be changed (unless one is currently not set, in which case
  848.  * a default name will be used).
  849.  */
  850. {
  851.     static int untitledCount = 0;
  852.     
  853.     if ((newName && ![newName isEqual:@""]) || !name) {
  854.      if (!newName || [newName isEqual:@""]) {
  855.         newName = UNTITLED;
  856.         // Should probably keep count of current UNTITLED documents.  When that goes to 0, set untitledCount = 0.
  857.         if (untitledCount) {
  858.         newName = [newName stringByAppendingString:[NSString stringWithFormat:@"%d", untitledCount]];
  859.         }
  860.         untitledCount++;
  861.     }
  862.     if (![newName isEqual:name]) {
  863.         [name autorelease];
  864.         name = [newName copyWithZone:(NSZone *)[self zone]];
  865.     }
  866.     }
  867.  
  868.     if ((newDirectory && ![newDirectory isEqual:@""]) || !directory) {
  869.      if (!newDirectory || [newDirectory isEqual:@""]) {
  870.         newDirectory = [NSApp currentDirectory];
  871.     }
  872.     if (![newDirectory isEqual:directory]) {
  873.         [directory autorelease];
  874.         directory = [newDirectory copyWithZone:(NSZone *)[self zone]];
  875.     }
  876.     }
  877.  
  878.     [window setTitleWithRepresentedFilename:[self filename]];
  879.     NSSetZoneName((NSZone *)[self zone], [self filename]); 
  880. }
  881.  
  882. - (BOOL)setName:(NSString *)file
  883. /*
  884.  * If file is a full path name, then both the name and directory of the
  885.  * document is updated appropriately, otherwise, only the name is changed.
  886.  */
  887. {
  888.     if (file) {
  889.         NSString *lastComponent = [file lastPathComponent];
  890.     
  891.     if (![lastComponent isEqual:@""]) {
  892.         [self setName:lastComponent andDirectory:[file stringByDeletingLastPathComponent]];
  893.         return YES;
  894.     } else {
  895.         [self setName:file andDirectory:nil];
  896.         return YES;
  897.     }
  898.     }
  899.  
  900.     return NO;
  901. }
  902.  
  903. - (void)setTemporaryTitle:(NSString *)title
  904. {
  905.     [window setTitle:title];
  906.     haveSavedDocument = NO;
  907.     [self setName:title andDirectory:NSHomeDirectory()]; 
  908. }
  909.  
  910. - (BOOL)saveTo:(NSString *)file using:(SEL)writeMethod
  911. /*
  912.  * Performed by the saveTo: method, this method uses the writeMethod
  913.  * to have the GraphicView write itself in some foreign format (i.e., not
  914.  * in Draw archive format).  It does some work to make the default name
  915.  * of the file being saved to be the name of the document with the appropriate
  916.  * extension.  It brings up the SavePanel so the user can specify the name
  917.  * of the file to save to.
  918.  */
  919. {
  920.     NSData *data;
  921.     
  922.     if (file && writeMethod && (data = [view performSelector:writeMethod])) {
  923.         return [data writeToFile:file atomically:NO];    
  924.     }
  925.  
  926.     return NO;
  927. }
  928.  
  929. - (BOOL)saveToTIFFFile:(NSString *)file
  930. {
  931.     if (![[file pathExtension] isEqualToString:@"tiff"]) file = [file stringByAppendingPathExtension:@"tiff"];
  932.     return [self saveTo:file using:@selector(dataForTIFF)];
  933. }
  934.  
  935. - (BOOL)saveToEPSFile:(NSString *)file
  936. {
  937.     if (![[file pathExtension] isEqualToString:@"eps"]) file = [file stringByAppendingPathExtension:@"eps"];
  938.     return [self saveTo:file using:@selector(dataForEPS)];
  939. }
  940.     
  941. - (BOOL)saveDocument
  942. /*
  943.  * This just creates a property list with all the pertinent info and writes it
  944.  * out to an ASCII file.  Not super efficient, but easy to debug and examplary
  945.  * of how to use a property list to store a tree of objects.
  946.  *
  947.  * See GraphicView's propertylist method for more details on how the GraphicView
  948.  * is archived.
  949.  */
  950. {
  951.     BOOL savedOk = NO;
  952.     NSString *filename = [self filename];
  953.     NSString *backupFilename;
  954.     NSString *fileDirectory = nil;
  955.     NSFileManager *fileManager = [NSFileManager defaultManager];
  956.     NSMutableDictionary *fileContents;
  957.     BOOL alreadyHasFormEntries = NO, isDirectory = NO;
  958.  
  959.     if ([fileManager fileExistsAtPath:filename isDirectory:&isDirectory] && isDirectory) {
  960.         fileDirectory = filename;
  961.         filename = [filename stringByAppendingPathComponent:DRAW_DOCUMENT_NAME];
  962.     }
  963.  
  964.     alreadyHasFormEntries = isDirectory && [fileManager fileExistsAtPath:[fileDirectory stringByAppendingPathComponent:@"form.info"]];
  965.  
  966.     backupFilename = [[[filename stringByDeletingPathExtension] stringByAppendingString:@"~"] stringByAppendingPathExtension:DRAW_EXTENSION];
  967.     if (([fileManager fileExistsAtPath:backupFilename] && ![fileManager removeFileAtPath:backupFilename handler:[self class]]) ||
  968.         ([fileManager fileExistsAtPath:filename] && ![fileManager movePath:filename toPath:backupFilename handler:[self class]])) {
  969.         NSRunAlertPanel(SAVE_TITLE, CANT_CREATE_BACKUP, nil, nil, nil);
  970.     } else if (!isDirectory && ([view hasGraphicsWhichWriteFiles] || [view hasFormEntries]) &&
  971.                [fileManager createDirectoryAtPath:filename attributes:nil]) {
  972.         fileDirectory = filename;
  973.         filename = [filename stringByAppendingPathComponent:DRAW_DOCUMENT_NAME];
  974.     }
  975.  
  976.     if ([fileManager isWritableFileAtPath:[filename stringByDeletingLastPathComponent]]) {
  977.         [window makeFirstResponder:view];
  978.         fileContents = [NSMutableDictionary dictionaryWithCapacity:10];
  979.         [fileContents setObject:propertyListFromInt(NEW_DRAW_VERSION) forKey:VERSION_KEY];
  980.         [fileContents setObject:[NSArchiver archivedDataWithRootObject:printInfo] forKey:PRINTINFO_KEY];
  981.         [fileContents setObject:[window stringWithSavedFrame] forKey:FRAME_KEY];
  982.         [fileContents setObject:propertyListFromNSRect([view bounds]) forKey:SIZE_KEY];
  983.         [fileContents setObject:propertyListFromInt([Graphic currentGraphicIdentifier]) forKey:GRAPHIC_IDENT_KEY];
  984.         [fileContents setObject:[view propertyList] forKey:VIEW_KEY];
  985.         savedOk = [fileContents writeToFile:filename atomically:YES];
  986.         haveSavedDocument = savedOk;
  987.         if (fileDirectory) {
  988.             if ([view hasFormEntries]) {
  989.                 if (alreadyHasFormEntries || NSRunAlertPanel(SAVE_TITLE, FORM_WARNING, YES_BUTTON, NO_BUTTON, nil)) {
  990.                     [view writeFormEntriesToFile:[fileDirectory stringByAppendingPathComponent:@"form.info"]];
  991.                     [self saveTo:[fileDirectory stringByAppendingPathComponent:@"form.eps"] using:@selector(dataForEPS)];
  992.                 }
  993.             }
  994.             [view allowGraphicsToWriteFilesIntoDirectory:fileDirectory];
  995.         }
  996.     } else {
  997.         NSRunAlertPanel(SAVE_TITLE, DIR_NOT_WRITABLE, nil, nil, nil);
  998.     }
  999.  
  1000.     if (!savedOk) NSRunAlertPanel(SAVE_TITLE, CANT_SAVE, nil, nil, nil);
  1001.  
  1002.     return savedOk;
  1003. }
  1004.  
  1005. - (BOOL)isSameAs:(NSString *)filename
  1006. {
  1007.     return [[[self filename] stringByResolvingSymlinksInPath] isEqual:[filename stringByResolvingSymlinksInPath]];
  1008. }
  1009.  
  1010. /* Window delegate methods. */
  1011.  
  1012.  
  1013. - (BOOL)windowShouldClose:(id <NSMenuItem>)invokingMenuItem cancellable:(BOOL)cancellable
  1014. /*
  1015.  * If the GraphicView has been edited, then this asks the user if she
  1016.  * wants to save the changes before closing the window.
  1017.  *
  1018.  * Returning nil from this method informs the caller that the window should
  1019.  * NOT be closed.  Anything else implies it should be closed.
  1020.  */
  1021. {
  1022.     int save;
  1023.     NSString * action;
  1024.  
  1025.     action = [invokingMenuItem title];
  1026.  
  1027.     if (!action || [action isEqual:@""]) action = CLOSE;
  1028.  
  1029.     if ([self isDirty]) {
  1030.     if (cancellable) {
  1031.         save = NSRunAlertPanel(action, SAVE_CHANGES, SAVE, DONT_SAVE, CANCEL, name);
  1032.     } else {
  1033.         save = NSRunAlertPanel(action, SAVE_CHANGES, SAVE, DONT_SAVE, nil, name);
  1034.     }
  1035.     if (save != NSAlertDefaultReturn && save != NSAlertAlternateReturn) {
  1036.         return NO;
  1037.     } else {
  1038.         [window endEditingFor:self];    /* terminate any editing */
  1039.             if ((save == NSAlertDefaultReturn) && ![self save:invokingMenuItem]) return NO;
  1040.     }
  1041.     }
  1042.  
  1043.     return YES;
  1044. }
  1045.  
  1046. - (void)close
  1047. {
  1048.     [linkManager noteDocumentClosed];
  1049.     [linkManager release];
  1050.     linkManager = nil;
  1051.     [self reset:self];
  1052.     [self autorelease];
  1053. }
  1054.  
  1055. - (BOOL)windowShouldClose:(NSWindow *)sender
  1056. {
  1057.     if ([self windowShouldClose:nil cancellable:YES]) {
  1058.     [self close];
  1059.     return YES;
  1060.     } else {
  1061.     return NO;
  1062.     }
  1063. }
  1064.  
  1065. - windowDidBecomeMain:(NSWindow *)sender
  1066. /*
  1067.  * Set the cursor appropriately depending on which tool is currently selected.
  1068.  */
  1069. {
  1070.     [self performSelector:@selector(resetCursor:) withObject:[[NSNumber numberWithInt:50] retain] afterDelay:0.1];
  1071.     return self;
  1072. }
  1073.  
  1074. - (void)windowDidUpdate:(NSNotification *)notification
  1075. {
  1076.     if ([window isMainWindow]) [view updateLinksPanel];
  1077. }
  1078.  
  1079. - (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize
  1080. /*
  1081.  * Constrains the size of the window to never be larger than the
  1082.  * GraphicView inside it (including the ScrollView around it).
  1083.  */
  1084. {
  1085.     NSRect fRect, cRect;
  1086.  
  1087.     cRect.size = contentSizeForView(view);
  1088.     fRect = [[window class] frameRectForContentRect:cRect styleMask:[window styleMask]];
  1089.     if ([[window contentView] horizontalRulerIsVisible]) fRect.size.height += [Ruler width];
  1090.     if ([[window contentView] verticalRulerIsVisible]) fRect.size.width += [Ruler width];
  1091.     frameSize.width = MIN(fRect.size.width, frameSize.width);
  1092.     frameSize.height = MIN(fRect.size.height, frameSize.height);
  1093.     frameSize.width = MAX(MIN_WINDOW_WIDTH, frameSize.width);
  1094.     frameSize.height = MAX(MIN_WINDOW_HEIGHT, frameSize.height);
  1095.  
  1096.     return frameSize;
  1097. }
  1098.  
  1099. - (void)windowDidResize:(NSNotification *)sender
  1100. /*
  1101.  * Just makes sure the selection is visible after resizing.
  1102.  */
  1103. {
  1104.     [view scrollSelectionToVisible];
  1105. }
  1106.  
  1107. - windowWillMiniaturize:(NSWindow *)sender toMiniwindow:counterpart
  1108. {
  1109.     NSString *extension;
  1110.     NSString *title;
  1111.  
  1112.     title = [self name];
  1113.     extension = [title pathExtension];
  1114.     if ([extension isEqual:DRAW_EXTENSION]) {
  1115.         title = [title stringByDeletingPathExtension];
  1116.     }
  1117.     [counterpart setTitle:title];
  1118.     return self;
  1119. }
  1120.  
  1121. - windowWillReturnFieldEditor:(NSWindow *)sender toObject:client
  1122. {
  1123.     if (!drawFieldEditor) drawFieldEditor = [[DrawSpellText alloc] initWithFrame:NSZeroRect];
  1124.     return drawFieldEditor;
  1125. }
  1126.  
  1127. /* Validates whether a menu command makes sense now */
  1128.  
  1129. static NSString *showRuler;
  1130. static NSString *hideRuler;
  1131. static BOOL menuStringsInitted = NO;
  1132.  
  1133. static void initMenuItemStrings(void)
  1134. {
  1135.     showRuler = SHOW_RULER;
  1136.     hideRuler = HIDE_RULER;
  1137.     menuStringsInitted = YES;
  1138. }
  1139.  
  1140. - (BOOL)validateMenuItem:(id <NSMenuItem>)anItem
  1141. /*
  1142.  * Validates whether a menu command that DrawDocument responds to
  1143.  * is valid at the current time.
  1144.  */
  1145. {
  1146.     SEL action = [anItem action];
  1147.  
  1148.     if (!menuStringsInitted) initMenuItemStrings();
  1149.     if (action == @selector(save:)) {
  1150.     return YES;
  1151.     } else if (action == @selector(revertToSaved:)) {
  1152.     return ([self isDirty] && haveSavedDocument);
  1153.     } else if (action == @selector(saveAs:)) {
  1154.     return (haveSavedDocument || ![view isEmpty]);
  1155.     } else if (action == @selector(saveTo:)) {
  1156.     return ![view isEmpty];
  1157.     } else if (action == @selector(saveLink:)) {
  1158.     return (haveSavedDocument && ![view hasEmptySelection]);
  1159.     } else if (action == @selector(close:)) {
  1160.     return YES;
  1161.     } else if (action == @selector(hideRuler:)) {
  1162.     if ([[window contentView] eitherRulerIsVisible]) {
  1163.         [anItem setTitleWithMnemonic:hideRuler];
  1164.         [anItem setEnabled:NO];
  1165.     } else {
  1166.         [anItem setTitleWithMnemonic:showRuler];
  1167.         [anItem setEnabled:NO];
  1168.     }
  1169.     } else if (action == @selector(alignSelLeft:) ||
  1170.            action == @selector(alignSelRight:) ||
  1171.            action == @selector(alignSelCenter:) ||
  1172.            action == @selector(checkSpelling:) ||
  1173.            action == @selector(showGuessPanel:)) {
  1174.     return [[window fieldEditor:NO forObject:NSApp] superview] ? YES : NO;
  1175.     } else if (action == @selector(printDocument:)) {
  1176.     return(![view isEmpty]);
  1177.     }
  1178.  
  1179.     return [super validateMenuItem:anItem];
  1180. }
  1181.  
  1182. /* Cursor-setting method */
  1183.  
  1184. - (void)resetCursor:(NSNumber *)countdownNumber
  1185. /*
  1186.  * Sets the document's cursor according to whatever the current graphic is.
  1187.  * Makes the graphic view the first responder if there isn't one or if
  1188.  * no tool is selected (the cursor is the normal one).
  1189.  */
  1190. {
  1191.     id fr, cursor = [NSApp cursor];
  1192.     NSScrollView *scrollview = [window contentView];
  1193.  
  1194.     if ([scrollview isKindOfClass:[NSScrollView class]]) {
  1195.         [scrollview setDocumentCursor:cursor];
  1196.         fr = [window firstResponder];
  1197.         if (!fr || fr == window || cursor == [NSCursor arrowCursor]) {
  1198.             [window makeFirstResponder:view];
  1199.         }
  1200.     } else {
  1201.     int countdown = [countdownNumber intValue];
  1202.     if (countdown-- > 0) {
  1203.             [self performSelector:@selector(resetCursor:) withObject:[[NSNumber numberWithInt:countdown] retain] afterDelay:0.1];
  1204.     }
  1205.         [countdownNumber release];
  1206.     }
  1207. }
  1208.  
  1209. - (void)resetCursor
  1210. {
  1211.    [self resetCursor:0];
  1212. }
  1213.  
  1214. /* Getting the graphicView */
  1215.  
  1216. - (GraphicView *)graphicView
  1217. {
  1218.     return view;
  1219. }
  1220.  
  1221. - (NSString *)description
  1222. {
  1223.     return [(NSDictionary *)[NSDictionary dictionaryWithObjectsAndKeys:name, @"Name", directory, @"Directory", view, @"View", haveSavedDocument ? @"Yes" : @"No", @"SavedDocument", nil] description];
  1224. }
  1225.  
  1226. @end
  1227.