This is MultDoc.m in view mode; [Download] [Up]
#import "MultDoc.h" #import "MultApp.h" #import "Utility.h" #import <appkit/MenuCell.h> #import <appkit/PrintInfo.h> #import <appkit/SavePanel.h> #import <appkit/ScrollView.h> #import <appkit/Text.h> #import <mach/mach_init.h> #import <objc/List.h> @implementation MultDoc: Responder // 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 should handle all the document-level responsibilities */ /* that a nice document in a multi-doc environment should handle: */ /* creation, deletion, keeping track of dirty bits, not closing w/out */ /* allowing the user to save, etc. Most of this code is lifted from */ /* DrawDocument, or TextLab; heavy use is made of the firstResponder */ /* mechanism. Thanks again, guys. */ // The default implementation loads windows with scrollviews from a nib file. // To modify, the revert/save/newFromFile methods should be changed to use // the view (subclass) that acts as the content view of the Window. // - Subrata Sircar (ssircar@canon.com) /* Version 0.9b Apr-19-92 First Public Release */ /* Version 0.95b Apr-29-92 Multiple Save Types */ /* Version 1.00b Jan-19-93 Updated for 3.0 */ /* Factory (Class) variables */ static char *default_format = NULL; // Default title format static const char *extension = NULL; // Document extension static const int myVersion = 100; // version number * 100 static id zoneList = nil; static id appDelegate = NULL; // app delegate instance static NXCoord ORIGX = 100.0;// doc starting position static NXCoord ORIGY = 100.0; static int posInc = 20.0; // increment to position #define APP_DELEGATE [[self class] appDelegate] + initialize /* Class variable initialization. DO NOT call from subclasses. */ { if (self == [MultDoc class]) { [self setVersion:myVersion]; [self setExtension:LocalString("txt")]; [self setDefault:LocalString("Untitled%d")]; } return self; } /* Extension to use in Workspace file names */ + setExtension:(const char *)newExtension { extension = newExtension; // Must be a constant string! return self; } + (const char *)extension { return extension; } /* Default Title string for new documents */ + setDefault:(const char *)newDefault { char temp[MAXPATHLEN+1]; if (default_format) NX_FREE(default_format); sprintf(temp,"%s.%s",newDefault,[[self class] extension]); default_format = NXCopyStringBufferFromZone(temp,MyZone); return self; } + (const char *)default { return default_format; } /* The application delegate object is a class variable so all instances * can access it without keeping a pointer for each instance. */ + setAppDelegate:anObject { appDelegate = anObject; return self; } + appDelegate { return appDelegate; } /* Factory zone methods. I adopt the virtual zone strategy used by Draw. */ + (NXZone *)newZone { if (!zoneList || ![zoneList count]) { return NXCreateZone(vm_page_size, vm_page_size, YES); } else { return (NXZone *)[zoneList removeLastObject]; } } + (void)reuseZone:(NXZone *)aZone { if (!zoneList) zoneList = [List new]; [zoneList addObject:(id)aZone]; NXNameZone(aZone, "Unused"); } + allocFromZone:(NXZone *)aZone { return [self notImplemented:@selector(allocFromZone:)]; } + alloc { return [self notImplemented:@selector(alloc)]; } /* New creation methods */ - setUpNib /* * Load the document's nib file and perform any nib-specific initialization */ { if (LoadLocalNib(LocalString("MultDoc.nib"),self,NO,MyZone)) { [[view docView] setDelegate:self]; [window makeFirstResponder:[view docView]]; return self; } else { char temp[MAXPATHLEN+1]; sprintf(temp,"%s %s\n",LocalString("Could not load requested resource"),LocalString("MultDoc.nib")); Notify(LocalString("Resource Error"),temp); return nil; } } - instanceAwake /* * Set up any instance variables or other initialization that needs doing */ { NXRect frameRect; calcFrame(printInfo, &frameRect); if (frameRect.size.width > 1000) frameRect.size.width = 1000; if (frameRect.size.height > 700) frameRect.size.height = 700; [window sizeWindow:frameRect.size.width :frameRect.size.height]; [window moveTo:ORIGX :ORIGY]; ORIGX += posInc; ORIGY -= posInc; if (ORIGX > 700) ORIGX = 110; if (ORIGY < 0) ORIGY = 70; [window setDelegate:self]; [self dirty:NO]; [self setSavedDocument:NO]; [self setEmpty:YES]; [self setName:NULL andDirectory:NULL]; return self; } + newFromFile:(const char *)file { NXStream *stream; char *strPtr = rindex(file, '.'); if (strcmp([[self class] extension],++strPtr)) return [[self class] newFromPasteboard:[Pasteboard newByFilteringFile:file]]; stream = NXMapFile(file,NX_READONLY); if (stream) { self = [self new]; [[view docView] readText:stream]; [[view docView] sizeToFit]; [view display]; [self setName:file]; [self setSavedDocument:YES]; [self setEmpty:NO]; [window makeKeyAndOrderFront:self]; NXCloseMemory(stream,NX_FREEBUFFER); return self; } else { Notify(LocalString("MultDoc: can't open file"),file); return nil; } } + new { self = [[self class] newFromZone:[[self class] newZone]]; return self; } + newFromZone:(NXZone *)zone; /* * All creation methods end up filtered through newFromZone, so * it is as general as possible, leaving the subclass specific code * to the above functions. */ { self = [super allocFromZone:zone]; printInfo = [PrintInfo new]; [printInfo setMarginLeft:36.0 right:36.0 top:36.0 bottom:36.0]; if ([self setUpNib]) { [self instanceAwake]; return self; } else { [self free]; return nil; } } + newFromPasteboard:pasteboard { char *stringPos, *tempPtr, *pathList; char tempBuf[MAXPATHLEN+1]; char **filePath = NULL; int files = 0, length = 0, count = 0; BOOL flag = NO; id pBoard = [Pasteboard newByFilteringTypesInPasteboard:pasteboard]; if ([pBoard findAvailableTypeFrom:&NXFilenamePboardType num:1]) { if ([pBoard readType:NXFilenamePboardType data:&pathList length:&length]) flag = YES; } else if ([pBoard findAvailableTypeFrom:&NXAsciiPboardType num:1]) { if ([pBoard readType:NXAsciiPboardType data:&pathList length:&length]) flag = YES; } if (!flag) { Notify(LocalString("MultDoc"),LocalString("Could not parse or filter ASCII data from the requested source.")); return nil; } stringPos = tempPtr = pathList; /* This code just parses the pathList for the filenames and explictly */ /* null-terminates them after copying into a buffer. It appends that */ /* buffer to the dynamically-allocated filelist which keeps track of it */ /* the number of tabs + 1 equals the number of files dragged in */ while (stringPos = index(stringPos, '\t')) { count = (int)(stringPos-tempPtr); strncpy(tempBuf,tempPtr,count); *(tempBuf+count) = '\0'; filePath = addFile(tempBuf,filePath,files,MyZone); files++; stringPos++; tempPtr=stringPos; } count = strlen(tempPtr); strncpy(tempBuf,tempPtr,count); *(tempBuf+count) = '\0'; filePath = addFile(tempBuf,filePath,files,MyZone); files++; while (files--) { if (![MultDoc newFromFile:filePath[files]]) Notify(LocalString("MultDoc: can't open file"),filePath[files]); } freeList(filePath); [pBoard deallocatePasteboardData:pathList length:length]; return self; } - free { NX_FREE(name); NX_FREE(directory); if ([printInfo isKindOf:[PrintInfo class]]) [printInfo free]; if ([window isKindOf:[Window class]]) [window free]; [[self class] reuseZone:MyZone]; return [super free]; } - view /* * Returns the view associated with this document. */ { return view; } - (BOOL)needsSaving /* * Returns the dirty BOOL associated with this document. */ { return dirty; } - (BOOL)isEmpty /* * Returns the empty BOOL associated with this document. */ { return empty; } - (BOOL)hasSavedDocument { return haveSavedDocument; } - setEmpty:(BOOL)flag /* * Sets the BOOL to flag. */ { empty = flag; return self; } - dirty:(BOOL)flag /* * Sets the BOOL to flag. */ { dirty = flag; [window setDocEdited:flag]; return self; } - setSavedDocument:(BOOL)flag /* * Sets the BOOL to flag. */ { haveSavedDocument = flag; return self; } - printInfo { return printInfo; } /* Target/Action methods */ - save:sender /* * Saves the file. If this document has never been saved to disk, * then a SavePanel is put up to ask the user what file name she * wishes to use to save the document. */ { id savepanel = [APP_DELEGATE saveAsPanel:self]; const char *dir = NULL; if (![self hasSavedDocument]) { if ([savepanel runModalForDirectory:directory file:name]) [self setName:[savepanel filename]]; else return nil; } [self save]; dir = [self directory]; // chdir(dir); [APP_DELEGATE setDefaultDir:dir]; return self; } - saveAs:sender /* Changes the document name. IF you cancel, it does the right thing */ /* and undoes the changes to the state bits. */ { BOOL oldSavedFile, oldDirty; oldDirty = [self needsSaving]; oldSavedFile = [self hasSavedDocument]; [self dirty:YES]; [self setSavedDocument:NO]; if ([self save:sender]) return self; else { [self setSavedDocument:oldSavedFile]; [self dirty:oldDirty]; return nil; } } - revertToSaved:sender /* * Revert the document back to what is on the disk. */ { MultDoc *oldPtr; if (![self hasSavedDocument] || ![self needsSaving] || (NXRunAlertPanel(LocalString("Revert"), LocalString("%s has been edited. Are you sure you want to undo changes?"), LocalString("Revert"), LocalString("Cancel"), NULL, name) != NX_ALERTDEFAULT)) { return self; } oldPtr = self; self = [[oldPtr class] newFromFile:[oldPtr fileName]]; [oldPtr free]; return self; } - save { NXStream *ts; int fd; fd = open([self fileName], O_CREAT | O_WRONLY | O_TRUNC, 0666); ts = NXOpenFile(fd, NX_WRITEONLY); if (ts) { [[view docView] writeText:ts]; NXFlush(ts); NXClose(ts); [self dirty:NO]; [self setSavedDocument:YES]; } else Notify(LocalString("MultDoc:save - can't create file"),[self fileName]); close(fd); return self; } - changeLayout:sender /* * Puts up a PageLayout panel and allows the user to pick a different * size paper to work on. After she does so, the view is resized to the * new paper size. * Since the PrintInfo is effectively part of the document, we dirty * the view (by performing the dirty method). */ { NXRect frame; if ([[APP_DELEGATE pageLayout:self] runModal] == NX_OKTAG) { calcFrame(printInfo, &frame); [window sizeWindow:frame.size.width :frame.size.height]; [self dirty:YES]; [window display]; } return self; } /* Methods related to naming/saving this document. */ - (const char *)fileName /* * Gets the fully specified file name of the document. * If directory is NULL, then the currentDirectory is used. * If name is NULL, then the default title is used. */ { static char filenamebuf[MAXPATHLEN+1]; if (!directory && !name) [self setName:NULL andDirectory:NULL]; if (directory) { strcpy(filenamebuf, directory); strcat(filenamebuf, "/"); } else filenamebuf[0] = '\0'; if (name) strcat(filenamebuf, name); return filenamebuf; } - (const char *)directory { return directory; } - (const char *)name { return name; } - setName:(const char *)newName andDirectory:(const char *)newDirectory /* * Updates the name and directory of the document. * newName or newDirectory can be NULL, in which case the name or directory * will not be changed (unless one is currently not set, in which case * a default name will be used). */ { char newNameBuf[MAXPATHLEN+1]; static int uniqueCount = 1; if ((newName && *newName) || !name) { if (!newName || !*newName) { sprintf(newNameBuf, [[self class] default], uniqueCount++); newName = newNameBuf; } else if (name) NX_FREE(name); name = NXCopyStringBufferFromZone(newName, MyZone); } if ((newDirectory && (*newDirectory == '/')) || !directory) { if (!newDirectory || (*newDirectory != '/')) newDirectory = [APP_DELEGATE currentDirectory]; else if (directory) NX_FREE(directory); directory = NXCopyStringBufferFromZone(newDirectory, MyZone); } [window setTitleAsFilename:[self fileName]]; NXNameZone(MyZone, [self fileName]); return self; } - setName:(const char *)file /* * If file is a full path name, then both the name and directory of the * document is updated appropriately, otherwise, only the name is changed. */ { char *lastComponent; char path[MAXPATHLEN+1]; if (file) { strcpy(path, file); lastComponent = rindex(path, '/'); if (lastComponent) { *lastComponent++ = '\0'; return [self setName:lastComponent andDirectory:path]; } else return [self setName:file andDirectory:NULL]; } return self; } /* Window delegate methods. */ - windowWillClose:sender /* * If the document has been edited, then this asks the user if she * wants to save the changes before closing the window. When the window * is closed, the document itself must be freed. This is accomplished * via Application's delayedFree: mechanism. Unfortunately, by the time * delayedFree: frees the document, the window and view instance variables * will already have automatically been freed by virtue of the window's being * closed. Thus, those instance variables must be set to nil to avoid their * being freed twice. (This behavior is set in the window flags in IB.) * * Returning nil from this method informs the caller that the window should * NOT be closed. Anything else implies it should be closed. */ { int save; if ([self needsSaving] && ![self isEmpty]) { [window makeKeyAndOrderFront:self]; save = NXRunAlertPanel(LocalString("Close"), LocalString("%s has changes. Save them?"), LocalString("Save"), LocalString("Don't Save"), LocalString("Cancel"), name); if (save != NX_ALERTDEFAULT && save != NX_ALERTALTERNATE) return nil; else { [window endEditingFor:self]; /* terminate any editing */ if (save == NX_ALERTDEFAULT) { if (![self save:nil]) return nil; } } } window = nil; view = nil; if (printInfo) [NXApp setPrintInfo:nil]; [NXApp delayedFree:self]; // chdir([APP_DELEGATE currentDirectory]); return self; } - windowDidBecomeMain:sender /* * Switch the Application's PrintInfo to the document's when the document * window becomes the main window. Also set the cursor appropriately * depending on which tool is currently selected. */ { [NXApp setPrintInfo:printInfo]; return self; } - windowWillMiniaturize:sender toMiniwindow:counterpart { char *dot; char title[MAXPATHLEN+1]; strcpy(title, [self name]); dot = rindex(title, '.'); if (dot && !strcmp(++dot, extension)) *(--dot) = '\0'; [counterpart setTitle:title]; return self; } /* Validates whether a menu command makes sense now */ - (BOOL)validateCommand:menuCell { SEL action = [menuCell action]; if (action == @selector(revertToSaved:)) return ([self needsSaving] && [self hasSavedDocument]); else if (action == @selector(saveAs:)) return (![self isEmpty] && [self hasSavedDocument]); else if (action == @selector(close:)) return YES; return YES; } /* Text Delegate methods */ - textDidGetKeys:sender isEmpty:(BOOL)flag { if (![self needsSaving]) [self dirty:YES]; [self setEmpty:flag]; return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.