home *** CD-ROM | disk | FTP | other *** search
/ OpenStep 4.2J (Developer) / os42jdev.iso / NextDeveloper / Examples / AppKit / Yap / Document.m < prev    next >
Text File  |  1996-08-30  |  17KB  |  484 lines

  1. /*
  2.  *  Document.m
  3.  *  Author: Ali Ozer
  4.  *  Created: Jan 28, 1995 for TextEdit
  5.  *  Modified: Apr 96
  6.  *
  7.  *  A generic text document class.
  8.  *
  9.  *  You may freely copy, distribute and reuse the code in this example.
  10.  *  NeXT disclaims any warranty of any kind, expressed or implied,
  11.  *  as to its fitness for any particular use.
  12.  */
  13.  
  14. #import <AppKit/AppKit.h>
  15. #import "Document.h"
  16. #import "TextFinder.h"
  17. #import "Preferences.h"
  18.  
  19. @implementation Document
  20.  
  21. /* Creates an untitled document. Doesn't bring the window up, but does position it correctly.
  22. */   
  23. - (id)init {
  24.     static NSPoint cascadePoint = {0.0, 0.0};
  25.  
  26.     if (!(self = [super init])) return nil;
  27.  
  28.     if (![NSBundle loadNibNamed:@"Document" owner:self])  {
  29.         NSLog(@"Failed to load Document.nib");
  30.         [self release];
  31.         return nil;
  32.     }
  33.  
  34.     if (NSEqualPoints(cascadePoint, NSZeroPoint)) {    /* First time through... */
  35.         NSRect frame = [[self window] frame];
  36.         cascadePoint = NSMakePoint(frame.origin.x, NSMaxY(frame));
  37.     }
  38.     cascadePoint = [[self window] cascadeTopLeftFromPoint:cascadePoint];
  39.     [[self window] setDelegate:self];
  40.     [[self textView] setDelegate:self];
  41.  
  42.     return self;
  43. }
  44.  
  45. /* Creates a document from the specified path. Doesn't bring the window up, but does position it correctly.
  46. */   
  47. - (id)initWithPath:(NSString *)filename {
  48.     if (!(self = [self init])) return nil;
  49.     if (filename && ![self loadFromPath:filename]) {
  50.         [self release];
  51.         return nil;
  52.     }
  53.     if (filename) {
  54.     [[self class] setLastOpenSavePanelDirectory:[filename stringByDeletingLastPathComponent]];
  55.     }
  56.     [[self textView] setSelectedRange:NSMakeRange(0, 0)];
  57.     [self setDocumentName:filename];
  58.     return self;
  59. }
  60.  
  61. /* Should create a new zone... 
  62. */
  63. + (Document *)newUntitled {
  64.     Document *document = [[self alloc] initWithPath:nil];
  65.     if (document) {     
  66.         [[document window] makeKeyAndOrderFront:nil];
  67.     }
  68.     return document;
  69. }
  70.  
  71. /* Should create a new zone...
  72. */
  73. + (Document *)newWithPath:(NSString *)filename {
  74.     Document *document = [self documentForPath:filename];
  75.     if (!document) {
  76.         document =  [[self alloc] initWithPath:filename];
  77.     }
  78.     if (document) {     
  79.         [[document window] makeKeyAndOrderFront:nil];
  80.     }
  81.     return document;
  82. }
  83.     
  84. /* Clear the delegates of the text views and window, then release all resources and go away...
  85. */
  86. - (void)dealloc {
  87.     [[self textView] setDelegate:nil];
  88.     [[self window] setDelegate:nil];
  89.     [documentName release];
  90.     
  91.     [super dealloc];
  92. }
  93.  
  94. /* A dummy outlet (without an actual instance variable)
  95. */   
  96. - (void)setScrollView:(id)anObject {
  97. #ifdef WIN32
  98.     [anObject setBorderType:NSBezelBorder];
  99. #endif
  100. }
  101.  
  102. /* Close all documents, prompting the user if necessary. Returns NO if the user cancels the operation.
  103. */
  104. + (BOOL)closeAllDocuments {
  105.    NSArray *windows = [[NSApplication sharedApplication] windows];
  106.    unsigned count = [windows count];
  107.    BOOL needsSaving = NO;
  108.  
  109.    // Determine if there are any unsaved documents...
  110.  
  111.    while (!needsSaving && count--) {
  112.        NSWindow *window = [windows objectAtIndex:count];
  113.        Document *document = [self documentForWindow:window];
  114.        if (document && [document isDocumentEdited]) needsSaving = YES;
  115.    }
  116.    if (needsSaving) {
  117.        int choice = NSRunAlertPanel(NSLocalizedString(@"Quit", @"Title of alert panel which comes up when user chooses Quit and there are unsaved documents."),
  118.                        NSLocalizedString(@"You have unsaved documents.", @"Message in the alert panel which comes up when user chooses Quit and there are unsaved documents."),
  119.                        NSLocalizedString(@"Review Unsaved", @"Choice (on a button) given to user which allows him/her to review all unsaved documents if he/she quits the application without saving them all first."),
  120.                        NSLocalizedString(@"Quit Anyway", @"Choice (on a button) given to user which allows him/her to quit the application even though there are unsaved documents."),
  121.                        NSLocalizedString(@"Cancel", @"Button choice allowing user to cancel quit"));
  122.        if (choice == NSAlertOtherReturn)  {        /* Cancel */
  123.            return NO;
  124.        } else if (choice == NSAlertAlternateReturn) {    /* Quit anyway */
  125.            return YES;
  126.        } else {    /* Review unsaved */
  127.            count = [windows count];
  128.            while (count--) {
  129.                NSWindow *window = [windows objectAtIndex:count];
  130.                Document *document = [self documentForWindow:window];
  131.                if (document) {
  132.                    [window makeKeyAndOrderFront:nil];
  133.                    if (![document canCloseDocument]) {
  134.                        return NO;
  135.                    }            
  136.                }
  137.            }
  138.        }
  139.    }
  140.    return YES;
  141. }
  142.  
  143. /* Saves all open documents... Returns NO if the user cancels out of the save.
  144. */
  145. + (BOOL)saveAllDocuments {
  146.     NSArray *windows = [NSApp windows];
  147.     unsigned count = [windows count];
  148.     while (count--) {
  149.         NSWindow *window = [windows objectAtIndex:count];
  150.         Document *document = [self documentForWindow:window];
  151.         if (document) {
  152.             if ([document isDocumentEdited]) {
  153.                 if (![document saveDocument:NO]) return NO;
  154.             }
  155.         }
  156.     }
  157.     return YES;
  158. }
  159.  
  160. - (NSTextView *)textView {
  161.     return textView;
  162. }
  163.  
  164. - (NSWindow *)window {
  165.     return [[self textView] window];
  166. }
  167.  
  168. + (NSString *)cleanedUpPath:(NSString *)filename {
  169.     NSString *resolvedSymlinks = [filename stringByResolvingSymlinksInPath];
  170.     if ([resolvedSymlinks length] > 0) {
  171.         NSString *standardized = [resolvedSymlinks stringByStandardizingPath];
  172.         return [standardized length] ? standardized : resolvedSymlinks;
  173.     }
  174.     return filename;
  175. }
  176.  
  177. - (void)setDocumentName:(NSString *)filename {
  178.     [documentName autorelease];
  179.     if (filename) {
  180.         documentName = [[filename stringByResolvingSymlinksInPath] copyWithZone:[self zone]];
  181.         [[self window] setTitleWithRepresentedFilename:documentName];
  182.     } else {
  183.         [[self window] setTitle:NSLocalizedString(@"UNTITLED", @"Name of new, untitled document")];
  184.         documentName = nil;
  185.     }
  186. }
  187.  
  188. - (NSString *)documentName {
  189.     return documentName;
  190. }
  191.  
  192. - (void)setDocumentEdited:(BOOL)flag {
  193.     if (flag != isDocumentEdited) {
  194.         isDocumentEdited = flag;
  195.         [[self window] setDocumentEdited:isDocumentEdited];
  196.     }
  197. }
  198.  
  199. - (BOOL)isDocumentEdited {
  200.     return isDocumentEdited;
  201. }
  202.  
  203. /**** User commands... ****/
  204.  
  205. - (void)printDocument:(id)sender {
  206.     NSPrintInfo *printInfo = [[NSPrintInfo sharedPrintInfo] copy];
  207.     [printInfo setHorizontalPagination:NSFitPagination];
  208.     [printInfo setHorizontallyCentered:NO];
  209.     [printInfo setVerticallyCentered:NO];
  210.     [[NSPrintOperation printOperationWithView:[self textView] printInfo:printInfo] runOperation];
  211.     [printInfo release];
  212. }
  213.  
  214. - (void)revert:(id)sender {
  215.     if (documentName) {
  216.         NSString *fileName = [documentName lastPathComponent];
  217.         int choice = NSRunAlertPanel(NSLocalizedString(@"Revert", @"Title of alert confirming revert"), 
  218.                 NSLocalizedString(@"Revert to saved version of %@?", @"Message confirming revert of specified document name."), 
  219.                 NSLocalizedString(@"OK", @"OK."), NSLocalizedString(@"Cancel", @"Button choice allowing user to cancel."), nil, fileName);
  220.         if (choice == NSAlertDefaultReturn) {
  221.             if (![self loadFromPath:documentName]) {
  222.                 (void)NSRunAlertPanel(NSLocalizedString(@"Couldn't Revert", @"Title of alert indicating file couldn't be reverted"), 
  223.                 NSLocalizedString(@"Couldn't revert to saved version of %@.", @"Message indicating file couldn't be reverted."), 
  224.                 NSLocalizedString(@"OK", @"OK."), nil, nil, documentName);
  225.             } else {
  226.                 [self setDocumentEdited:NO];
  227.             }
  228.         }
  229.     }
  230. }
  231.  
  232. - (void)close:(id)sender {
  233.     [[self window] close];
  234. }
  235.  
  236. - (void)saveTo:(id)sender { /* ??? Not correct */
  237.     [self saveAs:sender];
  238. }
  239.  
  240. - (void)saveAs:(id)sender {
  241.     (void)[self saveDocument:YES];
  242. }
  243.  
  244. - (void)save:(id)sender {
  245.     (void)[self saveDocument:NO];
  246. }
  247.  
  248. /**** Find submenu commands ****/
  249.  
  250. - (void)orderFrontFindPanel:(id)sender {
  251.     [[TextFinder sharedInstance] orderFrontFindPanel:sender];
  252. }
  253.  
  254. - (void)findNext:(id)sender {
  255.     [[TextFinder sharedInstance] findNext:sender];
  256. }
  257.  
  258. - (void)findPrevious:(id)sender {
  259.     [[TextFinder sharedInstance] findPrevious:sender];
  260. }
  261.  
  262. - (void)enterSelection:(id)sender {
  263.     NSRange range = [[self textView] selectedRange];
  264.     if (range.length) {
  265.         [[TextFinder sharedInstance] setFindString:[[[self textView] string] substringWithRange:range]];
  266.     } else {
  267.         NSBeep();
  268.     }
  269. }
  270.  
  271. - (void)jumpToSelection:(id)sender {
  272.     [textView scrollRangeToVisible:[[self textView] selectedRange]];
  273. }
  274.  
  275. /**** Document read/write routines... ****/
  276.  
  277. + (NSArray *)fileTypes {
  278.     return nil;
  279. }
  280.  
  281. /* Opens a new document. Puts up an open panel.
  282. */
  283. + (NSArray *)documentsFromOpenPanel {
  284.     NSArray *fileTypes = [self fileTypes];
  285.     NSMutableArray *documents = [NSMutableArray array];
  286.     NSOpenPanel *panel = [NSOpenPanel openPanel];
  287.     [panel setAllowsMultipleSelection:YES];
  288.     [panel setDirectory:[self openSavePanelDirectory]];
  289.     if (fileTypes ? [panel runModalForTypes:fileTypes] : [panel runModal]) {
  290.         NSArray *filenames = [panel filenames];
  291.         unsigned cnt, numFiles = [filenames count];
  292.         for (cnt = 0; cnt < numFiles; cnt++) {
  293.         Document *document;
  294.             NSString *filename = [filenames objectAtIndex:cnt];
  295.             if (!(document = [self newWithPath:filename])) {
  296.                 NSString *alternate = (cnt + 1 == numFiles) ? nil : NSLocalizedString(@"Abort", @"Button allowing user to abort opening multiple files after one couldn't be opened");
  297.                 unsigned choice = NSRunAlertPanel(NSLocalizedString(@"File system error", @"Title of alert indicating file couldn't be opened"),
  298.                                 NSLocalizedString(@"Couldn't open file %@.", @"Message indicating file couldn't be opened."),
  299.                                 NSLocalizedString(@"OK", @"OK"), alternate, nil, filename);
  300.                 if (choice == NSCancelButton) break;
  301.             } else {
  302.          [documents addObject:document];
  303.         }
  304.         }
  305.     }
  306.     return documents;
  307. }
  308.  
  309. /* Loads document from the specified path. Returns NO if the file could not be loaded.
  310. */
  311. - (BOOL)loadFromPath:(NSString *)fileName {
  312.     NSString *fileContents;
  313.     if (fileContents = [[NSString alloc] initWithContentsOfFile:fileName]) {
  314.         [[self textView] setString:fileContents];
  315.         [fileContents release];
  316.         return YES;
  317.     } else {
  318.         return NO;
  319.     }
  320. }
  321.  
  322. /* Saves the document. Puts up save panel if necessary or if showSavePanel is YES. Returns NO if the user cancels the save...
  323. */
  324. - (BOOL)saveDocument:(BOOL)showSavePanel {
  325.     NSString *nameForSaving = [self documentName];
  326.         
  327.     while (1) {
  328.         if (!nameForSaving || showSavePanel) {
  329.             if (![self getDocumentName:&nameForSaving oldName:nameForSaving]) return NO;    /* Cancelled */
  330.         }
  331.         if ([self saveToPath:nameForSaving]) {
  332.             [self setDocumentName:nameForSaving];
  333.             [self setDocumentEdited:NO];
  334.         [[self class] setLastOpenSavePanelDirectory:[nameForSaving stringByDeletingLastPathComponent]];
  335.         return YES;
  336.         } else {
  337.             NSRunAlertPanel(@"Couldn't Save",
  338.                 NSLocalizedString(@"Couldn't save document as %@.", @"Message indicating document couldn't be saved under the given name."),
  339.                 NSLocalizedString(@"OK", @"OK."), nil, nil, nameForSaving);
  340.             nameForSaving = nil;
  341.         }
  342.     }
  343.     return YES;
  344. }
  345.  
  346. /* Puts up a save panel to get a final name from the user. If the user cancels, returns NO. If encoding is non-NULL, puts up the encoding accessory.
  347. */
  348. - (BOOL)getDocumentName:(NSString **)newName oldName:(NSString *)oldName {
  349.     NSSavePanel *panel = [NSSavePanel savePanel];
  350.     [panel setTitle:NSLocalizedString(@"Save", @"Title of save and alert panels when saving")];
  351.     if (oldName ? [panel runModalForDirectory:[oldName stringByDeletingLastPathComponent] file:[oldName lastPathComponent]] : [panel runModalForDirectory:[[self class] openSavePanelDirectory] file:@""]) {
  352.         *newName = [panel filename];
  353.         return YES;
  354.     } else {
  355.         return NO;
  356.     }
  357. }
  358.  
  359. /* Saves to specified path. Will create backups and save fileswritable if so desired... Returns NO if file cannot be saved.
  360. */
  361. - (BOOL)saveToPath:(NSString *)fileName {
  362.     BOOL success = NO;
  363.     NSFileManager *fileManager = [NSFileManager defaultManager];
  364.     NSDictionary *curAttributes = [fileManager fileAttributesAtPath:fileName traverseLink:YES];
  365.     NSString *actualFileNameToSave = [fileName stringByResolvingSymlinksInPath];    /* Follow links to save */
  366.  
  367.     if (curAttributes && ![[Preferences objectForKey:DeleteBackup] boolValue]) {    /* Don't delete backup file... */
  368.         NSString *backupFileName;
  369.         backupFileName = [actualFileNameToSave stringByAppendingString:@"~"];
  370.         if ([fileManager fileExistsAtPath:backupFileName]) {
  371.             (void)[fileManager removeFileAtPath:backupFileName handler:nil];
  372.         }
  373.         (void)[fileManager movePath:actualFileNameToSave toPath:backupFileName handler:nil];    
  374.     }
  375.  
  376.     success = [[[self textView] string] writeToFile:actualFileNameToSave atomically:YES];
  377.  
  378.     /* Apply the original permissions to the new file. */
  379.     if (success && curAttributes) {
  380.         id permissions = [curAttributes objectForKey:NSFilePosixPermissions];
  381.         if (permissions) {
  382.             [fileManager changeFileAttributes:[NSDictionary dictionaryWithObjectsAndKeys:permissions, NSFilePosixPermissions, nil] atPath:actualFileNameToSave];
  383.         }
  384.     }
  385.     return success;
  386. }
  387.  
  388. /**** Delegation ****/
  389.  
  390. - (BOOL)windowShouldClose:(id)sender {
  391.     return [self canCloseDocument];
  392. }
  393.  
  394. - (void)windowWillClose:(NSNotification *)notification {
  395.     NSWindow *window = [self window];
  396.     [window setDelegate:nil];
  397.     [self release];
  398. }
  399.  
  400. - (void)textDidChange:(NSNotification *)textObject {
  401.     if (!isDocumentEdited) {
  402.         [self setDocumentEdited:YES];
  403.     }
  404. }
  405.  
  406. /**** Misc ****/
  407.  
  408. /* Returns YES if the document can be closed. If the document is edited, gives the user a chance to save. Returns NO if the user cancels.
  409. */
  410. - (BOOL)canCloseDocument {
  411.     if (isDocumentEdited) {
  412.         int result = NSRunAlertPanel(
  413.                         NSLocalizedString(@"Close", @"Title of alert panel which comes when the user tries to quit or close a window containing an unsaved document."),
  414.                         NSLocalizedString(@"Document has been edited. Save?", @"Question asked of user when he/she tries to close a window containing an unsaved document."),
  415.                         NSLocalizedString(@"Save", @"Button choice which allows the user to save the document."),
  416.                         NSLocalizedString(@"Don't Save", @"Button choice which allows the user to abort the save of a document which is being closed."),
  417.                         NSLocalizedString(@"Cancel", @"Button choice allowing user to cancel."));
  418.         if (result == NSAlertDefaultReturn) {    /* Save */
  419.             if (![self saveDocument:NO]) return NO;
  420.         } else if (result == NSAlertOtherReturn) {    /* Cancel */
  421.             return NO;
  422.         }    /* Don't save case falls through to the YES return */
  423.     }
  424.     return YES;
  425. }
  426.  
  427. /* Return the document in the specified window.
  428. */
  429. + (Document *)documentForWindow:(NSWindow *)window {
  430.     id delegate = [window delegate];
  431.     return (delegate && [delegate isKindOfClass:[Document class]]) ? delegate : nil;
  432. }
  433.  
  434. /* Return an existing document...
  435. */
  436. + (Document *)documentForPath:(NSString *)filename {
  437.     NSArray *windows = [[NSApplication sharedApplication] windows];
  438.     unsigned cnt, numWindows = [windows count];
  439.     filename = [self cleanedUpPath:filename];    /* Clean up the incoming path */
  440.     for (cnt = 0; cnt < numWindows; cnt++) {
  441.         Document *document = [self documentForWindow:[windows objectAtIndex:cnt]];
  442.         NSString *docName = [document documentName];    
  443.         if (docName && [filename isEqual:[self cleanedUpPath:docName]]) return document;
  444.     }
  445.     return nil;
  446. }
  447.  
  448. + (unsigned)numberOfOpenDocuments {
  449.     NSArray *windows = [[NSApplication sharedApplication] windows];
  450.     unsigned cnt, numWindows = [windows count], numDocuments = 0;
  451.     for (cnt = 0; cnt < numWindows; cnt++) {
  452.         if ([self documentForWindow:[windows objectAtIndex:cnt]]) numDocuments++;
  453.     }
  454.     return numDocuments;
  455. }
  456.  
  457. static NSString *lastOpenSavePanelDir = nil;
  458.  
  459. /* Sets the directory in which a save was last done...
  460. */
  461. + (void)setLastOpenSavePanelDirectory:(NSString *)dir {
  462.     if (lastOpenSavePanelDir != dir) {
  463.     [lastOpenSavePanelDir autorelease];
  464.     lastOpenSavePanelDir = [dir copy];
  465.     }
  466. }
  467.  
  468. /* Returns the directory in which open/save panels should come up... If the OpenPanelFollowsMainWindow default is set, we have the open panel follow the main window.
  469. */
  470. + (NSString *)openSavePanelDirectory {
  471.     if ([[NSUserDefaults standardUserDefaults] boolForKey:@"OpenPanelFollowsMainWindow"]) {
  472.     Document *doc = [self documentForWindow:[NSApp mainWindow]];
  473.     if (doc && [doc documentName]) {
  474.             return [[doc documentName] stringByDeletingLastPathComponent];
  475.     }
  476.     } else if (lastOpenSavePanelDir) {
  477.     return lastOpenSavePanelDir;
  478.     }
  479.     return NSHomeDirectory();
  480. }
  481.  
  482.  
  483. @end
  484.