home *** CD-ROM | disk | FTP | other *** search
/ OpenStep 4.2J (Developer) / os42jdev.iso / NextDeveloper / Examples / AppKit / TextEdit / Document.m < prev    next >
Text File  |  1996-12-12  |  48KB  |  1,128 lines

  1. /*
  2.         Document.m
  3.     Copyright (c) 1995-1996, NeXT Software, Inc.
  4.         All rights reserved.
  5.         Author: Ali Ozer
  6.  
  7.     You may freely copy, distribute and reuse the code in this example.
  8.     NeXT disclaims any warranty of any kind, expressed or implied,
  9.     as to its fitness for any particular use.
  10.  
  11.         Document object for Edit...
  12. */
  13.  
  14. #import <AppKit/AppKit.h>
  15. #import <math.h>
  16. #import <stdio.h>                // for NULL
  17. #import "Document.h"
  18. #import "MultiplePageView.h"
  19. #import "TextFinder.h"
  20. #import "Preferences.h"
  21.  
  22. @implementation Document
  23.  
  24. - (void)setupInitialTextViewSharedState {
  25.     NSTextView *textView = [self firstTextView];
  26.     
  27.     [textView setUsesFontPanel:YES];
  28.     [textView setDelegate:self];
  29.     [self setRichText:[[Preferences objectForKey:RichText] boolValue]];
  30.     [self setHyphenationFactor:0.0];
  31. }
  32.  
  33. - (id)init {
  34.     static NSPoint cascadePoint = {0.0, 0.0};
  35.     NSLayoutManager *layoutManager;
  36.     NSZone *zone = [self zone];
  37.         
  38.     self = [super init];
  39.     textStorage = [[NSTextStorage allocWithZone:zone] init];
  40.  
  41.     if (![NSBundle loadNibNamed:@"Document" owner:self])  {
  42.         NSLog(@"Failed to load Document.nib");
  43.         [self release];
  44.         return nil;
  45.     }
  46.  
  47.     layoutManager = [[NSLayoutManager allocWithZone:zone] init];
  48.     [textStorage addLayoutManager:layoutManager];
  49.     [layoutManager setDelegate:self];
  50.     [layoutManager release];
  51.  
  52.     encodingIfPlainText = [[Preferences objectForKey:PlainTextEncoding] intValue];
  53.  
  54.     [self setPrintInfo:[NSPrintInfo sharedPrintInfo]];
  55.     [[self printInfo] setHorizontalPagination:NSFitPagination];
  56.     [[self printInfo] setHorizontallyCentered:NO];
  57.     [[self printInfo] setVerticallyCentered:NO];
  58.         
  59.     // This gives us our first view
  60.     [self setHasMultiplePages:[[Preferences objectForKey:ShowPageBreaks] boolValue]];
  61.  
  62.     // This ensures the first view gets set up correctly
  63.     [self setupInitialTextViewSharedState];
  64.  
  65.     if (NSEqualPoints(cascadePoint, NSZeroPoint)) {    /* First time through... */
  66.         NSRect frame = [[self window] frame];
  67.     cascadePoint = NSMakePoint(frame.origin.x, NSMaxY(frame));
  68.     }
  69.     cascadePoint = [[self window] cascadeTopLeftFromPoint:cascadePoint];
  70.     [[self window] setDelegate:self];
  71.  
  72.     // Set the window size from defaults...
  73.     if ([self hasMultiplePages]) {
  74.         [self setViewSize:[[scrollView documentView] pageRectForPageNumber:0].size];
  75.     } else {
  76.         int windowHeight = [[Preferences objectForKey:WindowHeight] intValue];
  77.         int windowWidth = [[Preferences objectForKey:WindowWidth] intValue];
  78.         NSFont *font = [Preferences objectForKey:[self isRichText] ? RichTextFont : PlainTextFont];
  79.         NSSize size;
  80.         if ([font glyphIsEncoded:'n']) {    /* Better to use n-width than the maximum, for rich text fonts... */
  81.             size.width = [font advancementForGlyph:'n'].width;
  82.         } else {
  83.         size.width = [font maximumAdvancement].width;
  84.         }
  85.         size.width  = ceil(size.width * windowWidth + [[[self firstTextView] textContainer] lineFragmentPadding] * 2.0);
  86.         size.height = ceil([font boundingRectForFont].size.height) * windowHeight;
  87.         [self setViewSize:size];
  88.     }
  89.  
  90.     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(fixUpScrollViewBackgroundColor:)
  91.     name:NSSystemColorsDidChangeNotification object:nil];
  92.  
  93.     potentialSaveDirectory = nil;
  94.     return self;
  95. }
  96.  
  97. - (id)initWithPath:(NSString *)filename encoding:(int)encoding uniqueZone:(BOOL)flag {
  98.     if (!(self = [self init])) {
  99.         if (flag) NSRecycleZone([self zone]);
  100.         return nil;
  101.     }
  102.     uniqueZone = flag;    /* So if something goes wrong we can recycle the zone correctly in dealloc */
  103.     if (filename && ![self loadFromPath:filename encoding:encoding]) {
  104.         [self release];
  105.         return nil;
  106.     }
  107.     if (filename) {
  108.     [Document setLastOpenSavePanelDirectory:[filename stringByDeletingLastPathComponent]];
  109.     }
  110.     [[self firstTextView] setSelectedRange:NSMakeRange(0, 0)];
  111.     [self setDocumentName:filename];
  112.     return self;
  113. }
  114.  
  115. + (BOOL)openUntitled {
  116.     NSZone *zone = NSCreateZone(NSPageSize(), NSPageSize(), YES);
  117.     Document *document = [[self allocWithZone:zone] initWithPath:nil encoding:UnknownStringEncoding uniqueZone:YES];
  118.     if (document) {
  119.     [document setPotentialSaveDirectory:[Document openSavePanelDirectory]];
  120.     [document setDocumentName:nil];
  121.         [[document window] makeKeyAndOrderFront:nil];
  122.         return YES;
  123.     } else {
  124.         return NO;
  125.     }
  126. }
  127.  
  128. + (BOOL)openDocumentWithPath:(NSString *)filename encoding:(int)encoding {
  129.     Document *document = [self documentForPath:filename];
  130.     if (!document) {
  131.         NSZone *zone = NSCreateZone(NSPageSize(), NSPageSize(), YES);
  132.         document =  [[self allocWithZone:zone] initWithPath:filename encoding:encoding uniqueZone:YES];
  133.     }
  134.     if (document) {
  135.         [document doForegroundLayoutToCharacterIndex:[[Preferences objectForKey:ForegroundLayoutToIndex] intValue]];
  136.         [[document window] makeKeyAndOrderFront:nil];
  137.         return YES;
  138.     } else {
  139.         return NO;
  140.     }
  141. }
  142.  
  143. /* Clear the delegates of the text views and window, then release all resources and go away...
  144. */
  145. - (void)dealloc {
  146.     [[NSNotificationCenter defaultCenter] removeObserver:self name:NSSystemColorsDidChangeNotification object:nil];
  147.     [[NSNotificationCenter defaultCenter] removeObserver:self name:NSTextStorageDidProcessEditingNotification object:[self textStorage]];
  148.     [[self firstTextView] setDelegate:nil];
  149.     [[self window] setDelegate:nil];
  150.     [documentName release];
  151.     [textStorage release];
  152.     [printInfo release];
  153.     if (uniqueZone) {
  154.         NSRecycleZone([self zone]);
  155.     }
  156.     [super dealloc];
  157. }
  158.  
  159. - (NSTextView *)firstTextView {
  160.     return [[self layoutManager] firstTextView];
  161. }
  162.  
  163. - (NSSize)viewSize {
  164.     return [scrollView contentSize];
  165. }
  166.  
  167. - (void)setViewSize:(NSSize)size {
  168.     NSWindow *window = [scrollView window];
  169.     NSRect origWindowFrame = [window frame];
  170.     NSSize scrollViewSize = [NSScrollView frameSizeForContentSize:size hasHorizontalScroller:[scrollView hasHorizontalScroller] hasVerticalScroller:[scrollView hasVerticalScroller] borderType:[scrollView borderType]];
  171.     [window setContentSize:scrollViewSize];
  172.     [window setFrameTopLeftPoint:NSMakePoint(origWindowFrame.origin.x, NSMaxY(origWindowFrame))];
  173. }
  174.  
  175. /* This method causes the text to be laid out in the foreground (approximately) up to the indicated character index.
  176. */
  177. - (void)doForegroundLayoutToCharacterIndex:(unsigned)loc {
  178.     unsigned len;
  179.     if (loc > 0 && (len = [[self textStorage] length]) > 0) {
  180.         NSRange glyphRange;
  181.         if (loc >= len) loc = len - 1;
  182.         /* Find out which glyph index the desired character index corresponds to */
  183.         glyphRange = [[self layoutManager] glyphRangeForCharacterRange:NSMakeRange(loc, 1) actualCharacterRange:NULL];
  184.         if (glyphRange.location > 0) {
  185.             /* Now cause layout by asking a question which has to determine where the glyph is */
  186.             (void)[[self layoutManager] textContainerForGlyphAtIndex:glyphRange.location - 1 effectiveRange:NULL];
  187.         }
  188.     }
  189. }
  190.  
  191. + (NSString *)cleanedUpPath:(NSString *)filename {
  192.     NSString *resolvedSymlinks = [filename stringByResolvingSymlinksInPath];
  193.     if ([resolvedSymlinks length] > 0) {
  194.         NSString *standardized = [resolvedSymlinks stringByStandardizingPath];
  195.         return [standardized length] ? standardized : resolvedSymlinks;
  196.     }
  197.     return filename;
  198. }
  199.  
  200. - (void)setDocumentName:(NSString *)filename {
  201.     [documentName autorelease];
  202.     if (filename) {
  203.         documentName = [[filename stringByResolvingSymlinksInPath] copyWithZone:[self zone]];
  204.         [[self window] setTitleWithRepresentedFilename:documentName];
  205.         if (uniqueZone) NSSetZoneName([self zone], documentName);
  206.     } else {
  207.     NSString *untitled = NSLocalizedString(@"UNTITLED", @"Name of new, untitled document");
  208.         if ([self isRichText]) untitled = [untitled stringByAppendingPathExtension:@"rtf"];
  209.     if (potentialSaveDirectory) {
  210.         [[self window] setTitleWithRepresentedFilename:[potentialSaveDirectory stringByAppendingPathComponent:untitled]];
  211.     } else {
  212.         [[self window] setTitle:untitled];
  213.     }
  214.         if (uniqueZone) NSSetZoneName([self zone], untitled);
  215.         documentName = nil;
  216.     }
  217. }
  218.  
  219. - (NSString *)documentName {
  220.     return documentName;
  221. }
  222.  
  223. - (void)setPotentialSaveDirectory:(NSString *)nm {
  224.     if (! [[Preferences objectForKey:OpenPanelFollowsMainWindow] boolValue]) return;
  225.     [potentialSaveDirectory autorelease];
  226.     potentialSaveDirectory = [nm copy];
  227. }
  228.  
  229. - (NSString *)potentialSaveDirectory {
  230.     if (! [[Preferences objectForKey:OpenPanelFollowsMainWindow] boolValue]) {
  231.     return NSHomeDirectory();
  232.     }
  233.     return potentialSaveDirectory;
  234. }
  235.  
  236. - (void)setDocumentEdited:(BOOL)flag {
  237.     if (flag != isDocumentEdited) {
  238.         isDocumentEdited = flag;
  239.         [[self window] setDocumentEdited:isDocumentEdited];
  240.     }
  241. }
  242.  
  243. - (BOOL)isDocumentEdited {
  244.     return isDocumentEdited;
  245. }
  246.  
  247. - (NSTextStorage *)textStorage {
  248.     return textStorage;
  249. }
  250.  
  251. - (NSWindow *)window {
  252.     return [[self firstTextView] window];
  253. }
  254.  
  255. - (NSLayoutManager *)layoutManager {
  256.     return [[textStorage layoutManagers] objectAtIndex:0];
  257. }
  258.  
  259. - (void)setPrintInfo:(NSPrintInfo *)anObject {
  260.     if (printInfo == anObject || [[printInfo dictionary] isEqual:[anObject dictionary]]) return;
  261.  
  262.     [printInfo autorelease];
  263.     printInfo = [anObject copyWithZone:[self zone]];
  264.  
  265.     if ([self hasMultiplePages]) {
  266.         unsigned cnt, numberOfPages = [self numberOfPages];
  267.         MultiplePageView *pagesView = [scrollView documentView];
  268.         NSArray *textContainers = [[self layoutManager] textContainers];
  269.  
  270.         [pagesView setPrintInfo:printInfo];
  271.         
  272.         for (cnt = 0; cnt < numberOfPages; cnt++) {
  273.             NSRect textFrame = [pagesView documentRectForPageNumber:cnt];
  274.             NSTextContainer *textContainer = [textContainers objectAtIndex:cnt];
  275.             [textContainer setContainerSize:textFrame.size];
  276.             [[textContainer textView] setFrame:textFrame];
  277.         }
  278.     }
  279. }
  280.  
  281. - (NSPrintInfo *)printInfo {
  282.     return printInfo;
  283. }
  284.  
  285. /* Multiple page related code */
  286.  
  287. - (unsigned)numberOfPages {
  288.     if (hasMultiplePages) {
  289.         return [[scrollView documentView] numberOfPages];
  290.     } else {
  291.         return 1;
  292.     }
  293. }
  294.  
  295. - (BOOL)hasMultiplePages {
  296.     return hasMultiplePages;
  297. }
  298.  
  299. - (void)addPage {
  300.     NSZone *zone = [self zone];
  301.     unsigned numberOfPages = [self numberOfPages];
  302.     MultiplePageView *pagesView = [scrollView documentView];
  303.     NSSize textSize = [pagesView documentSizeInPage];
  304.     NSTextContainer *textContainer = [[NSTextContainer allocWithZone:zone] initWithContainerSize:textSize];
  305.     NSTextView *textView;
  306.     [pagesView setNumberOfPages:numberOfPages + 1];
  307.     textView = [[NSTextView allocWithZone:zone] initWithFrame:[pagesView documentRectForPageNumber:numberOfPages] textContainer:textContainer];
  308.     [textView setHorizontallyResizable:NO];
  309.     [textView setVerticallyResizable:NO];
  310.     [pagesView addSubview:textView];
  311.     [[self layoutManager] addTextContainer:textContainer];
  312.     [textView release];
  313.     [textContainer release];
  314. }
  315.  
  316. - (void)removePage {
  317.     unsigned numberOfPages = [self numberOfPages];
  318.     NSArray *textContainers = [[self layoutManager] textContainers];
  319.     NSTextContainer *lastContainer = [textContainers objectAtIndex:[textContainers count] - 1];
  320.     MultiplePageView *pagesView = [scrollView documentView];
  321.     [pagesView setNumberOfPages:numberOfPages - 1];
  322.     [[lastContainer textView] removeFromSuperview];
  323.     [[lastContainer layoutManager] removeTextContainerAtIndex:[textContainers count] - 1];
  324. }
  325.  
  326. /* This method determines whether the document has multiple pages or not. It can be called at anytime.
  327. */   
  328. - (void)setHasMultiplePages:(BOOL)flag {
  329.     NSZone *zone = [self zone];
  330.  
  331.     hasMultiplePages = flag;
  332.  
  333.     if (hasMultiplePages) {
  334.         NSTextView *textView = [self firstTextView];
  335.         MultiplePageView *pagesView = [[MultiplePageView allocWithZone:zone] init];
  336.  
  337.         [scrollView setDocumentView:pagesView];
  338.  
  339.         [pagesView setPrintInfo:[self printInfo]];
  340.         // MF: Add the first new page before we remove the old container so we can avoid losing all the shared text view state.
  341.         [self addPage];
  342.         if (textView) {
  343.             [[self layoutManager] removeTextContainerAtIndex:0];
  344.         }
  345.         [scrollView setHasHorizontalScroller:YES];
  346.  
  347.     } else {
  348.         NSSize size = [scrollView contentSize];
  349.         NSTextContainer *textContainer = [[NSTextContainer allocWithZone:zone] initWithContainerSize:NSMakeSize(size.width, FLT_MAX)];
  350.         NSTextView *textView = [[NSTextView allocWithZone:zone] initWithFrame:NSMakeRect(0.0, 0.0, size.width, size.height) textContainer:textContainer];
  351.  
  352.         // Insert the single container as the first container in the layout manager before removing the existing pages in order to preserve the shared view state.
  353.         [[self layoutManager] insertTextContainer:textContainer atIndex:0];
  354.  
  355.         if ([[scrollView documentView] isKindOfClass:[MultiplePageView class]]) {
  356.             NSArray *textContainers = [[self layoutManager] textContainers];
  357.             unsigned cnt = [textContainers count];
  358.             while (cnt-- > 1) {
  359.                 [[self layoutManager] removeTextContainerAtIndex:cnt];
  360.             }
  361.         }
  362.  
  363.         [textContainer setWidthTracksTextView:YES];
  364.         [textContainer setHeightTracksTextView:NO];        /* Not really necessary */
  365.         [textView setHorizontallyResizable:NO];            /* Not really necessary */
  366.         [textView setVerticallyResizable:YES];
  367.     [textView setAutoresizingMask:NSViewWidthSizable];
  368.         [textView setMinSize:size];    /* Not really necessary; will be adjusted by the autoresizing... */
  369.         [textView setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];    /* Will be adjusted by the autoresizing... */  
  370.  
  371.         /* The next line should cause the multiple page view and everything else to go away */
  372.         [scrollView setDocumentView:textView];
  373.         [scrollView setHasHorizontalScroller:NO];
  374.         
  375.         [textView release];
  376.         [textContainer release];
  377.     }
  378.     [[scrollView window] makeFirstResponder:[self firstTextView]];
  379. }
  380.  
  381. /* This method is called at startup and in response to a colors changed notification to fix up the color displayed in the background area of the scrollview (actually clipview) when in "wrap to page" mode.
  382.  
  383.    The reason we need to get fancy here is to assure that the color on Windows is a blend of black and the "3D Objects" color (which is what some Windows apps do). If the color was a regular system color, registering for the notification would not be necessary.
  384. */
  385. - (void)fixUpScrollViewBackgroundColor:(NSNotification *)notification {
  386.     NSColor *backgroundColor;
  387.     if (NSInterfaceStyleForKey(NSInterfaceStyleDefault, scrollView) == NSWindows95InterfaceStyle) {
  388.     backgroundColor = [[NSColor controlColor] blendedColorWithFraction:0.5 ofColor:[NSColor blackColor]];
  389.     if (backgroundColor == nil) backgroundColor = [NSColor controlColor];
  390.     } else {
  391.     backgroundColor = [NSColor controlColor];
  392.     }
  393.     [[scrollView contentView] setBackgroundColor:backgroundColor]; 
  394. }
  395.  
  396. /* Outlet methods */
  397.  
  398. - (void)setScrollView:(id)anObject {
  399.     scrollView = anObject;
  400.     [scrollView setHasVerticalScroller:YES];
  401.     [scrollView setHasHorizontalScroller:NO];
  402.     [[scrollView contentView] setAutoresizesSubviews:YES];
  403.     [self fixUpScrollViewBackgroundColor:nil];
  404.     if (NSInterfaceStyleForKey(NSInterfaceStyleDefault, scrollView) == NSWindows95InterfaceStyle) {
  405.     [scrollView setBorderType:NSBezelBorder];
  406.     }        
  407. }
  408.         
  409. static NSPopUpButton *encodingPopupButton = nil;
  410. static NSView *encodingAccessory = nil;
  411.  
  412. + (void)loadEncodingPopupButton:(NSPopUpButton *)anObject {
  413. }
  414.  
  415. + (void)setEncodingPopupButton:(id)anObject {        /* Outlet method... */
  416.     encodingPopupButton = [anObject retain];
  417.     [self loadEncodingPopupButton:encodingPopupButton];
  418. }
  419.  
  420. + (void)setEncodingAccessory:(id)anObject {    /* Outlet method... */
  421.     encodingAccessory = [anObject retain];
  422. }
  423.  
  424. /* Use this method to get the accessory. It reinitailizes the popup, selects the specified item, and also includes or deletes the first entry (corresponding to "Default")
  425. */
  426. + (NSView *)encodingAccessory:(int)encoding includeDefaultEntry:(BOOL)includeDefaultItem {
  427.     if (!encodingAccessory) {
  428.         if (![NSBundle loadNibNamed:@"EncodingAccessory" owner:self])  {
  429.             NSLog(@"Failed to load EncodingAccessory.nib");
  430.         }
  431.     }
  432.     SetUpEncodingPopupButton(encodingPopupButton, encoding, includeDefaultItem);
  433.     return encodingAccessory;
  434. }
  435.  
  436. - (void)removeAttachments {
  437.     NSTextStorage *attrString = [self textStorage];
  438.     unsigned loc = 0;
  439.     unsigned end = [attrString length];
  440.     [attrString beginEditing];
  441.     while (loc < end) {    /* Run through the string in terms of attachment runs */
  442.         NSRange attachmentRange;    /* Attachment attribute run */
  443.         NSTextAttachment *attachment = [attrString attribute:NSAttachmentAttributeName atIndex:loc longestEffectiveRange:&attachmentRange inRange:NSMakeRange(loc, end-loc)];
  444.         if (attachment) {    /* If there is an attachment, make sure it is valid */
  445.             unichar ch = [[attrString string] characterAtIndex:loc];
  446.             if (ch == NSAttachmentCharacter) {
  447.                 [attrString replaceCharactersInRange:NSMakeRange(loc, 1) withString:@""];
  448.                 end = [attrString length];    /* New length */
  449.             } else {
  450.                 loc++;    /* Just skip over the current character... */
  451.             }
  452.         }     else {
  453.             loc = NSMaxRange(attachmentRange);
  454.         }
  455.     }
  456.     [attrString endEditing];
  457. }
  458.  
  459. /* The hyphenation methods are added to NSLayoutManager in OPENSTEP 4.2. Given that we'd like the app to keep on running against earlier versions of OPENSTEP, we make all hyphenation-related calls check to see if they are implemented.
  460. */
  461. static BOOL hyphenationSupported(void) {
  462.     return [NSLayoutManager instancesRespondToSelector:@selector(setHyphenationFactor:)];
  463. }
  464.  
  465. - (void)setHyphenationFactor:(float)factor {
  466.     if (hyphenationSupported()) [[self layoutManager] setHyphenationFactor:factor];
  467. }
  468.  
  469. - (float)hyphenationFactor {
  470.     return hyphenationSupported() ? [[self layoutManager] hyphenationFactor] : 0.0;
  471. }
  472.  
  473. /* ??? Doesn't check to see if the prev value is the same! (Otherwise the first time doesn't work...)
  474. */
  475. - (void)setRichText:(BOOL)flag {
  476.     NSTextView *view = [self firstTextView];
  477.     NSMutableDictionary *textAttributes = [[NSMutableDictionary alloc] initWithCapacity:2];
  478.  
  479.     isRichText = flag;
  480.  
  481.     if (isRichText) {
  482.     [textAttributes setObject:[Preferences objectForKey:RichTextFont] forKey:NSFontAttributeName];
  483.         [textAttributes setObject:[NSParagraphStyle defaultParagraphStyle] forKey:NSParagraphStyleAttributeName];
  484.  
  485.         // Make sure we aren't watching the DidProcessEditing notification since we don't adjust tab stops in rich text.
  486.         [[NSNotificationCenter defaultCenter] removeObserver:self name:NSTextStorageDidProcessEditingNotification object:[self textStorage]];
  487.     } else {
  488.         [textAttributes setObject:[Preferences objectForKey:PlainTextFont] forKey:NSFontAttributeName];
  489.         [textAttributes setObject:[NSParagraphStyle defaultParagraphStyle] forKey:NSParagraphStyleAttributeName];
  490.         [self removeAttachments];
  491.         
  492.         // Register for DidProcessEditing to fix the tabstops.
  493.         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textStorageDidProcessEditing:) name:NSTextStorageDidProcessEditingNotification object:[self textStorage]];
  494.     }
  495.     
  496.     [view setRichText:isRichText];
  497.     [view setUsesRuler:isRichText];    /* If NO, this correctly gets rid of the ruler if it was up */
  498.     [view setImportsGraphics:isRichText];
  499.  
  500.     if ([textStorage length]) {
  501.         [textStorage setAttributes:textAttributes range: NSMakeRange(0, [textStorage length])];
  502.     }
  503.     [view setTypingAttributes:textAttributes];
  504.     [textAttributes release];
  505. }
  506.  
  507. - (BOOL)isRichText {
  508.     return isRichText;
  509. }
  510.  
  511. /* User commands... */
  512.  
  513. - (void)printDocumentUsingPrintPanel:(BOOL)uiFlag {
  514.     NSPrintOperation *op;
  515.  
  516.     op = [NSPrintOperation printOperationWithView:[scrollView documentView] printInfo:printInfo];
  517.     [op setShowPanels:uiFlag];
  518.     [op runOperation];
  519. }
  520.  
  521. - (void)printDocument:(id)sender {
  522.     [self printDocumentUsingPrintPanel:YES];
  523. }
  524.  
  525. - (void)toggleRich:(id)sender {
  526.     if (isRichText && ([textStorage length] > 0)) {
  527.         int choice = NSRunAlertPanel(NSLocalizedString(@"Make Plain Text", @"Title of alert confirming Make Plain Text"), 
  528.             NSLocalizedString(@"Convert document to plain text? This will lose fonts, colors, and other text attribute settings.", @"Message confirming Make Plain Text"), 
  529.             NSLocalizedString(@"OK", @"OK."), NSLocalizedString(@"Cancel", @"Button choice allowing user to cancel."), nil);
  530.         if (choice != NSAlertDefaultReturn) return;
  531.     }
  532.     [self setRichText:!isRichText];
  533.     [self setDocumentEdited:YES];
  534.     [self setDocumentName:nil];
  535. }
  536.  
  537. - (void)togglePageBreaks:(id)sender {
  538.     [self setHasMultiplePages:![self hasMultiplePages]];
  539. }
  540.  
  541. - (void)toggleHyphenation:(id)sender {
  542.     [self setHyphenationFactor:([self hyphenationFactor] > 0.0) ? 0.0 : 0.9];    /* Toggle between 0.0 and 0.9 */
  543.     if ([self isRichText]) {
  544.         [self setDocumentEdited:YES];
  545.     }
  546. }
  547.  
  548. - (void)runPageLayout:(id)sender {
  549.     NSPrintInfo *tempPrintInfo = [[self printInfo] copy];
  550.     NSPageLayout *pageLayout = [NSPageLayout pageLayout];
  551.     int runResult;
  552.     runResult = [pageLayout runModalWithPrintInfo:tempPrintInfo];
  553.     if (runResult == NSOKButton) {
  554.         [self setPrintInfo:tempPrintInfo];
  555.     }
  556.     [tempPrintInfo release];
  557. }
  558.  
  559. - (void)revert:(id)sender {
  560.     if (documentName) {
  561.         NSString *fileName = [documentName lastPathComponent];
  562.         int choice = NSRunAlertPanel(NSLocalizedString(@"Revert", @"Title of alert confirming revert"), 
  563.                 NSLocalizedString(@"Revert to saved version of %@?", @"Message confirming revert of specified document name."), 
  564.                 NSLocalizedString(@"OK", @"OK."), NSLocalizedString(@"Cancel", @"Button choice allowing user to cancel."), nil, fileName);
  565.         if (choice == NSAlertDefaultReturn) {
  566.             if (![self loadFromPath:documentName encoding:encodingIfPlainText]) {
  567.                 (void)NSRunAlertPanel(NSLocalizedString(@"Couldn't Revert", @"Title of alert indicating file couldn't be reverted"), 
  568.                 NSLocalizedString(@"Couldn't revert to saved version of %@.", @"Message indicating file couldn't be reverted."), 
  569.                 NSLocalizedString(@"OK", @"OK."), nil, nil, documentName);
  570.             } else {
  571.                 [self setDocumentEdited:NO];
  572.             }
  573.         }
  574.     }
  575. }
  576.  
  577. - (void)close:(id)sender {
  578.     [[self window] close];
  579. }
  580.  
  581. /* Not correct! */
  582. - (void)saveTo:(id)sender {
  583.     [self saveAs:sender];
  584. }
  585.  
  586. - (void)saveAs:(id)sender {
  587.     (void)[self saveDocument:YES];
  588. }
  589.  
  590. - (void)save:(id)sender {
  591.     (void)[self saveDocument:NO];
  592. }
  593.  
  594. + (void)openWithEncodingAccessory:(BOOL)flag {
  595.     NSOpenPanel *panel = [NSOpenPanel openPanel];
  596.     if (flag) {
  597.         [panel setAccessoryView:[self encodingAccessory:[[Preferences objectForKey:PlainTextEncoding] intValue] includeDefaultEntry:YES]];
  598.     }
  599.     [panel setAllowsMultipleSelection:YES];
  600.     [panel setDirectory:[Document openSavePanelDirectory]];
  601.     if ([panel runModal]) {
  602.         NSArray *filenames = [panel filenames];
  603.         unsigned cnt, numFiles = [filenames count];
  604.         for (cnt = 0; cnt < numFiles; cnt++) {
  605.             NSString *filename = [filenames objectAtIndex:cnt];
  606.             if (![Document openDocumentWithPath:filename encoding:flag ? [[encodingPopupButton selectedItem] tag] : UnknownStringEncoding]) {
  607.                 NSString *alternate = (cnt + 1 == numFiles) ? nil : NSLocalizedString(@"Abort", @"Button allowing user to abort opening multiple files after one couldn't be opened");
  608.                 unsigned choice = NSRunAlertPanel(NSLocalizedString(@"File system error", @"Title of alert indicating file couldn't be opened"), 
  609.                 NSLocalizedString(@"Couldn't open file %@.", @"Message indicating file couldn't be opened."), 
  610.                 NSLocalizedString(@"OK", @"OK"), alternate, nil, filename);
  611.                 if (choice == NSCancelButton) break;
  612.             }
  613.         }
  614.     }
  615. }
  616.  
  617. + (void)open:(id)sender {
  618.     [self openWithEncodingAccessory:YES];
  619. }
  620.  
  621. /* Find submenu commands */
  622.  
  623. - (void)orderFrontFindPanel:(id)sender {
  624.     [[TextFinder sharedInstance] orderFrontFindPanel:sender];
  625. }
  626.  
  627. - (void)findNext:(id)sender {
  628.     [[TextFinder sharedInstance] findNext:sender];
  629. }
  630.  
  631. - (void)findPrevious:(id)sender {
  632.     [[TextFinder sharedInstance] findPrevious:sender];
  633. }
  634.  
  635. - (void)enterSelection:(id)sender {
  636.     NSRange range = [[self firstTextView] selectedRange];
  637.     if (range.length) {
  638.     [[TextFinder sharedInstance] setFindString:[[textStorage string] substringWithRange:range]];
  639.     } else {
  640.         NSBeep();
  641.     }
  642. }
  643.  
  644. - (void)jumpToSelection:(id)sender {
  645.     NSTextView *textView = [self firstTextView];
  646.     [textView scrollRangeToVisible:[textView selectedRange]];
  647. }
  648.  
  649.  
  650.  
  651. /* 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.
  652. */
  653. - (BOOL)canCloseDocument {
  654.     if (isDocumentEdited) {
  655.         int result = NSRunAlertPanel(
  656.                         NSLocalizedString(@"Close", @"Title of alert panel which comes when the user tries to quit or close a window containing an unsaved document."),
  657.                         NSLocalizedString(@"Document has been edited. Save?", @"Question asked of user when he/she tries to close a window containing an unsaved document."),
  658.                         NSLocalizedString(@"Save", @"Button choice which allows the user to save the document."),
  659.                         NSLocalizedString(@"Don't Save", @"Button choice which allows the user to abort the save of a document which is being closed."),
  660.                         NSLocalizedString(@"Cancel", @"Button choice allowing user to cancel."));
  661.         if (result == NSAlertDefaultReturn) {    /* Save */
  662.             if (![self saveDocument:NO]) return NO;
  663.         } else if (result == NSAlertOtherReturn) {    /* Cancel */
  664.             return NO;
  665.     }    /* Don't save case falls through to the YES return */
  666.     }
  667.     return YES;
  668. }
  669.  
  670. /* Saves the document. Puts up save panel if necessary or if showSavePanel is YES. Returns NO if the user cancels the save...
  671. */
  672. - (BOOL)saveDocument:(BOOL)showSavePanel {
  673.     NSString *nameForSaving = [self documentName];
  674.     int encodingForSaving;
  675.     BOOL haveToChangeType = NO;
  676.     BOOL showEncodingAccessory = NO;
  677.         
  678.     if ([self isRichText]) {
  679.         if (nameForSaving && [@"rtfd" isEqualToString:[nameForSaving pathExtension]]) {
  680.             encodingForSaving = RichTextWithGraphicsStringEncoding;
  681.         } else {
  682.         encodingForSaving = [textStorage containsAttachments] ? RichTextWithGraphicsStringEncoding : RichTextStringEncoding;
  683.             if ((encodingForSaving == RichTextWithGraphicsStringEncoding) && nameForSaving && [@"rtf" isEqualToString:[nameForSaving pathExtension]]) {
  684.                 nameForSaving = nil;    /* Force the user to provide a new name... */
  685.             }
  686.         }
  687.     } else {
  688.         NSString *string = [textStorage string];
  689.         showEncodingAccessory = YES;
  690.         encodingForSaving = encodingIfPlainText;
  691.         if ((encodingForSaving != UnknownStringEncoding) && ![string canBeConvertedToEncoding:encodingForSaving]) {
  692.             haveToChangeType = YES;
  693.             encodingForSaving = UnknownStringEncoding;
  694.         }
  695.         if (encodingForSaving == UnknownStringEncoding) {
  696.             NSStringEncoding defaultEncoding = [[Preferences objectForKey:PlainTextEncoding] intValue];
  697.             if ([string canBeConvertedToEncoding:defaultEncoding]) {
  698.                 encodingForSaving = defaultEncoding;
  699.             } else {
  700.                 const int *plainTextEncoding = SupportedEncodings();
  701.                 while (*plainTextEncoding != -1) {
  702.                     if ((*plainTextEncoding >= 0) && (*plainTextEncoding != defaultEncoding) && (*plainTextEncoding != NSUnicodeStringEncoding) && (*plainTextEncoding != NSUTF8StringEncoding) && [string canBeConvertedToEncoding:*plainTextEncoding]) {
  703.                         encodingForSaving = *plainTextEncoding;
  704.                         break;
  705.                     }
  706.                     plainTextEncoding++;
  707.                 }
  708.             }
  709.             if (encodingForSaving == UnknownStringEncoding) encodingForSaving = NSUnicodeStringEncoding;
  710.             if (haveToChangeType) {
  711.                 (void)NSRunAlertPanel(NSLocalizedString(@"Save Plain Text", @"Title of save and alert panels when saving plain text"), 
  712.             NSLocalizedString(@"Document can no longer be saved using its original %@ encoding. Please choose another encoding (%@ is one possibility).", @"Contents of alert panel informing user that the file's string encoding needs to be changed"), 
  713.             NSLocalizedString(@"OK", @"OK."), nil, nil, [NSString localizedNameOfStringEncoding:encodingIfPlainText], [NSString localizedNameOfStringEncoding:encodingForSaving]);
  714.             }
  715.         }
  716.     }
  717.  
  718.     while (1) {
  719.         if (!nameForSaving || haveToChangeType || showSavePanel) {
  720.             if (![self getDocumentName:&nameForSaving encoding:(haveToChangeType || showEncodingAccessory) ? &encodingForSaving : NULL oldName:nameForSaving oldEncoding:encodingForSaving]) return NO;    /* Cancelled */
  721.         }
  722.         /* The value of updateFileNames: below will have to become conditional on whether we're doing Save To at some point.  Also, we'll want to avoid doing the stuff inside the if if we're doing Save To. */
  723.         if ([self saveToPath:nameForSaving encoding:encodingForSaving updateFilenames:YES]) {
  724.             if (![self isRichText]) encodingIfPlainText = encodingForSaving;
  725.             [self setDocumentName:nameForSaving];
  726.             [self setDocumentEdited:NO];
  727.         [Document setLastOpenSavePanelDirectory:[nameForSaving stringByDeletingLastPathComponent]];
  728.         return YES;
  729.         } else {
  730.             NSRunAlertPanel(@"Couldn't Save",
  731.                 NSLocalizedString(@"Couldn't save document as %@.", @"Message indicating document couldn't be saved under the given name."),
  732.                 NSLocalizedString(@"OK", @"OK."), nil, nil, nameForSaving);
  733.             nameForSaving = nil;
  734.         }
  735.     }
  736.     return YES;
  737. }
  738.  
  739. /* 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.
  740. */
  741. - (BOOL)getDocumentName:(NSString **)newName encoding:(int *)encodingForSaving oldName:(NSString *)oldName oldEncoding:(int)encoding {
  742.     NSSavePanel *panel = [NSSavePanel savePanel];
  743.     switch (encoding) {
  744.         case RichTextStringEncoding:
  745.             [panel setRequiredFileType:@"rtf"];
  746.             [panel setTitle:NSLocalizedString(@"Save RTF", @"Title of save and alert panels when saving RTF")];
  747.             encodingForSaving = NULL;
  748.             break;
  749.         case RichTextWithGraphicsStringEncoding:
  750.             [panel setRequiredFileType:@"rtfd"];
  751.             [panel setTitle:NSLocalizedString(@"Save RTFD", @"Title of save and alert panels when saving RTFD")];
  752.             encodingForSaving = NULL;
  753.             break;
  754.         default:
  755.             [panel setTitle:NSLocalizedString(@"Save Plain Text", @"Title of save and alert panels when saving plain text")];
  756.             if (encodingForSaving) {
  757.                 unsigned cnt;
  758.                 [panel setAccessoryView:[[self class] encodingAccessory:*encodingForSaving includeDefaultEntry:NO]];
  759.                 for (cnt = 0; cnt < [encodingPopupButton numberOfItems]; cnt++) {
  760.                     int encoding = [[encodingPopupButton itemAtIndex:cnt] tag];
  761.                     if ((encoding != UnknownStringEncoding) && ![[textStorage string] canBeConvertedToEncoding:encoding]) {
  762.                         [[encodingPopupButton itemAtIndex:cnt] setEnabled:NO];
  763.                     }
  764.                 }
  765.             }
  766.             break;
  767.     }
  768.     if (potentialSaveDirectory) {
  769.     [Document setLastOpenSavePanelDirectory:potentialSaveDirectory];
  770.     }
  771.     if (oldName ? [panel runModalForDirectory:[oldName stringByDeletingLastPathComponent] file:[oldName lastPathComponent]] : [panel runModalForDirectory:[Document openSavePanelDirectory] file:@""]) {
  772.         *newName = [panel filename];
  773.     if (potentialSaveDirectory) {
  774.         [self setPotentialSaveDirectory:nil];
  775.     }
  776.         if (encodingForSaving) *encodingForSaving = [[encodingPopupButton selectedItem] tag];
  777.         return YES;
  778.     } else {
  779.         return NO;
  780.     }
  781. }
  782.  
  783. /* Window delegation messages */
  784.  
  785. - (BOOL)windowShouldClose:(id)sender {
  786.     return [self canCloseDocument];
  787. }
  788.  
  789. - (void)windowWillClose:(NSNotification *)notification {
  790.     NSWindow *window = [self window];
  791.     [window setDelegate:nil];
  792.     [self release];
  793. }
  794.  
  795. /* Text view delegation messages */
  796.  
  797. - (void)textDidChange:(NSNotification *)textObject {
  798.     if (!isDocumentEdited) {
  799.         [self setDocumentEdited:YES];
  800.     }
  801. }
  802.  
  803. - (void)textView:(NSTextView *)view doubleClickedOnCell:(id <NSTextAttachmentCell>)cell inRect:(NSRect)rect {
  804.     BOOL success = NO;
  805.     NSString *name = [[[cell attachment] fileWrapper] filename];
  806.     if (name && documentName && ![name isEqualToString:@""] && ![documentName isEqualToString:@""]) {
  807.     NSString *fullPath = [documentName stringByAppendingPathComponent:name];
  808.         success = [[NSWorkspace sharedWorkspace] openFile:fullPath];
  809.     }
  810.     if (!success) {
  811.         NSBeep();
  812.     }
  813. }
  814.  
  815. - (void)textView:(NSTextView *)view draggedCell:(id <NSTextAttachmentCell>)cell inRect:(NSRect)rect event:(NSEvent *)event {
  816.     BOOL success = NO;
  817.     NSString *name = [[[cell attachment] fileWrapper] filename];
  818.     if (name && documentName && ![name isEqualToString:@""] && ![documentName isEqualToString:@""]) {
  819.         NSString *fullPath = [documentName stringByAppendingPathComponent:name];
  820.         NSImage *image = nil;
  821.         if ([cell isKindOfClass:[NSCell class]]) image = [(NSCell *)cell image];    /* Cheezy; should really draw the cell into an image... */
  822.         if (!image) image = [[NSWorkspace sharedWorkspace] iconForFile:fullPath];
  823.         if (image) {
  824.             NSSize cellSizeInBaseCoords = [view convertSize:rect.size toView:nil];
  825.             NSSize imageSize = [image size];
  826.             NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
  827.             [pasteboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:nil];
  828.             [pasteboard setPropertyList:[NSArray arrayWithObject:fullPath] forType:NSFilenamesPboardType];
  829.             if (!NSEqualSizes(cellSizeInBaseCoords, imageSize)) {
  830.                 NSPoint mouseDownLocation = [view convertPoint:[event locationInWindow] fromView:nil];
  831.                 rect.origin.x = mouseDownLocation.x - (imageSize.width - floor(imageSize.width / 4.0));
  832.                 rect.origin.y = mouseDownLocation.y - (imageSize.height - floor(imageSize.height / 4.0));
  833.             } else {
  834.                 // We need to pass the image origin which means we need to unflip the rect we're getting.
  835.                 rect.origin.y += rect.size.height;
  836.             }
  837.             [view dragImage:image at:rect.origin offset:NSZeroSize event:event pasteboard:pasteboard source:self slideBack:YES];
  838.             success = YES;
  839.         }
  840.     }
  841.     if (!success) {
  842.         NSBeep();
  843.     }
  844. }
  845.  
  846. - (unsigned)draggingSourceOperationMaskForLocal:(BOOL)flag {
  847.     return NSDragOperationGeneric | NSDragOperationCopy;
  848. }
  849.  
  850. /*** Layout manager delegation message ***/
  851.  
  852. - (void)layoutManager:(NSLayoutManager *)layoutManager didCompleteLayoutForTextContainer:(NSTextContainer *)textContainer atEnd:(BOOL)layoutFinishedFlag {
  853.  
  854.     if ([self hasMultiplePages]) {
  855.         NSArray *containers = [layoutManager textContainers];
  856.  
  857.         if (!layoutFinishedFlag || (textContainer == nil)) {
  858.             // Either layout is not finished or it is but there are glyphs laid nowhere.
  859.             NSTextContainer *lastContainer = [containers lastObject];
  860.  
  861.             if ((textContainer == lastContainer) || (textContainer == nil)) {
  862.                 // Add a new page only if the newly full container is the last container or the nowhere container.
  863.                 [self addPage];
  864.             }
  865.         } else {
  866.             // Layout is done and it all fit.  See if we can axe some pages.
  867.             unsigned lastUsedContainerIndex = [containers indexOfObjectIdenticalTo:textContainer];
  868.             unsigned numContainers = [containers count];
  869.             while (++lastUsedContainerIndex < numContainers) {
  870.                 [self removePage];
  871.             }
  872.         }
  873.     }
  874. }
  875.  
  876. /*** Text storage notification message ***/
  877.  
  878. static NSArray *tabStopArrayForFontAndTabWidth(NSFont *font, unsigned tabWidth) {
  879.     static NSMutableArray *array = nil;
  880.     static float currentWidthOfTab = -1;
  881.     float charWidth;
  882.     float widthOfTab;
  883.     unsigned i;
  884.  
  885.     if ([font glyphIsEncoded:(NSGlyph)' ']) {
  886.         charWidth = [font advancementForGlyph:(NSGlyph)' '].width;
  887.     } else {
  888.         charWidth = [font maximumAdvancement].width;
  889.     }
  890.     widthOfTab = (charWidth * tabWidth);
  891.  
  892.     if (!array) {
  893.         array = [[NSMutableArray allocWithZone:NULL] initWithCapacity:100];
  894.     }
  895.  
  896.     if (widthOfTab != currentWidthOfTab) {
  897.         //NSLog(@"Calculating tabstops for font %@, tabWidth %u, real width %f.", font, tabWidth, widthOfTab);
  898.         [array removeAllObjects];
  899.         for (i = 1; i <= 100; i++) {
  900.             NSTextTab *tab = [[NSTextTab alloc] initWithType:NSLeftTabStopType location:widthOfTab * i];
  901.             [array addObject:tab];
  902.             [tab release];
  903.         }
  904.         currentWidthOfTab = widthOfTab;
  905.     }
  906.  
  907.     return array;
  908. }
  909.  
  910. - (void)textStorageDidProcessEditing:(NSNotification *)notification {
  911.     NSTextStorage *theTextStorage = [notification object];
  912.     NSString *string = [theTextStorage string];
  913.  
  914.     if ([string length] > 0) {
  915.         // Generally NSTextStorage's attached to plain text NSTextViews only have one font.  But this is not generally true.  To ensure the tabstops are uniform throughout the document we always base them on the font of the first character in the NSTextStorage.
  916.         NSFont *font = [theTextStorage attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL];
  917.  
  918.         // Substitute a screen font is the layout manager will do so for display.
  919.         // MF: printing will probably be an issue here...
  920.         font = [[self layoutManager] substituteFontForFont:font];
  921.         if ([font isFixedPitch]) {
  922.             unsigned tabWidth = [[Preferences objectForKey:TabWidth] intValue];
  923.             NSArray *desiredTabStops = tabStopArrayForFontAndTabWidth(font, tabWidth);
  924.             NSRange editedRange = [theTextStorage editedRange];
  925.             NSRange eRange;
  926.             NSParagraphStyle *paraStyle;
  927.             NSMutableParagraphStyle *newStyle;
  928.  
  929.             editedRange = [string lineRangeForRange:editedRange];
  930.  
  931.             // We will traverse the edited range by paragraphs fixing the paragraph styles
  932.             while (editedRange.length > 0) {
  933.                 paraStyle = [theTextStorage attribute:NSParagraphStyleAttributeName atIndex:editedRange.location longestEffectiveRange:&eRange inRange:editedRange];
  934.                 if (!paraStyle) {
  935.                     paraStyle = [NSParagraphStyle defaultParagraphStyle];
  936.                 }
  937.                 eRange = NSIntersectionRange(editedRange, eRange);
  938.                 if (![[paraStyle tabStops] isEqual:desiredTabStops]) {
  939.                     // Make sure we don't change stuff outside editedRange.
  940.                     newStyle = [paraStyle mutableCopyWithZone:[theTextStorage zone]];
  941.                     [newStyle setTabStops:desiredTabStops];
  942.                     [theTextStorage addAttribute:NSParagraphStyleAttributeName value:newStyle range:eRange];
  943.                     [newStyle release];
  944.                 }
  945.                 if (NSMaxRange(eRange) < NSMaxRange(editedRange)) {
  946.                     editedRange.length = NSMaxRange(editedRange) - NSMaxRange(eRange);
  947.                     editedRange.location = NSMaxRange(eRange);
  948.                 } else {
  949.                     editedRange = NSMakeRange(NSMaxRange(editedRange), 0);
  950.                 }
  951.             }
  952.         }
  953.     }
  954. }
  955.  
  956. /* Return the document in the specified window.
  957. */
  958. + (Document *)documentForWindow:(NSWindow *)window {
  959.     id delegate = [window delegate];
  960.     return (delegate && [delegate isKindOfClass:[Document class]]) ? delegate : nil;
  961. }
  962.  
  963. /* Return an existing document...
  964. */
  965. + (Document *)documentForPath:(NSString *)filename {
  966.     NSArray *windows = [[NSApplication sharedApplication] windows];
  967.     unsigned cnt, numWindows = [windows count];
  968.     filename = [self cleanedUpPath:filename];    /* Clean up the incoming path */
  969.     for (cnt = 0; cnt < numWindows; cnt++) {
  970.         Document *document = [Document documentForWindow:[windows objectAtIndex:cnt]];
  971.     NSString *docName = [document documentName];    
  972.     if (docName && [filename isEqual:[self cleanedUpPath:docName]]) return document;
  973.     }
  974.     return nil;
  975. }
  976.  
  977. + (unsigned)numberOfOpenDocuments {
  978.     NSArray *windows = [[NSApplication sharedApplication] windows];
  979.     unsigned cnt, numWindows = [windows count], numDocuments = 0;
  980.     for (cnt = 0; cnt < numWindows; cnt++) {
  981.         if ([Document documentForWindow:[windows objectAtIndex:cnt]]) numDocuments++;
  982.     }
  983.     return numDocuments;
  984. }
  985.  
  986. /* Menu validation: Arbitrary numbers to determine the state of the menu items whose titles change. Speeds up the validation... Not zero. */   
  987. #define TagForFirst 42
  988. #define TagForSecond 43
  989.  
  990. static void validateToggleItem(NSMenuItem *aCell, BOOL useFirst, NSString *first, NSString *second) {
  991.     if (useFirst) {
  992.         if ([aCell tag] != TagForFirst) {
  993.             [aCell setTitleWithMnemonic:first];
  994.             [aCell setTag:TagForFirst];
  995.             [((NSMenu *)[[(NSCell *)aCell controlView] window]) sizeToFit];
  996.         }
  997.     } else {
  998.         if ([aCell tag] != TagForSecond) {
  999.             [aCell setTitleWithMnemonic:second];
  1000.             [aCell setTag:TagForSecond];
  1001.             [((NSMenu *)[[(NSCell *)aCell controlView] window]) sizeToFit];
  1002.         }
  1003.     }
  1004. }
  1005.  
  1006. /* Menu validation
  1007. */
  1008. - (BOOL)validateMenuItem:(NSMenuItem *)aCell {
  1009.     SEL action = [aCell action];
  1010.     if (action == @selector(toggleRich:)) {
  1011.     validateToggleItem(aCell, [self isRichText], NSLocalizedString(@"&Make Plain Text", @"Menu item to make the current document plain text"), NSLocalizedString(@"&Make Rich Text", @"Menu item to make the current document rich text"));
  1012.     } else if (action == @selector(togglePageBreaks:)) {
  1013.         validateToggleItem(aCell, [self hasMultiplePages], NSLocalizedString(@"&Wrap to Window", @"Menu item to cause text to be laid out to size of the window"), NSLocalizedString(@"&Wrap to Page", @"Menu item to cause text to be laid out to the size of the currently selected page type"));
  1014.     } else if (action == @selector(toggleHyphenation:)) {
  1015.         if (!hyphenationSupported()) return NO;    /* Disable it... */
  1016.         validateToggleItem(aCell, ([self hyphenationFactor] > 0.0), NSLocalizedString(@"Disallow &Hyphenation", @"Menu item to disallow hyphenation in the document"), NSLocalizedString(@"Allow &Hyphenation", @"Menu item to allow hyphenation in the document"));
  1017.     }
  1018.     return YES;
  1019. }
  1020.  
  1021. static NSString *lastOpenSavePanelDir = nil;
  1022.  
  1023. /* Sets the directory in which a save was last done...
  1024. */
  1025. + (void)setLastOpenSavePanelDirectory:(NSString *)dir {
  1026.     if (lastOpenSavePanelDir != dir) {
  1027.     [lastOpenSavePanelDir autorelease];
  1028.     lastOpenSavePanelDir = [dir copy];
  1029.     }
  1030. }
  1031.  
  1032. /* Returns the directory in which open/save panels should come up...
  1033. */
  1034. + (NSString *)openSavePanelDirectory {
  1035.     if ([[Preferences objectForKey:OpenPanelFollowsMainWindow] boolValue]) {
  1036.     Document *doc = [Document documentForWindow:[NSApp mainWindow]];
  1037.     if (doc && [doc documentName]) {
  1038.             return [[doc documentName] stringByDeletingLastPathComponent];
  1039.     } else if (doc && lastOpenSavePanelDir) {
  1040.         return lastOpenSavePanelDir;
  1041.     }
  1042.     } else if (lastOpenSavePanelDir) {
  1043.     return lastOpenSavePanelDir;
  1044.     }
  1045.     return NSHomeDirectory();
  1046. }
  1047.  
  1048. @end
  1049.  
  1050. /* Get list of supported encodings from the file Encodings.txt (containing comma-separated ints). If the file doesn't exist, a default, built-in list is used.
  1051. */
  1052. const int *SupportedEncodings(void) {
  1053.     static const int *encodings = NULL;
  1054.     static const int plainTextFileStringEncodingsSupported[] = {
  1055.         NSNEXTSTEPStringEncoding, NSISOLatin1StringEncoding, NSWindowsCP1252StringEncoding, NSISOLatin2StringEncoding, NSJapaneseEUCStringEncoding, NSUTF8StringEncoding, NSUnicodeStringEncoding, -1
  1056.     };
  1057.     if (!encodings) {
  1058.         NSString *str = [[NSBundle mainBundle] pathForResource:@"Encodings" ofType:@"txt"];
  1059.         if (str && (str = [[NSString alloc] initWithContentsOfFile:str])) {
  1060.             unsigned numEncodings = 0;
  1061.             int encoding;
  1062.             NSScanner *scanner = [NSScanner scannerWithString:str];
  1063.             [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@" ,\t"]];
  1064.             while ([scanner scanInt:&encoding]) if ([NSString localizedNameOfStringEncoding:encoding]) numEncodings++;
  1065.             if (numEncodings) {
  1066.                 int *tmp = NSZoneMalloc(NULL, (numEncodings + 1) * sizeof(int));
  1067.                 encodings = tmp;
  1068.                 [scanner setScanLocation:0];
  1069.                 while ([scanner scanInt:tmp]) if ([NSString localizedNameOfStringEncoding:*tmp]) tmp++;
  1070.                 *tmp = -1;
  1071.             }
  1072.         [str release];
  1073.         }
  1074.         if (!encodings) encodings = plainTextFileStringEncodingsSupported;    /* Default value... */
  1075.     }
  1076.     return encodings;
  1077. }
  1078.  
  1079. void SetUpEncodingPopupButton(NSPopUpButton *popup, int selectedEncoding, BOOL includeDefaultItem) {
  1080.     BOOL defaultItemIsIncluded = NO;
  1081.     unsigned cnt = [popup numberOfItems];
  1082.     if (cnt <= 1) {    /* Seems like the popup hasn't been initialized yet... */
  1083.         const int *enc = SupportedEncodings();
  1084.         [popup removeAllItems];
  1085.         while (*enc != -1) {
  1086.             [popup addItemWithTitle:[NSString localizedNameOfStringEncoding:*enc]];
  1087.             [[popup lastItem] setTag:*enc];
  1088.             enc++;
  1089.         }
  1090.     } else {    /* Check to see if the default item is in there... */
  1091.         while (!defaultItemIsIncluded && cnt-- > 0) {
  1092.             NSButtonCell *item = [popup itemAtIndex:cnt];
  1093.             if ([item tag] == UnknownStringEncoding) defaultItemIsIncluded = YES;
  1094.         }
  1095.     }
  1096.     if (includeDefaultItem && !defaultItemIsIncluded) {
  1097.         [popup insertItemWithTitle:NSLocalizedString(@"Default", @"Encoding popup entry indicating default encoding") atIndex:0];
  1098.         [[popup itemAtIndex:0] setTag:UnknownStringEncoding];
  1099.     } else if (!includeDefaultItem && defaultItemIsIncluded) {
  1100.         [popup removeItemAtIndex:0];
  1101.     }
  1102.     defaultItemIsIncluded = includeDefaultItem;
  1103.     cnt = [popup numberOfItems];
  1104.     while (cnt-- > 0) {
  1105.         NSButtonCell *item = [popup itemAtIndex:cnt];
  1106.         [item setEnabled:YES];
  1107.         if ([item tag] == selectedEncoding) [popup selectItemAtIndex:cnt];
  1108.     }
  1109. }
  1110.  
  1111. /*
  1112.  
  1113.  1/28/95 aozer    Created for Edit II.
  1114.  2/16/95 aozer    Added multiple page support.
  1115.  3/28/95 mferris Removed some old code and fixed firstTextView method to just call layout manager instead of doing it itself.
  1116.  4/11/95 aozer    Paper size read/write support
  1117.  8/16/95 aozer    Added plain text tabs (but they're semi broken)
  1118.  10/4/95 aozer    Combined open/save and open/save with encoding panels
  1119.   3/3/96 aozer    Got rid of page layout accessory
  1120.           Switched over to public RTF/attributed string API
  1121.  4/19/96 aozer    Foreground layout
  1122.            Create each document in its own zone
  1123.  8/29/96 rick    Added potentialSaveDirectory for untitled docs, to follow the main window at
  1124.           time the untitled doc is created
  1125.  11/7/96 aozer    Hyphenation support
  1126.  
  1127. */
  1128.