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.