1/** <title>NSDocument</title>
2
3   <abstract>The abstract document class</abstract>
4
5   Copyright (C) 1999 Free Software Foundation, Inc.
6
7   Author: Carl Lindberg <Carl.Lindberg@hbo.com>
8   Date: 1999
9   Modifications: Fred Kiefer <FredKiefer@gmx.de>
10   Date: June 2000, Dec 2006
11
12   This file is part of the GNUstep GUI Library.
13
14   This library is free software; you can redistribute it and/or
15   modify it under the terms of the GNU Lesser General Public
16   License as published by the Free Software Foundation; either
17   version 2 of the License, or (at your option) any later version.
18
19   This library is distributed in the hope that it will be useful,
20   but WITHOUT ANY WARRANTY; without even the implied warranty of
21   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
22   Lesser General Public License for more details.
23
24   You should have received a copy of the GNU Lesser General Public
25   License along with this library; see the file COPYING.LIB.
26   If not, see <http://www.gnu.org/licenses/> or write to the
27   Free Software Foundation, 51 Franklin Street, Fifth Floor,
28   Boston, MA 02110-1301, USA.
29*/
30
31#import <Foundation/NSData.h>
32#import <Foundation/NSError.h>
33#import <Foundation/NSException.h>
34#import <Foundation/NSFileManager.h>
35#import <Foundation/NSNotification.h>
36#import <Foundation/NSProcessInfo.h>
37#import <Foundation/NSUndoManager.h>
38#import <Foundation/NSURL.h>
39#import "AppKit/NSBox.h"
40#import "AppKit/NSDocument.h"
41#import "AppKit/NSFileWrapper.h"
42#import "AppKit/NSSavePanel.h"
43#import "AppKit/NSPageLayout.h"
44#import "AppKit/NSPopUpButton.h"
45#import "AppKit/NSPrintInfo.h"
46#import "AppKit/NSPrintOperation.h"
47#import "AppKit/NSView.h"
48#import "NSDocumentFrameworkPrivate.h"
49
50#import "GSGuiPrivate.h"
51
52static inline NSError*
53create_error(int code, NSString* desc)
54{
55  return [NSError errorWithDomain: @"NSDocument"
56                  code: code
57                  userInfo: [NSDictionary
58                                dictionaryWithObjectsAndKeys: desc,
59                                NSLocalizedDescriptionKey, nil]];
60}
61
62@implementation NSDocument
63
64+ (NSArray *) readableTypes
65{
66  // FIXME: Should allow for filterable types
67  return [[NSDocumentController sharedDocumentController]
68          _readableTypesForClass: self];
69}
70
71+ (NSArray *) writableTypes
72{
73  // FIXME: Should allow for filterable types
74  return [[NSDocumentController sharedDocumentController]
75          _writableTypesForClass: self];
76}
77
78+ (BOOL) isNativeType: (NSString *)type
79{
80  return ([[self readableTypes] containsObject: type] &&
81          [[self writableTypes] containsObject: type]);
82}
83
84/*
85 * Private helper macro to check, if the method given via the selector sel
86 * has been overridden in the current subclass.
87 */
88#define OVERRIDDEN(sel) ([self methodForSelector: @selector(sel)] != [[NSDocument class] instanceMethodForSelector: @selector(sel)])
89
90- (id) init
91{
92  static int untitledCount = 1;
93  NSArray *fileTypes;
94
95  self = [super init];
96  if (self != nil)
97    {
98      _document_index = untitledCount++;
99      _window_controllers = [[NSMutableArray alloc] init];
100      fileTypes = [[self class] readableTypes];
101      _doc_flags.has_undo_manager = YES;
102
103      /* Set our default type */
104      if ([fileTypes count])
105       {
106         [self setFileType: [fileTypes objectAtIndex: 0]];
107         ASSIGN(_save_type, [fileTypes objectAtIndex: 0]);
108       }
109    }
110  return self;
111}
112
113/**
114 * Initialises the receiver with the contents of the document at fileName
115 * assuming that the type of data is as specified by fileType.<br />
116 * Destroys the receiver and returns nil on failure.
117 */
118- (id) initWithContentsOfFile: (NSString*)fileName ofType: (NSString*)fileType
119{
120  self = [self init];
121  if (self != nil)
122    {
123      // Setting these values first is contrary to the documentation,
124      // but mathces the reported behaviour on Cocoa.
125      [self setFileType: fileType];
126      [self setFileName: fileName];
127      if (![self readFromFile: fileName ofType: fileType])
128        {
129          NSRunAlertPanel (_(@"Load failed"),
130                          _(@"Could not load file %@."),
131                           nil, nil, nil, fileName);
132          DESTROY(self);
133        }
134    }
135  return self;
136}
137
138/**
139 * Initialises the receiver with the contents of the document at url
140 * assuming that the type of data is as specified by fileType.<br />
141 * Destroys the receiver and returns nil on failure.
142 */
143- (id) initWithContentsOfURL: (NSURL*)url ofType: (NSString*)fileType
144{
145  self = [self init];
146  if (self != nil)
147    {
148      [self setFileType: fileType];
149      [self setFileName: [url path]];
150      if (![self readFromURL: url ofType: fileType])
151        {
152          NSRunAlertPanel(_(@"Load failed"),
153                          _(@"Could not load URL %@."),
154                          nil, nil, nil, [url absoluteString]);
155          DESTROY(self);
156        }
157    }
158  return self;
159}
160
161- (id) initForURL: (NSURL *)forUrl
162withContentsOfURL: (NSURL *)url
163           ofType: (NSString *)type
164            error: (NSError **)error
165{
166  self = [self initWithType: type error: error];
167  if (self != nil)
168    {
169      [self setFileType: type];
170      if (forUrl)
171        [self setFileURL: forUrl];
172      if ([self readFromURL: url
173                     ofType: type
174                      error: error])
175        {
176          if (![url isEqual: forUrl])
177            {
178              [self setAutosavedContentsFileURL: url];
179              [self updateChangeCount: NSChangeReadOtherContents];
180            }
181        }
182      else
183        {
184          DESTROY(self);
185        }
186    }
187  return self;
188}
189
190- (id) initWithContentsOfURL: (NSURL *)url
191                      ofType: (NSString *)type
192                       error: (NSError **)error
193{
194  if (OVERRIDDEN(initWithContentsOfFile:ofType:) && [url isFileURL])
195    {
196      self = [self initWithContentsOfFile: [url path] ofType: type];
197    }
198  else
199    {
200      self = [self initForURL: url
201                   withContentsOfURL: url
202                   ofType: type
203                   error: error];
204    }
205
206  [self setFileModificationDate: [NSDate date]];
207  return self;
208}
209
210- (id) initWithType: (NSString *)type
211              error: (NSError **)error
212{
213  self = [self init];
214  if (self != nil)
215    {
216      [self setFileType: type];
217    }
218  return self;
219}
220
221- (void) dealloc
222{
223  [[NSNotificationCenter defaultCenter] removeObserver: self];
224  RELEASE(_undo_manager);
225  RELEASE(_file_name);
226  RELEASE(_file_url);
227  RELEASE(_file_type);
228  RELEASE(_last_component_file_name);
229  RELEASE(_autosaved_file_url);
230  RELEASE(_file_modification_date);
231  RELEASE(_window_controllers);
232  RELEASE(_window);
233  RELEASE(_print_info);
234  RELEASE(_printOp_delegate);
235  RELEASE(_save_panel_accessory);
236  RELEASE(_spa_button);
237  RELEASE(_save_type);
238  [super dealloc];
239}
240
241- (NSString *) fileName
242{
243  return _file_name;
244}
245
246- (void) setFileName: (NSString *)fileName
247{
248  NSURL *fileUrl;
249
250  if (fileName && ![fileName isAbsolutePath])
251    {
252      NSString *dir = [[NSFileManager defaultManager] currentDirectoryPath];
253
254      if (dir)
255	{
256	  fileName = [dir stringByAppendingPathComponent: fileName];
257	}
258    }
259
260  fileUrl = fileName ? [NSURL fileURLWithPath: fileName] : nil;
261
262  // This check is to prevent super calls from recursing.
263  if (!OVERRIDDEN(setFileName:))
264    {
265      [self setFileURL: fileUrl];
266    }
267  else
268    {
269      ASSIGN(_file_name, fileName);
270      ASSIGN(_file_url, fileUrl);
271      [self setLastComponentOfFileName: [_file_name lastPathComponent]];
272    }
273}
274
275- (NSString *) fileType
276{
277  return _file_type;
278}
279
280- (void) setFileType: (NSString *)type
281{
282  ASSIGN(_file_type, type);
283}
284
285- (NSURL *) fileURL
286{
287  if (OVERRIDDEN(fileName))
288    {
289      NSString *fileName = [self fileName];
290
291      return fileName ? [NSURL fileURLWithPath: fileName] : nil;
292    }
293  else
294    {
295      return _file_url;
296    }
297}
298
299- (void) setFileURL: (NSURL *)url
300{
301  if (OVERRIDDEN(setFileName:) &&
302      ((url == nil) || [url isFileURL]))
303    {
304      [self setFileName: [url path]];
305    }
306  else
307    {
308      ASSIGN(_file_url, url);
309      ASSIGN(_file_name, (url && [url isFileURL]) ? [url path] : (NSString*)nil);
310      [self setLastComponentOfFileName: [[_file_url path] lastPathComponent]];
311    }
312}
313
314- (NSDate *) fileModificationDate
315{
316  return _file_modification_date;
317}
318
319- (void) setFileModificationDate: (NSDate *)date
320{
321  ASSIGN(_file_modification_date, date);
322}
323
324- (NSString *) lastComponentOfFileName
325{
326  return _last_component_file_name;
327}
328
329- (void) setLastComponentOfFileName: (NSString *)str
330{
331  ASSIGN(_last_component_file_name, str);
332
333  [[self windowControllers] makeObjectsPerformSelector:
334                                @selector(synchronizeWindowTitleWithDocumentName)];
335}
336
337- (NSArray *) windowControllers
338{
339  return _window_controllers;
340}
341
342- (void) addWindowController: (NSWindowController *)windowController
343{
344  [_window_controllers addObject: windowController];
345  if ([windowController document] != self)
346    {
347      [windowController setDocument: self];
348      [windowController setDocumentEdited: [self isDocumentEdited]];
349    }
350}
351
352- (void) removeWindowController: (NSWindowController *)windowController
353{
354  if ([_window_controllers containsObject: windowController])
355    {
356      [windowController setDocumentEdited: NO];
357      [windowController setDocument: nil];
358      [_window_controllers removeObject: windowController];
359    }
360}
361
362- (NSString *) windowNibName
363{
364  return nil;
365}
366
367// private; called during nib load.
368// we do not retain the window, since it should
369// already have a retain from the nib.
370- (void) setWindow: (NSWindow *)aWindow
371{
372  _window = aWindow;
373}
374
375- (NSWindow *) windowForSheet
376{
377  NSWindow *win;
378
379  if (([_window_controllers count] > 0) &&
380      ((win = [[_window_controllers objectAtIndex: 0] window]) != nil))
381    {
382      return win;
383    }
384
385  /* Note: While Apple's documentation says that this method returns
386   * [NSApp mainWindow] if the document has no window controllers, the
387   * actual implementation returns nil and, in fact, the header files
388   * on OS X also say so. Since it would be very unreasonable to attach a
389   * document modal sheet to a window that doesn't belong to this document,
390   * we do the same here, too. */
391  return nil;
392}
393
394/**
395 * Creates the window controllers for the current document.  Calls
396 * addWindowController: on the receiver to add them to the controller
397 * array.
398 */
399- (void) makeWindowControllers
400{
401  NSString *name = [self windowNibName];
402
403  if (name != nil && [name length] > 0)
404    {
405      NSWindowController *controller;
406
407      controller = [[NSWindowController alloc] initWithWindowNibName: name
408                                                               owner: self];
409      [self addWindowController: controller];
410      RELEASE(controller);
411    }
412  else
413    {
414      [NSException raise: NSInternalInconsistencyException
415                  format: @"%@ must override either -windowNibName "
416        @"or -makeWindowControllers", NSStringFromClass([self class])];
417    }
418}
419
420/**
421 * Makes all the documents windows visible by ordering them to the
422 * front and making them main or key.<br />
423 * If the document has no windows, this method has no effect.
424 */
425- (void) showWindows
426{
427  [_window_controllers makeObjectsPerformSelector: @selector(showWindow:)
428                                      withObject: self];
429}
430
431- (BOOL) isDocumentEdited
432{
433  return _change_count != 0 || _doc_flags.permanently_modified;
434}
435
436- (void) updateChangeCount: (NSDocumentChangeType)change
437{
438  int i, count = [_window_controllers count];
439  BOOL isEdited;
440
441  switch (change)
442    {
443    case NSChangeDone:          _change_count++;
444                                _autosave_change_count++;
445                                break;
446    case NSChangeUndone:        _change_count--;
447                                _autosave_change_count--;
448                                break;
449    case NSChangeReadOtherContents:
450                                _doc_flags.permanently_modified = 1;
451                                break;
452    case NSChangeCleared:        _change_count = 0;
453                                _autosave_change_count = 0;
454                                _doc_flags.permanently_modified = 0;
455                                _doc_flags.autosave_permanently_modified = 0;
456                                break;
457    case NSChangeAutosaved:     _autosave_change_count = 0;
458                                _doc_flags.autosave_permanently_modified = 0;
459                                break;
460    }
461
462    /*
463     * NOTE: Apple's implementation seems to not call -isDocumentEdited
464     * here but directly checks to see if _changeCount == 0.  It seems it
465     * would be better to call the method in case it's overridden by a
466     * subclass, but we may want to keep Apple's behavior.
467     */
468  isEdited = [self isDocumentEdited];
469
470  for (i = 0; i < count; i++)
471    {
472      [[_window_controllers objectAtIndex: i] setDocumentEdited: isEdited];
473    }
474}
475
476- (BOOL) canCloseDocument
477{
478  int result;
479
480  if (![self isDocumentEdited])
481    return YES;
482
483  result = NSRunAlertPanel (_(@"Close"),
484                            _(@"%@ has changed.  Save?"),
485                            _(@"Save"), _(@"Cancel"), _(@"Don't Save"),
486                            [self displayName]);
487
488#define Save     NSAlertDefaultReturn
489#define Cancel   NSAlertAlternateReturn
490#define DontSave NSAlertOtherReturn
491
492  switch (result)
493    {
494      // return NO if save failed
495    case Save:
496      {
497        [self saveDocument: nil];
498        return ![self isDocumentEdited];
499      }
500    case DontSave:        return YES;
501    case Cancel:
502    default:              return NO;
503    }
504}
505
506- (void) canCloseDocumentWithDelegate: (id)delegate
507                  shouldCloseSelector: (SEL)shouldCloseSelector
508                          contextInfo: (void *)contextInfo
509{
510  BOOL result = [self canCloseDocument];
511
512  if (delegate != nil && shouldCloseSelector != NULL)
513    {
514      void (*meth)(id, SEL, id, BOOL, void*);
515      meth = (void (*)(id, SEL, id, BOOL, void*))[delegate methodForSelector:
516                                                               shouldCloseSelector];
517      if (meth)
518        meth(delegate, shouldCloseSelector, self, result, contextInfo);
519    }
520}
521
522- (BOOL) shouldCloseWindowController: (NSWindowController *)windowController
523{
524  if (![_window_controllers containsObject: windowController]) return YES;
525
526  /* If it's the last window controller, pop up a warning */
527  /* maybe we should count only loaded window controllers (or visible windows). */
528  if ([windowController shouldCloseDocument]
529      || [_window_controllers count] == 1)
530    {
531      return [self canCloseDocument];
532    }
533
534  return YES;
535}
536
537- (void) shouldCloseWindowController: (NSWindowController *)windowController
538                            delegate: (id)delegate
539                 shouldCloseSelector: (SEL)callback
540                         contextInfo: (void *)contextInfo
541{
542  /* If it's the last window controller, pop up a warning */
543  /* maybe we should count only loaded window controllers (or visible windows). */
544  if ([_window_controllers containsObject: windowController]
545      && ([windowController shouldCloseDocument]
546          || [_window_controllers count] == 1))
547    {
548      [self canCloseDocumentWithDelegate: delegate
549            shouldCloseSelector: callback
550            contextInfo: contextInfo];
551      return;
552    }
553
554  if (delegate != nil && callback != NULL)
555    {
556      void (*meth)(id, SEL, id, BOOL, void*);
557      meth = (void (*)(id, SEL, id, BOOL, void*))[delegate methodForSelector:
558                                                               callback];
559
560      if (meth)
561        meth(delegate, callback, self, YES, contextInfo);
562    }
563}
564
565- (NSString *) displayName
566{
567  if ([self lastComponentOfFileName] != nil)
568    {
569      if ([self fileNameExtensionWasHiddenInLastRunSavePanel])
570        {
571          return [[self lastComponentOfFileName] stringByDeletingPathExtension];
572        }
573      else
574        {
575          return [self lastComponentOfFileName];
576        }
577    }
578  else
579    {
580      return [NSString stringWithFormat: _(@"Untitled-%d"), _document_index];
581    }
582}
583
584- (BOOL) keepBackupFile
585{
586  return NO;
587}
588
589- (NSData *) dataRepresentationOfType: (NSString *)type
590{
591  [NSException raise: NSInternalInconsistencyException format:@"%@ must implement %@",
592               NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
593  return nil;
594}
595
596- (NSData *) dataOfType: (NSString *)type
597                  error: (NSError **)error
598{
599  if (OVERRIDDEN(dataRepresentationOfType:))
600    {
601      if (error)
602	*error = nil;
603      return [self dataRepresentationOfType: type];
604    }
605
606  [NSException raise: NSInternalInconsistencyException format:@"%@ must implement %@",
607               NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
608  return nil;
609}
610
611- (BOOL) loadDataRepresentation: (NSData *)data ofType: (NSString *)type
612{
613  [NSException raise: NSInternalInconsistencyException format:@"%@ must implement %@",
614               NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
615  return NO;
616}
617
618- (NSFileWrapper *) fileWrapperRepresentationOfType: (NSString *)type
619{
620  NSData *data = [self dataRepresentationOfType: type];
621
622  if (data == nil)
623    return nil;
624
625  return AUTORELEASE([[NSFileWrapper alloc] initRegularFileWithContents: data]);
626}
627
628- (NSFileWrapper *) fileWrapperOfType: (NSString *)type
629                                error: (NSError **)error
630{
631  NSData *data;
632
633  if (OVERRIDDEN(fileWrapperRepresentationOfType:))
634    {
635      if (error)
636	*error = nil;
637      return [self fileWrapperRepresentationOfType: type];
638    }
639
640  data = [self dataOfType: type error: error];
641
642  if (data == nil)
643    {
644      if (error && !(*error))
645          *error = create_error(0, NSLocalizedString(@"Could not create data for type.",
646                                                     @"Error description"));
647      return nil;
648    }
649  return AUTORELEASE([[NSFileWrapper alloc] initRegularFileWithContents: data]);
650}
651
652- (BOOL) loadFileWrapperRepresentation: (NSFileWrapper *)wrapper
653                                ofType: (NSString *)type
654{
655  if ([wrapper isRegularFile])
656    {
657      return [self loadDataRepresentation: [wrapper regularFileContents]
658                   ofType: type];
659    }
660
661    /*
662     * This even happens on a symlink.  May want to use
663     * -stringByResolvingAllSymlinksInPath somewhere, but Apple doesn't.
664     */
665  NSLog(@"%@ must be overridden if your document deals with file packages.",
666        NSStringFromSelector(_cmd));
667
668  return NO;
669}
670
671- (BOOL) writeToFile: (NSString *)fileName ofType: (NSString *)type
672{
673  return [[self fileWrapperRepresentationOfType: type]
674           writeToFile: fileName atomically: YES updateFilenames: YES];
675}
676
677- (BOOL) readFromFile: (NSString *)fileName ofType: (NSString *)type
678{
679  NSFileWrapper *wrapper = AUTORELEASE([[NSFileWrapper alloc] initWithPath: fileName]);
680  return [self loadFileWrapperRepresentation: wrapper ofType: type];
681}
682
683- (BOOL) revertToSavedFromFile: (NSString *)fileName ofType: (NSString *)type
684{
685  return [self readFromFile: fileName ofType: type];
686}
687
688- (BOOL) writeToURL: (NSURL *)url ofType: (NSString *)type
689{
690  if ([url isFileURL])
691    {
692      return [self writeToFile: [url path] ofType: type];
693    }
694
695  return NO;
696}
697
698- (BOOL) readFromURL: (NSURL *)url ofType: (NSString *)type
699{
700  if ([url isFileURL])
701    {
702      return [self readFromFile: [url path] ofType: type];
703    }
704
705  return NO;
706}
707
708- (BOOL) revertToSavedFromURL: (NSURL *)url ofType: (NSString *)type
709{
710  return [self readFromURL: url ofType: type];
711}
712
713- (BOOL) readFromData: (NSData *)data
714               ofType: (NSString *)type
715                error: (NSError **)error
716{
717  if (OVERRIDDEN(loadDataRepresentation:ofType:))
718    {
719      if (error)
720	*error = nil;
721      return [self loadDataRepresentation: data
722                   ofType: type];
723    }
724
725  [NSException raise: NSInternalInconsistencyException format:@"%@ must implement %@",
726               NSStringFromClass([self class]), NSStringFromSelector(_cmd)];
727  return NO;
728}
729
730- (BOOL) readFromFileWrapper: (NSFileWrapper *)wrapper
731                      ofType: (NSString *)type
732                       error: (NSError **)error
733{
734  if (OVERRIDDEN(loadFileWrapperRepresentation:ofType:))
735    {
736      if (error)
737	*error = nil;
738      return [self loadFileWrapperRepresentation: wrapper ofType: type];
739    }
740
741  if ([wrapper isRegularFile])
742    {
743      return [self readFromData: [wrapper regularFileContents]
744                         ofType: type
745                          error: error];
746    }
747
748  if (error)
749    {
750      *error = create_error(0, NSLocalizedString(@"File wrapper is no file.",
751                                                 @"Error description"));
752    }
753  return NO;
754}
755
756- (BOOL) readFromURL: (NSURL *)url
757              ofType: (NSString *)type
758               error: (NSError **)error
759{
760  if ([url isFileURL])
761    {
762      NSString *fileName = [url path];
763
764      if (OVERRIDDEN(readFromFile:ofType:))
765        {
766	  if (error)
767	    *error = nil;
768          return [self readFromFile: fileName ofType: type];
769        }
770      else
771        {
772          NSFileWrapper *wrapper = AUTORELEASE([[NSFileWrapper alloc] initWithPath: fileName]);
773
774          return [self readFromFileWrapper: wrapper
775                       ofType: type
776                       error: error];
777        }
778    }
779  else
780    {
781      return [self readFromData: [url resourceDataUsingCache: YES]
782                   ofType: type
783                   error: error];
784    }
785}
786
787- (BOOL) revertToContentsOfURL: (NSURL *)url
788                        ofType: (NSString *)type
789                         error: (NSError **)error
790{
791  if (OVERRIDDEN(revertToSavedFromURL:ofType:))
792    {
793      return [self revertToSavedFromURL: url ofType: type];
794    }
795  if (OVERRIDDEN(revertToSavedFromFile:ofType:) && [url isFileURL])
796    {
797      return [self revertToSavedFromFile:[url path] ofType: type];
798    }
799  return [self readFromURL: url
800                    ofType: type
801                     error: error];
802}
803
804- (BOOL) writeToFile: (NSString *)fileName
805              ofType: (NSString *)type
806        originalFile: (NSString *)origFileName
807       saveOperation: (NSSaveOperationType)saveOp
808{
809  return [self writeToFile: fileName ofType: type];
810}
811
812
813- (NSString *) _backupFileNameFor: (NSString *)newFileName
814{
815  NSString *extension = [newFileName pathExtension];
816  NSString *backupFilename = [newFileName stringByDeletingPathExtension];
817
818  backupFilename = [backupFilename stringByAppendingString:@"~"];
819  return [backupFilename stringByAppendingPathExtension: extension];
820}
821
822- (BOOL) _writeBackupForFile: (NSString *)newFileName
823                      toFile: (NSString *)backupFilename
824{
825  NSFileManager *fileManager = [NSFileManager defaultManager];
826
827  /* NSFileManager movePath: will fail if destination exists */
828  /* Save panel has already asked if the user wants to replace it */
829  if ([fileManager fileExistsAtPath: backupFilename])
830    {
831      [fileManager removeFileAtPath: backupFilename handler: nil];
832    }
833
834  // Move or copy?
835  if (![fileManager movePath: newFileName toPath: backupFilename handler: nil] &&
836      [self keepBackupFile])
837    {
838      int result = NSRunAlertPanel(_(@"File Error"),
839                                   _(@"Can't create backup file.  Save anyways?"),
840                                   _(@"Save"), _(@"Cancel"), nil);
841
842      if (result != NSAlertDefaultReturn) return NO;
843    }
844
845  return YES;
846}
847
848- (BOOL) writeWithBackupToFile: (NSString *)fileName
849                        ofType: (NSString *)fileType
850                 saveOperation: (NSSaveOperationType)saveOp
851{
852  NSFileManager *fileManager = [NSFileManager defaultManager];
853  NSString *backupFilename = nil;
854  BOOL isNativeType = [[self class] isNativeType: fileType];
855
856  if (fileName && isNativeType)
857    {
858      if ([fileManager fileExistsAtPath: fileName])
859        {
860          backupFilename = [self _backupFileNameFor: fileName];
861
862          if (![self _writeBackupForFile: fileName
863                     toFile: backupFilename])
864            {
865              return NO;
866            }
867        }
868
869      if ([self writeToFile: fileName
870                ofType: fileType
871                originalFile: backupFilename
872                saveOperation: saveOp])
873        {
874          // FIXME: Should set the file attributes
875
876          if (saveOp != NSSaveToOperation)
877            {
878              [self _removeAutosavedContentsFile];
879              [self setFileName: fileName];
880              [self setFileType: fileType];
881              [self updateChangeCount: NSChangeCleared];
882            }
883
884          if (backupFilename && ![self keepBackupFile])
885            {
886              [fileManager removeFileAtPath: backupFilename handler: nil];
887            }
888
889          return YES;
890        }
891    }
892
893  return NO;
894}
895
896- (BOOL) writeSafelyToURL: (NSURL *)url
897                   ofType: (NSString *)type
898         forSaveOperation: (NSSaveOperationType)op
899                    error: (NSError **)error
900{
901  NSURL *original = [self fileURL];
902  NSFileManager *fileManager = [NSFileManager defaultManager];
903  NSString *backupFilename = nil;
904  BOOL isNativeType = [[self class] isNativeType: type];
905
906  if (OVERRIDDEN(writeWithBackupToFile:ofType:saveOperation:))
907    {
908      BOOL isAutosave = NO;
909
910      if (op == NSAutosaveOperation)
911        {
912          op = NSSaveToOperation;
913          isAutosave = YES;
914        }
915
916      if (error)
917	*error = nil;
918      if (![self writeWithBackupToFile: [url path]
919                 ofType: type
920                 saveOperation: op])
921	{
922          if (error)
923            {
924              *error = create_error(0, NSLocalizedString(@"Could not write backup file.",
925                                                         @"Error description"));
926            }
927	  return NO;
928	}
929
930      if (isAutosave)
931	{
932	  [self setAutosavedContentsFileURL: url];
933	  [self updateChangeCount: NSChangeAutosaved];
934	}
935      return YES;
936    }
937
938  if (!isNativeType || (url == nil))
939    {
940      if (error)
941        {
942          *error = create_error(0, NSLocalizedString(@"Not a writable type or no URL given.",
943                                                     @"Error description"));
944        }
945      return NO;
946    }
947
948  if (op == NSSaveOperation)
949    {
950      if ([url isFileURL])
951        {
952          NSString *fileName;
953
954          fileName = [url path];
955          if ([fileManager fileExistsAtPath: fileName])
956            {
957              backupFilename = [self _backupFileNameFor: fileName];
958
959              if (![self _writeBackupForFile: fileName
960                         toFile: backupFilename])
961                {
962		  if (error)
963                    {
964                      *error = create_error(0, NSLocalizedString(@"Could not write backup file.",
965                                                                 @"Error description"));
966                    }
967                  return NO;
968                }
969            }
970        }
971    }
972
973  if (![self writeToURL: url
974             ofType: type
975             forSaveOperation: op
976             originalContentsURL: original
977             error: error])
978    {
979      return NO;
980    }
981
982  if ([url isFileURL])
983    {
984      NSDictionary *attrs;
985
986      attrs = [self fileAttributesToWriteToURL: url
987                    ofType: type
988                    forSaveOperation: op
989                    originalContentsURL: original
990                    error: error];
991      [fileManager changeFileAttributes: attrs atPath: [url path]];
992    }
993
994  if (op == NSAutosaveOperation)
995    {
996      [self setAutosavedContentsFileURL: url];
997      [self updateChangeCount: NSChangeAutosaved];
998    }
999  else if (op != NSSaveToOperation)
1000    {
1001      [self _removeAutosavedContentsFile];
1002      [self setFileURL: url];
1003      [self setFileType: type];
1004      [self updateChangeCount: NSChangeCleared];
1005    }
1006
1007  if (backupFilename && ![self keepBackupFile])
1008    {
1009      [fileManager removeFileAtPath: backupFilename handler: nil];
1010    }
1011
1012  return YES;
1013}
1014
1015- (BOOL) writeToURL: (NSURL *)url
1016             ofType: (NSString *)type
1017              error: (NSError **)error
1018{
1019  if ([url isFileURL])
1020    {
1021      NSFileWrapper *wrapper;
1022
1023      if (OVERRIDDEN(writeToFile:ofType:))
1024        {
1025	  if (error)
1026	    *error = nil;
1027          return [self writeToFile: [url path] ofType: type];
1028        }
1029
1030      wrapper = [self fileWrapperOfType: type
1031                      error: error];
1032      if (wrapper == nil)
1033        {
1034	  if (error && !(*error))
1035            {
1036              *error = create_error(0, NSLocalizedString(@"Could not write file wrapper.",
1037                                                         @"Error description"));
1038            }
1039          return NO;
1040        }
1041
1042      if (error)
1043	*error = nil;
1044      return [wrapper writeToFile: [url path] atomically: YES updateFilenames: YES];
1045    }
1046  else
1047    {
1048      NSData *data =  [self dataOfType: type error: error];
1049
1050      if (data == nil)
1051          return NO;
1052
1053      return [url setResourceData: data];
1054    }
1055}
1056
1057- (BOOL) writeToURL: (NSURL *)url
1058             ofType: (NSString *)type
1059   forSaveOperation: (NSSaveOperationType)op
1060originalContentsURL: (NSURL *)orig
1061              error: (NSError **)error
1062{
1063  if (OVERRIDDEN(writeToFile:ofType:originalFile:saveOperation:))
1064    {
1065      if (op == NSAutosaveOperation)
1066        {
1067          op = NSSaveToOperation;
1068        }
1069
1070      if (error)
1071	*error = nil;
1072      return [self writeToFile: [url path]
1073                   ofType: type
1074                   originalFile: [orig path]
1075                   saveOperation: op];
1076    }
1077
1078  return [self writeToURL: url
1079               ofType: type
1080               error: error];
1081}
1082
1083- (IBAction) changeSaveType: (id)sender
1084{
1085  NSDocumentController *controller =
1086    [NSDocumentController sharedDocumentController];
1087  NSArray *extensions;
1088
1089  ASSIGN(_save_type, [[sender selectedItem] representedObject]);
1090  extensions = [controller fileExtensionsFromType: _save_type];
1091  if ([extensions containsObject: @"*"])
1092    extensions = nil;
1093  [(NSSavePanel *)[sender window] setAllowedFileTypes: extensions];
1094}
1095
1096- (NSInteger) runModalSavePanel: (NSSavePanel *)savePanel
1097              withAccessoryView: (NSView *)accessoryView
1098{
1099  NSString *directory, *file;
1100
1101  if (accessoryView)
1102    {
1103      [savePanel setAccessoryView: accessoryView];
1104    }
1105
1106  if ([self fileName])
1107    {
1108      directory = [[self fileName] stringByDeletingLastPathComponent];
1109      file = [[self fileName] lastPathComponent];
1110      if (![savePanel allowsOtherFileTypes])
1111	{
1112	  NSArray *exts = [savePanel allowedFileTypes];
1113	  if ([exts count] && ![exts containsObject: [file pathExtension]] &&
1114	      ![exts containsObject: @"*"])
1115	    {
1116	      file = [file stringByDeletingPathExtension];
1117	      file = [file stringByAppendingPathExtension:
1118			     [exts objectAtIndex: 0]];
1119	    }
1120	}
1121      return [savePanel runModalForDirectory: directory file: file];
1122    }
1123
1124  return [savePanel runModal];
1125}
1126
1127- (BOOL) prepareSavePanel: (NSSavePanel *)savePanel
1128{
1129  return YES;
1130}
1131
1132- (BOOL) shouldRunSavePanelWithAccessoryView
1133{
1134  return YES;
1135}
1136
1137- (void) _createPanelAccessory
1138{
1139  if (_save_panel_accessory == nil)
1140    {
1141      NSRect accessoryFrame = NSMakeRect(0,0,380,70);
1142      NSRect spaFrame = NSMakeRect(115,14,150,22);
1143
1144      _save_panel_accessory = [[NSBox alloc] initWithFrame: accessoryFrame];
1145      [(NSBox *)_save_panel_accessory setTitle: _(@"File Type")];
1146      [_save_panel_accessory setAutoresizingMask:
1147                            NSViewWidthSizable | NSViewHeightSizable];
1148      _spa_button = [[NSPopUpButton alloc] initWithFrame: spaFrame];
1149      [_spa_button setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable | NSViewMinYMargin |
1150                 NSViewMaxYMargin | NSViewMinXMargin | NSViewMaxXMargin];
1151      [_spa_button setTarget: self];
1152      [_spa_button setAction: @selector(changeSaveType:)];
1153      [_save_panel_accessory addSubview: _spa_button];
1154    }
1155}
1156
1157- (void) _addItemsToSpaButtonFromArray: (NSArray *)types
1158{
1159  NSString *type, *title;
1160  int i, count = [types count];
1161
1162  [_spa_button removeAllItems];
1163  for (i = 0; i < count; i++)
1164    {
1165      type = [types objectAtIndex: i];
1166      title = [[NSDocumentController sharedDocumentController]
1167		displayNameForType: type];
1168      [_spa_button addItemWithTitle: title];
1169      [[_spa_button itemAtIndex: i] setRepresentedObject: type];
1170    }
1171
1172  // if it's more than one, then
1173  [_spa_button setEnabled: (count > 0)];
1174
1175  // if we have some items, select the current filetype.
1176  if (count > 0)
1177    {
1178      [_spa_button selectItemAtIndex:
1179	[_spa_button indexOfItemWithRepresentedObject: [self fileType]]];
1180    }
1181}
1182
1183- (NSSavePanel *)_runSavePanelForSaveOperation: (NSSaveOperationType)saveOperation
1184{
1185  NSString *title;
1186  NSString *directory;
1187  NSArray *types;
1188  NSDocumentController *controller;
1189  NSSavePanel *savePanel = [NSSavePanel savePanel];
1190
1191  ASSIGN(_save_type, [self fileType]);
1192  controller = [NSDocumentController sharedDocumentController];
1193  types = [self writableTypesForSaveOperation: saveOperation];
1194
1195  if ([self shouldRunSavePanelWithAccessoryView])
1196    {
1197      if (_save_panel_accessory == nil)
1198        [self _createPanelAccessory];
1199
1200      [self _addItemsToSpaButtonFromArray: types];
1201
1202      [savePanel setAccessoryView: _save_panel_accessory];
1203    }
1204
1205  if ([types count] > 0)
1206    {
1207      NSArray *extensions = [controller fileExtensionsFromType: [self fileType]];
1208      if ([extensions containsObject: @"*"])
1209	extensions = nil;
1210      [savePanel setAllowedFileTypes: extensions];
1211    }
1212
1213  switch (saveOperation)
1214    {
1215    case NSSaveAsOperation: title = _(@"Save As"); break;
1216    case NSSaveToOperation: title = _(@"Save To"); break;
1217    case NSSaveOperation:
1218    default:
1219      title = _(@"Save");
1220      break;
1221   }
1222
1223  [savePanel setTitle: title];
1224
1225  if ([self fileName])
1226    directory = [[self fileName] stringByDeletingLastPathComponent];
1227  else
1228    directory = [controller currentDirectory];
1229  [savePanel setDirectory: directory];
1230
1231  if (!OVERRIDDEN(runModalSavePanel:withAccessoryView:))
1232    {
1233      if (![self prepareSavePanel: savePanel])
1234	{
1235	  return nil;
1236	}
1237    }
1238  if ([self runModalSavePanel: savePanel
1239	    withAccessoryView: [savePanel accessoryView]])
1240    {
1241      return savePanel;
1242    }
1243
1244  return nil;
1245}
1246
1247- (NSString *) fileNameFromRunningSavePanelForSaveOperation: (NSSaveOperationType)saveOperation
1248{
1249  NSSavePanel *savePanel = [self _runSavePanelForSaveOperation: saveOperation];
1250
1251  if (savePanel)
1252    {
1253      return [savePanel filename];
1254    }
1255
1256  return nil;
1257}
1258
1259- (void) runModalSavePanelForSaveOperation: (NSSaveOperationType)saveOperation
1260                                  delegate: (id)delegate
1261                           didSaveSelector: (SEL)didSaveSelector
1262                               contextInfo: (void *)contextInfo
1263{
1264  // FIXME: Commit registered editors
1265  NSSavePanel *savePanel = [self _runSavePanelForSaveOperation: saveOperation];
1266
1267  if (savePanel)
1268    {
1269      if (OVERRIDDEN(saveToFile:saveOperation:delegate:didSaveSelector:contextInfo:))
1270        {
1271          [self saveToFile: [savePanel filename]
1272                saveOperation: saveOperation
1273                delegate: delegate
1274                didSaveSelector: didSaveSelector
1275                contextInfo: contextInfo];
1276        }
1277      else
1278        {
1279          [self saveToURL: [savePanel URL]
1280                ofType: [self fileTypeFromLastRunSavePanel]
1281                forSaveOperation: saveOperation
1282                delegate: delegate
1283                didSaveSelector: didSaveSelector
1284                contextInfo: contextInfo];
1285        }
1286    }
1287}
1288
1289- (NSArray *) writableTypesForSaveOperation: (NSSaveOperationType)op
1290{
1291  NSArray *types = [object_getClass(self) writableTypes];
1292  NSMutableArray *muTypes;
1293  int i, len;
1294
1295  if (op == NSSaveToOperation)
1296    {
1297      return types;
1298    }
1299
1300  len = [types count];
1301  muTypes = [NSMutableArray arrayWithCapacity: len];
1302  for (i = 0; i < len; i++)
1303    {
1304      NSString *type;
1305
1306      type = [types objectAtIndex: i];
1307      if ([[self class] isNativeType: type])
1308        {
1309          [muTypes addObject: type];
1310        }
1311    }
1312
1313  return muTypes;
1314}
1315
1316- (BOOL) fileNameExtensionWasHiddenInLastRunSavePanel
1317{
1318  // FIXME
1319  return NO;
1320}
1321
1322- (NSString *) fileNameExtensionForType: (NSString *)typeName
1323                          saveOperation: (NSSaveOperationType)saveOperation
1324{
1325  NSArray *exts = [[NSDocumentController sharedDocumentController]
1326                      fileExtensionsFromType: typeName];
1327
1328  if ([exts count] && ![exts containsObject: @"*"])
1329    return (NSString *)[exts objectAtIndex: 0];
1330
1331  return @"";
1332}
1333
1334- (BOOL) shouldChangePrintInfo: (NSPrintInfo *)newPrintInfo
1335{
1336  return YES;
1337}
1338
1339- (NSPrintInfo *) printInfo
1340{
1341  return _print_info? _print_info : [NSPrintInfo sharedPrintInfo];
1342}
1343
1344- (void) setPrintInfo: (NSPrintInfo *)printInfo
1345{
1346  NSUndoManager *undoManager = [self undoManager];
1347
1348  if (undoManager != nil)
1349    {
1350      [[undoManager prepareWithInvocationTarget: self]
1351	setPrintInfo: _print_info];
1352      // FIXME undoManager -setActionName:
1353    }
1354  ASSIGN(_print_info, printInfo);
1355  if (undoManager == nil)
1356    {
1357      [self updateChangeCount: NSChangeDone];
1358    }
1359}
1360
1361
1362// Page layout panel (Page Setup)
1363- (BOOL) preparePageLayout: (NSPageLayout *)pageLayout
1364{
1365  return YES;
1366}
1367
1368- (NSInteger) runModalPageLayoutWithPrintInfo: (NSPrintInfo *)printInfo
1369{
1370  NSPageLayout *pageLayout;
1371
1372  pageLayout = [NSPageLayout pageLayout];
1373  if ([self preparePageLayout: pageLayout])
1374    {
1375      return [pageLayout runModalWithPrintInfo: printInfo];
1376    }
1377  else
1378    {
1379      return NSCancelButton;
1380    }
1381}
1382
1383- (void) _documentDidRunModalPageLayout: (NSDocument*)doc
1384                               accepted: (BOOL)accepted
1385                            contextInfo: (void *)contextInfo
1386{
1387  NSPrintInfo *printInfo = (NSPrintInfo *)contextInfo;
1388
1389  if (accepted && [self shouldChangePrintInfo: printInfo])
1390    {
1391      [self setPrintInfo: printInfo];
1392    }
1393}
1394
1395- (IBAction) runPageLayout: (id)sender
1396{
1397  NSPrintInfo *printInfo = AUTORELEASE([[self printInfo] copy]);
1398
1399  [self runModalPageLayoutWithPrintInfo: printInfo
1400        delegate: self
1401        didRunSelector: @selector(_documentDidRunModalPageLayout:accepted:contextInfo:)
1402        contextInfo: printInfo];
1403}
1404
1405- (void) runModalPageLayoutWithPrintInfo: (NSPrintInfo *)info
1406                                delegate: (id)delegate
1407                          didRunSelector: (SEL)sel
1408                             contextInfo: (void *)context
1409{
1410  NSPageLayout *pageLayout;
1411
1412  pageLayout = [NSPageLayout pageLayout];
1413  if ([self preparePageLayout: pageLayout])
1414    {
1415      [pageLayout beginSheetWithPrintInfo: info
1416                  modalForWindow: [self windowForSheet]
1417                  delegate: delegate
1418                  didEndSelector: sel
1419                  contextInfo: context];
1420    }
1421}
1422
1423/* This is overridden by subclassers; the default implementation does nothing. */
1424- (void) printShowingPrintPanel: (BOOL)flag
1425{
1426}
1427
1428- (IBAction) printDocument: (id)sender
1429{
1430  [self printDocumentWithSettings: [NSDictionary dictionary]
1431        showPrintPanel: YES
1432        delegate: nil
1433        didPrintSelector: NULL
1434        contextInfo: NULL];
1435}
1436
1437- (void) printDocumentWithSettings: (NSDictionary *)settings
1438                    showPrintPanel: (BOOL)flag
1439                          delegate: (id)delegate
1440                  didPrintSelector: (SEL)sel
1441                       contextInfo: (void *)context
1442{
1443  NSPrintOperation *printOp;
1444  NSError *error;
1445
1446  if (OVERRIDDEN(printShowingPrintPanel:))
1447    {
1448      // FIXME: More communication with the panel is needed.
1449      return [self printShowingPrintPanel: flag];
1450    }
1451
1452  printOp = [self printOperationWithSettings: settings
1453                  error: &error];
1454  if (printOp != nil)
1455    {
1456      [printOp setShowsPrintPanel: flag];
1457      [self runModalPrintOperation: printOp
1458            delegate: delegate
1459            didRunSelector: sel
1460            contextInfo: context];
1461    }
1462  else
1463    {
1464      [self presentError: error];
1465
1466      if (delegate != nil && sel != NULL)
1467        {
1468          void (*meth)(id, SEL, id, BOOL, void*);
1469          meth = (void (*)(id, SEL, id, BOOL, void*))[delegate methodForSelector: sel];
1470          if (meth)
1471              meth(delegate, sel, self, NO, context);
1472        }
1473    }
1474}
1475
1476- (NSPrintOperation *) printOperationWithSettings: (NSDictionary *)settings
1477                                            error: (NSError **)error
1478{
1479  if (error)
1480    *error = nil;
1481  return nil;
1482}
1483
1484- (void) runModalPrintOperation: (NSPrintOperation *)op
1485                       delegate: (id)delegate
1486                 didRunSelector: (SEL)sel
1487                    contextInfo: (void *)context
1488{
1489  ASSIGN(_printOp_delegate, delegate);
1490  _printOp_didRunSelector = sel;
1491  [op runOperationModalForWindow: [self windowForSheet]
1492      delegate: self
1493      didRunSelector: @selector(_runModalPrintOperationDidSucceed:contextInfo:)
1494      contextInfo: context];
1495}
1496
1497- (void) _runModalPrintOperationDidSucceed: (BOOL)success
1498                               contextInfo: (void *)context
1499{
1500  id delegate = _printOp_delegate;
1501  SEL didRunSelector = _printOp_didRunSelector;
1502  void (*didRun)(id, SEL, NSDocument *, BOOL, id);
1503
1504  if (delegate && [delegate respondsToSelector: didRunSelector])
1505    {
1506      didRun = (void (*)(id, SEL, NSDocument *, BOOL, id))
1507          [delegate methodForSelector: didRunSelector];
1508      didRun(delegate, didRunSelector, self, success, context);
1509    }
1510  DESTROY(_printOp_delegate);
1511}
1512
1513- (BOOL) validateMenuItem: (NSMenuItem *)anItem
1514{
1515  return [self validateUserInterfaceItem: anItem];
1516}
1517
1518- (BOOL) validateUserInterfaceItem: (id <NSValidatedUserInterfaceItem>)anItem
1519{
1520  if (sel_isEqual([anItem action], @selector(revertDocumentToSaved:)))
1521    return ([self fileName] != nil && [self isDocumentEdited]);
1522  if (sel_isEqual([anItem action], @selector(saveDocument:)))
1523    return [self isDocumentEdited];
1524
1525  // FIXME should validate spa popup items; return YES if it's a native type.
1526
1527  return YES;
1528}
1529
1530- (NSString *) fileTypeFromLastRunSavePanel
1531{
1532  return _save_type;
1533}
1534
1535- (NSDictionary *) fileAttributesToWriteToFile: (NSString *)fullDocumentPath
1536                                        ofType: (NSString *)docType
1537                                 saveOperation: (NSSaveOperationType)saveOperationType
1538{
1539  // FIXME: Implement. Should set NSFileExtensionHidden
1540  return [NSDictionary dictionary];
1541}
1542
1543- (NSDictionary *) fileAttributesToWriteToURL: (NSURL *)url
1544                                       ofType: (NSString *)type
1545                             forSaveOperation: (NSSaveOperationType)op
1546                          originalContentsURL: (NSURL *)original
1547                                        error: (NSError **)error
1548{
1549  // FIXME: Implement. Should set NSFileExtensionHidden
1550  if (error)
1551    *error = nil;
1552
1553  return [NSDictionary dictionary];
1554}
1555
1556- (IBAction) saveDocument: (id)sender
1557{
1558  [self saveDocumentWithDelegate: nil
1559        didSaveSelector: NULL
1560        contextInfo: NULL];
1561}
1562
1563- (IBAction) saveDocumentAs: (id)sender
1564{
1565  [self runModalSavePanelForSaveOperation: NSSaveAsOperation
1566        delegate: nil
1567        didSaveSelector: NULL
1568        contextInfo: NULL];
1569}
1570
1571- (IBAction) saveDocumentTo: (id)sender
1572{
1573  [self runModalSavePanelForSaveOperation: NSSaveToOperation
1574        delegate: nil
1575        didSaveSelector: NULL
1576        contextInfo: NULL];
1577}
1578
1579- (void) saveDocumentWithDelegate: (id)delegate
1580                  didSaveSelector: (SEL)didSaveSelector
1581                      contextInfo: (void *)contextInfo
1582{
1583  NSURL *fileURL = [self fileURL];
1584  NSString *type = [self fileType];
1585
1586  if ((fileURL != nil) && (type != nil))
1587    {
1588      [self saveToURL: fileURL
1589            ofType: type
1590            forSaveOperation: NSSaveOperation
1591            delegate: delegate
1592            didSaveSelector: didSaveSelector
1593            contextInfo: contextInfo];
1594    }
1595  else
1596    {
1597      [self runModalSavePanelForSaveOperation: NSSaveOperation
1598            delegate: delegate
1599            didSaveSelector: didSaveSelector
1600            contextInfo: contextInfo];
1601    }
1602}
1603
1604- (void) saveToFile: (NSString *)fileName
1605      saveOperation: (NSSaveOperationType)saveOperation
1606           delegate: (id)delegate
1607    didSaveSelector: (SEL)didSaveSelector
1608        contextInfo: (void *)contextInfo
1609{
1610  BOOL saved = NO;
1611
1612  if (fileName != nil)
1613    {
1614      saved = [self writeWithBackupToFile: fileName
1615                    ofType: [self fileTypeFromLastRunSavePanel]
1616                    saveOperation: saveOperation];
1617      if (saved &&
1618	  (saveOperation == NSSaveOperation ||
1619	   saveOperation == NSSaveAsOperation))
1620	{
1621	  [[NSDocumentController sharedDocumentController]
1622	    noteNewRecentDocument: self];
1623	}
1624    }
1625
1626  if (delegate != nil && didSaveSelector != NULL)
1627    {
1628      void (*meth)(id, SEL, id, BOOL, void*);
1629      meth = (void (*)(id, SEL, id, BOOL, void*))[delegate methodForSelector:
1630                                                               didSaveSelector];
1631      if (meth)
1632        meth(delegate, didSaveSelector, self, saved, contextInfo);
1633    }
1634}
1635
1636- (BOOL) saveToURL: (NSURL *)url
1637            ofType: (NSString *)type
1638  forSaveOperation: (NSSaveOperationType)op
1639             error: (NSError **)error
1640{
1641  BOOL saved =
1642    [self writeSafelyToURL: url
1643	  ofType: type
1644          forSaveOperation: op
1645          error: error];
1646  if (saved && (op == NSSaveOperation || op == NSSaveAsOperation))
1647    {
1648      [[NSDocumentController sharedDocumentController]
1649	noteNewRecentDocument: self];
1650    }
1651  return saved;
1652}
1653
1654- (void) saveToURL: (NSURL *)url
1655            ofType: (NSString *)type
1656  forSaveOperation: (NSSaveOperationType)op
1657          delegate: (id)delegate
1658   didSaveSelector: (SEL)didSaveSelector
1659       contextInfo: (void *)contextInfo
1660{
1661  NSError *error;
1662  BOOL saved;
1663
1664  saved = [self saveToURL: url
1665                ofType: type
1666                forSaveOperation: op
1667                error: &error];
1668  if (!saved)
1669    {
1670      [self presentError: error];
1671    }
1672  else if (op == NSSaveOperation || op == NSSaveAsOperation)
1673    {
1674      [[NSDocumentController sharedDocumentController]
1675	noteNewRecentDocument: self];
1676    }
1677
1678  if (delegate != nil && didSaveSelector != NULL)
1679    {
1680      void (*meth)(id, SEL, id, BOOL, void*);
1681      meth = (void (*)(id, SEL, id, BOOL, void*))[delegate methodForSelector:
1682                                                               didSaveSelector];
1683      if (meth)
1684        meth(delegate, didSaveSelector, self, saved, contextInfo);
1685    }
1686}
1687
1688- (IBAction) revertDocumentToSaved: (id)sender
1689{
1690  int result;
1691  NSError *error;
1692
1693  result = NSRunAlertPanel
1694    (_(@"Revert"),
1695     _(@"%@ has been edited.  Are you sure you want to undo changes?"),
1696     _(@"Revert"), _(@"Cancel"), nil,
1697     [self displayName]);
1698
1699  if (result == NSAlertDefaultReturn)
1700    {
1701      // FIXME: Revert registered editors
1702      if ([self revertToContentsOfURL: [self fileURL]
1703                ofType: [self fileType]
1704                error: &error])
1705        {
1706          [self updateChangeCount: NSChangeCleared];
1707          [[self undoManager] removeAllActions];
1708          [self _removeAutosavedContentsFile];
1709        }
1710      else
1711        {
1712          [self presentError: error];
1713        }
1714    }
1715}
1716
1717/** Closes all the windows owned by the document, then removes itself
1718    from the list of documents known by the NSDocumentController. This
1719    method does not ask the user if they want to save the document before
1720    closing. It is closed without saving any information.
1721 */
1722- (void) close
1723{
1724  if (_doc_flags.in_close == NO)
1725    {
1726      int count = [_window_controllers count];
1727      /* Closing a windowController will also send us a close, so make
1728         sure we don't go recursive */
1729      _doc_flags.in_close = YES;
1730
1731      if (count > 0)
1732        {
1733          NSWindowController *array[count];
1734          [_window_controllers getObjects: array];
1735          while (count-- > 0)
1736            [array[count] close];
1737        }
1738      [self _removeAutosavedContentsFile];
1739      AUTORELEASE(RETAIN(self));
1740      [[NSDocumentController sharedDocumentController] removeDocument: self];
1741    }
1742}
1743
1744- (void) windowControllerWillLoadNib: (NSWindowController *)windowController {}
1745- (void) windowControllerDidLoadNib: (NSWindowController *)windowController  {}
1746
1747- (NSUndoManager *) undoManager
1748{
1749  if (_undo_manager == nil && [self hasUndoManager])
1750    {
1751      [self setUndoManager: AUTORELEASE([[NSUndoManager alloc] init])];
1752    }
1753
1754  return _undo_manager;
1755}
1756
1757- (void) setUndoManager: (NSUndoManager *)undoManager
1758{
1759  if (undoManager != _undo_manager)
1760    {
1761      NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
1762
1763      if (_undo_manager)
1764        {
1765          [center removeObserver: self
1766                  name: NSUndoManagerWillCloseUndoGroupNotification
1767                  object: _undo_manager];
1768          [center removeObserver: self
1769                  name: NSUndoManagerDidUndoChangeNotification
1770                  object: _undo_manager];
1771          [center removeObserver: self
1772                  name: NSUndoManagerDidRedoChangeNotification
1773                  object: _undo_manager];
1774        }
1775
1776      ASSIGN(_undo_manager, undoManager);
1777
1778      if (_undo_manager == nil)
1779        {
1780          [self setHasUndoManager: NO];
1781        }
1782      else
1783        {
1784          [center addObserver: self
1785                  selector:@selector(_changeWasDone:)
1786                  name: NSUndoManagerWillCloseUndoGroupNotification
1787                  object: _undo_manager];
1788          [center addObserver: self
1789                  selector:@selector(_changeWasUndone:)
1790                  name: NSUndoManagerDidUndoChangeNotification
1791                  object: _undo_manager];
1792          [center addObserver: self
1793            selector:@selector(_changeWasRedone:)
1794            name: NSUndoManagerDidRedoChangeNotification
1795            object: _undo_manager];
1796        }
1797    }
1798}
1799
1800- (BOOL) hasUndoManager
1801{
1802  return _doc_flags.has_undo_manager;
1803}
1804
1805- (void) setHasUndoManager: (BOOL)flag
1806{
1807  if (_undo_manager && !flag)
1808    [self setUndoManager: nil];
1809
1810  _doc_flags.has_undo_manager = flag;
1811}
1812
1813- (BOOL) presentError: (NSError *)error
1814{
1815  error = [self willPresentError: error];
1816  return [[NSDocumentController sharedDocumentController] presentError: error];
1817}
1818
1819- (void) presentError: (NSError *)error
1820       modalForWindow: (NSWindow *)window
1821             delegate: (id)delegate
1822   didPresentSelector: (SEL)sel
1823          contextInfo: (void *)context
1824{
1825  error = [self willPresentError: error];
1826  [[NSDocumentController sharedDocumentController] presentError: error
1827                                                   modalForWindow: window
1828                                                   delegate: delegate
1829                                                   didPresentSelector: sel
1830                                                   contextInfo: context];
1831}
1832
1833- (NSError *) willPresentError: (NSError *)error
1834{
1835  return error;
1836}
1837
1838- (NSURL *) autosavedContentsFileURL
1839{
1840  return _autosaved_file_url;
1841}
1842
1843- (void) setAutosavedContentsFileURL: (NSURL *)url
1844{
1845  ASSIGN(_autosaved_file_url, url);
1846  [[NSDocumentController sharedDocumentController]
1847      _recordAutosavedDocument: self];
1848}
1849
1850- (void) autosaveDocumentWithDelegate: (id)delegate
1851                  didAutosaveSelector: (SEL)didAutosaveSelector
1852                          contextInfo: (void *)context
1853{
1854  NSURL *url = [self autosavedContentsFileURL];
1855  NSString *type = [self autosavingFileType];
1856
1857  if (url == nil)
1858    {
1859      static NSString *processName = nil;
1860      NSString *path;
1861      NSString *ext = [self fileNameExtensionForType: type
1862                            saveOperation: NSAutosaveOperation];
1863
1864      if (!processName)
1865        processName = [[[NSProcessInfo processInfo] processName] copy];
1866
1867      path = [[NSDocumentController sharedDocumentController]
1868                 _autosaveDirectory: YES];
1869      path = [path stringByAppendingPathComponent:
1870                       [NSString stringWithFormat: @"%@-%d",
1871                                 processName, _document_index]];
1872      path = [path stringByAppendingPathExtension: ext];
1873      url = [NSURL fileURLWithPath: path];
1874    }
1875
1876  [self saveToURL: url
1877        ofType: type
1878        forSaveOperation: NSAutosaveOperation
1879        delegate: delegate
1880        didSaveSelector: didAutosaveSelector
1881        contextInfo: context];
1882}
1883
1884- (NSString *) autosavingFileType
1885{
1886  return [self fileType];
1887}
1888
1889- (BOOL) hasUnautosavedChanges
1890{
1891  return _autosave_change_count != 0 || _doc_flags.autosave_permanently_modified;
1892}
1893
1894@end
1895
1896@implementation NSDocument(Private)
1897
1898/*
1899 * This private method is used to transfer window ownership to the
1900 * NSWindowController in situations (such as the default) where the
1901 * document is set to the nib owner, and thus owns the window immediately
1902 * following the loading of the nib.
1903 */
1904- (NSWindow *) _transferWindowOwnership
1905{
1906  NSWindow *window = _window;
1907  _window = nil;
1908  return AUTORELEASE(window);
1909}
1910
1911- (void) _removeWindowController: (NSWindowController *)windowController
1912{
1913  if ([_window_controllers containsObject: windowController])
1914    {
1915      BOOL autoClose = [windowController shouldCloseDocument];
1916
1917      [windowController setDocument: nil];
1918      [_window_controllers removeObject: windowController];
1919
1920      if (autoClose || [_window_controllers count] == 0)
1921        {
1922          [self close];
1923        }
1924    }
1925}
1926
1927- (void) _removeAutosavedContentsFile
1928{
1929  NSURL *url = [self autosavedContentsFileURL];
1930
1931  if (url)
1932    {
1933      NSString *path = [[url path] retain];
1934
1935      [self setAutosavedContentsFileURL: nil];
1936      [[NSFileManager defaultManager] removeFileAtPath: path handler: nil];
1937      [path release];
1938    }
1939}
1940
1941- (void) _changeWasDone: (NSNotification *)notification
1942{
1943  /* Prevent a document from appearing unmodified after saving the
1944   * document, undoing a number of changes, and then making an equal
1945   * number of changes.  Ditto for autosaved changes.
1946   */
1947  if (_change_count < 0)
1948    _doc_flags.permanently_modified = 1;
1949  if (_autosave_change_count < 0)
1950    _doc_flags.autosave_permanently_modified = 1;
1951  [self updateChangeCount: NSChangeDone];
1952}
1953
1954- (void) _changeWasUndone: (NSNotification *)notification
1955{
1956  [self updateChangeCount: NSChangeUndone];
1957}
1958
1959- (void) _changeWasRedone: (NSNotification *)notification
1960{
1961  /* FIXME
1962   * Mac OS X 10.5 uses a new constant NSChangeRedone here, but
1963   * old applications are not prepared to handle this constant
1964   * and expect NSChangeDone instead.
1965   */
1966  [self updateChangeCount: NSChangeDone];
1967}
1968
1969@end
1970