|
|
1.1 root 1: #import "draw.h"
2:
3: const int DrawVersion = 48; /* 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 = documentInWindow(window);
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 information 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" as the 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 *fcsMenu;
351: MenuCell *cell;
352: const char *key, *value;
353: NXStringTable *table;
354: char path[MAXPATHLEN+1];
355:
356: if (fcsMenu = [anObject target]) {
357: if ([[NXBundle mainBundle] getPath:path forResource:"CoverSheet" ofType:"strings"]) {
358: if (table = [[[NXStringTable allocFromZone:[self zone]] init] readFromFile:path]) {
359: NXHashState state = [table initState];
360: while ([table nextState:&state key:(void **)&key value:(void **)&value]) {
361: cell = [fcsMenu addItem:value action:@selector(addLocalizableCoverSheetEntry:) keyEquivalent:0];
362: [cell setAltTitle:key];
363: }
364: [table free];
365: }
366: }
367: [fcsMenu addItem:FAX_NOTE action:@selector(addCoverSheetEntry:) keyEquivalent:0];
368: }
369:
370: return self;
371: }
372:
373: /* Target/Action methods */
374:
375: - info:sender
376: /*
377: * Brings up the information panel.
378: */
379: {
380: char buf[20];
381:
382: if (!infoPanel) {
383: NXZone *zone = NXCreateChildZone([self zone], vm_page_size, vm_page_size, YES);
384: NXNameZone(zone, "InfoPanel");
385: [self loadNibSection:"InfoPanel.nib" owner:self withNames:NO fromZone:zone];
386: [infoPanel setFrameAutosaveName:"InfoPanel"];
387: sprintf(buf, "(v%2d)", DrawVersion);
388: [version setStringValue:buf];
389: NXMergeZone(zone);
390: }
391:
392: [infoPanel orderFront:self];
393:
394: return self;
395: }
396:
397: #define NO_HELP NXLocalString("Couldn't find any help! Sorry ...", NULL, "Message given to the user when the help document could not be found.")
398:
399: - help:sender
400: /*
401: * Loads up the Help draw document.
402: * Note the use of NXBundle so that the Help document can be localized.
403: */
404: {
405: DrawDocument *document = nil;
406: char path[MAXPATHLEN+1];
407:
408: if ([[NXBundle mainBundle] getPath:path forResource:"Help" ofType:"draw"]) {
409: if (document = openDocument(path, NO)) {
410: [document setTemporaryTitle:NXLocalString("Help", NULL, "The title of the help document.")];
411: [[[document view] window] makeKeyAndOrderFront:self];
412: }
413: }
414:
415: if (!document) NXRunAlertPanel(NULL, NO_HELP, NULL, NULL, NULL);
416:
417: return self;
418: }
419:
420: - new:sender
421: /*
422: * Creates a new document--called by pressing New in the Document menu.
423: */
424: {
425: [DrawDocument new];
426: return self;
427: }
428:
429: - open:sender
430: /*
431: * Called by pressing Open... in the Window menu.
432: */
433: {
434: const char *directory;
435: const char *const *files;
436: const char *const drawFileTypes[2] = { "draw", NULL };
437: OpenPanel *openpanel = [[OpenPanel new] allowMultipleFiles:YES];
438:
439: directory = [self currentDirectory];
440: if (directory && (*directory == '/')) [openpanel setDirectory:directory];
441: if ([openpanel runModalForTypes:drawFileTypes]) {
442: files = [openpanel filenames];
443: directory = [openpanel directory];
444: while (files && *files) {
445: if (*files) haveOpenedDocument = openFile(directory, *files, YES) || haveOpenedDocument;
446: files++;
447: }
448: }
449:
450: return self;
451: }
452:
453: - saveAll:sender
454: /*
455: * Saves all the documents.
456: */
457: {
458: int count;
459: Window *window;
460:
461: count = [windowList count];
462: while (count--) {
463: window = [windowList objectAt:count];
464: [documentInWindow(window) save:sender];
465: }
466:
467: return nil;
468: }
469:
470: #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.")
471: #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.")
472: #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.")
473: #define QUIT NXLocalString("Quit", NULL, "The operation of exiting the application.")
474:
475: - terminate:sender cancellable:(BOOL)cancellable
476: /*
477: * Makes sure all documents get an opportunity
478: * to be saved before exiting the program. If we are terminating because
479: * the user logged out of the workspace (or powered off), then we cannot
480: * give the user the option of cancelling the quit (that's what the
481: * cancellable flag is for).
482: */
483: {
484: int count, choice;
485: Window *window;
486: id document;
487:
488: count = [windowList count];
489: while (count--) {
490: window = [windowList objectAt:count];
491: document = [window delegate];
492: if ([document respondsTo:@selector(isDirty)] && [document isDirty]) {
493: if (cancellable) {
494: choice = NXRunAlertPanel(QUIT, UNSAVED_DOCUMENTS, REVIEW_UNSAVED, QUIT_ANYWAY, CANCEL);
495: } else {
496: choice = NXRunAlertPanel(QUIT, UNSAVED_DOCUMENTS, REVIEW_UNSAVED, QUIT_ANYWAY, NULL);
497: }
498: if (choice == NX_ALERTOTHER) {
499: return self;
500: } else if (choice == NX_ALERTDEFAULT) {
501: count = [windowList count];
502: while (count--) {
503: window = [windowList objectAt:count];
504: document = [window delegate];
505: if ([document respondsTo:@selector(windowWillClose:cancellable:)]) {
506: if (![document windowWillClose:sender cancellable:cancellable] && cancellable) {
507: return self;
508: } else {
509: [document close];
510: [window close];
511: }
512: }
513: }
514: }
515: break;
516: }
517: }
518:
519: return nil;
520: }
521:
522: - terminate:sender
523: /*
524: * Overridden to give user the opportunity to save unsaved files.
525: */
526: {
527: if (![self terminate:sender cancellable:YES]) {
528: return [super terminate:sender];
529: } else {
530: return self;
531: }
532: }
533:
534: /*
535: * Application object delegate methods.
536: * Since we don't have an application delegate, messages that would
537: * normally be sent there are sent to the Application object itself instead.
538: */
539:
540: - appDidInit:(Application *)sender
541: /*
542: * Makes the tool palette not ever become the key window.
543: * Check for files to open specified on the command line.
544: * Initialize the menus.
545: * If there are no open documents (and we are not being
546: * launched to service a Services request or otherwise
547: * being invoked due to interapplication communication),
548: * then open a blank one.
549: */
550: {
551: int i;
552: Panel *toolWindow;
553:
554: toolWindow = [tools window];
555: [toolWindow setFrameAutosaveName:[toolWindow title]];
556: [toolWindow setBecomeKeyOnlyIfNeeded:YES];
557: [toolWindow setFloatingPanel:YES];
558: [toolWindow orderFront:self];
559:
560: if (NXArgc > 1) {
561: for (i = 1; i < NXArgc; i++) {
562: haveOpenedDocument = openDocument(NXArgv[i], YES) || haveOpenedDocument;
563: }
564: }
565:
566: if (!haveOpenedDocument && !NXGetDefaultValue([self appName], "NXServiceLaunch")) [self new:self];
567:
568: if (NXGetDefaultValue([self appName], "Quit")) {
569: [self activateSelf:YES];
570: NXPing();
571: [self terminate:nil];
572: }
573:
574: initMenu([NXApp mainMenu]);
575:
576: return self;
577: }
578:
579: - (int)app:sender openFile:(const char *)path type:(const char *)type
580: /*
581: * This method is performed whenever a user double-clicks on an icon
582: * in the Workspace Manager representing a Draw program document.
583: */
584: {
585: if (type && !strcmp(type, "draw")) {
586: if (openDocument(path, YES)) {
587: haveOpenedDocument = YES;
588: return YES;
589: }
590: }
591:
592: return NO;
593: }
594:
595: - (BOOL)appAcceptsAnotherFile:(Application *)sender
596: /*
597: * We accept any number of appOpenFile:type: messages.
598: */
599: {
600: return YES;
601: }
602:
603: - app:sender powerOffIn:(int)ms andSave:(int)andSave
604: /*
605: * Give the user a chance to save his documents.
606: */
607: {
608: if (andSave) [self terminate:nil cancellable:NO];
609: return self;
610: }
611:
612: /* Listener/Speaker remote methods */
613:
614: /*
615: * Note that anybody can send these messages to a running version
616: * of Draw, so we protect the ones that can affect the current
617: * document by only allowing them to happen if the Draw default
618: * "RemoteControl" is set.
619: */
620:
621: - (int)msgDirectory:(const char **)fullPath ok:(int *)flag
622: {
623: *fullPath = [self currentDirectory];
624: if (*fullPath) {
625: *flag = YES;
626: } else {
627: *fullPath = "";
628: *flag = NO;
629: }
630: return 0;
631: }
632:
633: - (int)msgVersion:(const char **)aString ok:(int *)flag
634: {
635: char buf[20];
636:
637: sprintf(buf, "3.0 (v%2d)", DrawVersion);
638: *aString = NXCopyStringBuffer(buf);
639: *flag = YES;
640:
641: return 0;
642: }
643:
644: - (int)msgFile:(const char **)fullPath ok:(int *)flag
645: {
646: const char *file;
647:
648: file = [[self currentDocument] filename];
649: if (file) {
650: *fullPath = file;
651: *flag = YES;
652: } else {
653: *fullPath = "";
654: *flag = NO;
655: }
656:
657: return 0;
658: }
659:
660: - (BOOL)shouldRunPrintPanel:sender
661: /*
662: * When NXApp prints, don't bring up the PrintPanel.
663: */
664: {
665: return NO;
666: }
667:
668: - (int)msgPrint:(const char *)fullPath ok:(int *)flag
669: {
670: BOOL close;
671: id document = nil;
672:
673: if (NXGetDefaultValue("Draw", "RemoteControl")) {
674: InMsgPrint = YES;
675: if (document = [findDocument(fullPath) delegate]) {
676: close = NO;
677: } else {
678: document = openDocument(fullPath, NO);
679: if (document) haveOpenedDocument = YES;
680: close = YES;
681: }
682: if (document && ![[document view] isEmpty]) {
683: [NXApp setPrintInfo:[document printInfo]];
684: [[document view] printPSCode:self];
685: if (close) [[[document view] window] close];
686: *flag = YES;
687: } else {
688: *flag = NO;
689: }
690: InMsgPrint = NO;
691: } else {
692: *flag = NO;
693: }
694:
695: return 0;
696: }
697:
698: - (int)msgSelection:(const char **)bytes length:(int *)len asType:(const char *)aType ok:(int *)flag
699: {
700: int maxlen;
701: NXStream *stream;
702:
703: if (NXGetDefaultValue("Draw", "RemoteControl")) {
704: if (!strcmp(aType, DrawPboardType)) {
705: stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
706: if ([[[self currentDocument] view] copySelectionToStream:stream]) {
707: NXGetMemoryBuffer(stream, (char **)bytes, len, &maxlen);
708: *flag = YES;
709: } else {
710: *flag = NO;
711: }
712: NXClose(stream);
713: } else if (!strcmp(aType, NXPostScriptPboardType)) {
714: stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
715: if ([[[self currentDocument] view] copySelectionAsPSToStream:stream]) {
716: NXGetMemoryBuffer(stream, (char **)bytes, len, &maxlen);
717: *flag = YES;
718: } else {
719: *flag = NO;
720: }
721: NXClose(stream);
722: } else if (!strcmp(aType, NXTIFFPboardType)) {
723: stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
724: if ([[[self currentDocument] view] copySelectionAsTIFFToStream:stream]) {
725: NXGetMemoryBuffer(stream, (char **)bytes, len, &maxlen);
726: *flag = YES;
727: } else {
728: *flag = NO;
729: }
730: NXClose(stream);
731: } else {
732: *flag = NO;
733: }
734: } else {
735: *flag = NO;
736: }
737:
738: if (!*flag) {
739: *bytes = "";
740: *len = 0;
741: }
742:
743: return 0;
744: }
745:
746: - (int)msgCopyAsType:(const char *)aType ok:(int *)flag
747: {
748: if (NXGetDefaultValue("Draw", "RemoteControl")) {
749: if (!strcmp(aType, NXPostScriptPboardType) ||
750: !strcmp(aType, NXTIFFPboardType) ||
751: !strcmp(aType, DrawPboardType)) {
752: *flag = ([[[self currentDocument] view] copy:self] ? YES : NO);
753: } else {
754: *flag = NO;
755: }
756: } else {
757: *flag = NO;
758: }
759: return 0;
760: }
761:
762: - (int)msgCutAsType:(const char *)aType ok:(int *)flag
763: {
764: if (NXGetDefaultValue("Draw", "RemoteControl")) {
765: if (!strcmp(aType, NXPostScriptPboardType) ||
766: !strcmp(aType, NXTIFFPboardType) ||
767: !strcmp(aType, DrawPboardType)) {
768: *flag = ([[[self currentDocument] view] cut:self] ? YES : NO);
769: } else {
770: *flag = NO;
771: }
772: } else {
773: *flag = NO;
774: }
775: return 0;
776: }
777:
778: - (int)msgPaste:(int *)flag;
779: {
780: if (NXGetDefaultValue("Draw", "RemoteControl")) {
781: *flag = ([[[self currentDocument] view] paste:self] ? YES : NO);
782: } else {
783: *flag = NO;
784: }
785: return 0;
786: }
787:
788: - (int)msgQuit:(int *)flag
789: {
790: if (NXGetDefaultValue("Draw", "RemoteControl")) {
791: *flag = ([self terminate:self cancellable:YES] ? NO : YES);
792: if (*flag) [NXApp perform:@selector(terminate:) with:nil afterDelay:1 cancelPrevious:YES];
793: } else {
794: *flag = NO;
795: }
796: return 0;
797: }
798:
799: /* Global cursor setting */
800:
801: - cursor
802: /*
803: * This is called by DrawDocument objects who want to set the cursor
804: * depending on what the currently selected tool is (as well as on whether
805: * the Control key has been pressed indicating that the select tool is
806: * temporarily set--see sendEvent:).
807: */
808: {
809: id theCursor = nil;
810: if (!cursorPushed) theCursor = [[self currentGraphic] cursor];
811: return theCursor ? theCursor : NXArrow;
812: }
813:
814: - sendEvent:(NXEvent *)event
815: /*
816: * We override this because we need to find out when the control key is down
817: * so we can set the arrow cursor so the user knows she is (temporarily) in
818: * select mode.
819: */
820: {
821: if (event && event->type < NX_KITDEFINED) { /* mouse or keyboard event */
822: if (event->flags & NX_CONTROLMASK) {
823: if (!cursorPushed && currentGraphic) {
824: cursorPushed = YES;
825: [[self currentDocument] resetCursor];
826: }
827: } else if (cursorPushed) {
828: cursorPushed = NO;
829: [[self currentDocument] resetCursor];
830: }
831: }
832:
833: return [super sendEvent:event];
834: }
835:
836: /* Automatic update methods */
837:
838: - (BOOL)menuItemUpdate:(MenuCell *)menuCell
839: /*
840: * Method called by all menu items which send their actions to the
841: * First Responder. First, if the object which would respond were the
842: * action sent down the responder chain also responds to the message
843: * validateCommand:, then it is sent validateCommand: to determine
844: * whether that command is valid now, otherwise, if there is a responder
845: * to the message, then it is assumed that the item is valid.
846: * The method returns YES if the cell has changed its appearance (so that
847: * the caller (a Menu) knows to redraw it).
848: */
849: {
850: SEL action;
851: id responder, target;
852: BOOL enable;
853:
854: target = [menuCell target];
855: enable = [menuCell isEnabled];
856:
857: if (!target) {
858: action = [menuCell action];
859: responder = [self calcTargetForAction:action];
860: if ([responder respondsTo:@selector(validateCommand:)]) {
861: enable = [responder validateCommand:menuCell];
862: } else {
863: enable = responder ? YES : NO;
864: }
865: }
866:
867: if ([menuCell isEnabled] != enable) {
868: [menuCell setEnabled:enable];
869: return YES;
870: } else {
871: return NO;
872: }
873: }
874:
875: - (BOOL)validateCommand:(MenuCell *)menuCell
876: /*
877: * The only command DrawApp itself controls is saveAll:.
878: * Save All is enabled only if there are any documents open.
879: */
880: {
881: SEL action = [menuCell action];
882:
883: if (action == @selector(saveAll:)) {
884: return findDocument(NULL) ? YES : NO;
885: }
886:
887: return YES;
888: }
889:
890: /*
891: * This is a very funky method and tricks of this sort are not generally
892: * recommended, but this hack is done so that new Graphic subclasses can
893: * be added to the program without changing even one line of code (except,
894: * of course, to implement the subclass itself).
895: *
896: * The objective-C method objc_lookUpClass() is used to find the factory object
897: * corresponding to the name of the icon of the cell sending the setCurrentGraphic:
898: * message.
899: *
900: * Again, this is not recommended procedure, but it illustrates how
901: * objective-C can be used to make some funky runtime dependent decisions.
902: */
903:
904: - setCurrentGraphic:sender
905: /*
906: * The sender's icon is queried. If that name corresponds to the name
907: * of a class, then that class is set as the currentGraphic. If not,
908: * then the select tool is put into effect.
909: */
910: {
911: id cell;
912: const char *className;
913:
914: if (cell = [sender selectedCell]) {
915: if (className = [cell icon]) {
916: currentGraphic = objc_lookUpClass(className);
917: } else {
918: currentGraphic = nil;
919: }
920: if (!currentGraphic) [tools selectCellAt:0 :0];
921: [[self currentDocument] resetCursor];
922: }
923:
924: return self;
925: }
926:
927: @end
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.