This is Document.m in view mode; [Download] [Up]
#import "Deck.h" #import "Document.h" #import "AppWithDoc.h" #import <appkit/Window.h> #import <appkit/ScrollView.h> #import <appkit/TextField.h> #import <appkit/Text.h> #import <appkit/SavePanel.h> #import <appkit/Listener.h> #import <appkit/Speaker.h> #import <appkit/nextstd.h> #import <objc/hashtable.h> /* for NXCopyStringBuffer() */ #import <objc/List.h> /* for NXCopyStringBuffer() */ #import <text/pathutil.h> @implementation Document /* * This class is used to keep track of a document. * * Its contents and window instance variables keep track of the contents of the * document as well as its window and sub Views. The name and directory * specify where the document is to be saved. haveSavedDocument keeps track * of whether a disk file is associated with the document yet (i.e. if it has * ever been saved). * * The Document class's responsibilities: * * 1. Manage the window which displays the document's contents. This includes * ensuring that if the window contains an unsaved document and the user * tries to close it, the user gets an opportunity to save his changes. * 2. Saving the document to a disk file. */ /* Private Functions */ static const char DEFAULT_TITLE[] = "Untitled%d.%s"; #define SCROLLVIEW_BORDER NX_NOBORDER #define WINDOW_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_FLAGSCHANGEDMASK) static void getContentSizeForView(id view, NXSize * contentSize) /* * Calculates the size of the window's contentView by accounting for the * existence of the ScrollView around the GraphicView. No scrollers are * assumed since we are interested in the minimum size need to enclose the * entire view and, if the entire view is visible, we don't need scroll bars! */ { NXRect viewFrame; [view getFrame:&viewFrame]; [ScrollView getFrameSize:contentSize forContentSize:&viewFrame.size horizScroller:NO vertScroller:NO borderType:SCROLLVIEW_BORDER]; } static id createWindowFor(id view, NXRect * windowContentRect) /* * Creates a window for the specified view. If windowContentRect is not NULL, * then it is used as the contentView of the newly created window. */ { NXSize screenSize; id scrollView, window; NXRect defaultWindowContentRect; if (!windowContentRect) { windowContentRect = &defaultWindowContentRect; getContentSizeForView(view, &windowContentRect->size); [NXApp getScreenSize:&screenSize]; if (windowContentRect->size.width > screenSize.width / 2.0) { windowContentRect->size.width = floor(screenSize.width / 2.0); } if (windowContentRect->size.height > screenSize.height - 60.0) { windowContentRect->size.height = screenSize.height - 60.0; } windowContentRect->origin.x = screenSize.width - 85.0 - windowContentRect->size.width; windowContentRect->origin.y = floor((screenSize.height - windowContentRect->size.height) / 2.0); } window = [Window newContent:windowContentRect style:NX_TITLEDSTYLE backing:NX_BUFFERED buttonMask:NX_ALLBUTTONS defer:YES]; scrollView = [ScrollView newFrame:windowContentRect]; [scrollView setVertScrollerRequired:NO]; [scrollView setHorizScrollerRequired:NO]; [scrollView setBorderType:SCROLLVIEW_BORDER]; [scrollView setDocView:view]; [window setContentView:scrollView]; [window addToEventMask:WINDOW_MASK]; [window makeFirstResponder:view]; return window; } /* Factory methods */ + new /* * Creates a new, empty, document */ { self = [super new]; haveSavedDocument = NO; return self; } + newFromContents:content /* * Creates a new, empty, document, with contents and a window */ { self = [self new]; contents = content; if ([contents isKindOf:[View class]]) [self getWindowForView:contents]; [window setDocEdited:YES]; return self; } + newFromFile:(const char *)file /* * Opens an existing document from the specified file. */ { self = [super new]; if (contents = [NXApp readFromFile:file]) { if ([contents isKindOf:[View class]]) [self getWindowForView:contents]; haveSavedDocument = YES; return self; } else { NotifyArg("Open", "File %s not found.", file); [self free]; return nil; } } /* Create and Destroy Methods */ /* * Generate windows; set delegates */ -getWindowFromNib:(const char *)nibFile { return [self getWindow:[NXApp loadNibSection:nibFile owner:self]]; } - getWindowForView:newView { view = newView; [self getWindow:createWindowFor(view, NULL)]; [self resetScrollers]; return self; } - getWindow:aWindow { window=[NXApp locateWindow:aWindow]; [window setDelegate:self]; [window makeKeyAndOrderFront:self]; [NXApp addDocument:[self filename]]; return self; } - free /* * Free all subsisting objects */ { [view free]; [contents free]; [window free]; NX_FREE(name); NX_FREE(directory); return [super free]; } /* State Variables */ - contents /* * Returns the Contents associated with this document. */ { return contents; } - window /* * Returns the Window associated with this document. */ { return window; } - view /* * Returns the document view */ { return view; } /* * Action Methods for manipulating the Contents */ - find:sender /* * Find a string. Use a panel to set parameters */ { Panel *FindPanel; List *viewList = [[sender superview] subviews]; int count = [viewList count]; static id panelView; id found; if (!panelView) while (count--) { panelView = [viewList objectAt:count]; if ([panelView isKindOfGivenName:"TextField"] && [panelView isEditable]) break; } if ([contents respondsTo:@selector(findSTR:)]) { if (found = [contents findSTR:[panelView stringValue]]) { return found; } else { NotifyArg("Find", "Can't find more %s.",[panelView stringValue]); return nil; } } else { return panelView; } } - doAction:(SEL)action for:(STR)caller /* * Pass action methods on to Contents */ { [contents perform:action with:self]; return [view update]; } /* Action methods for Referencing Apps */ - reference:(STR)refName { port_t refPort; int res; int len; NXSelPt start, end; STR refText; if (!(refPort = NXPortFromName(refName, NULL))) { Notify(refName, "Port not found."); return self; } [[self view] getSel:&start:&end]; if (len = (end.cp - start.cp)) { refText = malloc(len + 1); [view getSubstring:refText start:start.cp length:len]; refText[len] = '\0'; } else { Notify(refName, "No Selection"); return self; } if ([[[NXApp appSpeaker] setSendPort:refPort] openFile:refText ok:&res]) Notify(refName, "Message failed."); return self; } - webster:sender { return [self reference:"Webster"]; } - librarian:sender { return [self reference:"Librarian"]; } - quotation:sender { return [self reference:"Quotation"]; } - 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; if (!haveSavedDocument) { savepanel = [NXApp saveAsPanel]; if ([savepanel runModalForDirectory:directory file:name]) [self setName:[savepanel filename]]; else return self; } [self save]; return self; } - saveAs:sender /* * Save under a different name */ { [window setDocEdited:YES]; haveSavedDocument = NO; return [self save:sender]; } - revertToSaved:sender /* * Revert the document back to what is on the disk. */ { id newContents; if (!haveSavedDocument || ![window isDocEdited] || (NXRunAlertPanel ("Revert", "%s was edited. Are you sure you want to undo changes?", "Revert", "Cancel", NULL, name) != NX_ALERTDEFAULT)) return self; [window endEditingFor:self]; if (newContents = [NXApp readFromFile:[self filename]]) { [contents free]; contents = newContents; [view update]; [window setDocEdited:NO]; } else { NotifyArg("Revert", "I/O error. Can't find file %s.", [self filename]); } return self; } /* Methods related to naming/saving this document. */ - (const STR)filename /* * Gets the fully specified file name of the document. If directory is NULL, * then the Home is used. If name is NULL, then the default title is used. */ { static char filename[MAXPATHLEN + 1]; if (!directory && !name) [self setName:NULL andDirectory:NULL]; strcpy(filename, fullPath(directory, name)); return filename; } - (const STR)directory { return directory; } - (const STR)name { return name; } #define ISnewName (newName && *newName) #define ISnewDir (newDirectory && (*newDirectory == '/')) - 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 title[MAXPATHLEN + 5]; char oldName[MAXPATHLEN + 1]; char newNameBuf[MAXPATHLEN + 1]; static int uniqueCount = 1; if (directory && name) strcpy(oldName,[self filename]); else oldName[0] = '\0'; if (ISnewName || !name) { if (!ISnewName) { sprintf(newNameBuf, DEFAULT_TITLE,uniqueCount++,[NXApp extension]); newName = newNameBuf; } else if (name) { NX_FREE(name); } name = NXCopyStringBuffer(newName); } if (ISnewDir || !directory) { if (!ISnewDir) newDirectory = NXHomeDirectory(); else if (directory) NX_FREE(directory); directory = NXCopyStringBuffer(newDirectory); } if (strcmp(oldName,[self filename])) { strcpy(title, name); strcat(title, " \320 ");/* \320 is a "long dash" */ strcat(title, directory); [window setTitle:title]; [NXApp renameDocument:oldName to:[self filename]]; } return self; } - setName:(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. */ { const char * lastComponent; const char * path; if (file) { path = parentname(file); lastComponent = basename(file); if (*path != '.') return [self setName:lastComponent andDirectory:path]; else return [self setName:file andDirectory:NULL]; } return self; } - save /* * Writes out the document (i.e, the Contents). * * See Contents's write: method for more details on how it is archived. */ { if ([window isDocEdited]) if ([NXApp write:contents toFile:[self filename]]) { haveSavedDocument = YES; [window setDocEdited:NO]; } else { NotifyArg("Save", "Can't create file %s.", [self filename]); } return self; } /* Text & Window delegate methods. */ - windowWillClose:sender /* * If the Contents 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 * ContentsDocument itself must be freed. This is accomplished via Application's * delayedFree: mechanism. Unfortunately, by the time delayedFree: frees the * Document, the window instance variable 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. * * 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 ([window isDocEdited] && (haveSavedDocument || contents)) { save = NXRunAlertPanel("Close", "%s has changes. Save them?", "Save", "Don't Save", "Cancel", name); if (save == NX_ALERTOTHER) { return nil; } else { [sender endEditingFor:self]; /* terminate any editing */ if (save == NX_ALERTDEFAULT) [self save:nil]; } } window = nil; [NXApp delayedFree:self]; [NXApp removeDocument:[self filename]]; return self; } - windowWillResize:sender toSize:(NXSize *) size /* * Constrains the size of the window to never be larger than the GraphicView * inside it (including the ScrollView around it). */ { NXRect frameRect, contentRect; getContentSizeForView(view, &contentRect.size); [[sender class] getFrameRect:&frameRect forContentRect:&contentRect style:[sender style]]; if (size->width > frameRect.size.width) size->width = frameRect.size.width; if (size->height > frameRect.size.height) size->height = frameRect.size.height; return self; } - windowDidResize:sender /* * Ensures that if the view is bigger than the window's content view, scroll * bars are added to the ScrollView. */ { return [self resetScrollers]; } - resetScrollers /* * Checks to see if the new window size requires a vertical or horizontal * scroll bar. Note that if a scroll bar is required, the window may have to * be resized to accomodate it. Called whenever the user resizes the window */ { id scrollView; NXSize scrollViewSize, contentSize; BOOL resizeHoriz = NO, resizeVert = NO; NXRect contentRect, windowFrame, viewFrame; if (window) { [window getFrame:&windowFrame]; [[window class] getContentRect:&contentRect forFrameRect:&windowFrame style:[window style]]; scrollView = [window contentView]; [scrollView setHorizScrollerRequired:YES]; [scrollView setVertScrollerRequired:YES]; getContentSizeForView(view, &contentSize); if (contentRect.size.width >= contentSize.width || contentRect.size.height >= contentSize.height) { if (contentRect.size.width >= contentSize.width) { [scrollView setHorizScrollerRequired:NO]; resizeHoriz = YES; } if (contentRect.size.height >= contentSize.height) { [scrollView setVertScrollerRequired:NO]; resizeVert = YES; } [view getFrame:&viewFrame]; [[scrollView class] getFrameSize:&scrollViewSize forContentSize:&viewFrame.size horizScroller:!resizeHoriz vertScroller:!resizeVert borderType:[scrollView borderType]]; if (!resizeHoriz) scrollViewSize.width = contentRect.size.width; if (!resizeVert) scrollViewSize.height = contentRect.size.height; [window sizeWindow:scrollViewSize.width:scrollViewSize.height]; } } return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.