home *** CD-ROM | disk | FTP | other *** search
/ OpenStep (Enterprise) / OpenStepENTCD.toast / OEDEV / DEV.Z / DrawApp.m < prev    next >
Text File  |  1996-04-25  |  20KB  |  695 lines

  1. #import "draw.h"
  2. #import "compatibility.h"
  3.  
  4. const int DrawVersion = 50;    /* minor version of the program */
  5.  
  6. @implementation DrawApp : NSApplication
  7. /*
  8.  * This class is used primarily to handle the opening of new documents
  9.  * and other application-wide activity (such as responding to messages from
  10.  * the tool palette).  It listens for requests from the Workspace Manager
  11.  * to open a draw-format file as well as target/action messages from the
  12.  * New and Open... menu items.
  13.  */
  14.  
  15. /* Private C functions used to implement methods in this class. */
  16.  
  17. static DrawDocument *documentInWindow(NSWindow *window)
  18. /*
  19.  * Checks to see if the passed window's delegate is a DrawDocument.
  20.  * If it is, it returns that document, otherwise it returns nil.
  21.  */
  22. {
  23.     id document = [window delegate];
  24.     return [document isKindOfClass:[DrawDocument class]] ? document : nil;
  25. }
  26.  
  27. static NSWindow *findDocument(NSString *name)
  28. /*
  29.  * Searches the window list looking for a DrawDocument with the specified name.
  30.  * Returns the window containing the document if found.
  31.  * If name == NULL then the first document found is returned.
  32.  */
  33. {
  34.     int count;
  35.     DrawDocument *document;
  36.     NSWindow *window;
  37.     NSArray *windows;
  38.  
  39.     windows = [NSApp windows];
  40.     count = [windows count];
  41.     while (count--) {
  42.     window = [windows objectAtIndex:count];
  43.     document = documentInWindow(window);
  44.     if ((!name && document) || [document isSameAs:name]) return window;
  45.     }
  46.  
  47.     return nil;
  48. }
  49.  
  50. static DrawDocument *openFile(NSString *directory, NSString *name, BOOL display)
  51. /*
  52.  * Opens a file with the given name in the specified directory.
  53.  * If we already have that file open, it is ordered front.
  54.  * Returns the document if successful, nil otherwise.
  55.  */
  56. {
  57.     NSWindow *window;
  58.  
  59.     if (name && ![name isEqual:@""]) {
  60.         NSFileManager *fileMgr = [NSFileManager defaultManager];
  61.     if ([directory isEqual:@""]) directory = @".";
  62.     directory = [directory stringByStandardizingPath];
  63.     if ([fileMgr changeCurrentDirectoryPath:directory]) {
  64.         NSString *newPath = [fileMgr currentDirectoryPath];
  65.         newPath = [newPath stringByAppendingPathComponent:name];
  66.         window = findDocument(newPath);
  67.         if (window) {
  68.         if (display) [window makeKeyAndOrderFront:window];
  69.         return [window delegate];
  70.         } else {
  71.         DrawDocument *document = [DrawDocument newFromFile:newPath andDisplay:display];
  72.                 return document;
  73.         }
  74.     } else {
  75.         NSRunAlertPanel(OPEN_TITLE, INVALID_PATH, nil, nil, nil, directory);
  76.     }
  77.     }
  78.  
  79.     return nil;
  80. }
  81.  
  82. static DrawDocument *openDocument(NSString *document, BOOL display)
  83. /*
  84.  * Takes a full path to a document and splits it into the
  85.  * directory and document name, ensures that it has a proper
  86.  * extension, checks to see if such a file exists, and, if so,
  87.  * calls openFile().
  88.  */
  89. {
  90.     NSString *directory;
  91.     NSString *name;
  92.     
  93.     if (![[document pathExtension] isEqual:@"draw"]) {
  94.         document = [document stringByAppendingPathExtension:@"draw"];
  95.     }
  96.     name = [document lastPathComponent];
  97.     if (![name isEqual:@""]) {
  98.     directory = [document stringByDeletingLastPathComponent];
  99.     } else {
  100.     name = document;
  101.     directory = nil;
  102.     }
  103.         
  104.     // Make sure the file exists before we go try to open it.
  105.     if (! [[NSFileManager defaultManager] fileExistsAtPath:document])  {
  106.     return nil;
  107.     }
  108.  
  109.     return openFile(directory, name, display);
  110. }
  111.  
  112. /* Public methods */
  113.  
  114. + (void)initialize
  115. /*
  116.  * Initializes the defaults.
  117.  */
  118. {
  119.     NSMutableDictionary *registrationDict = [NSMutableDictionary dictionary];
  120.     [registrationDict setObject:@"5" forKey:@"KnobWidth"];
  121.     [registrationDict setObject:@"5" forKey:@"KnobHeight"];
  122.     [registrationDict setObject:@"1" forKey:@"KeyMotionDelta"];
  123.     [registrationDict setObject:@"YES" forKey:@"RemoteControl"];
  124.     [registrationDict setObject:@"NO" forKey:@"EnableObjectLinks"];
  125.     [[NSUserDefaults standardUserDefaults] registerDefaults:registrationDict];
  126. }
  127.  
  128. - init
  129. {
  130.     if ((self = [super init])) {
  131.         [self setDelegate:self]; // so that we get NSApp delegation methods
  132.     }
  133.     return self;
  134. }
  135.  
  136. /* General application status and information querying/modifying methods. */
  137.  
  138. - (Class)currentGraphic
  139. /*
  140.  * The current factory to use to create new Graphics.
  141.  */
  142. {
  143.     return currentGraphic;
  144. }
  145.  
  146. - (DrawDocument *)currentDocument
  147. /*
  148.  * The DrawDocument in the main window (dark gray title bar).
  149.  */
  150. {
  151.     return documentInWindow([self mainWindow]);
  152. }
  153.  
  154. - (NSString *)currentDirectory
  155. /*
  156.  * Directory where Draw is currently "working."
  157.  */
  158. {
  159.     NSString * cdir = [[self currentDocument] directory];
  160.     return (cdir && ![cdir isEqual:@""]) ? cdir : (haveOpenedDocument ? [((NSOpenPanel *)[NSOpenPanel openPanel]) directory] : NSHomeDirectory());
  161. }
  162.  
  163. /*
  164.  * Call these to enter/exit the TextGraphic tool.
  165.  * These are used currently by the Undo architecture.
  166.  */
  167.  
  168. - (void)startEditMode
  169. {
  170.     [tools selectCellAtRow:1 column:0];
  171.     [tools sendAction]; 
  172. }
  173.  
  174. - (void)endEditMode
  175. {
  176.     [tools selectCellAtRow:0 column:0];
  177.     [tools sendAction]; 
  178. }
  179.  
  180. /* Application-wide shared panels */
  181.  
  182. static NSString *cleanTitle(NSString * menuItem)
  183. /*
  184.  * Just strips off trailing "..."'s.
  185.  */
  186. {
  187.     NSString *returnTitle = menuItem;
  188.  
  189.     if ([menuItem hasSuffix:@"..."]) {
  190.         int index = [menuItem rangeOfString:@"..." options:NSBackwardsSearch|NSAnchoredSearch].location;
  191.         returnTitle = [menuItem substringToIndex:index];
  192.     }
  193.  
  194.     return returnTitle;
  195. }
  196.  
  197. - (void)changeSaveType:(NSMatrix *)sender
  198. /*
  199.  * Called by the SavePanel accessory view whenever the user chooses
  200.  * a different type of file to save to.  setRequiredFileType: does
  201.  * not affect the SavePanel while it is running.  It only has effect
  202.  * when the user has chosen a file, and the SavePanel ensures that it
  203.  * has the correct extension by adding it if it doesn't have it already.
  204.  * This message gets here via the Responder chain from the SavePanel.
  205.  */
  206. {
  207.     NSSavePanel *saveToPanel = [self saveToPanel:nil];
  208.  
  209.     switch ([sender selectedRow]) {
  210.         case 0: [saveToPanel setRequiredFileType:DRAW_EXTENSION]; break;
  211.         case 1: [saveToPanel setRequiredFileType:@"eps"]; break;
  212.         case 2: [saveToPanel setRequiredFileType:@"tiff"]; break;
  213.     }
  214. }
  215.  
  216. - (NSSavePanel *)saveToPanel:(id <NSMenuItem>)invokingMenuItem
  217. /*
  218.  * Returns a SavePanel with the accessory view which allows the user to
  219.  * pick which type of file she wants to save.  The title of the Panel is
  220.  * set to whatever was on the menu item that brought it up (it is assumed
  221.  * invokingMenuItem cause the action which requires the SavePanel to come up).
  222.  * If you want the currently-in-use saveToPanel, just pass nil to this method.
  223.  */
  224. {
  225.     static NSSavePanel *currentSaveToPanel = nil;
  226.     NSString *theTitle;
  227.  
  228.     if (invokingMenuItem || !currentSaveToPanel) {
  229.     if (!savePanelAccessory) {
  230.         [NSBundle loadNibNamed:@"SavePanelAccessory" owner:self];
  231.     }
  232.     currentSaveToPanel = [NSSavePanel savePanel];
  233.     theTitle = cleanTitle([invokingMenuItem title]);
  234.     if (theTitle) [currentSaveToPanel setTitle:theTitle];
  235.     [currentSaveToPanel setAccessoryView:savePanelAccessory];
  236.     [spamatrix selectCellAtRow:0 column:0];
  237.     [currentSaveToPanel setRequiredFileType:@"draw"];
  238.     }
  239.  
  240.     return currentSaveToPanel;
  241. }
  242.  
  243. - (NSSavePanel *)saveAsPanel:(id <NSMenuItem>)invokingMenuItem
  244. /*
  245.  * Returns a regular SavePanel with "draw" as the required file type.
  246.  */
  247. {
  248.     NSSavePanel *savepanel = [NSSavePanel savePanel];
  249.     NSString *theTitle;
  250.  
  251.     [savepanel setAccessoryView:nil];
  252.     theTitle = cleanTitle([invokingMenuItem title]);
  253.     if (theTitle) [savepanel setTitle:theTitle];
  254.     [savepanel setRequiredFileType:@"draw"];
  255.  
  256.     return savepanel;
  257. }
  258.  
  259. - (GridView *)gridInspector
  260. /*
  261.  * Returns the application-wide inspector for a document's grid.
  262.  * Note that if we haven't yet loaded the GridView panel, we do it.
  263.  * The instance variable gridInspector is set in setGridInspector:
  264.  * since it is set as an outlet of the owner (self, i.e. DrawApp).
  265.  */
  266. {
  267.     if (!gridInspector) [NSBundle loadNibNamed:@"GridView" owner:self];
  268.     return gridInspector;
  269. }
  270.  
  271. - (NSPanel *)inspectorPanel
  272. /*
  273.  * Returns the application-wide inspector for Graphics.
  274.  */
  275. {
  276.     if (!inspectorPanel) {
  277.     [NSBundle loadNibNamed:@"InspectorPanel" owner:self];
  278.     [inspectorPanel setFrameAutosaveName:@"Inspector"];
  279.     [inspectorPanel setBecomesKeyOnlyIfNeeded:YES];
  280.     [[inspectorPanel delegate] preset];
  281.     }
  282.     return inspectorPanel;
  283. }
  284.  
  285. - (DrawPageLayout *)pageLayout
  286. /*
  287.  * Returns the application-wide DrawPageLayout panel.
  288.  */
  289. {
  290.     static DrawPageLayout *dpl = nil;
  291.  
  292.     if (!dpl) {
  293.     dpl = [[DrawPageLayout pageLayout] retain];
  294.     [NSBundle loadNibNamed:@"PageLayoutAccessory" owner:dpl];
  295.     }
  296.  
  297.     return dpl;
  298. }
  299.  
  300. - (void)orderFrontInspectorPanel:sender
  301. /*
  302.  * Creates the inspector panel if it doesn't exist, then orders it front.
  303.  */
  304. {
  305.     [[self inspectorPanel] orderFront:self]; 
  306. }
  307.  
  308. /* Setting up the Fax Cover Sheet menu */
  309.  
  310. #define aLCSE @selector(addLocalizableCoverSheetEntry:)
  311.  
  312. - setFaxCoverSheetMenu:anObject
  313. /*
  314.  * This goes through all the entries in CoverSheet.strings and makes
  315.  * an entry in the cover sheet menu for them.  This is kind of a kooky
  316.  * method, but it makes Draw much more usable as a Fax Cover Sheet
  317.  * editor.
  318.  */
  319. {
  320.     NSMenu *fcsMenu;
  321.     id cell;
  322.     NSString *coverSheetStringsFileContents;
  323.     NSDictionary *table;
  324.     NSString *path;
  325.     NSEnumerator *enumerator;
  326.     NSString *key, *value;
  327.  
  328.     fcsMenu = [anObject target];
  329.     if (fcsMenu) {
  330.         path = [[NSBundle mainBundle] pathForResource:@"CoverSheet" ofType:@"strings"];
  331.         if (path) {
  332.             coverSheetStringsFileContents = [NSString stringWithContentsOfFile:path];
  333.             if (coverSheetStringsFileContents) {
  334.                 table = [coverSheetStringsFileContents propertyListFromStringsFileFormat];
  335.                 if (table) {
  336.                     enumerator = [table keyEnumerator];
  337.                     while ((key = [enumerator nextObject])) {
  338.                         value = [table objectForKey:key];
  339.                         cell = [fcsMenu addItemWithTitle:value action:aLCSE keyEquivalent:@""];
  340.                         [cell setTarget:nil];
  341.                         [cell setTag:(int)key]; // Yikes! Casting NSString * to int!
  342.                     }
  343.                 }
  344.             }
  345.         }
  346.         cell = [fcsMenu addItemWithTitle:FAX_NOTE action:@selector(addCoverSheetEntry:) keyEquivalent:@""];
  347.         [cell setTarget:nil];
  348.         [cell setTag:0];
  349.     }
  350.  
  351.     return self;
  352. }
  353.  
  354. /* Target/Action methods */
  355.  
  356. - (void)info:sender
  357. /*
  358.  * Brings up the information panel.
  359.  */
  360. {
  361.     if (!infoPanel) {
  362.     [NSBundle loadNibNamed:@"InfoPanel" owner:self];
  363.     [infoPanel setFrameAutosaveName:@"InfoPanel"];
  364.     [version setStringValue:[NSString stringWithFormat:@"(v%2d)", DrawVersion]];
  365.     }
  366.  
  367.     [infoPanel orderFront:self]; 
  368. }
  369.  
  370. - help:sender
  371. /*
  372.  * Loads up the Help draw document.
  373.  * Note the use of NSBundle so that the Help document can be localized.
  374.  * Should use the standard OpenStep help mechanism.
  375.  */
  376. {
  377.     DrawDocument *document = nil;
  378.     NSString *path;
  379.  
  380.     if ((path = [[NSBundle mainBundle] pathForResource:@"Help" ofType:@"draw"])) {
  381.     if ((document = openDocument(path, NO))) {
  382.         [document setTemporaryTitle:HELP];
  383.         [[[document view] window] makeKeyAndOrderFront:self];
  384.     }
  385.     }
  386.  
  387.     if (!document) NSRunAlertPanel(nil, NO_HELP, nil, nil, nil);
  388.  
  389.     return self;
  390. }
  391.  
  392. - (void)new:sender
  393. /*
  394.  * Creates a new document--called by pressing New in the Document menu.
  395.  */
  396. {
  397.     [DrawDocument new];
  398. }
  399.  
  400. - (void)open:sender
  401. /*
  402.  * Called by pressing Open... in the Window menu.
  403.  */
  404. {
  405.     int numFiles;
  406.     int index = 0;
  407.     NSString *directory;
  408.     NSArray *files;
  409.     NSArray *drawFileTypes = [[[NSArray alloc] initWithObjects:@"draw", nil] autorelease];
  410.     NSOpenPanel *openpanel = [NSOpenPanel openPanel];
  411.     
  412.     [openpanel setAllowsMultipleSelection:YES];
  413.     directory = [self currentDirectory];
  414.     if (directory) [openpanel setDirectory:directory];
  415.     if ([openpanel runModalForTypes:drawFileTypes]) {
  416.     files = [openpanel filenames];
  417.         numFiles = [files count];
  418.     while (index < numFiles) {
  419.         NSString *fullPath = [files objectAtIndex:index];
  420.         haveOpenedDocument = (openFile([fullPath stringByDeletingLastPathComponent], [fullPath lastPathComponent], YES) ? YES : NO) || haveOpenedDocument;
  421.         index++;
  422.     }
  423.     } 
  424. }
  425.  
  426. - saveAll:(id <NSMenuItem>)invokingMenuItem
  427. /*
  428.  * Saves all the documents.
  429.  */
  430. {
  431.     int count;
  432.     NSWindow *window;
  433.  
  434.     count = [[self windows] count];
  435.     while (count--) {
  436.     window = [[self windows] objectAtIndex:count];
  437.         [documentInWindow(window) save:invokingMenuItem];
  438.     }
  439.  
  440.     return nil;
  441. }
  442.  
  443. - terminate:(id <NSMenuItem>)invokingMenuItem cancellable:(BOOL)cancellable
  444. /*
  445.  * Makes sure all documents get an opportunity
  446.  * to be saved before exiting the program.  If we are terminating because
  447.  * the user logged out of the workspace (or powered off), then we cannot
  448.  * give the user the option of cancelling the quit (that's what the
  449.  * cancellable flag is for).
  450.  */
  451. {
  452.     int count, choice;
  453.     NSWindow *window;
  454.     id document;
  455.  
  456.     count = [[self windows] count];
  457.     while (count--) {
  458.     window = [[self windows] objectAtIndex:count];
  459.      document = [window delegate];
  460.     if ([document respondsToSelector:@selector(isDirty)] && [document isDirty]) {
  461.         if (cancellable) {
  462.         choice = NSRunAlertPanel(QUIT_TITLE, UNSAVED_DOCUMENTS, REVIEW_UNSAVED, QUIT_ANYWAY, CANCEL);
  463.         } else {
  464.         choice = NSRunAlertPanel(QUIT_TITLE, UNSAVED_DOCUMENTS, REVIEW_UNSAVED, QUIT_ANYWAY, nil);
  465.         }
  466.         if (choice == NSAlertOtherReturn)  {
  467.         return self;
  468.         } else if (choice == NSAlertDefaultReturn) {
  469.         count = [[self windows] count];
  470.         while (count--) {
  471.             window = [[self windows] objectAtIndex:count];
  472.             document = [window delegate];
  473.             if ([document respondsToSelector:@selector(windowShouldClose:cancellable:)]) {
  474.             if (![document windowShouldClose:invokingMenuItem cancellable:cancellable] && cancellable) {
  475.                 return self;
  476.             } else {
  477.                 [document close];
  478.                 [window close];
  479.             }
  480.             }
  481.         }
  482.         }
  483.         break;
  484.     }
  485.     }
  486.  
  487.     return nil;
  488. }
  489.  
  490. - (void)terminate:(id <NSMenuItem>)invokingMenuItem
  491. /*
  492.  * Overridden to give user the opportunity to save unsaved files.
  493.  */
  494. {
  495.     if (![self terminate:invokingMenuItem cancellable:YES]) {
  496.         [super terminate:invokingMenuItem];
  497.     }
  498. }
  499.  
  500. /*
  501.  * Application object delegate methods.
  502.  * Since we don't have an application delegate, messages that would
  503.  * normally be sent there are sent to the Application object itself instead.
  504.  */
  505.  
  506. static void deactivateKeyUIinToolWindow(NSMatrix *tools)
  507. {
  508.     NSArray *cellArray = [tools cells];
  509.     NSEnumerator *enumerator = [cellArray objectEnumerator];
  510.     NSCell *cell;
  511.     while ((cell = [enumerator nextObject])) {
  512.     [cell setRefusesFirstResponder:YES];
  513.     }
  514. }
  515.  
  516. - (void)applicationDidFinishLaunching:(NSNotification *)notification
  517. /*
  518.  * Makes the tool palette not ever become the key window.
  519.  * Check for files to open specified on the command line.
  520.  * Initialize the menus.
  521.  * If there are no open documents (and we are not being
  522.  * launched to service a Services request or otherwise
  523.  * being invoked due to interapplication communication),
  524.  * then open a blank one.
  525.  */
  526. {
  527.     NSPanel *toolWindow;
  528.     NSArray *arguments;
  529.     NSEnumerator *enumerator;
  530.     NSString *arg = nil;
  531.  
  532.     deactivateKeyUIinToolWindow(tools);
  533.     toolWindow = (NSPanel *)[tools window];
  534.     [toolWindow setFrameAutosaveName:[toolWindow title]];
  535.     [toolWindow setBecomesKeyOnlyIfNeeded:YES];
  536.     [toolWindow setFloatingPanel:YES];
  537.     [toolWindow orderFront:self];
  538.  
  539.     if (![[NSUserDefaults standardUserDefaults] boolForKey:@"EnableObjectLinks"]) {
  540.         [editMenu removeItem:linkMenuItem]; // ObjectLinks not supported under OpenStep
  541.     }
  542.  
  543.     arguments = [[NSProcessInfo processInfo] arguments];
  544.     enumerator = [arguments objectEnumerator];
  545.     while ((arg = [enumerator nextObject])) {
  546.         if ([[NSFileManager defaultManager] fileExistsAtPath:arg]) {
  547.             haveOpenedDocument = (openDocument(arg, YES) ? YES : NO) || haveOpenedDocument;
  548.         }
  549.     }
  550.  
  551.     if (!haveOpenedDocument && ![[NSUserDefaults standardUserDefaults] objectForKey:@"NSServiceLaunch"]) {
  552.     [self new:self];
  553.     }
  554.  
  555.     if ([[NSUserDefaults standardUserDefaults] objectForKey:@"Quit"]) {
  556.     [self activateIgnoringOtherApps:YES];
  557.     PSWait();
  558.     [self terminate:nil];
  559.     }
  560. }
  561.  
  562. - (BOOL)application:(NSApplication *)sender openFile:(NSString *)path
  563. {
  564.     if (openDocument(path, YES)) {
  565.         haveOpenedDocument = YES;
  566.         return YES;
  567.     }
  568.     return NO;
  569. }
  570.  
  571. - (BOOL)application:(NSApplication *)sender printFile:(NSString *)path
  572. {
  573.     id document;
  574.  
  575.     if ((document = findDocument(path))) {
  576.         [[document delegate] printDocumentWithPanels:NO];
  577.         return YES;
  578.     } else {
  579.         if ((document = [DrawDocument newFromFile:path andDisplay:NO])) {
  580.             [document printDocumentWithPanels:NO];
  581.             return YES;
  582.         }
  583.     }
  584.  
  585.     return NO;
  586. }
  587.  
  588. - (void)workspaceWillPowerOff:(NSNotification *)notification 
  589. /*
  590.  * Give the user a chance to save his documents.
  591.  */
  592. {
  593.     [self terminate:nil cancellable:NO];
  594. }
  595.  
  596. /* Global cursor setting */
  597.  
  598. - (NSCursor *)cursor
  599. /*
  600.  * This is called by DrawDocument objects who want to set the cursor
  601.  * depending on what the currently selected tool is (as well as on whether
  602.  * the Control key has been pressed indicating that the select tool is
  603.  * temporarily set--see sendEvent:).
  604.  */
  605. {
  606.     NSCursor *theCursor = nil;
  607.     if (!cursorPushed) theCursor = [[self currentGraphic] cursor];
  608.     return theCursor ? theCursor : [NSCursor arrowCursor];
  609. }
  610.  
  611. - (void)sendEvent:(NSEvent *)event 
  612. /*
  613.  * We override this because we need to find out when the control key is down
  614.  * so we can set the arrow cursor so the user knows she is (temporarily) in
  615.  * select mode.
  616.  */
  617. {
  618.     if (event && [event type] < NSAppKitDefined) {    /* mouse or keyboard event */
  619. #ifdef WIN32
  620.          if ([event modifierFlags] & NSCommandKeyMask) { /* overload ctrl key for both key equivs and temporary select */
  621.              if (!cursorPushed && currentGraphic
  622.              && currentGraphic != [TextGraphic class] /* but we need to be able to do ctrl-b for bold */
  623.                  ) {
  624. #else
  625.         if ([event modifierFlags] & NSControlKeyMask) {
  626.             if (!cursorPushed && currentGraphic) {
  627. #endif
  628.         cursorPushed = YES;
  629.         [[self currentDocument] resetCursor];
  630.         }
  631.     } else if (cursorPushed) {
  632.         cursorPushed = NO;
  633.         [[self currentDocument] resetCursor];
  634.     }
  635.     }
  636.  
  637.     [super sendEvent:event];
  638. }
  639.  
  640. - (BOOL)validateMenuItem:(id <NSMenuItem>)anItem
  641. /*
  642.  * The only command DrawApp itself controls is saveAll:.
  643.  * Save All is enabled only if there are any documents open.
  644.  */
  645. {
  646.     if ([anItem action] == @selector(saveAll:)) {
  647.     return findDocument(nil) ? YES : NO;
  648.     }
  649.     return YES;
  650. }
  651.  
  652.  
  653. /*
  654.  * This is a very funky method and tricks of this sort are not generally
  655.  * recommended, but this hack is done so that new Graphic subclasses can
  656.  * be added to the program without changing even one line of code (except,
  657.  * of course, to implement the subclass itself).  One day it'd be nice to
  658.  * show dynamically loading a new graphic from a bundle.
  659.  *
  660.  * The objective-C runtime function NSClassFromString is used to find the factory object
  661.  * corresponding to the name of the icon of the cell sending the setCurrentGraphic:
  662.  * message.
  663.  *
  664.  * Again, this is not recommended procedure, but it illustrates how
  665.  * objective-C can be used to make some funky runtime dependent decisions.
  666.  */
  667.  
  668. - (void)setCurrentGraphic:(NSMatrix *)sender
  669. /*
  670.  * The sender's selectedCell's icon is queried.  If that name corresponds
  671.  * to the name of a class, then that class is set as the currentGraphic.
  672.  * If not, then the select tool is put into effect.
  673.  */
  674. {
  675.     id cell;
  676.     NSString *className;
  677.  
  678.     if ((cell = [sender selectedCell])) {
  679.     if ((className = [[cell image] name])) {
  680.         currentGraphic = NSClassFromString(className);
  681.     } else {
  682.         currentGraphic = nil;
  683.     }
  684.     if (!currentGraphic) [tools selectCellAtRow:0 column:0];
  685.     [[self currentDocument] resetCursor];
  686.     } 
  687. }
  688.  
  689. - (NSString *)description
  690. {
  691.     return [(NSDictionary *)[NSDictionary dictionaryWithObjectsAndKeys:[version stringValue], @"Version", cursorPushed ? @"Yes" : @"No", @"Cursor Pushed", haveOpenedDocument ? @"Yes" : @"No", @"Have Opened Document", nil] description];
  692. }
  693.  
  694. @end
  695.