home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.0 / NeXTSTEP3.0.iso / NextDeveloper / Examples / AppKit / Draw / DrawDocument.m < prev    next >
Text File  |  1992-07-22  |  44KB  |  1,417 lines

  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 lastRUgged 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 chaRU    rameString)
  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 RUremoveFile(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 wiRUxtreme 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 (!RUList || ![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 errRU 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.     }
  421.     }
  422.  
  423.     if (stream) {
  424.     if (newDocument = [self newFromStream:stream]) {
  425.         [newDocument setName:path];
  426.         [newDocument setLinkManager:[[NXDataLinkManager allocFromZone:[newDocument zone]] initWithDelegate:newDocument fromFile:path]];
  427.         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!
  428.         if (display) [newDocument->window makeKeyAndOrderFront:newDocument];
  429.     }
  430.     NXCloseMemory(stream, NX_FREEBUFFER);
  431.     return newDocument;
  432.     } else {
  433.     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.");
  434.     return nil;
  435.     }
  436. }
  437.  
  438. + newFromFile:(const char *)file
  439. {
  440.     return [self newFromFile:file andDisplay:YES];
  441. }
  442.  
  443. - init
  444. {
  445.     [super init];
  446.     [self registerForServicesMenu];
  447.     return self;
  448. }
  449.  
  450. - free
  451. {
  452.     [self reset:self];
  453.     if ([NXApp printInfo] == printInfo) [NXApp setPrintInfo:nil];
  454.     [printInfo free];
  455.     [linkManager free];
  456.     NX_FREE(name);
  457.     NX_FREE(directory);
  458.     NX_FREE(iconPathList);
  459.     [[self class] reuseZone:[self zRU];
  460.     return [super free];
  461. }
  462.  
  463. /* Data link methods -- see gvLinks.m and Links.rtf for more info. */
  464.  
  465. - setLinkManager:(NXDataLinkManager *)aLinkManager
  466. {
  467.     linkManager = aLinkManager;
  468.     [view setLinkManager:aLinkManager];
  469.     return self;
  470. }
  471.  
  472. - showSelection:(NXSelection *)selection
  473. {
  474.     return [view showSelection:selection];
  475. }
  476.  
  477. - copyToPasteboard:(Pasteboard *)pasteboard at:(NXSelection *)selection cheapCopyAllowed:(BOOL)flag
  478. {
  479.     return [view copyToPasteboard:pasteboard at:selection cheapCopyAllowed:flag];
  480. }
  481.  
  482. - pasteFromPasteboard:(Pasteboard *)pasteboard at:(NXSelection *)selection
  483. {
  484.     return [view pasteFromPasteboard:pasteboard at:selection];
  485. }
  486.  
  487. - importFile:(const char *)filename at:(NXSelection *)selection
  488. {
  489.     return [view importFile:filename at:selection];
  490. }
  491.  
  492. - windowForSelection:(NXSelection *)selection
  493. {
  494.     return window;
  495. }
  496.  
  497. - dataLinkManager:linkManager didBreakLink:(NXDataLink *)aLink
  498. {
  499.     return [view breakLinkAndRedrawOutlines:aLink];
  500. }
  501.  
  502. - dataLinkManagerRedrawLinkOutlines:(NXDataLinkManager *)sender
  503. {
  504.     return [view breakLinkAndRedrawOutlines:nil];
  505. }
  506.  
  507. - (BOOL)dataLinkManagerTracksLinksIndividually:(NXDataLinkManager *)sender
  508. {
  509.     return YES;
  510. }
  511.  
  512. - dataLinkManager:(NXDataLinkManager *)sender startTrackingLink:(NXDataLink *)link
  513. {
  514.     [view startTrackingLink:link];
  515.     return self;
  516. }
  517.  
  518. - dataLinkManager:(NXDataLinkManager *)sender stopTrackingLink:(NXDataLink *)link
  519. {
  520.     [view stopTrackingLink:link];
  521.     return self;
  522. }
  523.  
  524. - dataLinkManagerDidEditLinks:(NXDataLinkManager *)sender
  525. {
  526.     [self dirty:self];
  527.     [view updateLinksPanel];
  528.     return self;
  529. }
  530.  
  531. - saveLink:sender
  532. {
  533.     NXSelection *selection;
  534.     NXDataLink *link;
  535.  
  536.     selection = [view currentSelection];
  537.     link = [[NXDataLink alloc] initLinkedToSourceSelection:selection managedBy:linkManager supportingTypes:TypesDrawExports() count:NUM_TYPES_DRAW_EXPORTS];
  538.     [link saveLinkIn:[self filename]];
  539.     [link free];
  540.  
  541.     return self;
  542. }
  543.  
  544. /*
  545.  * Overridden from ChangeManager (Undo stuff)
  546.  */
  547.  
  548. - changeWasDone
  549. {
  550.     [super changeWasDone];
  551.     [window setDocEdited:[self isDirty]];
  552.     [linkManager documentEdited];
  553.     return self;
  554. }
  555.  
  556. - changeWasUndone
  557. {
  558.     [super changeWasUndone];
  559.     [window setDocEdited:[self isDirty]];
  560.     [linkManager documentEdited];
  561.     return self;
  562. }
  563.  
  564. - changeWasRedone
  565. {
  566.     [super changeWasRedone];
  567.     [windowRUDocEdited:[self isDirty]];
  568.     [linkManager documentEdited];
  569.     return self;
  570. }
  571.  
  572. - clean:sender
  573. {
  574.     [super clean:sender];
  575.     [window setDocEdited:NO];
  576.     return self;
  577. }
  578.  
  579. - dirty:sender
  580. {
  581.     [super dirty:sender];
  582.     [window setDocEdited:YES];
  583.     [linkManager documentEdited];
  584.     return self;
  585. }
  586.  
  587. /* Services menu support methods. */
  588.  
  589. /* Services menu registrar */
  590.  
  591. - registerForServicesMenu
  592. {
  593.     static BOOL registered = NO;
  594.     const char *validSendTypes[2];
  595.  
  596.     if (!registered) {
  597.     registered = YES;
  598.     validSendTypes[0] = NXFilenamePboardType;
  599.     validSendTypes[1] = NULL;
  600.     [NXApp registerServicesMenuSendTypes:validSendTypes andReturnTypes:NULL];
  601.     }
  602.  
  603.     return self;
  604. }
  605.  
  606. - validRequestorForSendType:(NXAtom)sendType andReturnType:(NXAtom)returnType
  607. /*
  608.  * Services menu support.
  609.  * We are a valid requestor if the send type is filename
  610.  * and there is no return data from the request.
  611.  */
  612. {
  613.     return (haveSavedDocument && sendType == NXFilenamePboardType && (!returnType || !*returnType)) ? self : nil;
  614. }
  615.  
  616. #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.")
  617. #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.")
  618.  
  619. - writeSelectionToPasteboard:pboard types:(NXAtom *)types
  620. /*
  621.  * Services menu support.
  622.  * Here we are asked by the Services menu mechanism to supply
  623.  * the filename (which we said we were a valid requestor for
  624.  * in the above method).
  625.  */
  626. {
  627.     int save;
  628.  
  629.     if (haveSavedDocument) {
  630.     while (types && *types) if (*types == NXFilenamePboardType) break; else types++;
  631.     if (types && *types) {
  632.         if ([self isDirty]) {
  633.         save = NXRunAlertPanel(SERVICE, SAVE_FOR_SERVICE, SAVE, DONT_SAVE, NULL);
  634.         if (save == NX_ALERTDEFAULT) {
  635.             if ([self save]) [linkManager documentSaved];
  636.         }
  637.         }
  638.         [pboard declareTypes:&NXFilenamePboardType num:1 owner:self];
  639.         [pboard writeType:RUlenamePboardType data:[self filename] length:strlen([self filename])+1];
  640.         return self;
  641.     }
  642.     }
  643.  
  644.     return nil;
  645. }
  646.  
  647. /* Other methods. */
  648.  
  649. - resetScrollers
  650. /*
  651.  * Checks to see if the new window size is too large.
  652.  * Called whenever the page layout (either by user action or
  653.  * by the opening or reverting of a file) is changed or
  654.  * the user resizes the window.
  655.  */
  656. {
  657.     SyncScrollView *scrollView;
  658.     NXSize contentSize;
  659.     NXRect contentRect, windowFrame;
  660.     BOOL updateRuler = NO;
  661.  
  662.     if (window) {
  663.     [window getFrame:&windowFrame];
  664.     [[window class] getContentRect:&contentRect
  665.               forFrameRect:&windowFrame
  666.                  style:[window style]];
  667.     scrollView = [window contentView];
  668.     getContentSizeForView(view, &contentSize);
  669.     if ([scrollView horizontalRulerIsVisible]) {
  670.         contentSize.height += [Ruler width];
  671.         updateRuler = YES;
  672.     }
  673.     if ([scrollView verticalRulerIsVisible]) {
  674.         contentSize.width += [Ruler width];
  675.         updateRuler = YES;
  676.     }
  677.     if (contentRect.size.width >= contentSize.width || contentRect.size.height >= contentSize.height) {
  678.         contentSize.width = MIN(contentRect.size.width, contentSize.width);
  679.         contentSize.height = MIN(contentRect.size.height, contentSize.height);
  680.         [window sizeWindow:contentSize.width :contentSize.height];
  681.     }
  682.     if (updateRuler) [scrollView updateRuler];
  683.     }
  684.  
  685.     return self;
  686. }
  687.  
  688. - view
  689. /*
  690.  * Returns the GraphicView associated with this document.
  691.  */
  692. {
  693.     return view;
  694. }
  695.  
  696. - printInfo
  697. /*
  698.  * Returns the PrintInfo object associated with this document.
  699.  */
  700. {
  701.     return printInfo;
  702. }
  703.  
  704. /* Target/Action methods */
  705.  
  706. #define BAD_MARGINS NXLocalString("The margins or paper size specified are invalid.", NULL, NULL)
  707.  
  708. - changeLayout:sender
  709. /*
  710.  * Puts up a PageLayout panel and allows the user to pick a different
  711.  * size paper to work on.  After she does so, the view is resized to the
  712.  * new paper size.
  713.  * Since the PrintInfo is effectively part of the document, we note that
  714.  * the document is now dirty (by performing the dirty method).
  715.  */
  716. {
  717.     NXRect frame;
  718.     float lm, rm, tm, bm;
  719.     float savedlm, savedrm, savedtm, savedbm;
  720.     const NXRect *paperRect;
  721.  
  722.     [printInfo getMarginLeft:&savedlm right:&savedrm top:&savedtm bottom:&savedbm];
  723.     if ([[NXApp pageLayout] runModal] == NX_OKTAG) {
  724.     paperRect = [printInfo paperRect];
  725.     [printInfo getMarginLeft:&lm right:&rm top:&tm bottoRUm];
  726.     if (lm < 0.0 || rm < 0.0 || tm < 0.0 || bm < 0.0 ||
  727.         paperRect->size.width - lm - rm < 0.0 || paperRect->size.height - tm - bm < 0.0) {
  728.         [printInfo setMarginLeft:savedlm right:savedrm top:savedtm bottom:savedbm];
  729.         NXRunAlertPanel(NULL, BAD_MARGINS, NULL, NULL, NULL);
  730.         return self;
  731.     }
  732.     calcFrame(printInfo, &frame);
  733.     [view sizeTo:frame.size.width :frame.size.height];
  734.     [self resetScrollers];
  735.     [view display];
  736.     [self dirty:self];
  737.     }
  738.  
  739.     return self;
  740. }
  741.  
  742. - changeGrid:sender
  743. /*
  744.  * Changes the grid by putting up a modal panel asking the user what
  745.  * she wants the grid to look like.
  746.  */
  747. {
  748.     [[NXApp gridInspector] runModalForGraphicView:view];
  749.     return self;
  750. }
  751.  
  752. - close:sender
  753. {
  754.     [window performClose:self];
  755.     return self;
  756. }
  757.  
  758. - save:sender
  759. /*
  760.  * Saves the file.  If this document has never been saved to disk,
  761.  * then a SavePanel is put up to ask the user what file name she
  762.  * wishes to use to save the document.
  763.  */
  764. {
  765.     if (haveSavedDocument) {
  766.         if ([self save]) {
  767.         [linkManager documentSaved];
  768.         [self clean:self];
  769.     }
  770.     return self;
  771.     } else {
  772.         return [self saveAs:sender];
  773.     }
  774. }
  775.  
  776. - saveAs:sender
  777. {
  778.     struct stat st;
  779.     const char *spfname;
  780.     SavePanel *savepanel;
  781.     char cwd[MAXPATHLEN+1], path[MAXPATHLEN+1];
  782.  
  783.     savepanel = [NXApp saveAsPanel:sender];
  784.     if ([savepanel runModalForDirectory:directory file:name]) {
  785.     getwd(cwd);
  786.     spfname = [savepanel filename];
  787.     if (stat(spfname, &st) && errno == ENOENT) mkdir(spfname, 0755);
  788.     if (chdir(spfname) || !getwd(path)) strcpy(path, spfname);
  789.     chdir(cwd);
  790.     [self setName:path];
  791.     if ([self save]) {
  792.         [linkManager documentSavedAs:path];
  793.         [self clean:self];
  794.     }
  795.     return self;
  796.     } 
  797.     
  798.     return nil;
  799. }
  800.  
  801. - changeSaveType:sender
  802. /*
  803.  * Called by the SavePanel accessory view whenever the user chooses
  804.  * a different type of file to save to.  The window of the sender
  805.  * is, of course, the SavePanel itself.  setRequiredFileType: does
  806.  * not affect the SavePanel while it is running.  It only has effect
  807.  * when the user has chosen a file, and the SavePanel ensures that it
  808.  * has the correct extension by adding it if it doesn't have it already.
  809.  * This message gets here via the Responder chain from the SavePanel.
  810.  */
  811. {
  812.     switch ([sender selectedRow]) {
  813.     case 0: [[sender window] setRequiredFileType:"draw"]; break;
  814.     case 1RUsender window] setRequiredFileType:"eps"]; break;
  815.     case 2: [[sender window] setRequiredFileType:"tiff"]; break;
  816.     }
  817.     return self;
  818. }
  819.  
  820. - saveTo:sender
  821. /*
  822.  * This takes the document and saves it as a Draw document file, PostScript
  823.  * file, or TIFF file.  If the document type chosen is Draw document, then
  824.  * this saves the file, but DOES NOT make that file the currently edited
  825.  * file (this makes it easy to save your document elsewhere as a backup
  826.  * and keep on going in the current document).
  827.  *
  828.  * If PostScript or TIFF is selected, then the document is written out
  829.  * in the appropriate format.  In the case of PostScript and TIFF, the
  830.  * actual saving is done using the more general method saveAs:using:.
  831.  */
  832. {
  833.     const char *file;
  834.     SavePanel *savepanel;
  835.     char *type, *savedName, *savedDirectory;
  836.     BOOL reallyHaveSavedDocument;
  837.     char buffer[MAXPATHLEN+1];
  838.  
  839.     strcpy(buffer, name);
  840.     type = strrchr(buffer, '.');
  841.     if (type) *type = '\0';
  842.     savepanel = [NXApp saveToPanel:sender];
  843.     if (![savepanel runModalForDirectory:directory file:buffer]) return self;
  844.  
  845.     file = [savepanel filename];
  846.     type = strrchr(file, '.');
  847.     if (type) {
  848.     if (!strcmp(type, ".eps")) {
  849.         [self saveTo:file using:@selector(writePSToStream:)];
  850.     } else if (!strcmp(type, ".tiff")) {
  851.         [self saveTo:file using:@selector(writeTIFFToStream:)];
  852.     } else if (!strcmp(type, ".draw")) {
  853.         reallyHaveSavedDocument = haveSavedDocument;
  854.         savedName = name;            /* save current name */
  855.         savedDirectory = directory;        /* save current directory */
  856.         name = NULL; directory = NULL;    /* clear current filename */
  857.         [self setName:file];        /* temporarily change name */
  858.         if ([self save]) {            /* save, then restore name */
  859.         [linkManager documentSavedTo:[self filename]];
  860.         }
  861.         [self setName:savedName andDirectory:savedDirectory];
  862.         haveSavedDocument = reallyHaveSavedDocument;
  863.     }
  864.     }
  865.  
  866.     return self;
  867. }
  868.  
  869. #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.")
  870. #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 diskRUt he has made changes in the interim which would be lost if he did actually revert.  The %s is the name of the document.")
  871.  
  872. - revertToSaved:sender
  873. /*
  874.  * Revert the document back to what is on the disk.
  875.  */ 
  876. {
  877.     struct stat st;
  878.     const char *file;
  879.     NXStream *stream = NULL;
  880.     NXRect viewFrame, visibleRect;
  881.     char path[MAXPATHLEN+1];
  882.  
  883.     if (!haveSavedDocument || ![self isDirty] || (NXRunAlertPanel(REVERT_TITLE, SURE_TO_REVERT, REVERT, CANCEL, NULL, name) != NX_ALERTDEFAULT)) {
  884.     return self;
  885.     }
  886.  
  887.     [view getVisibleRect:&visibleRect];
  888.     [window endEditingFor:self];
  889.  
  890.     file = [self filename];
  891.     if (!stat(file, &st)) {
  892.     if ((st.st_mode & S_IFMT) == S_IFDIR) {
  893.         strcpy(path, file);
  894.         strcat(path, "/document.draw");
  895.         stream = NXMapFile(path, NX_READONLY);
  896.     } else {
  897.         stream = NXMapFile(file, NX_READONLY);
  898.     }
  899.     }
  900.  
  901.     if (stream && [self loadDocument:stream frameSize:NULL frameString:NULL]) {
  902.     [linkManager documentReverted];
  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.     [view updateLinksPanel];
  918.     NXCloseMemory(stream, NX_FREEBUFFER);
  919.     } else {
  920.     if (stream) NXCloseMemory(stream, NX_FREEBUFFER);
  921.     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.");
  922.     }
  923.  
  924.     return self;
  925. }
  926.  
  927. - showTextRuler:sender
  928. /*
  929.  * Sent to cause the Text object ruler to be displayed.
  930.  * Only does anything if the rulers are already visible.
  931.  */
  932. {
  933.     SyncScrollView *scrollView = [window contentView];
  934.  
  935.     if ([scrollView verticalRulerIsVisible] && [scrollView horizontalRulerIsVisible]) {
  936.     [scrollView showHorizontalRuler:NO];
  937.     [sender toggleRuler:sender];
  938.     }
  939.  
  940.     return self;
  941. }
  942.  
  943. - hideRuler:sender
  944. /*
  945.  * If sender is nil, we assume the sender wants the
  946.  * ruler hidden, otheRU e, we toggle the ruler.
  947.  * If sender is the field editor itself, we do nothing
  948.  * (this allows the field editor to demand that the
  949.  * ruler stay up).
  950.  */
  951. {
  952.     SyncScrollView *scrollView = [window contentView];
  953.     Text *fe = [window getFieldEditor:NO for:NXApp];
  954.  
  955.     if (!sender && [scrollView verticalRulerIsVisible]) {
  956.     [fe toggleRuler:sender];
  957.     [window disableDisplay];
  958.     [scrollView toggleRuler:nil];
  959.     if ([scrollView verticalRulerIsVisible]) [scrollView showHorizontalRuler:YES];
  960.     [window reenableDisplay];
  961.     [scrollView resizeSubviews:(NXSize *)0];
  962.     } else if (sender) {
  963.     if ([scrollView verticalRulerIsVisible]) {
  964.         [scrollView showVerticalRuler:NO];
  965.         [scrollView showHorizontalRuler:NO];
  966.         if (![fe window]) [scrollView toggleRuler:nil];
  967.     } else {
  968.         [scrollView showVerticalRuler:YES];
  969.         if ([fe window]) {
  970.         [scrollView showHorizontalRuler:NO];
  971.         } else {
  972.         [scrollView showHorizontalRuler:YES];
  973.         [scrollView toggleRuler:nil];
  974.         }
  975.     }
  976.     if ([fe superview] != nil)
  977.         [fe toggleRuler:sender];
  978.     }
  979.  
  980.     return self;
  981. }
  982.  
  983. /* Methods related to naming/saving this document. */
  984.  
  985. - (const char *)filename
  986. /*
  987.  * Gets the fully specified file name of the document.
  988.  * If directory is NULL, then the currentDirectory is used.
  989.  * If name is NULL, then the default title is used.
  990.  */
  991. {
  992.     static char filenamebuf[MAXPATHLEN+1];
  993.  
  994.     if (!directory && !name) {
  995.     [self setName:NULL andDirectory:NULL];
  996.     }
  997.     if (directory) {
  998.     strcpy(filenamebuf, directory);
  999.     strcat(filenamebuf, "/");
  1000.     } else {
  1001.     filenamebuf[0] = '\0';
  1002.     }
  1003.     if (name) {
  1004.     strcat(filenamebuf, name);
  1005.     }
  1006.  
  1007.     return filenamebuf;
  1008. }
  1009.  
  1010. - (const char *)directory
  1011. {
  1012.     return directory;
  1013. }
  1014.  
  1015. - (const char *)name
  1016. {
  1017.     return name;
  1018. }
  1019.  
  1020. - setName:(const char *)newName andDirectory:(const char *)newDirectory
  1021. /*
  1022.  * Updates the name and directory of the document.
  1023.  * newName or newDirectory can be NULL, in which case the name or directory
  1024.  * will not be changed (unless one is currently not set, in which case
  1025.  * a default name will be used).
  1026.  */
  1027. {
  1028.     char oldName[MAXPATHLEN+1];
  1029.  
  1030.     if (directory && name) {
  1031.     strcpy(oldName, [self filename]);
  1032.     } else {
  1033.     oldName[0] = '\0';
  1034.     }
  1035.  
  1036.     if ((newName && *newName) || !name) {
  1037.      if (!newName || !*newName) newName = NXLocalString("UNTITLED", NULL, "The name of a document which the user has not yet given RU!me to.");
  1038.     NX_FREE(name);
  1039.     name = NXCopyStringBufferFromZone(newName, [self zone]);
  1040.     }
  1041.  
  1042.     if ((newDirectory && (*newDirectory == '/')) || !directory) {
  1043.      if (!newDirectory || (*newDirectory != '/')) {
  1044.         newDirectory = [NXApp currentDirectory];
  1045.     }
  1046.     NX_FREE(directory);
  1047.     directory = NXCopyStringBufferFromZone(newDirectory, [self zone]);
  1048.     }
  1049.  
  1050.     [window setTitleAsFilename:[self filename]];
  1051.     NXNameZone([self zone], [self filename]);
  1052.  
  1053.     return self;
  1054. }
  1055.  
  1056. - (BOOL)setName:(const char *)file
  1057. /*
  1058.  * If file is a full path name, then both the name and directory of the
  1059.  * document is updated appropriately, otherwise, only the name is changed.
  1060.  */
  1061. {
  1062.     char *lastComponent;
  1063.     char path[MAXPATHLEN+1];
  1064.  
  1065.     if (file) {
  1066.     strcpy(path, file);
  1067.     lastComponent = strrchr(path, '/');
  1068.     if (lastComponent) {
  1069.         *lastComponent++ = '\0';
  1070.         [self setName:lastComponent andDirectory:path];
  1071.         return YES;
  1072.     } else {
  1073.         [self setName:file andDirectory:NULL];
  1074.         return YES;
  1075.     }
  1076.     }
  1077.  
  1078.     return NO;
  1079. }
  1080.  
  1081. - setTemporaryTitle:(const char *)title
  1082. {
  1083.     [window setTitle:title];
  1084.     haveSavedDocument = NO;
  1085.     NX_FREE(directory);
  1086.     NX_FREE(name);
  1087.     directory = NXCopyStringBufferFromZone(NXHomeDirectory(), [self zone]);
  1088.     name = NXCopyStringBufferFromZone(title, [self zone]);
  1089.     return self;
  1090. }
  1091.  
  1092. - saveTo:(const char *)file using:(SEL)streamWriter
  1093. /*
  1094.  * Performed by the saveTo: method, this method uses the streamWriter method
  1095.  * to have the GraphicView write itself in some foreign format (i.e., not
  1096.  * in Draw archive format).  It does some work to make the default name
  1097.  * of the file being saved to be the name of the document with the appropriate
  1098.  * extension.  It brings up the SavePanel so the user can specify the name
  1099.  * of the file to save to.
  1100.  */
  1101. {
  1102.     NXStream *stream;
  1103.  
  1104.     if (!file || !streamWriter) return self;
  1105.  
  1106.     stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  1107.     if (stream) {
  1108.     [view perform:streamWriter with:(id)stream];
  1109.     NXSaveToFile(stream, file);
  1110.     NXCloseMemory(stream, NX_FREEBUFFER);
  1111.     }
  1112.  
  1113.     return self;
  1114. }
  1115.  
  1116. - save
  1117. /*
  1118.  * Writes out the document in three steps:
  1119.  *
  1120.  * 1. Write the printInfo object
  1121.  * 2. Write the frame of the window
  1122.  * 3. Write the GraphicView itself.
  1123.  * 4. Write form information if necessary
  1124.  *
  1125.  * See GraphicView's write: method for more details on how the GraphicView
  1126.  * is archived.
  1127.  *
  1128. RU"ote the "This is questionable" line.  It's questionable because it means
  1129.  * that Draw documents saved under a particular SYSTEM version can really only
  1130.  * be opened under the same SYSTEM version no matter which version of DRAW
  1131.  * writes the file.  In other words, if we take 2.0 Draw on a 3.0 system and
  1132.  * save a file, that file will NOT be able to be opened with the same version
  1133.  * of Draw (2.0) on a 2.0 system.  The bottom line is that archiving Application
  1134.  * Kit objects as part of the file format of your documents ties the user forever
  1135.  * not only to the version of Draw that saved the file (obviously you cannot open
  1136.  * a file saved with Draw version 3.0 with Draw version 2.0--that's okay, we'll
  1137.  * accept that restriction), but also to the version of the system (meaning a file
  1138.  * saved with Draw 2.0 may not be openable by Draw 2.0 if the save and read don't
  1139.  * occur on the same version of NeXTSTEP).  Since Draw is always "re-released"
  1140.  * (for free, no less) whenever new versions of NeXTSTEP are released, this is
  1141.  * probably okay for Draw, but probably IS NOT OKAY for 3rd party applications!
  1142.  *
  1143.  * The right way to do it is just to have the GraphicView write out its data
  1144.  * directly to the typedstream (i.e. don't call NXWriteObject on the GraphicView,
  1145.  * just call a method you invent called writeToTypedStream: in GraphicView--your
  1146.  * implementation of writeToTypedStream: in GraphicView must remember to explicitly
  1147.  * write out any instance variables of View that need to be saved and restored
  1148.  * when the document is saved and reopened).
  1149.  *
  1150.  * The writing out of the PrintInfo object is more problematic.  There is no real
  1151.  * way for you to know how to write that out yourself.  NeXT will have to be careful
  1152.  * not to change the archiving of this object in an incompatible way (backwards or
  1153.  * forwards).
  1154.  */
  1155. {
  1156.     char *s;
  1157.     struct stat st;
  1158.     int cgi, version;
  1159.     volatile BOOL savedOk = NO;
  1160.     NXTypedStream *ts = NULL;
  1161.     const char *saveFile = [self filename];
  1162.     BOOL alreadyHasFormEntries = NO;
  1163.     char buffer[MAXPATHLEN+1], cwd[MAXPATHLEN+1];
  1164.  
  1165.     if (!stat(saveFile, &st) && (st.st_mode & S_IFMT) != S_IFDIR) unlink(saveFile); // remove 1.0/2.0 files
  1166.     errno = 0;
  1167.     if (getwd(cwd) && (!chdir(saveFile) || errno == ENOENT)) {
  1168.     if (errno != ENOENT) {
  1169.         alreadyHasFormEntries = !stat("form.info", &st);
  1170.     RU#getwd(buffer);
  1171.     } else {
  1172.         strcpy(buffer, saveFile);
  1173.     }
  1174.     if (s = strrchr(buffer, '/')) {
  1175.         *s++ = '\0';
  1176.         chdir(buffer);
  1177.         s = NXCopyStringBufferFromZone(s, [self zone]);
  1178.     } else {
  1179.         s = NXCopyStringBufferFromZone(saveFile, [self zone]);
  1180.     }
  1181.     strcpy(buffer, s);
  1182.     strcat(buffer, "~");
  1183.     if ((removeFile(buffer) && errno != ENOENT) || (rename(s, buffer) && errno != ENOENT)) {
  1184.         NXRunLocalizedAlertPanel(NULL, "Save", "Can't create backup file.", NULL, NULL, NULL);
  1185.     } else if (!mkdir(s, 0755)) {
  1186.         chdir(s);
  1187.         ts = NXOpenTypedStreamForFile("document.draw", NX_WRITEONLY);
  1188.     }
  1189.     free(s);
  1190.     if (ts) {
  1191.         NX_DURING
  1192.         version = NEW_DRAW_VERSION;
  1193.         [window makeFirstResponder:view];
  1194.         NXWriteType(ts, "i", &version);
  1195.         NXWriteRootObject(ts, printInfo);
  1196.         [window saveFrameToString:(s = buffer)];
  1197.         NXWriteType(ts, "*", &s);
  1198.         cgi = [Graphic currentGraphicIdentifier];
  1199.         NXWriteType(ts, "i", &cgi);
  1200.         NXWriteRootObject(ts, view);    // This is questionable. See above.
  1201.         NXCloseTypedStream(ts);
  1202.         savedOk = YES;
  1203.         haveSavedDocument = YES;
  1204.         if ([view hasFormEntries]) {
  1205.             if (alreadyHasFormEntries || NXRunLocalizedAlertPanel(NULL, "Save", "This document has Form Entries in it.  Do you wish to save them with the document?", "Yes", "No", NULL)) {
  1206.             [view writeFormEntriesToFile:"form.info"];
  1207.             strcpy(buffer, saveFile);
  1208.             strcat(buffer, "/form.eps");
  1209.             [self saveTo:buffer using:@selector(writePSToStream:)];
  1210.             }
  1211.         }
  1212.         NX_HANDLER
  1213.         NX_ENDHANDLER
  1214.     }
  1215.     }
  1216.  
  1217.     chdir(cwd);
  1218.  
  1219.     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.");
  1220.  
  1221.     return savedOk ? self : nil;
  1222. }
  1223.  
  1224. - (BOOL)isSameAs:(const char *)filename
  1225. {
  1226.     struct stat me, other;
  1227.     if (!stat([self filename], &me) && !stat(filename, &other) &&
  1228.     me.st_dev == other.st_dev && me.st_ino == other.st_ino) return YES;
  1229.     return NO;
  1230. }
  1231.  
  1232. /* Window delegate methods. */
  1233.  
  1234. #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 contentRU$ the window have not been saved.  The %s is the name of the document.")
  1235.  
  1236. - windowWillClose:sender cancellable:(BOOL)cancellable
  1237. /*
  1238.  * If the GraphicView has been edited, then this asks the user if she
  1239.  * wants to save the changes before closing the window.
  1240.  *
  1241.  * Returning nil from this method informs the caller that the window should
  1242.  * NOT be closed.  Anything else implies it should be closed.
  1243.  */
  1244. {
  1245.     int save;
  1246.     const char *action = [[sender selectedCell] title];
  1247.  
  1248.     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.");
  1249.  
  1250.     if ([self isDirty]) {
  1251.     if (cancellable) {
  1252.         save = NXRunAlertPanel(action, SAVE_CHANGES, SAVE, DONT_SAVE, CANCEL, name);
  1253.     } else {
  1254.         save = NXRunAlertPanel(action, SAVE_CHANGES, SAVE, DONT_SAVE, NULL, name);
  1255.     }
  1256.     if (save != NX_ALERTDEFAULT && save != NX_ALERTALTERNATE) {
  1257.         return nil;
  1258.     } else {
  1259.         [window endEditingFor:self];    /* terminate any editing */
  1260.         if ((save == NX_ALERTDEFAULT) && ![self save:sender]) return nil;
  1261.     }
  1262.     }
  1263.  
  1264.     return self;
  1265. }
  1266.  
  1267. - close
  1268. {
  1269.     [linkManager documentClosed];
  1270.     [linkManager free];
  1271.     linkManager = nil;
  1272.     [self reset:self];
  1273.     [NXApp delayedFree:self];
  1274.     return self;
  1275. }
  1276.  
  1277. - windowWillClose:(Window *)sender
  1278. {
  1279.     if ([self windowWillClose:nil cancellable:YES]) {
  1280.     return [self close];
  1281.     } else {
  1282.     return nil;
  1283.     }
  1284. }
  1285.  
  1286. - windowDidBecomeMain:(Window *)sender
  1287. /*
  1288.  * Switch the Application's PrintInfo to the document's when the document
  1289.  * window becomes the main window.  Also set the cursor appropriately
  1290.  * depending on which tool is currently selected.
  1291.  */
  1292. {
  1293.     [NXApp setPrintInfo:printInfo];
  1294.     [self resetCursor];
  1295.     return self;
  1296. }
  1297.  
  1298. - windowDidUpdate:(Window *)sender
  1299. {
  1300.     if ([window isMainWindow]) [view updateLinksPanel];
  1301.     return self;
  1302. }
  1303.  
  1304. - windowWillResize:(Window *)sender toSize:(NXSize *)size
  1305. /*
  1306.  * Constrains the size of the window to never be larger than the
  1307.  * GraphicView inside it (including the ScrollView around it).
  1308.  */
  1309. {
  1310.     NXRect fRect, cRect;
  1311.  
  1312.     getContentSizeForView(view, &cRect.size);
  1313.     [[window class] getFrameRect:&fRect forContentRect:&cRect style:[window style]];
  1314.     if ([[window contentView] horizontalRulerIsVisible]) fRect.size.height += [Ruler width];
  1315.     if ([[window contentViewRU%rticalRulerIsVisible]) fRect.size.width += [Ruler width];
  1316.     size->width = MIN(fRect.size.width, size->width);
  1317.     size->height = MIN(fRect.size.height, size->height);
  1318.     size->width = MAX(MIN_WINDOW_WIDTH, size->width);
  1319.     size->height = MAX(MIN_WINDOW_HEIGHT, size->height);
  1320.  
  1321.     return self;
  1322. }
  1323.  
  1324. - windowDidResize:(Window *)sender
  1325. /*
  1326.  * Just makes sure the selection is visible after resizing.
  1327.  */
  1328. {
  1329.     [view scrollSelectionToVisible];
  1330.     return self;
  1331. }
  1332.  
  1333. - windowWillMiniaturize:(Window *)sender toMiniwindow:counterpart
  1334. {
  1335.     char *dot;
  1336.     char title[MAXPATHLEN+1];
  1337.  
  1338.     strcpy(title, [self name]);
  1339.     dot = strrchr(title, '.');
  1340.     if (dot && !strcmp(dot, ".draw")) *dot = '\0';
  1341.     [counterpart setTitle:title];
  1342.     return self;
  1343. }
  1344.  
  1345. - windowWillReturnFieldEditor:(Window *)sender toObject:client
  1346. {
  1347.     if (!undoFieldEditor) undoFieldEditor = [[UndoText alloc] initFrame:NULL];
  1348.     return undoFieldEditor;
  1349. }
  1350.  
  1351. /* Validates whether a menu command makes sense now */
  1352.  
  1353. #define HIDE_RULER NXLocalString("Hide Ruler", NULL, "Menu item which hides the ruler.")
  1354. #define SHOW_RULER NXLocalString("Show Ruler", NULL, "Menu item which unhides the ruler.")
  1355.  
  1356. - (BOOL)validateCommand:(MenuCell *)menuCell
  1357. /*
  1358.  * Validates whether a menu command that DrawDocument responds to
  1359.  * is valid at the current time.
  1360.  */
  1361. {
  1362.     SEL action = [menuCell action];
  1363.  
  1364.     if (action == @selector(save:)) {
  1365.     return YES;
  1366.     } else if (action == @selector(revertToSaved:)) {
  1367.     return ([self isDirty] && haveSavedDocument);
  1368.     } else if (action == @selector(saveAs:)) {
  1369.     return (haveSavedDocument || ![view isEmpty]);
  1370.     } else if (action == @selector(saveTo:)) {
  1371.     return ![view isEmpty];
  1372.     } else if (action == @selector(saveLink:)) {
  1373.     return (haveSavedDocument && ![view hasEmptySelection]);
  1374.     } else if (action == @selector(close:)) {
  1375.     return YES;
  1376.     } else if (action == @selector(hideRuler:)) {
  1377.     if ([[window contentView] eitherRulerIsVisible] && strcmp([menuCell title], HIDE_RULER)) {
  1378.         [menuCell setTitle:HIDE_RULER];
  1379.         [menuCell setEnabled:NO];
  1380.     } else if (![[window contentView] eitherRulerIsVisible] && strcmp([menuCell title], SHOW_RULER)) {
  1381.         [menuCell setTitle:SHOW_RULER];
  1382.         [menuCell setEnabled:NO];
  1383.     }
  1384.     } else if (action == @selector(alignSelLeft:) ||
  1385.            action == @selector(alignSelRight:) ||
  1386.            action == @selector(alignSelRU&er:) ||
  1387.            action == @selector(checkSpelling:) ||
  1388.            action == @selector(showGuessPanel:)) {
  1389.     return [[window getFieldEditor:NO for:NXApp] superview] ? YES : NO;
  1390.     }
  1391.  
  1392.     return [super validateCommand:menuCell];
  1393. }
  1394.  
  1395. /* Cursor-setting method */
  1396.  
  1397. - resetCursor
  1398. /*
  1399.  * Sets the document's cursor according to whatever the current graphic is.
  1400.  * Makes the graphic view the first responder if there isn't one or if
  1401.  * no tool is selected (the cursor is the normal one).
  1402.  */
  1403. {
  1404.     id fr, cursor = [NXApp cursor];
  1405.     ScrollView *scrollview = [window contentView];
  1406.  
  1407.     [scrollview setDocCursor:cursor];
  1408.     fr = [window firstResponder];
  1409.     if (!fr || fr == window || cursor == NXArrow) {
  1410.     [window makeFirstResponder:view];
  1411.     }
  1412.  
  1413.     return self;
  1414. }
  1415.  
  1416. @end
  1417.