home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.0 / NeXTSTEP3.0.iso / NextDeveloper / Examples / AppKit / Draw / DrawApp.m < prev    next >
Text File  |  1992-07-20  |  24KB  |  927 lines

  1. #import "draw.h"
  2.  
  3. const int DrawVersion = 39;    /* minor version of the program */
  4.  
  5. @implementation DrawApp : Application
  6. /*
  7.  * This class is used primarily to handle the opening of new documents
  8.  * and other application-wide activity (such as responding to messages from
  9.  * the tool palette).  It listens for requests from the Workspace Manager
  10.  * to open a draw-format file as well as target/action messages from the
  11.  * New and Open... menu items.  It also keeps the menus in sync by
  12.  * fielding the menu items' updateActions.
  13.  */
  14.  
  15. /* Private C functions used to implement methods in this class. */
  16.  
  17. static void initMenu(Menu *menu)
  18. /*
  19.  * Sets the updateAction for every menu item which sends to the
  20.  * First Responder (i.e. their target is nil).  When autoupdate is on,
  21.  * every event will be followed by an update of each of the menu items
  22.  * which is visible.  This keep all unavailable menu items dimmed out
  23.  * so that the user knows what options are available at any given time.
  24.  * Returns the activate menu if is found in this menu.
  25.  */ 
  26. {
  27.     int count;
  28.     Matrix *matrix;
  29.     MenuCell *cell;
  30.     id matrixTarget, cellTarget;
  31.  
  32.     matrix = [menu itemList];
  33.     matrixTarget = [matrix target];
  34.  
  35.     count = [matrix cellCount];
  36.     while (count--) {
  37.     cell = [matrix cellAt:count :0];
  38.     cellTarget = [cell target];
  39.     if (!matrixTarget && !cellTarget) {
  40.         [cell setUpdateAction:@selector(menuItemUpdate:) forMenu:menu];
  41.     } else if ([cell hasSubmenu]) {
  42.         initMenu(cellTarget);
  43.     }
  44.     }
  45. }
  46.  
  47. static DrawDocument *documentInWindow(Window *window)
  48. /*
  49.  * Checks to see if the passed window's delegate is a DrawDocument.
  50.  * If it is, it returns that document, otherwise it returns nil.
  51.  */
  52. {
  53.     id document = [window delegate];
  54.     return [document isKindOf:[DrawDocument class]] ? document : nil;
  55. }
  56.  
  57. static Window *findDocument(const char *name)
  58. /*
  59.  * Searches the window list looking for a DrawDocument with the specified name.
  60.  * Returns the window containing the document if found.
  61.  * If name == NULL then the first document found is returned.
  62.  */
  63. {
  64.     int count;
  65.     DrawDocument *document;
  66.     Window *window;
  67.     List *windowList;
  68.  
  69.     windowList = [NXApp windowList];
  70.     count = [windowList count];
  71.     while (count--) {
  72.     window = [windowList objectAt:count];
  73.     document = documentInWindowRTsdow);
  74.     if ([document isSameAs:name]) return window;
  75.     }
  76.  
  77.     return nil;
  78. }
  79.  
  80. static DrawDocument *openFile(const char *directory, const char *name, BOOL display)
  81. /*
  82.  * Opens a file with the given name in the specified directory.
  83.  * If we already have that file open, it is ordered front.
  84.  * Returns the document if successful, nil otherwise.
  85.  */
  86. {
  87.     Window *window;
  88.     char buffer[MAXPATHLEN+1], path[MAXPATHLEN+1];
  89.  
  90.     if (name && *name) {
  91.     if (!directory) {
  92.         directory = ".";
  93.     } else if (*directory != '/') {
  94.         strcpy(buffer, "./");
  95.         strcat(buffer, directory);
  96.         directory = buffer;
  97.     }
  98.     if (!chdir(directory) && getwd(path)) {
  99.         strcat(path, "/");
  100.         strcat(path, name);
  101.         window = findDocument(path);
  102.         if (window) {
  103.         if (display) [window makeKeyAndOrderFront:window];
  104.         return [window delegate];
  105.         } else {
  106.         return [DrawDocument newFromFile:path andDisplay:display];
  107.         }
  108.     } else {
  109.         NXRunLocalizedAlertPanel(NULL, "Open", "Invalid path: %s", NULL, NULL, NULL, directory, "Alert shown to user when he has tried to open up a file and has specified a file in a directory which is inaccessible.  The directory is either unreadable or does not exist for some reason.");
  110.     }
  111.     }
  112.  
  113.     return nil;
  114. }
  115.  
  116. static DrawDocument *openDocument(const char *document, BOOL display)
  117. {
  118.     char *directory, *name, *ext;
  119.     char buffer[MAXPATHLEN+1];
  120.  
  121.     strcpy(buffer, document);
  122.     ext = strrchr(buffer, '.');
  123.     if (!ext || strcmp(ext, ".draw")) strcat(buffer, ".draw");
  124.     name = strrchr(buffer, '/');
  125.     if (name) {
  126.     *name++ = '\0';
  127.     directory = buffer;
  128.     } else {
  129.     name = buffer;
  130.     directory = NULL;
  131.     }
  132.  
  133.     return openFile(directory, name, display);
  134. }
  135.  
  136. /* Public methods */
  137.  
  138. + initialize
  139. /*
  140.  * Initializes the defaults.
  141.  */
  142. {
  143.     const NXDefaultsVector DrawDefaults = {
  144.     { "KnobWidth", "5" },
  145.     { "KnobHeight", "5" },
  146.     { "KeyMotionDelta", "1"},
  147.     { "Quit", NULL },
  148.     { "RemoteControl", "YES" },
  149.     { "HideCursorOnMove", NULL },
  150.     { NULL, NULL }
  151.     };
  152.  
  153.     NXRegisterDefaults("Draw", DrawDefaults);
  154.  
  155.     return self;
  156. }
  157.  
  158. + new
  159. /*
  160.  * setAutoupdate:YES means that updateWindows will be called after
  161.  * every event is processed (this is how we keep our inspector and
  162.  * our menu items up to date).
  163.  */
  164. {
  165.     self = [super new];
  166.     [self setAutoupdate:YES];
  167.     return self;
  168. }
  169.  
  170. /* General application status and inforRTton querying/modifying methods. */
  171.  
  172. - currentGraphic
  173. /*
  174.  * The current factory to use to create new Graphics.
  175.  */
  176. {
  177.     return currentGraphic;
  178. }
  179.  
  180. - (DrawDocument *)currentDocument
  181. /*
  182.  * The DrawDocument in the main window (dark gray title bar).
  183.  */
  184. {
  185.     return documentInWindow(mainWindow);
  186. }
  187.  
  188. - (const char *)currentDirectory
  189. /*
  190.  * Directory where Draw is currently "working."
  191.  */
  192. {
  193.     const char *cdir = [[self currentDocument] directory];
  194.     return (cdir && *cdir) ? cdir : (haveOpenedDocument ? [[OpenPanel new] directory] : NXHomeDirectory());
  195. }
  196.  
  197. /*
  198.  * Call these to enter/exit the TextGraphic tool.
  199.  * These are used currently by the Undo architecture.
  200.  */
  201.  
  202. - startEditMode
  203. {
  204.     [tools selectCellAt:1 :0];
  205.     [tools sendAction];
  206.     return self;
  207. }
  208.  
  209. - endEditMode
  210. {
  211.     [tools selectCellAt:0 :0];
  212.     [tools sendAction];
  213.     return self;
  214. }
  215.  
  216. /* Application-wide shared panels */
  217.  
  218. static const char *cleanTitle(const char *menuItem, char *realTitle)
  219. /*
  220.  * Just strips off trailing "..."'s.
  221.  */
  222. {
  223.     int length = menuItem ? strlen(menuItem) : 0;
  224.  
  225.     if (length > 3 && !strcmp(menuItem+length-3, "...")) {
  226.     strcpy(realTitle, menuItem);
  227.     realTitle[length-3] = '\0';
  228.     return realTitle;
  229.     } else if (length > 1 && (menuItem[length-1] == '\274')) {
  230.     strcpy(realTitle, menuItem);
  231.     realTitle[length-1] = '\0';
  232.     return realTitle;
  233.     } else {
  234.     return menuItem;
  235.     }
  236. }
  237.  
  238. - (SavePanel *)saveToPanel:sender
  239. /*
  240.  * Returns a SavePanel with the accessory view which allows the user to
  241.  * pick which type of file she wants to save.  The title of the Panel is
  242.  * set to whatever was on the menu item that brought it up (it is assumed
  243.  * that sender is the menu that has the item in it that was chosen to
  244.  * cause the action which requires the SavePanel to come up).
  245.  */
  246. {
  247.     char title[100];
  248.     const char *theTitle;
  249.     SavePanel *savepanel = [SavePanel new];
  250.  
  251.     if (!savePanelAccessory) {
  252.     [self loadNibSection:"SavePanelAccessory.nib" owner:self withNames:NO fromZone:[savepanel zone]];
  253.     }
  254.     theTitle = cleanTitle([[sender selectedCell] title], title);
  255.     if (theTitle) [savepanel setTitle:theTitle];
  256.     [savepanel setAccessoryView:savePanelAccessory];
  257.     [spamatrix selectCellAt:0 :0];
  258.     [savepanel setRequiredFileType:"draw"];
  259.  
  260.     return savepanel;
  261. }
  262.  
  263. - (SavePanel *)saveAsPanel:sender
  264. /*
  265.  * Returns a regular SavePanel with "draw" asRU required file type.
  266.  */
  267. {
  268.     char title[100];
  269.     const char *theTitle;
  270.     SavePanel *savepanel = [SavePanel new];
  271.  
  272.     [savepanel setAccessoryView:nil];
  273.     theTitle = cleanTitle([[sender selectedCell] title], title);
  274.     if (theTitle) [savepanel setTitle:theTitle];
  275.     [savepanel setRequiredFileType:"draw"];
  276.  
  277.     return savepanel;
  278. }
  279.  
  280. - (GridView *)gridInspector
  281. /*
  282.  * Returns the application-wide inspector for a document's grid.
  283.  * Note that if we haven't yet loaded the GridView panel, we do it.
  284.  * The instance variable gridInspector is set in setGridInspector:
  285.  * since it is set as an outlet of the owner (self, i.e. DrawApp).
  286.  */
  287. {
  288.     if (!gridInspector) {
  289.     NXZone *zone = NXCreateChildZone([self zone], vm_page_size, vm_page_size, YES);
  290.     NXNameZone(zone, "GridView");
  291.     [self loadNibSection:"GridView.nib" owner:self withNames:NO fromZone:zone];
  292.     NXMergeZone(zone);
  293.     }
  294.     return gridInspector;
  295. }
  296.  
  297. - (Panel *)inspectorPanel
  298. /*
  299.  * Returns the application-wide inspector for Graphics.
  300.  */
  301. {
  302.     if (!inspectorPanel) {
  303.     NXZone *zone = NXCreateChildZone([self zone], vm_page_size, vm_page_size, YES);
  304.     NXNameZone(zone, "Inspector");
  305.     [self loadNibSection:"InspectorPanel.nib" owner:self withNames:NO fromZone:zone];
  306.     [inspectorPanel setFrameAutosaveName:"Inspector"];
  307.     [inspectorPanel setBecomeKeyOnlyIfNeeded:YES];
  308.     [[inspectorPanel delegate] preset];
  309.     NXMergeZone(zone);
  310.     }
  311.     return inspectorPanel;
  312. }
  313.  
  314. - (DrawPageLayout *)pageLayout
  315. /*
  316.  * Returns the application-wide DrawPageLayout panel.
  317.  */
  318. {
  319.     static DrawPageLayout *dpl = nil;
  320.  
  321.     if (!dpl) {
  322.     dpl = [DrawPageLayout new];
  323.     [self loadNibSection:"PageLayoutAccessory.nib" owner:dpl withNames:NO fromZone:[dpl zone]];
  324.     }
  325.  
  326.     return dpl;
  327. }
  328.  
  329. - orderFrontInspectorPanel:sender
  330. /*
  331.  * Creates the inspector panel if it doesn't exist, then orders it front.
  332.  */
  333. {
  334.     [[self inspectorPanel] orderFront:self];
  335.     return self;
  336. }
  337.  
  338. /* Setting up the Fax Cover Sheet menu */
  339.  
  340. #define FAX_NOTE NXLocalString("Notes", NULL, "Fax Cover Sheet item which allows the user to type in any thing he/she wants.")
  341.  
  342. - setFaxCoverSheetMenu:anObject
  343. /*
  344.  * This goes through all the entries in CoverSheet.strings and makes
  345.  * an entry in the cover sheet menu for them.  This is kind of a kooky
  346.  * method, but it makes Draw much more usable as a Fax Cover Sheet
  347.  * editor.
  348.  */
  349. {
  350.     Menu *fcsMenuRU  MenuCell *cell;
  351.     const char *key, *value;
  352.     NXStringTable *table;
  353.     char path[MAXPATHLEN+1];
  354.  
  355.     if (fcsMenu = [anObject target]) {
  356.     if ([[NXBundle mainBundle] getPath:path forResource:"CoverSheet" ofType:"strings"]) {
  357.         if (table = [[[NXStringTable allocFromZone:[self zone]] init] readFromFile:path]) {
  358.         NXHashState state = [table initState];
  359.         while ([table nextState:&state key:(void **)&key value:(void **)&value]) {
  360.             cell = [fcsMenu addItem:value action:@selector(addLocalizableCoverSheetEntry:) keyEquivalent:0];
  361.             [cell setAltTitle:key];
  362.         }
  363.         [table free];
  364.         }
  365.     }
  366.     [fcsMenu addItem:FAX_NOTE action:@selector(addCoverSheetEntry:) keyEquivalent:0];
  367.     }
  368.  
  369.     return self;
  370. }
  371.  
  372. /* Target/Action methods */
  373.  
  374. - info:sender
  375. /*
  376.  * Brings up the information panel.
  377.  */
  378. {
  379.     char buf[20];
  380.  
  381.     if (!infoPanel) {
  382.     NXZone *zone = NXCreateChildZone([self zone], vm_page_size, vm_page_size, YES);
  383.     NXNameZone(zone, "InfoPanel");
  384.     [self loadNibSection:"InfoPanel.nib" owner:self withNames:NO fromZone:zone];
  385.     [infoPanel setFrameAutosaveName:"InfoPanel"];
  386.     sprintf(buf, "(v%2d)", DrawVersion);
  387.     [version setStringValue:buf];
  388.     NXMergeZone(zone);
  389.     }
  390.  
  391.     [infoPanel orderFront:self];
  392.  
  393.     return self;
  394. }
  395.  
  396. #define NO_HELP NXLocalString("Couldn't find any help!  Sorry ...", NULL, "Message given to the user when the help document could not be found.")
  397.  
  398. - help:sender
  399. /*
  400.  * Loads up the Help draw document.
  401.  * Note the use of NXBundle so that the Help document can be localized.
  402.  */
  403. {
  404.     DrawDocument *document = nil;
  405.     char path[MAXPATHLEN+1];
  406.  
  407.     if ([[NXBundle mainBundle] getPath:path forResource:"Help" ofType:"draw"]) {
  408.     if (document = openDocument(path, NO)) {
  409.         [document setTemporaryTitle:NXLocalString("Help", NULL, "The title of the help document.")];
  410.         [[[document view] window] makeKeyAndOrderFront:self];
  411.     }
  412.     }
  413.  
  414.     if (!document) NXRunAlertPanel(NULL, NO_HELP, NULL, NULL, NULL);
  415.  
  416.     return self;
  417. }
  418.  
  419. - new:sender
  420. /*
  421.  * Creates a new document--called by pressing New in the Document menu.
  422.  */
  423. {
  424.     [DrawDocument new];
  425.     return self;
  426. }
  427.  
  428. - open:sender
  429. /*
  430.  * Called by pressing Open... in the Window menu.
  431.  */
  432. {
  433.     const char *directory;
  434.     const char *const *files;
  435.     const char *const drawFileTypes[2] = { "draw", NULL };
  436.     OpenPanel *openpanel = [[OpenPanel new] allowMultipleFiles:YES];
  437.  
  438.    RUectory = [self currentDirectory];
  439.     if (directory && (*directory == '/')) [openpanel setDirectory:directory];
  440.     if ([openpanel runModalForTypes:drawFileTypes]) {
  441.     files = [openpanel filenames];
  442.     directory = [openpanel directory];
  443.     while (files && *files) {
  444.         if (*files) haveOpenedDocument = openFile(directory, *files, YES) || haveOpenedDocument;
  445.         files++;
  446.     }
  447.     }
  448.  
  449.     return self;
  450. }
  451.  
  452. - saveAll:sender
  453. /*
  454.  * Saves all the documents.
  455.  */
  456. {
  457.     int count;
  458.     Window *window;
  459.  
  460.     count = [windowList count];
  461.     while (count--) {
  462.     window = [windowList objectAt:count];
  463.     [documentInWindow(window) save:sender];
  464.     }
  465.  
  466.     return nil;
  467. }
  468.  
  469. #define UNSAVED_DOCUMENTS NXLocalString("You have unsaved documents.", NULL, "Message given to user when he tries to quit the application without saving all of his documents.")
  470. #define REVIEW_UNSAVED NXLocalString("Review Unsaved", NULL, "Choice (on a button) given to user which allows him to review all his unsaved documents if he quits the application without saving them all first.")
  471. #define QUIT_ANYWAY NXLocalString("Quit Anyway", NULL, "Choice (on a button) given to user which allows him to quit the application even though he has not saved all of his documents first.")
  472. #define QUIT NXLocalString("Quit", NULL, "The operation of exiting the application.")
  473.  
  474. - terminate:sender cancellable:(BOOL)cancellable
  475. /*
  476.  * Makes sure all documents get an opportunity
  477.  * to be saved before exiting the program.  If we are terminating because
  478.  * the user logged out of the workspace (or powered off), then we cannot
  479.  * give the user the option of cancelling the quit (that's what the
  480.  * cancellable flag is for).
  481.  */
  482. {
  483.     int count, choice;
  484.     Window *window;
  485.     id document;
  486.  
  487.     count = [windowList count];
  488.     while (count--) {
  489.     window = [windowList objectAt:count];
  490.      document = [window delegate];
  491.     if ([document respondsTo:@selector(isDirty)] && [document isDirty]) {
  492.         if (cancellable) {
  493.         choice = NXRunAlertPanel(QUIT, UNSAVED_DOCUMENTS, REVIEW_UNSAVED, QUIT_ANYWAY, CANCEL);
  494.         } else {
  495.         choice = NXRunAlertPanel(QUIT, UNSAVED_DOCUMENTS, REVIEW_UNSAVED, QUIT_ANYWAY, NULL);
  496.         }
  497.         if (choice == NX_ALERTOTHER)  {
  498.         return self;
  499.         } else if (choice == NX_ALERTDEFAULT) {
  500.         count = [windowList count];
  501.         while (count--) {
  502.             window = [windowList objectAt:count];
  503.             documenRU[window delegate];
  504.             if ([document respondsTo:@selector(windowWillClose:cancellable:)]) {
  505.             if (![document windowWillClose:sender cancellable:cancellable] && cancellable) {
  506.                 return self;
  507.             } else {
  508.                 [document close];
  509.                 [window close];
  510.             }
  511.             }
  512.         }
  513.         }
  514.         break;
  515.     }
  516.     }
  517.  
  518.     return nil;
  519. }
  520.  
  521. - terminate:sender
  522. /*
  523.  * Overridden to give user the opportunity to save unsaved files.
  524.  */
  525. {
  526.     if (![self terminate:sender cancellable:YES]) {
  527.     return [super terminate:sender];
  528.     } else {
  529.     return self;
  530.     }
  531. }
  532.  
  533. /*
  534.  * Application object delegate methods.
  535.  * Since we don't have an application delegate, messages that would
  536.  * normally be sent there are sent to the Application object itself instead.
  537.  */
  538.  
  539. - appDidInit:(Application *)sender
  540. /*
  541.  * Makes the tool palette not ever become the key window.
  542.  * Check for files to open specified on the command line.
  543.  * Initialize the menus.
  544.  * If there are no open documents (and we are not being
  545.  * launched to service a Services request or otherwise
  546.  * being invoked due to interapplication communication),
  547.  * then open a blank one.
  548.  */
  549. {
  550.     int i;
  551.     Panel *toolWindow;
  552.  
  553.     toolWindow = [tools window];
  554.     [toolWindow setFrameAutosaveName:[toolWindow title]];
  555.     [toolWindow setBecomeKeyOnlyIfNeeded:YES];
  556.     [toolWindow setFloatingPanel:YES];
  557.     [toolWindow orderFront:self];
  558.  
  559.     if (NXArgc > 1) {
  560.     for (i = 1; i < NXArgc; i++) {
  561.         haveOpenedDocument = openDocument(NXArgv[i], YES) || haveOpenedDocument;
  562.     }
  563.     }
  564.  
  565.     if (!haveOpenedDocument && !NXGetDefaultValue([self appName], "NXServiceLaunch")) [self new:self];
  566.  
  567.     if (NXGetDefaultValue([self appName], "Quit")) {
  568.     [self activateSelf:YES];
  569.     NXPing();
  570.     [self terminate:nil];
  571.     }
  572.  
  573.     initMenu([NXApp mainMenu]);
  574.  
  575.     return self;
  576. }
  577.  
  578. - (int)app:sender openFile:(const char *)path type:(const char *)type
  579. /*
  580.  * This method is performed whenever a user double-clicks on an icon
  581.  * in the Workspace Manager representing a Draw program document.
  582.  */
  583. {
  584.     if (type && !strcmp(type, "draw")) {
  585.     if (openDocument(path, YES)) {
  586.         haveOpenedDocument = YES;
  587.         return YES;
  588.     }
  589.     }
  590.  
  591.     return NO;
  592. }
  593.  
  594. - (BOOL)appAcceptsAnotherFile:(Application *)sender
  595. /*
  596.  * We accept any number of appOpenFile:type: messages.
  597.  */
  598. {
  599.     return YES;
  600. }
  601.  
  602. - app:sender powerOffIn:(int)ms andSave:(int)andSave
  603. /*
  604.  * Give the user RUance to save his documents.
  605.  */
  606. {
  607.     if (andSave) [self terminate:nil cancellable:NO];
  608.     return self;
  609. }
  610.  
  611. /* Listener/Speaker remote methods */
  612.  
  613. /*
  614.  * Note that anybody can send these messages to a running version
  615.  * of Draw, so we protect the ones that can affect the current
  616.  * document by only allowing them to happen if the Draw default
  617.  * "RemoteControl" is set.
  618.  */
  619.  
  620. - (int)msgDirectory:(const char **)fullPath ok:(int *)flag
  621. {
  622.     *fullPath = [self currentDirectory];
  623.     if (*fullPath) {
  624.     *flag = YES;
  625.     } else {
  626.     *fullPath = "";
  627.     *flag = NO;
  628.     }
  629.     return 0;
  630. }
  631.  
  632. - (int)msgVersion:(const char **)aString ok:(int *)flag
  633. {
  634.     char buf[20];
  635.  
  636.     sprintf(buf, "3.0 (v%2d)", DrawVersion);
  637.     *aString = NXCopyStringBuffer(buf);
  638.     *flag = YES;
  639.  
  640.     return 0;
  641. }
  642.  
  643. - (int)msgFile:(const char **)fullPath ok:(int *)flag
  644. {
  645.     const char *file;
  646.  
  647.     file = [[self currentDocument] filename];
  648.     if (file) {
  649.     *fullPath = file;
  650.     *flag = YES;
  651.     } else {
  652.     *fullPath = "";
  653.     *flag = NO;
  654.     }
  655.  
  656.     return 0;
  657. }
  658.  
  659. - (BOOL)shouldRunPrintPanel:sender
  660. /*
  661.  * When NXApp prints, don't bring up the PrintPanel.
  662.  */
  663. {
  664.     return NO;
  665. }
  666.  
  667. - (int)msgPrint:(const char *)fullPath ok:(int *)flag
  668. {
  669.     BOOL close;
  670.     id document = nil;
  671.  
  672.     if (NXGetDefaultValue("Draw", "RemoteControl")) {
  673.     InMsgPrint = YES;
  674.     if (document = [findDocument(fullPath) delegate]) {
  675.         close = NO;
  676.     } else {
  677.         document = openDocument(fullPath, NO);
  678.         if (document) haveOpenedDocument = YES;
  679.         close = YES;
  680.     }
  681.     if (document && ![[document view] isEmpty]) {
  682.         [NXApp setPrintInfo:[document printInfo]];
  683.         [[document view] printPSCode:self];
  684.         if (close) [[[document view] window] close];
  685.         *flag = YES;
  686.     } else {
  687.         *flag = NO;
  688.     }
  689.     InMsgPrint = NO;
  690.     } else {
  691.     *flag = NO;
  692.     }
  693.  
  694.     return 0;
  695. }
  696.  
  697. - (int)msgSelection:(const char **)bytes length:(int *)len asType:(const char *)aType ok:(int *)flag
  698. {
  699.     int maxlen;
  700.     NXStream *stream;
  701.  
  702.     if (NXGetDefaultValue("Draw", "RemoteControl")) {
  703.     if (!strcmp(aType, DrawPboardType)) {
  704.         stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  705.         if ([[[self currentDocument] view] copySelectionToStream:stream]) {
  706.         NXGetMemoryBuffer(stream, (char **)bytes, len, &maxlen);
  707.         *flag = YES;
  708.         } else {
  709.         *flag = NO;
  710.         }
  711.         NXClose(stream);
  712.     } else if (!strcmp(aType, NXPostScriptPboardType)) {
  713.         stream RUOpenMemory(NULL, 0, NX_WRITEONLY);
  714.         if ([[[self currentDocument] view] copySelectionAsPSToStream:stream]) {
  715.         NXGetMemoryBuffer(stream, (char **)bytes, len, &maxlen);
  716.         *flag = YES;
  717.         } else {
  718.         *flag = NO;
  719.         }
  720.         NXClose(stream);
  721.     } else if (!strcmp(aType, NXTIFFPboardType)) {
  722.         stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  723.         if ([[[self currentDocument] view] copySelectionAsTIFFToStream:stream]) {
  724.         NXGetMemoryBuffer(stream, (char **)bytes, len, &maxlen);
  725.         *flag = YES;
  726.         } else {
  727.         *flag = NO;
  728.         }
  729.         NXClose(stream);
  730.     } else {
  731.         *flag = NO;
  732.     }
  733.     } else {
  734.     *flag = NO;
  735.     }
  736.     
  737.     if (!*flag) {
  738.     *bytes = "";
  739.     *len = 0;
  740.     }
  741.  
  742.     return 0;
  743. }
  744.  
  745. - (int)msgCopyAsType:(const char *)aType ok:(int *)flag
  746. {
  747.     if (NXGetDefaultValue("Draw", "RemoteControl")) {
  748.     if (!strcmp(aType, NXPostScriptPboardType) ||
  749.         !strcmp(aType, NXTIFFPboardType) ||
  750.         !strcmp(aType, DrawPboardType)) {
  751.         *flag = ([[[self currentDocument] view] copy:self] ? YES : NO);
  752.     } else {
  753.         *flag = NO;
  754.     }
  755.     } else {
  756.     *flag = NO;
  757.     }
  758.     return 0;
  759. }
  760.  
  761. - (int)msgCutAsType:(const char *)aType ok:(int *)flag
  762. {
  763.     if (NXGetDefaultValue("Draw", "RemoteControl")) {
  764.     if (!strcmp(aType, NXPostScriptPboardType) ||
  765.         !strcmp(aType, NXTIFFPboardType) ||
  766.         !strcmp(aType, DrawPboardType)) {
  767.         *flag = ([[[self currentDocument] view] cut:self] ? YES : NO);
  768.     } else {
  769.         *flag = NO;
  770.     }
  771.     } else {
  772.     *flag = NO;
  773.     }
  774.     return 0;
  775. }
  776.  
  777. - (int)msgPaste:(int *)flag;
  778. {
  779.     if (NXGetDefaultValue("Draw", "RemoteControl")) {
  780.     *flag = ([[[self currentDocument] view] paste:self] ? YES : NO);
  781.     } else {
  782.     *flag = NO;
  783.     }
  784.     return 0;
  785. }
  786.  
  787. - (int)msgQuit:(int *)flag
  788. {
  789.     if (NXGetDefaultValue("Draw", "RemoteControl")) {
  790.     *flag = ([self terminate:self cancellable:YES] ? NO : YES);
  791.     if (*flag) [NXApp perform:@selector(terminate:) with:nil afterDelay:1 cancelPrevious:YES];
  792.     } else {
  793.     *flag = NO;
  794.     }
  795.     return 0;
  796. }
  797.  
  798. /* Global cursor setting */
  799.  
  800. - cursor
  801. /*
  802.  * This is called by DrawDocument objects who want to set the cursor
  803.  * depending on what the currently selected tool is (as well as on whether
  804.  * the Control key has been pressed indicating that the select tool is
  805.  * temporarily set--see sendEvent:).
  806.  */
  807. {
  808.     id theCursor = nil;
  809.     if (!cursorPushed) theCursor = [[self currentGraphic] cursor];
  810.     return theCRUr ? theCursor : NXArrow;
  811. }
  812.  
  813. - sendEvent:(NXEvent *)event
  814. /*
  815.  * We override this because we need to find out when the control key is down
  816.  * so we can set the arrow cursor so the user knows she is (temporarily) in
  817.  * select mode.
  818.  */
  819. {
  820.     if (event && event->type < NX_KITDEFINED) {    /* mouse or keyboard event */
  821.     if (event->flags & NX_CONTROLMASK) {
  822.         if (!cursorPushed && currentGraphic) {
  823.         cursorPushed = YES;
  824.         [[self currentDocument] resetCursor];
  825.         }
  826.     } else if (cursorPushed) {
  827.         cursorPushed = NO;
  828.         [[self currentDocument] resetCursor];
  829.     }
  830.     }
  831.  
  832.     return [super sendEvent:event];
  833. }
  834.  
  835. /* Automatic update methods */
  836.  
  837. - (BOOL)menuItemUpdate:(MenuCell *)menuCell
  838. /*
  839.  * Method called by all menu items which send their actions to the
  840.  * First Responder.  First, if the object which would respond were the
  841.  * action sent down the responder chain also responds to the message
  842.  * validateCommand:, then it is sent validateCommand: to determine
  843.  * whether that command is valid now, otherwise, if there is a responder
  844.  * to the message, then it is assumed that the item is valid.
  845.  * The method returns YES if the cell has changed its appearance (so that
  846.  * the caller (a Menu) knows to redraw it).
  847.  */
  848. {
  849.     SEL action;
  850.     id responder, target;
  851.     BOOL enable;
  852.  
  853.     target = [menuCell target];
  854.     enable = [menuCell isEnabled];
  855.  
  856.     if (!target) {
  857.     action = [menuCell action];
  858.     responder = [self calcTargetForAction:action];
  859.     if ([responder respondsTo:@selector(validateCommand:)]) {
  860.         enable = [responder validateCommand:menuCell];
  861.     } else {
  862.         enable = responder ? YES : NO;
  863.     }
  864.     }
  865.  
  866.     if ([menuCell isEnabled] != enable) {
  867.     [menuCell setEnabled:enable];
  868.     return YES;
  869.     } else {
  870.     return NO;
  871.     }
  872. }
  873.  
  874. - (BOOL)validateCommand:(MenuCell *)menuCell
  875. /*
  876.  * The only command DrawApp itself controls is saveAll:.
  877.  * Save All is enabled only if there are any documents open.
  878.  */
  879. {
  880.     SEL action = [menuCell action];
  881.  
  882.     if (action == @selector(saveAll:)) {
  883.     return findDocument(NULL) ? YES : NO;
  884.     }
  885.  
  886.     return YES;
  887. }
  888.  
  889. /*
  890.  * This is a very funky method and tricks of this sort are not generally
  891.  * recommended, but this hack is done so that new Graphic subclasses can
  892.  * be added to the program without changing even one line of code (except,
  893.  * of course, to implement the subclass itself).
  894.  *
  895.  * The objective-C method RU_lookUpClass() is used to find the factory object
  896.  * corresponding to the name of the icon of the cell sending the setCurrentGraphic:
  897.  * message.
  898.  *
  899.  * Again, this is not recommended procedure, but it illustrates how
  900.  * objective-C can be used to make some funky runtime dependent decisions.
  901.  */
  902.  
  903. - setCurrentGraphic:sender
  904. /*
  905.  * The sender's icon is queried.  If that name corresponds to the name
  906.  * of a class, then that class is set as the currentGraphic.  If not,
  907.  * then the select tool is put into effect.
  908.  */
  909. {
  910.     id cell;
  911.     const char *className;
  912.  
  913.     if (cell = [sender selectedCell]) {
  914.     if (className = [cell icon]) {
  915.         currentGraphic = objc_lookUpClass(className);
  916.     } else {
  917.         currentGraphic = nil;
  918.     }
  919.     if (!currentGraphic) [tools selectCellAt:0 :0];
  920.     [[self currentDocument] resetCursor];
  921.     }
  922.  
  923.     return self;
  924. }
  925.  
  926. @end
  927.