diff --git a/ChangeLog b/ChangeLog index b0eab3900..88fc4c9e0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,14 @@ -1999-09-10 Pedro Ivo Andrade Tavares +1999-09-10 Carl Lindberg + + * Source/NSDocument.m: new file, implementation. + * Source/NSDocumentController.m: ditto. + * Source/NSWindowController.m: ditto. + * Headers/NSDocument.h + Headers/NSDocumentController.h + Headers/NSDocumentFrameworkPrivate.h + Headers/NSWindowController.h: new. + +1999-09-10 Pedro Ivo Andrade Tavares * Source/NSHelpManager.m: new file. See below for an explanation. * Headers/AppKit/NSHelpManager.h: new file. @@ -25,6 +35,13 @@ 1999-09-10 Michael Hanni + * Source/NSWindow.m: modifications to allow NSWindowController and + friends to work happily. Before windowShouldClose completes we + confer with NSWindowController if we should REALLY close the + window. + * Source/NSApplication.m: modifications for NSDocumentController. + If the delgate doesn't handle openFile: [NSDocumentController + sharedDocumentController] opens the file. * Source/NSTabView.m: fixed a display problem. Tabs on the bottom of the view work, but some display niggles remain. (Note: we beat Apple on this one, heh.) diff --git a/Headers/gnustep/gui/NSDocument.h b/Headers/gnustep/gui/NSDocument.h new file mode 100644 index 000000000..96c1b83a3 --- /dev/null +++ b/Headers/gnustep/gui/NSDocument.h @@ -0,0 +1,134 @@ + +#import +#import + +@class NSArray, NSMutableArray, NSData; +@class NSURL, NSUndoManager; + +@class NSWindow, NSView, NSSavePanel, NSMenuItem, NSPrintInfo, NSPopUpButton, NSFileWrapper; +@class NSDocumentController, NSWindowController; + + +typedef enum _NSDocumentChangeType { + NSChangeDone = 0, + NSChangeUndone = 1, + NSChangeCleared = 2 +} NSDocumentChangeType; + +typedef enum _NSSaveOperationType { + NSSaveOperation = 0, + NSSaveAsOperation = 1, + NSSaveToOperation = 2 +} NSSaveOperationType; + +@interface NSDocument : NSObject +{ + @private + NSWindow *_window; // Outlet for the single window case + NSMutableArray *_windowControllers; // WindowControllers for this document + NSString *_fileName; // Save location + NSString *_fileType; // file/document type + NSPrintInfo *_printInfo; // print info record + long _changeCount; // number of time the document has been changed + NSView *savePanelAccessory; // outlet for the accessory save-panel view + NSPopUpButton *spaButton; // outlet for "the File Format:" button in the save panel. + int _documentIndex; // Untitled index + NSUndoManager *_undoManager; // Undo manager for this document + struct __docFlags { + unsigned int inClose:1; + unsigned int hasUndoManager:1; + unsigned int RESERVED:30; + } _docFlags; + void *_reserved1; +} + +/*" Initialization "*/ +- (id)init; +- (id)initWithContentsOfFile:(NSString *)fileName ofType:(NSString *)fileType; +- (id)initWithContentsOfURL:(NSURL *)url ofType:(NSString *)fileType; + +/*" Window management "*/ +- (NSArray *)windowControllers; +- (void)addWindowController:(NSWindowController *)windowController; +- (BOOL)shouldCloseWindowController:(NSWindowController *)windowController; +- (void)showWindows; + +/*" Window controller creation "*/ +- (void)makeWindowControllers; // Manual creation +- (NSString *)windowNibName; // Automatic creation (Document will be the nib owner) + +/*" Window loading notifications "*/ +// Only called if the document is the owner of the nib +- (void)windowControllerWillLoadNib:(NSWindowController *)windowController; +- (void)windowControllerDidLoadNib:(NSWindowController *)windowController; + +/*" Edited flag "*/ +- (BOOL)isDocumentEdited; +- (void)updateChangeCount:(NSDocumentChangeType)change; + +/*" Display Name (window title) "*/ +- (NSString *)displayName; + +/*" Backup file "*/ +- (BOOL)keepBackupFile; + +/*" Closing "*/ +- (void)close; +- (BOOL)canCloseDocument; + +/*" Type and location "*/ +- (NSString *)fileName; +- (void)setFileName:(NSString *)fileName; +- (NSString *)fileType; +- (void)setFileType:(NSString *)type; ++ (NSArray *)readableTypes; ++ (NSArray *)writableTypes; ++ (BOOL)isNativeType:(NSString *)type; + +/*" Read/Write/Revert "*/ + +- (NSData *)dataRepresentationOfType:(NSString *)type; +- (BOOL)loadDataRepresentation:(NSData *)data ofType:(NSString *)type; + +- (NSFileWrapper *)fileWrapperRepresentationOfType:(NSString *)type; +- (BOOL)loadFileWrapperRepresentation:(NSFileWrapper *)wrapper ofType:(NSString *)type; + +- (BOOL)writeToFile:(NSString *)fileName ofType:(NSString *)type; +- (BOOL)readFromFile:(NSString *)fileName ofType:(NSString *)type; +- (BOOL)revertToSavedFromFile:(NSString *)fileName ofType:(NSString *)type; + +- (BOOL)writeToURL:(NSURL *)url ofType:(NSString *)type; +- (BOOL)readFromURL:(NSURL *)url ofType:(NSString *)type; +- (BOOL)revertToSavedFromURL:(NSURL *)url ofType:(NSString *)type; + +/*" Save panel "*/ +- (BOOL)shouldRunSavePanelWithAccessoryView; +- (NSString *)fileNameFromRunningSavePanelForSaveOperation:(NSSaveOperationType)saveOperation; +- (int)runModalSavePanel:(NSSavePanel *)savePanel withAccessoryView:(NSView *)accessoryView; + +/*" Printing "*/ +- (NSPrintInfo *)printInfo; +- (void)setPrintInfo:(NSPrintInfo *)printInfo; +- (BOOL)shouldChangePrintInfo:(NSPrintInfo *)newPrintInfo; +- (IBAction)runPageLayout:(id)sender; +- (int)runModalPageLayoutWithPrintInfo:(NSPrintInfo *)printInfo; +- (IBAction)printDocument:(id)sender; +- (void)printShowingPrintPanel:(BOOL)flag; + +/*" IB Actions "*/ +- (IBAction)saveDocument:(id)sender; +- (IBAction)saveDocumentAs:(id)sender; +- (IBAction)saveDocumentTo:(id)sender; +- (IBAction)revertDocumentToSaved:(id)sender; + +/*" Menus "*/ +- (BOOL)validateMenuItem:(NSMenuItem *)anItem; + +/*" Undo "*/ +- (NSUndoManager *)undoManager; +- (void)setUndoManager:(NSUndoManager *)undoManager; +- (BOOL)hasUndoManager; +- (void)setHasUndoManager:(BOOL)flag; + +@end + diff --git a/Headers/gnustep/gui/NSDocumentController.h b/Headers/gnustep/gui/NSDocumentController.h new file mode 100644 index 000000000..23c63f0b6 --- /dev/null +++ b/Headers/gnustep/gui/NSDocumentController.h @@ -0,0 +1,74 @@ + +#import +#import + +@class NSArray, NSMutableArray; +@class NSURL; +@class NSMenuItem, NSOpenPanel, NSWindow; +@class NSDocument; + +@interface NSDocumentController : NSObject +{ + @private + NSMutableArray *_documents; + struct __controllerFlags { + unsigned int shouldCreateUI:1; + unsigned int RESERVED:31; + } _controllerFlags; + NSArray *_types; // from info.plist with key NSTypes + void *_reserved1; + void *_reserved2; +} + ++ (id)sharedDocumentController; + +/*" document creation "*/ +// doesn't create the windowControllers +- (id)makeUntitledDocumentOfType:(NSString *)type; +- (id)makeDocumentWithContentsOfFile:(NSString *)fileName ofType:(NSString *)type; +// creates window controllers +- (id)openUntitledDocumentOfType:(NSString*)type display:(BOOL)display; +- (id)openDocumentWithContentsOfFile:(NSString *)fileName display:(BOOL)display; + +#if NS_URL +//- (id)makeDocumentWithContentsOfURL:(NSURL *)url ofType:(NSString *)type; +//- (id)openDocumentWithContentsOfURL:(NSURL *)url display:(BOOL)display; +#endif + +/*" With or without UI "*/ +- (BOOL)shouldCreateUI; +- (void)setShouldCreateUI:(BOOL)flag; + +/*" Actions "*/ +- (IBAction)saveAllDocuments:(id)sender; +- (IBAction)openDocument:(id)sender; +- (IBAction)newDocument:(id)sender; + +/*" Open panel "*/ +#if NS_URL +//- (NSArray *)URLsFromRunningOpenPanel; +#endif +- (NSArray *)fileNamesFromRunningOpenPanel; +- (int)runModalOpenPanel:(NSOpenPanel *)openPanel forTypes:(NSArray *)openableFileExtensions; + +/*" Document management "*/ +- (BOOL)closeAllDocuments; +- (BOOL)reviewUnsavedDocumentsWithAlertTitle:(NSString *)title cancellable:(BOOL)cancellable; +- (NSArray *)documents; +- (BOOL)hasEditedDocuments; +- (id)currentDocument; +- (NSString *)currentDirectory; +- (id)documentForWindow:(NSWindow *)window; +- (id)documentForFileName:(NSString *)fileName; + +/*" Menu validation "*/ +- (BOOL)validateMenuItem:(NSMenuItem *)anItem; + +/*" Types and extensions "*/ +- (NSString *)displayNameForType:(NSString *)type; +- (NSString *)typeFromFileExtension:(NSString *)fileExtension; +- (NSArray *)fileExtensionsFromType:(NSString *)type; +- (Class)documentClassForType:(NSString *)type; + +@end + diff --git a/Headers/gnustep/gui/NSDocumentFrameworkPrivate.h b/Headers/gnustep/gui/NSDocumentFrameworkPrivate.h new file mode 100644 index 000000000..708bc7f28 --- /dev/null +++ b/Headers/gnustep/gui/NSDocumentFrameworkPrivate.h @@ -0,0 +1,26 @@ + +#import "NSDocumentController.h" + +@interface NSDocumentController (Private) +- (NSArray *)_editorAndViewerTypesForClass:(Class)documentClass; +- (NSArray *)_editorTypesForClass:(Class)fp12; +- (NSArray *)_exportableTypesForClass:(Class)documentClass; +- (void)_removeDocument:(NSDocument *)document; +@end + + +#import "NSDocument.h" + +@interface NSDocument (Private) +- (void)_removeWindowController:(NSWindowController *)controller; +- (NSWindow *)_transferWindowOwnership; +@end + + +#import "NSWindowController.h" + +@interface NSWindowController (Private) +- (void)_windowDidLoad; +- (void)_synchronizeWindowTitleWithDocumentName; +- (void)setWindow:(NSWindow *)window; +@end diff --git a/Headers/gnustep/gui/NSWindow.h b/Headers/gnustep/gui/NSWindow.h index ae3c39ffa..64a11f834 100644 --- a/Headers/gnustep/gui/NSWindow.h +++ b/Headers/gnustep/gui/NSWindow.h @@ -56,6 +56,7 @@ @class NSScreen; @class NSText; @class NSView; +@class NSWindowController; enum { NSNormalWindowLevel = 0, @@ -134,6 +135,8 @@ extern NSSize NSTokenSize; BOOL menu_exclude; BOOL hides_on_deactivate; BOOL accepts_mouse_moved; + + NSWindowController *_windowController; /* Reserved for back-end use */ void *be_wind_reserved; @@ -218,6 +221,12 @@ extern NSSize NSTokenSize; - (NSText *) fieldEditor: (BOOL)createFlag forObject: anObject; +/* + * The window controller + */ +- (void)setWindowController:(NSWindowController *)windowController; +- (id)windowController; + /* * Window status and ordering */ diff --git a/Headers/gnustep/gui/NSWindowController.h b/Headers/gnustep/gui/NSWindowController.h new file mode 100644 index 000000000..49b45c8a1 --- /dev/null +++ b/Headers/gnustep/gui/NSWindowController.h @@ -0,0 +1,50 @@ + +#import +#import + +@class NSWindow, NSDocument, NSArray; + +@interface NSWindowController : NSObject +{ + @private + NSWindow *_window; + NSString *_windowNibName; + NSString *_windowFrameAutosaveName; + NSDocument *_document; + NSArray *_topLevelObjects; + id _owner; + struct ___wcFlags { + unsigned int shouldCloseDocument:1; + unsigned int shouldCascade:1; + unsigned int nibIsLoaded:1; + unsigned int RESERVED:29; + } _wcFlags; + void *_reserved1; + void *_reserved2; +} + +- (id)initWithWindowNibName:(NSString *)windowNibName; // self is the owner +- (id)initWithWindowNibName:(NSString *)windowNibName owner:(id)owner; +- (id)initWithWindow:(NSWindow *)window; + +- (NSString *)windowNibName; +- (id)owner; +- (void)setDocument:(NSDocument *)document; +- (id)document; +- (void)setWindowFrameAutosaveName:(NSString *)name; +- (NSString *)windowFrameAutosaveName; +- (void)setShouldCloseDocument:(BOOL)flag; +- (BOOL)shouldCloseDocument; +- (void)setShouldCascadeWindows:(BOOL)flag; +- (BOOL)shouldCascadeWindows; +- (void)close; +- (NSWindow *)window; +- (IBAction)showWindow:(id)sender; +- (NSString *)windowTitleForDocumentDisplayName:(NSString *)displayName; +- (BOOL)isWindowLoaded; +- (void)windowDidLoad; +- (void)windowWillLoad; +- (void)loadWindow; + +@end + diff --git a/Source/GNUmakefile b/Source/GNUmakefile index 3eb3aa480..451d1c958 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -65,6 +65,8 @@ NSCustomImageRep.m \ NSDataLink.m \ NSDataLinkManager.m \ NSDataLinkPanel.m \ +NSDocument.m \ +NSDocumentController.m \ NSEPSImageRep.m \ NSEvent.m \ NSFileWrapper.m \ @@ -125,6 +127,7 @@ NSTextStorage.m \ NSTextView.m \ NSView.m \ NSWindow.m \ +NSWindowController.m \ NSWorkspace.m \ GSComboSupport.m \ GSHelpManagerPanel.m \ @@ -171,6 +174,9 @@ AppKit/NSCustomImageRep.h \ AppKit/NSDataLink.h \ AppKit/NSDataLinkManager.h \ AppKit/NSDataLinkPanel.h \ +AppKit/NSDocument.h \ +AppKit/NSDocumentController.h \ +AppKit/NSDocumentFrameworkPrivate.h \ AppKit/NSDragging.h \ AppKit/NSEPSImageRep.h \ AppKit/NSEvent.h \ @@ -239,12 +245,14 @@ AppKit/NSTextStorage.h \ AppKit/NSTextView.h \ AppKit/NSView.h \ AppKit/NSWindow.h \ +AppKit/NSWindowController.h \ AppKit/NSWorkspace.h \ AppKit/GSTrackingRect.h \ AppKit/nsimage-tiff.h \ AppKit/GSMethodTable.h \ AppKit/DPSOperators.h \ AppKit/PSOperators.h \ +AppKit/GSHelpManagerPanel.h \ AppKit/GSPasteboardServer.h \ AppKit/GSServicesManager.h \ AppKit/GSWraps.h diff --git a/Source/NSApplication.m b/Source/NSApplication.m index cb2ed8e36..9645fa491 100644 --- a/Source/NSApplication.m +++ b/Source/NSApplication.m @@ -52,6 +52,7 @@ #include #include +#include #include #include #include @@ -217,6 +218,10 @@ NSApplication *NSApp = nil; { [delegate application: self openFile: filePath]; } + else + { + [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:filePath display:YES]; + } } else if ((filePath = [defs stringForKey: @"GSTempPath"]) != nil) { @@ -224,6 +229,10 @@ NSApplication *NSApp = nil; { [delegate application: self openTempFile: filePath]; } + else + { + [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:filePath display:YES]; + } } } @@ -1448,6 +1457,9 @@ NSAssert([event retainCount] > 0, NSInternalInconsistencyException); if ([delegate respondsToSelector: @selector(applicationShouldTerminate:)]) shouldTerminate = [delegate applicationShouldTerminate: sender]; + else + shouldTerminate = [[NSDocumentController sharedDocumentController] reviewUnsavedDocumentsWithAlertTitle:@"Quit" cancellable:YES]; + if (shouldTerminate) { app_should_quit = YES; diff --git a/Source/NSDocument.m b/Source/NSDocument.m new file mode 100644 index 000000000..5aaa3088b --- /dev/null +++ b/Source/NSDocument.m @@ -0,0 +1,653 @@ +#import +#import +#import +#import +#import +#import +#import +#import +#import + + +@implementation NSDocument + ++ (void)initialize +{ +} + ++ (NSArray *)readableTypes +{ + return [[NSDocumentController sharedDocumentController] + _editorAndViewerTypesForClass:self]; +} + ++ (NSArray *)writableTypes +{ + return [[NSDocumentController sharedDocumentController] _editorTypesForClass:self]; +} + ++ (BOOL)isNativeType:(NSString *)type +{ + return ([[self readableTypes] containsObject:type] && + [[self writableTypes] containsObject:type]); +} + + +- (id)init +{ + static int untitledCount = 1; + + [super init]; + _documentIndex = untitledCount++; + return self; +} + +- (id)initWithContentsOfFile:(NSString *)fileName ofType:(NSString *)fileType +{ + [super init]; + + if ([self readFromFile:fileName ofType:fileType]) + { + [self setFileType:fileType]; + [self setFileName:fileName]; + } + else + { + [self release]; + return nil; + } + + return self; +} + +- (id)initWithContentsOfURL:(NSURL *)url ofType:(NSString *)fileType +{ + [super init]; + + if ([self readFromURL:url ofType:fileType]) + { + [self setFileType:fileType]; + [self setFileName:[url path]]; + } + else + { + [self release]; + return nil; + } + + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [(NSObject*)_undoManager release]; + [_fileName release]; + [_fileType release]; + [_windowControllers release]; + [_window release]; + [_printInfo release]; + [savePanelAccessory release]; + [spaButton release]; + [super dealloc]; +} + +- (NSString *)fileName +{ + return _fileName; +} + +- (void)setFileName:(NSString *)fileName +{ + [fileName retain]; + [_fileName release]; + _fileName = fileName; + + [_windowControllers makeObjectsPerformSelector: + @selector(_synchronizeWindowTitleWithDocumentName)]; +} + +- (NSString *)fileType +{ + return _fileType; +} + +- (void)setFileType:(NSString *)type +{ + [type retain]; + [_fileType release]; + _fileType = type; +} + +- (NSArray *)windowControllers +{ + return _windowControllers; +} + +- (void)addWindowController:(NSWindowController *)windowController +{ + if (_windowControllers == nil) _windowControllers = [[NSMutableArray alloc] init]; + + [_windowControllers addObject:windowController]; + if ([windowController document] != self) + [windowController setDocument:self]; +} + +- (void)_removeWindowController:(NSWindowController *)windowController +{ + if ([_windowControllers containsObject:windowController]) + { + BOOL autoClose = [windowController shouldCloseDocument]; + + [windowController setDocument:nil]; + [_windowControllers removeObject:windowController]; + + if (autoClose || [_windowControllers count] == 0) + { + [self close]; + } + } +} + +- (NSString *)windowNibName +{ + return nil; +} + +// private; called during nib load. // we do not retain the window, since it should +// already have a retain from the nib. +- (void)setWindow:(NSWindow *)window +{ + _window = window; +} + +/* + * This private method is used to transfer window ownership to the + * NSWindowController in situations (such as the default) where the + * document is set to the nib owner, and thus owns the window immediately + * following the loading of the nib. + */ +- (NSWindow *)_transferWindowOwnership +{ + NSWindow *window = _window; + _window = nil; + return [window autorelease]; +} + +- (void)makeWindowControllers +{ + NSString *name = [self windowNibName]; + + if ([name length] > 0) + { + NSWindowController *controller; + controller = [[NSWindowController alloc] initWithWindowNibName:name owner:self]; + [self addWindowController:controller]; + [controller release]; + } + else + { + [NSException raise:NSInternalInconsistencyException + format:@"%@ must override either -windowNibName or -makeWindowControllers", + NSStringFromClass([self class])]; + } +} + +- (void)showWindows +{ + [_windowControllers makeObjectsPerformSelector:@selector(showWindow:) withObject:self]; +} + +- (BOOL)isDocumentEdited +{ + return _changeCount != 0; +} + +- (void)updateChangeCount:(NSDocumentChangeType)change +{ + int i, count = [_windowControllers count]; + BOOL isEdited; + + switch (change) + { + case NSChangeDone: _changeCount++; break; + case NSChangeUndone: _changeCount--; break; + case NSChangeCleared: _changeCount = 0; break; + } + + /* + * NOTE: Apple's implementation seems to not call -isDocumentEdited + * here but directly checks to see if _changeCount == 0. It seems it + * would be better to call the method in case it's overridden by a + * subclass, but we may want to keep Apple's behavior. + */ + isEdited = [self isDocumentEdited]; + + for (i=0; i 0) + [savePanel setRequiredFileType:[extensions objectAtIndex:0]]; + + switch (saveOperation) + { + // FIXME -- localize. + case NSSaveOperation: title = @"Save"; break; + case NSSaveAsOperation: title = @"Save As"; break; + case NSSaveToOperation: title = @"Save To"; break; + } + + [savePanel setTitle:title]; + if ([self fileName]) + [savePanel setDirectory:[[self fileName] stringByDeletingLastPathComponent]]; + + if ([self runModalSavePanel:savePanel withAccessoryView:accessory]) + { + return [savePanel filename]; + } + + return nil; +} + +- (BOOL)shouldChangePrintInfo:(NSPrintInfo *)newPrintInfo +{ + return YES; +} + +- (NSPrintInfo *)printInfo +{ + return _printInfo? _printInfo : [NSPrintInfo sharedPrintInfo]; +} + +- (void)setPrintInfo:(NSPrintInfo *)printInfo +{ + [printInfo retain]; + [_printInfo release]; + _printInfo = printInfo; +} + + + // Page layout panel (Page Setup) + +- (int)runModalPageLayoutWithPrintInfo:(NSPrintInfo *)printInfo +{ + return [[NSPageLayout pageLayout] runModalWithPrintInfo:printInfo]; +} + +- (IBAction)runPageLayout:(id)sender +{ + NSPrintInfo *printInfo = [self printInfo]; + + if ([self runModalPageLayoutWithPrintInfo:printInfo] && + [self shouldChangePrintInfo:printInfo]) + { + [self setPrintInfo:printInfo]; + [self updateChangeCount:NSChangeDone]; + } +} + +/* This is overridden by subclassers; the default implementation does nothing. */ +- (void)printShowingPrintPanel:(BOOL)flag +{ +} + +- (IBAction)printDocument:(id)sender +{ + [self printShowingPrintPanel:YES]; +} + +- (BOOL)validateMenuItem:(NSMenuItem *)anItem +{ + if ([anItem action] == @selector(revertDocumentToSaved:)) + return ([self fileName] != nil && [self isDocumentEdited]); + + // FIXME should validate spa popup items; return YES if it's a native type. + + return YES; +} + +- (NSString *)saveFileType +{ + // FIXME this should return type picked on save accessory + // return [spaPopupButton title]; + return [self fileType]; +} + +- (void)_doSaveAs:(NSSaveOperationType)saveOperation +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSString *filename = [self fileName]; + NSString *backupFilename = nil; + + if (filename == nil || saveOperation != NSSaveOperation) + { + filename = [self fileNameFromRunningSavePanelForSaveOperation:saveOperation]; + if (saveOperation == NSSaveOperation) saveOperation = NSSaveAsOperation; + } + + if (filename) + { + if ([fileManager fileExistsAtPath:filename]) + { + NSString *extension = [filename pathExtension]; + + backupFilename = [filename stringByDeletingPathExtension]; + backupFilename = [backupFilename stringByAppendingString:@"~"]; + backupFilename = [backupFilename stringByAppendingPathExtension:extension]; + + /* Save panel has already asked if the user wants to replace it */ + + /* NSFileManager movePath: will fail if destination exists */ + if ([fileManager fileExistsAtPath:backupFilename]) + [fileManager removeFileAtPath:backupFilename handler:nil]; + + // Move or copy? + if (![fileManager movePath:filename toPath:backupFilename handler:nil] && + [self keepBackupFile]) + { + //FIXME -- localize. + int result = NSRunAlertPanel(@"File Error", + @"Can't create backup file. Save anyways?", + @"Save", @"Cancel", nil); + + if (result != NSAlertDefaultReturn) return; + } + } + if ([self writeToFile:filename ofType:[self saveFileType]]) + { + if (saveOperation != NSSaveToOperation) + { + [self setFileName:filename]; + [self setFileType:[self saveFileType]]; + [self updateChangeCount:NSChangeCleared]; + } + + if (backupFilename && ![self keepBackupFile]) + { + [fileManager removeFileAtPath:backupFilename handler:nil]; + } + } + } +} + +- (IBAction)saveDocument:(id)sender +{ + [self _doSaveAs:NSSaveOperation]; +} + +- (IBAction)saveDocumentAs:(id)sender +{ + [self _doSaveAs:NSSaveAsOperation]; +} + +- (IBAction)saveDocumentTo:(id)sender +{ + [self _doSaveAs:NSSaveToOperation]; +} + +- (IBAction)revertDocumentToSaved:(id)sender +{ + int result; + + //FIXME -- localize. + result = NSRunAlertPanel(@"Revert", + @"%@ has been edited. Are you sure you want to undo changes?", + @"Revert", @"Cancel", nil, [self displayName]); + + if (result == NSAlertDefaultReturn && + [self revertToSavedFromFile:[self fileName] ofType:[self fileType]]) + { + [self updateChangeCount:NSChangeCleared]; + } +} + +- (void)close +{ + // We have an _docFlags.inClose flag, but I don't think we need to use it. + [_windowControllers makeObjectsPerformSelector:@selector(close)]; + [[NSDocumentController sharedDocumentController] _removeDocument:self]; +} + +- (void)windowControllerWillLoadNib:(NSWindowController *)windowController {} +- (void)windowControllerDidLoadNib:(NSWindowController *)windowController {} + +- (NSUndoManager *)undoManager +{ + if (_undoManager == nil && [self hasUndoManager]) + { + [self setUndoManager:[[[NSUndoManager alloc] init] autorelease]]; + } + + return _undoManager; +} + +- (void)setUndoManager:(NSUndoManager *)undoManager +{ + if (undoManager != _undoManager) + { + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + + if (_undoManager) + { + [center removeObserver:self + name:NSUndoManagerWillCloseUndoGroupNotification + object:_undoManager]; + [center removeObserver:self + name:NSUndoManagerDidUndoChangeNotification + object:_undoManager]; + [center removeObserver:self + name:NSUndoManagerDidRedoChangeNotification + object:_undoManager]; + } + + [(NSObject*)undoManager retain]; + [(NSObject*)_undoManager release]; + _undoManager = undoManager; + + if (_undoManager == nil) + { + [self setHasUndoManager:NO]; + } + else + { + [center addObserver:self + selector:@selector(_changeWasDone:) + name:NSUndoManagerWillCloseUndoGroupNotification + object:_undoManager]; + [center addObserver:self + selector:@selector(_changeWasUndone:) + name:NSUndoManagerDidUndoChangeNotification + object:_undoManager]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(_changeWasRedone:) + name:NSUndoManagerDidRedoChangeNotification + object:_undoManager]; + } + } +} + +- (BOOL)hasUndoManager +{ + return _docFlags.hasUndoManager; +} + +- (void)setHasUndoManager:(BOOL)flag +{ + if (_undoManager && !flag) + [self setUndoManager:nil]; + + _docFlags.hasUndoManager = flag; +} + +- (void)_changeWasDone:(NSNotification *)notification +{ [self updateChangeCount:NSChangeDone]; +} +- (void)_changeWasUndone:(NSNotification *)notification +{ [self updateChangeCount:NSChangeUndone]; +} +- (void)_changeWasRedone:(NSNotification *)notification +{ [self updateChangeCount:NSChangeDone]; +} + +@end + diff --git a/Source/NSDocumentController.m b/Source/NSDocumentController.m new file mode 100644 index 000000000..40cc24fef --- /dev/null +++ b/Source/NSDocumentController.m @@ -0,0 +1,528 @@ + + +#import +#import +#import +#import +#import +#import + + +static NSString *NSTypesKey = @"NSTypes"; +static NSString *NSNameKey = @"NSName"; +static NSString *NSRoleKey = @"NSRole"; +static NSString *NSHumanReadableNameKey = @"NSHumanReadableName"; +static NSString *NSUnixExtensionsKey = @"NSUnixExtensions"; +static NSString *NSDOSExtensionsKey = @"NSDOSExtensions"; +static NSString *NSMacOSTypesKey = @"NSMacOSTypes"; +static NSString *NSMIMETypesKey = @"NSMIMETypes"; +static NSString *NSDocumentClassKey = @"NSDocumentClass"; + +#define TYPE_INFO(name) TypeInfoForName(_types, name) + +static NSDictionary *TypeInfoForName(NSArray *types, NSString *typeName) +{ + int i, count = [types count]; + for (i=0; i #include +#include #include +#include #include #include #include @@ -476,6 +478,19 @@ static NSRecursiveLock *windowsLock; return _fieldEditor; } +/* + * Window controller + */ +- (void)setWindowController:(NSWindowController *)windowController +{ + ASSIGN(_windowController, windowController); +} + +- (id)windowController +{ + return _windowController; +} + /* * Window status and ordering */ @@ -1942,7 +1957,18 @@ static NSRecursiveLock *windowsLock; - (BOOL) windowShouldClose: (id)sender { if ([delegate respondsToSelector: @selector(windowShouldClose:)]) - return [delegate windowShouldClose: sender]; + { + BOOL ourReturn; + + ourReturn = [delegate windowShouldClose: sender]; + + if (ourReturn) + { + ourReturn = [[_windowController document] shouldCloseWindowController: _windowController]; + } + + return ourReturn; + } else return YES; } diff --git a/Source/NSWindowController.m b/Source/NSWindowController.m new file mode 100644 index 000000000..fc2d29fe8 --- /dev/null +++ b/Source/NSWindowController.m @@ -0,0 +1,302 @@ +#import +#import +#import +#import + +@implementation NSWindowController + +- (id)initWithWindowNibName:(NSString *)windowNibName +{ + return [self initWithWindowNibName:windowNibName owner:self]; +} + +- (id)initWithWindowNibName:(NSString *)windowNibName owner:(id)owner +{ + [self initWithWindow:nil]; + _windowNibName = [windowNibName retain]; + _owner = owner; + return self; +} + +- (id)initWithWindow:(NSWindow *)window +{ + [super init]; + + _window = [window retain]; + _windowFrameAutosaveName = @""; + _wcFlags.shouldCascade = YES; + _wcFlags.shouldCloseDocument = NO; + + if (_window) + { + [self _windowDidLoad]; + } + + return self; +} + +- (id)init +{ + return [self initWithWindowNibName:nil]; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [_windowNibName release]; + [_windowFrameAutosaveName release]; + [_topLevelObjects release]; + [_window autorelease]; + [super dealloc]; +} + +- (NSString *)windowNibName +{ + return _windowNibName; +} + +- (id)owner +{ + return _owner; +} + +- (void)setDocument:(NSDocument *)document +{ + _document = document; + [self _synchronizeWindowTitleWithDocumentName]; +} + +- (id)document +{ + return _document; +} + +- (void)setWindowFrameAutosaveName:(NSString *)name +{ + [name retain]; + [_windowFrameAutosaveName release]; + _windowFrameAutosaveName = name; + + if ([self isWindowLoaded]) + { + [[self window] setFrameAutosaveName:name? name : @""]; + } +} + +- (NSString *)windowFrameAutosaveName; +{ + return _windowFrameAutosaveName; +} + +- (void)setShouldCloseDocument:(BOOL)flag +{ + _wcFlags.shouldCloseDocument = flag; +} + +- (BOOL)shouldCloseDocument +{ + return _wcFlags.shouldCloseDocument; +} + +- (void)setShouldCascadeWindows:(BOOL)flag +{ + _wcFlags.shouldCascade = flag; +} + +- (BOOL)shouldCascadeWindows +{ + return _wcFlags.shouldCascade; +} + +- (void)close +{ + [_window close]; +} + + +- (void)_windowWillClose:(NSNotification *)notification +{ + if ([notification object] == _window) + { + if ([_window delegate] == self) [_window setDelegate:nil]; + if ([_window windowController] == self) [_window setWindowController:nil]; + + /* + * If the window is set to isReleasedWhenClosed, it will release + * itself, so nil out our reference so we don't release it again. + * We may want to unilaterally turn off the setting in the NSWindow + * instance so it doesn't cause problems. + * + * Apple's implementation doesn't seem to deal with this case, and + * crashes if isReleaseWhenClosed is set. + */ + if ([_window isReleasedWhenClosed]) + { + _window = nil; + } + + [_document _removeWindowController:self]; + } +} + +- (NSWindow *)window +{ + if (_window == nil && ![self isWindowLoaded]) + { + // Do all the notifications. Yes, the docs say this should + // be implemented here instead of in -loadWindow itself. + [self windowWillLoad]; + if ([_document respondsToSelector:@selector(windowControllerWillLoadNib:)]) + [_document windowControllerWillLoadNib:self]; + + [self loadWindow]; + + [self _windowDidLoad]; + if ([_document respondsToSelector:@selector(windowControllerDidLoadNib:)]) + [_document windowControllerDidLoadNib:self]; + } + + return _window; +} + +// Private method; the nib loading will call this. +- (void)setWindow:(NSWindow *)aWindow +{ + [_window autorelease]; + _window = [aWindow retain]; +} + +- (IBAction)showWindow:(id)sender +{ + NSWindow *window = [self window]; + + if ([window isKindOfClass:[NSPanel class]] && [(NSPanel*)window becomesKeyOnlyIfNeeded]) + [window orderFront:sender]; + else + [window makeKeyAndOrderFront:sender]; +} + +- (NSString *)windowTitleForDocumentDisplayName:(NSString *)displayName +{ + return displayName; +} + +- (void)_synchronizeWindowTitleWithDocumentName +{ + if (_document) + { + NSString *filename = [_document fileName]; + NSString *displayName = [_document displayName]; + NSString *title = [self windowTitleForDocumentDisplayName:displayName]; + + /* If they just want to display the filename, use the fancy method */ + if (filename != nil && [title isEqualToString:filename]) + { + [_window setTitleWithRepresentedFilename:filename]; + } + else + { + if (filename) [_window setRepresentedFilename:filename]; + [_window setTitle:title]; + } + } +} + +- (BOOL)isWindowLoaded +{ + return _wcFlags.nibIsLoaded; +} + +- (void)windowDidLoad +{ +} + +- (void)windowWillLoad +{ +} + +- (void)_windowDidLoad +{ + _wcFlags.nibIsLoaded = YES; + + [_window setWindowController:self]; + + [self _synchronizeWindowTitleWithDocumentName]; + + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(_windowWillClose:) + name:NSWindowWillCloseNotification + object:_window]; + + /* Make sure window sizes itself right */ + if ([_windowFrameAutosaveName length] > 0) + { + [_window setFrameUsingName:_windowFrameAutosaveName]; + [_window setFrameAutosaveName:_windowFrameAutosaveName]; + } + + if ([self shouldCascadeWindows]) + { + static NSPoint nextWindowLocation = { 0.0, 0.0 }; + static BOOL firstWindow = YES; + + if (firstWindow) + { + NSRect windowFrame = [_window frame]; + + /* Start with the frame as set */ + nextWindowLocation = NSMakePoint(NSMinX(windowFrame), NSMaxY(windowFrame)); + firstWindow = NO; + } + else + { + /* + * cascadeTopLeftFromPoint will "wrap" the point back to the + * top left if the normal cascading will cause the window to go + * off the screen. In Apple's implementation, this wraps to the + * extreme top of the screen, and offset only a small amount + * from the left. + */ + nextWindowLocation = [_window cascadeTopLeftFromPoint:nextWindowLocation]; + } + } + + [self windowDidLoad]; +} + +- (void)loadWindow +{ + if ([self isWindowLoaded]) return; + + if ([NSBundle loadNibNamed:_windowNibName owner:_owner]) + { + _wcFlags.nibIsLoaded = YES; + + if (_window == nil && _document && _owner == _document) + [self setWindow:[_document _transferWindowOwnership]]; + } + else + { + NSLog(@"%@: could not load nib named %@.nib", [self class], _windowNibName); + } +} + +/* + * There's no way I'll ever get these compatible if Apple's versions + * actually encode anything, sigh + */ +- initWithCoder:(NSCoder *)coder +{ + return [self init]; +} + +- (void)encodeWithCoder:(NSCoder *)coder +{ + // What are we supposed to encode? Window nib name? Or should these + // be empty, just to conform to NSCoding, so we do an -init on + // unarchival. ? +} + +@end +