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.