This is MultApp.m in view mode; [Download] [Up]
#import "MultApp.h" #import "MultDoc.h" //#import "MultDrop.h" #import "PrefDelegate.h" #import "PageMargin.h" #import "Utility.h" #define PAGE_LAYOUT LocalString("Page_Layout") #define INFO_PANEL LocalString("Info_Panel") #define HELP_PANEL LocalString("Help_Panel") #define PREF_PANEL LocalString("Preferences_Panel") #import <appkit/Menu.h> #import <appkit/MenuCell.h> #import <appkit/Listener.h> #import <appkit/Matrix.h> #import <appkit/OpenPanel.h> #import <appkit/publicWraps.h> #import <appkit/Speaker.h> #import <mach/mach_init.h> #import <objc/List.h> @implementation MultApp:Object // Canon Information Systems is not responsible for anything anyone does with // this code, nor are they responsible for the correctness of this code. // Basically, this has very little to do with the company I work for, and you // can't blame them. // This file is best read in a window as wide as this comment, and with tab // settings of 4 spaces and indent setting of 4 spaces (at least, that's what // I use). // You are welcome to do as you would with this file under the following // conditions. // First, I accept no blame for anything that goes wrong no matter how you // use it, no matter how catastrophic, not even if it stems from a bug in my // code. Second, please keep my notices on it when/if you distribute it. // Third, if you discover any bugs or have any comments, PLEASE TELL ME! // Code won't get better without people picking it apart and giving the writer // feedback. Fourth, if you modify it, please keep a notice that your version // is based on mine in the source files (and keep the notice that mine is based // on four other pieces of code :<). Thanks, and have fun. // - Subrata Sircar, ssircar@canon.com /* This class is intended as an Application Delegate. As such it */ /* is supposed to provide the document-management functionality */ /* that a multiple-document, drag-and-drop application should be */ /* able to do. This includes keeping track of the current thing, */ /* managing the shared panels, updating the defaults and menus, */ /* and controlling communication with the Workspace Manager. */ /* The paradigm used is that the application delegate relays new messages */ /* to the factory Document class, and every other doc message goes to */ /* the first responder. The delegate opens nib sections containing */ /* the info and preferences panel (the prefs panel should have a delegate */ /* to update it) and lets the helpObject worry about itself. Otherwise */ /* it acts on delegate messages. */ /* Source code stolen from Draw, Acceptor, and WhatsUpDoc. Thanks! */ /* Source code also taken from Ernest Prabhakar's BasicApp example, which */ /* includes more methods than mine does (for things like services, etc.) */ /* His examples are well worth checking out, especially if you need more */ /* functionality than this class can provide. */ /* - Subrata Sircar ssircar@canon.com Canon Information Systems */ /* Version 0.9b Apr-19-92 First Public Release */ /* Version 0.95b Jun-10-92 Minor File path fixes */ /* Version 1.00b Aug-10-92 Cleaned up #imports */ /* Version 1.1b Jan-10-93 Modified for 3.0 */ /* Preprocessor macros */ #define DocClass [[[self class] docClass] class] #define DropClass [[[self class] dropClass] class] #define DocExtension [DocClass extension] #define theVersion ((float)([[self class] version])/100.0) /* Factory (Class) Variables */ /* These are declared as static internal because I felt that all members */ /* of this class would use the same values for these variables. Subclasses */ /* can only manipulate these variables through the provided class methods. */ /* BE VERY CAREFUL about overriding methods which depends on the class */ /* variables without calling the super method - it might cause problems. */ static id docClass; // Class of Document to use static id dropClass; // Class of DropView to use static const char *theAppName; // Cache the appName for later use static const char *noValue; // Cache the local no and yes static const char *yesValue; static const char *VersionFormat; // Version Format String static const int myVersion = 110; // Version multiplied by 100 BOOL InMsgPrint = NO; /* Factory (Class) Methods */ + initialize /* Class variable initialization. DO NOT call from subclasses. */ { if (self == [MultApp class]) { [self setVersion:myVersion]; [self setDocClass:[MultDoc class]]; // [self setDropClass:[MultDrop class]]; VersionFormat = LocalString("Version %.2f"); theAppName = NXCopyStringBufferFromZone([NXApp appName],MyZone); noValue = LocalString("NO"); yesValue = LocalString("YES"); } return self; } + setDocClass:newDoc /* Sets the document class and the file extensions to be used. Requires */ /* the document class to implement a class method to return the extension */ /* and the file types. */ { docClass = newDoc; return self; } + docClass { return docClass; } + setDropClass:newDropView { dropClass = newDropView; return self; } + dropClass { return dropClass; } /* Private C functions used to implement methods in this class. */ static void initMenu(id menu) /* * Sets the updateAction for every menu item which sends to the * First Responder (i.e. their target is nil). When autoupdate is on, * every event will be followed by an update of each of the menu items * which is visible. This keep all unavailable menu items dimmed out * so that the user knows what options are available at any given time. */ { int count; id matrix, cell; id matrixTarget, cellTarget; matrix = [menu itemList]; matrixTarget = [matrix target]; count = [matrix cellCount]; while (count--) { cell = [matrix cellAt:count :0]; cellTarget = [cell target]; if (!matrixTarget && !cellTarget) { [cell setUpdateAction:@selector(menuItemUpdate:) forMenu:menu]; } else if ([cell hasSubmenu]) { initMenu(cellTarget); } else if (cellTarget == [NXApp delegate]) [cell setUpdateAction:@selector(menuItemUpdate:) forMenu:menu]; } } static id documentInWindow(id window) /* * Checks to see if the passed window's delegate is a document. * If it is, it returns that document, otherwise it returns nil. */ { id document = [window delegate]; if (document) return [document isKindOf:docClass] ? document : nil; else return nil; } static id findDocument(const char *name) /* * Searches the window list looking for a document with the specified name. * Returns the window containing the document if found. * If name == NULL then the first document found is returned. */ { int count; id document, window, windowList; windowList = [NXApp windowList]; count = [windowList count]; while (count--) { window = [windowList objectAt:count]; document = documentInWindow(window); if (document && (!name || !strcmp([document fileName], name))) return window; } return nil; } static id openFile(const char *directory, const char *name, BOOL display) /* * Opens a file with the given name in the specified directory. * If we already have that file open, it is ordered front. * Returns the document if successful, nil otherwise. */ { id window; char buffer[MAXPATHLEN+1], path[MAXPATHLEN+1], temp[MAXPATHLEN+1]; if (name && *name) { if (!directory) directory = "."; else if (*directory != '/') { strcpy(buffer, "./"); strcat(buffer, directory); directory = buffer; getwd(temp); if (!chdir(directory) && getwd(path)) chdir(temp); else { Notify(LocalString("Open: path invalid"), directory); return nil; } } else strcpy(path,directory); strcat(path, "/"); strcat(path, name); window = findDocument(path); if (window) { if (display) [window makeKeyAndOrderFront:window]; return [window delegate]; } else return [docClass newFromFile:path]; } return nil; } /* Public Methods */ - openFile:(const char *)path file:(const char *)type flag:(BOOL)flag { return openFile(path,type,flag); } - currentDocument { return documentInWindow([NXApp mainWindow]); } - (const char *)currentDirectory /* Returns the current document's directory */ { const char *retval = [[self currentDocument] directory]; if (!retval || !*retval) retval = defaultDir; return retval; } - setDefaultDir:(const char *)dir { sprintf(defaultDir,"%s",dir); return self; } - (const char *)launchDirectory { return launchDir; } /* Default support methods */ // These functions manage the app's behavior when it quits with unsaved documents - (BOOL)saveAll { return saveAll; } - setSaveAll:(BOOL)value { saveAll = value; return self; } - (BOOL)dumpAll { return dumpAll; } - setDumpAll:(BOOL)value { dumpAll = value; return self; } /* Shared Panels */ - info:sender /* * Returns the information Panel if it hasn't already been loaded. */ { id versionField; /* version field in info panel */ char buf[20]; if (!infoPanel) { NXZone *zone = NXCreateChildZone(MyZone, vm_page_size, vm_page_size, YES); NXNameZone(zone,"infoPanel"); if (LoadLocalNib(LocalString("Info.nib"),self,YES,zone)) { versionField = NXGetNamedObject("VersionNumber", infoPanel); if (versionField) { sprintf(buf, VersionFormat,theVersion); [versionField setStringValue:buf]; } [infoPanel setFrameUsingName:INFO_PANEL]; } else { char temp[MAXPATHLEN+1]; sprintf(temp,"%s %s\n",LocalString("Could not load requested resource"),LocalString("Info.nib")); Notify(LocalString("Resource Error"),temp); } NXMergeZone(zone); } [infoPanel orderFront:self]; return infoPanel; } - pref:sender /* * The preferences panel is a separate nib module and only loaded on demand. * When loaded, the object that manages the panel is asked to update it. */ { if (!prefPanel) { NXZone *zone = NXCreateChildZone(MyZone, vm_page_size, vm_page_size, YES); if (LoadLocalNib(LocalString("Pref.nib"),self,NO,zone)) { [prefPanel setFrameUsingName:PREF_PANEL]; } else { char temp[MAXPATHLEN+1]; sprintf(temp,"%s %s\n",LocalString("Could not load requested resource"),LocalString("Pref.nib")); Notify(LocalString("Resource Error"),temp); } NXMergeZone(zone); } [[prefPanel delegate] load:self]; [prefPanel makeKeyAndOrderFront:self]; return prefPanel; } - saveAsPanel:sender /* * Gets us a SavePanel with an accessory */ { id savepanel = [SavePanel new]; [savepanel setAccessoryView:nil]; [savepanel setRequiredFileType:DocExtension]; return savepanel; } - pageLayout:sender /* * Returns the application-wide PageLayout panel, with margins. */ { return pageMargin; } - stringSet:sender /* * Returns the application-wide string table, if it exists. */ { if (stringSet) return stringSet; else return nil; } /* Target/Action Methods */ - new:sender /* * Called by pressing New in the Document menu. */ { [[docClass class] new]; return self; } - open:sender /* * Called by pressing Open... in the Document menu. */ { const char *directory; const char *const *files; const char *const myType[2] = {DocExtension, NULL}; id openpanel = [[OpenPanel new] allowMultipleFiles:YES]; directory = [self currentDirectory]; if (directory && (*directory == '/')) [openpanel setDirectory:directory]; if ([openpanel runModalForTypes:myType]) { files = [openpanel filenames]; directory = [openpanel directory]; while (files && *files) { haveOpenedDocument = openFile(directory, *files, YES) || haveOpenedDocument; files++; } } return self; } - saveAll:sender /* * Saves all the documents. */ { int count; id windowList; windowList = [NXApp windowList]; count = [windowList count]; while (count--) { [documentInWindow([windowList objectAt:count]) save:self]; } return self; } - print:sender { return [[[self currentDocument] view] printPSCode:sender]; } - mailToMe:sender // Stolen from Opener 3.0. Thanks, Michael! { char subj[256], w[256] = "whoami"; char body[4096]="\ Subrata:\n\n\ Great source code! It runs like beauty in the night...\n\ BUT! I do have a few comments:\n\n\ <insert accolades, criticisms & suggestions here>\n\n\ Sincerely,\n\ "; #define call(a,b) [s performRemoteMethod:a with:b length:strlen(b)+1] id s = [NXApp appSpeaker]; port_t mailPort = NXPortFromName("Mail", NULL); // make sure app is launched mailPort = NXPortFromName("MailSendDemo",NULL); if (mailPort == PORT_NULL) { Notify(LocalString("Suggestion attempt failed with Missing Port to Mail"),LocalString("Check if Mail is running.")); return self; } [s setSendPort:mailPort]; sprintf(subj,"Comments and suggestions for ``%s'', Version %.2f ",theAppName,theVersion); strcat(body,execstr(w)); strcat(body,"\n"); call("setTo:","ssircar@canon.com"); call("setSubject:",subj); call("setBody:",body); return self; } /* Automatic update methods */ - (BOOL)menuItemUpdate:menuCell /* * Method called by all menu items which send their actions to the * First Responder. First, if the object which would respond if the * action was sent down the responder chain also responds to the message * validateCommand:, then it is sent validateCommand: to determine * whether that command is valid now; otherwise, if there is a responder * to the message, then it is assumed that the item is valid. * The method returns YES if the cell has changed its appearance (so that * the caller (a Menu) knows to redraw it). * This method also traps menu items sending to this class, and sets the * action the same way. */ { SEL action; id responder, target; BOOL enable; target = [menuCell target]; enable = [menuCell isEnabled]; if (!target) { action = [menuCell action]; responder = [NXApp calcTargetForAction:action]; if ([responder respondsTo:@selector(validateCommand:)]) { enable = [responder validateCommand:menuCell]; } else enable = responder ? YES : NO; } else if (target == self) enable = [self validateCommand:menuCell]; if ([menuCell isEnabled] != enable) { [menuCell setEnabled:enable]; return YES; } else return NO; } - (BOOL)validateCommand:menuCell // The only messages the delegate is asked to validate are SaveAll and Print. { SEL action = [menuCell action]; if ((action == @selector(saveAll:)) || (action == @selector(print:))) return findDocument(NULL) ? YES : NO; return YES; } - setupPageLayout:(float)lm :(float)rm :(float)tm :(float)bm { if (!pageMargin) { pageMargin = [PageMargin new]; [pageMargin setPlpAccessory:plpAccessory]; [pageMargin setFrameUsingName:PAGE_LAYOUT]; } [pageMargin setValues:lm right:rm top:tm bottom:bm]; [pageMargin writePrintInfo]; return self; } /* Application Delegate Methods */ - appWillInit:sender { char temp[MAXPATHLEN+1]; float lm,rm,tm,bm; #ifndef DEBUG /* Don't Dump Core if not Debugging */ struct rlimit rl={ 0, 0}; getrlimit( RLIMIT_CORE, &rl); rl.rlim_cur=0; setrlimit( RLIMIT_CORE, &rl); #endif /* Set the document class variable to point to us */ [DocClass setAppDelegate:self]; /* Check and load the application defaults. The basics are the quit */ /* behavior and the page margins. Also sets the menu updating flag. */ if (!NXGetDefaultValue(theAppName,LocalString("SaveAll"))) sprintf(temp,noValue); else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("SaveAll"))); if (!strcmp(temp,yesValue)) saveAll = YES; if (!NXGetDefaultValue(theAppName,LocalString("DumpAll"))) sprintf(temp,noValue); else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("DumpAll"))); if (!strcmp(temp,yesValue)) dumpAll = YES; if (!NXGetDefaultValue(theAppName,LocalString("LeftMargin"))) sprintf(temp,"36.0"); else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("LeftMargin"))); sscanf(temp,"%f",&lm); if (!NXGetDefaultValue(theAppName,LocalString("RightMargin"))) sprintf(temp,"36.0"); else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("RightMargin"))); sscanf(temp,"%f",&rm); if (!NXGetDefaultValue(theAppName,LocalString("TopMargin"))) sprintf(temp,"36.0"); else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("TopMargin"))); sscanf(temp,"%f",&tm); if (!NXGetDefaultValue(theAppName,LocalString("BottomMargin"))) sprintf(temp,"36.0"); else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("BottomMargin"))); sscanf(temp,"%f",&bm); [self setupPageLayout:lm :rm :tm :bm]; [NXApp setAutoupdate:YES]; return self; } - appDidInit:sender /* * Register our window with the Workspace for icon dropping. * Check for files to open specified on the command line. * Initialize the menus and check if we were opened to print or quit. * If there are no open documents, then open blank documents. */ { int i; char buffer[MAXPATHLEN+1], temp[MAXPATHLEN+1]; char *directory, *name, *ext; // NXRect theRect; // View *appIconView = [[NXApp appIcon] contentView]; // id myDropView = [DropClass allocFromZone:[appIconView zone]]; /* register our app icon view for image dragging destination */ // [appIconView getFrame:&theRect]; // [myDropView initFrame:&theRect]; // [appIconView addSubview:myDropView]; // [myDropView registerForDraggedTypes:&NXFilenamePboardType count:1]; // [myDropView registerForDraggedTypes:&NXAsciiPboardType count:1]; launchDir = appDirectory(); [self setDefaultDir:NXHomeDirectory()]; /* Check for command line files to open */ if (NXArgc > 1) { for (i = 1; i < NXArgc; i++) { strcpy(buffer, NXArgv[i]); ext = rindex(buffer, '.'); if (!ext) { strcat(buffer,"."); strcat(buffer, DocExtension); } if (*buffer == '/') { directory = "/"; name = buffer; name++; } else { name = rindex(buffer, '/'); if (name) { *name++ = '\0'; sprintf(temp,"%s/%s",launchDir,buffer); directory = temp; } else { name = buffer; directory = NULL; } } haveOpenedDocument = openFile(directory, name, YES) || haveOpenedDocument; } } if (!haveOpenedDocument) [self new:self]; /* if none opened, open one */ if (NXGetDefaultValue(theAppName,LocalString("Quit"))) { [NXApp activateSelf:YES]; NXPing(); [NXApp terminate:self]; } initMenu([NXApp mainMenu]); return self; } - app:sender willShowHelpPanel:panel { if (!helpPanel) { helpPanel = panel; [helpPanel setFrameUsingName:HELP_PANEL]; } return self; } - appWillTerminate:sender /* * Overridden to be sure all documents get an opportunity to be saved * before exiting the program. Save the defaults too. */ { float lm,rm,tm,bm; char temp[100]; id window, document; id windowList = [NXApp windowList]; int choice = 0, count = [windowList count]; [pageMargin saveFrameUsingName:PAGE_LAYOUT]; if (helpPanel) [helpPanel saveFrameUsingName:HELP_PANEL]; if (infoPanel) [infoPanel saveFrameUsingName:INFO_PANEL]; if (prefPanel) [prefPanel saveFrameUsingName:PREF_PANEL]; [pageMargin getValues:&lm right:&rm top:&tm bottom:&bm]; sprintf(temp,"%f",lm); NXWriteDefault([NXApp appName],LocalString("LeftMargin"),temp); sprintf(temp,"%f",rm); NXWriteDefault([NXApp appName],LocalString("RightMargin"),temp); sprintf(temp,"%f",tm); NXWriteDefault([NXApp appName],LocalString("TopMargin"),temp); sprintf(temp,"%f",bm); NXWriteDefault([NXApp appName],LocalString("BottomMargin"),temp); if (saveAll) { while (count--) { document = [[windowList objectAt:count] delegate]; if ([document respondsTo:@selector(needsSaving)] && [document needsSaving]) { [document save:self]; } } NXWriteDefault(theAppName,LocalString("SaveAll"),yesValue); NXWriteDefault(theAppName,LocalString("DumpAll"),noValue); } else if (dumpAll) { NXWriteDefault(theAppName,LocalString("DumpAll"),yesValue); NXWriteDefault(theAppName,LocalString("SaveAll"),noValue); } else { NXWriteDefault(theAppName,LocalString("DumpAll"),noValue); NXWriteDefault(theAppName,LocalString("SaveAll"),noValue); while (count--) { window = [windowList objectAt:count]; document = [window delegate]; if ([document respondsTo:@selector(needsSaving)] && [document needsSaving]) { choice = NXRunAlertPanel(LocalString("Quit"), LocalString("You have unsaved documents."), LocalString("Review Unsaved"), LocalString("Quit Anyway"), LocalString("Cancel")); if (choice == NX_ALERTOTHER) return nil; else if (choice == NX_ALERTALTERNATE) return self; else if (choice == NX_ALERTDEFAULT) { count = 0; choice = [windowList count]; while (choice--) { window = [windowList objectAt:choice]; document = [window delegate]; if ([document respondsTo:@selector(windowWillClose:)]) { if ([document windowWillClose:self]) [window close]; else return nil; } } } } } } return self; } - (int)appOpenFile:(const char *)path type:(const char *)type // This method is performed whenever a user double-clicks on an icon // in the Workspace Manager representing a app program document. // It is also called via command-drag-n-drop. { char *name; char directory[MAXPATHLEN+1]; if (type && !strcmp(type, DocExtension)) { strcpy(directory, path); name = rindex(directory, '/'); if (name) { #ifdef DEBUG printf("Directory %s: File %s\n",directory,name); #endif if (name != index(directory, '/')) { *name++ = '\0'; if (openFile(directory, name, YES)) { haveOpenedDocument = YES; return YES; } } else { name++; if (openFile("/", name, YES)) { haveOpenedDocument = YES; return YES; } } } } else { sprintf(directory,LocalString("%s is not a file %s can open. An attempt to filter appropriate data will be made."),path,[NXApp appName]); Notify(LocalString("File Open Warning"),directory); if ([DocClass newFromPasteboard:[Pasteboard newByFilteringFile:path]]) return YES; } return NO; } - (BOOL)appAcceptsAnotherFile:sender /* * We accept any number of appOpenFile:type: messages. */ { return YES; } - (int)app:sender unmounting:(const char *)fullPath { id windowList = [NXApp windowList]; int count = [windowList count]; id document = NULL; /* if any of our documents are in that path, close them and notify the user */ while (count--) { document = documentInWindow([windowList objectAt:count]); if (document && (!strncmp([document directory],fullPath,strlen(fullPath)))) { Notify(fullPath,LocalString("is being unmounted; documents here will close.")); /* Use performClose so that the window delegate will be notified. */ [[windowList objectAt:count] performClose:self]; } } /* the current directory is wherever our current document is so we're set */ return 1; } /* Listener/Speaker remote methods */ // Filename and path methods - (int)msgDirectory:(const char **)fullPath ok:(int *)flag { *fullPath = [self currentDirectory]; if (*fullPath) *flag = YES; else { *fullPath = ""; *flag = NO; } return 0; } - (int)msgFile:(const char **)fullPath ok:(int *)flag { const char *file; file = [[self currentDocument] fileName]; if (file) { *fullPath = file; *flag = YES; } else { *fullPath = ""; *flag = NO; } return 0; } // Miscellaneous messages we might want to handle - (int)msgPrint:(const char *)fullPath ok:(int *)flag { BOOL close; id document = nil; char *directory, *name; char temp[MAXPATHLEN+1]; char path[MAXPATHLEN+1]; char buffer[MAXPATHLEN+1]; InMsgPrint = YES; strcpy(buffer, fullPath); name = rindex(buffer, '/'); if (name) { *name++ = '\0'; directory = buffer; } else { name = buffer; getwd(temp); directory = temp; } strcpy(path,directory); strcat(path, "/"); strcat(path, name); document = [findDocument(path) delegate]; if (document) close = NO; else { document = openFile(directory, name, NO); if (document) haveOpenedDocument = YES; close = YES; } if (document && ![[document view] isEmpty]) { [NXApp setPrintInfo:[document printInfo]]; [[document view] printPSCode:self]; if (close) [[[document view] window] performClose:self]; *flag = YES; } else *flag = NO; InMsgPrint = NO; return 0; } - (int)msgVersion:(const char **)aString ok:(int *)flag { char buf[20]; sprintf(buf, VersionFormat, theVersion); *aString = NXCopyStringBuffer(buf); *flag = YES; return 0; } - (int)msgQuit:(int *)flag { *flag = ([NXApp terminate:self] ? NO : YES); return 0; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.