/**
Implements a panel that allows the user to save a file.
There is only one save panel per application and this panel is obtained by calling the +savePanel class method. From here, you should set the required file extension using -setRequiredFileType: When ready to show the panel, use the -runModal, or a similar method to show the panel in a modal session. Other methods allow you to set the initial directory and initially choosen file. The method will return one of NSOKButton or NSCancelButton depending on which button the user pressed.
Use the -filename method to retrieve the name of the file the user choose.
*/ @implementation NSSavePanel + (void) initialize { if (self == [NSSavePanel class]) { [self setVersion: 1]; ASSIGN (_fm, [NSFileManager defaultManager]); // A GNUstep feature if ([[NSUserDefaults standardUserDefaults] boolForKey: @"GSSavePanelShowProgress"]) { _gs_display_reading_progress = YES; } } } /** Returns the shared NSSavePanel instance */ + (NSSavePanel *) savePanel { if (_gs_gui_save_panel == nil) { _gs_gui_save_panel = [[NSSavePanel alloc] init]; } [_gs_gui_save_panel _resetDefaults]; return _gs_gui_save_panel; } // - (void) dealloc { [[NSNotificationCenter defaultCenter] removeObserver: self]; TEST_RELEASE (_fullFileName); TEST_RELEASE (_directory); TEST_RELEASE (_requiredFileType); [super dealloc]; } // If you do a simple -init, we initialize the panel with // the system size/mask/appearance/subviews/etc. If you do more // complicated initializations, you get a simple panel from super. -(id) init { [self _initWithoutGModel]; /* * All these are set automatically _directory = nil; _fullFileName = nil; _requiredFileType = nil; _delegate = nil; _treatsFilePackagesAsDirectories = NO; _delegateHasCompareFilter = NO; _delegateHasShowFilenameFilter = NO; _delegateHasValidNameFilter = NO; _delegateHasDirectoryDidChange = NO; _delegateHasSelectionDidChange = NO; */ [self _getOriginalSize]; return self; } /** Sets an accessory view which is shown near the bottom of the panel. The panel is automatically expanded with enough room to show the extra view. You can use this extra view to customize various characteristics of the file selection mechanism. For instance you could add a popup button which allows the user to select the format that the file is saved in (e.g. rtf or txt). See also -validateVisibleColumns . */ - (void) setAccessoryView: (NSView*)aView { NSRect accessoryViewFrame, bottomFrame; NSRect tmpRect; NSSize contentSize, contentMinSize; float addedHeight, accessoryWidth; if (aView == _accessoryView) return; /* The following code is very tricky. Please think and test a lot before changing it. */ /* Remove old accessory view if any */ if (_accessoryView != nil) { /* Remove accessory view */ accessoryViewFrame = [_accessoryView frame]; [_accessoryView removeFromSuperview]; /* Change the min size before doing the resizing otherwise it could be a problem. */ [self setMinSize: _originalMinSize]; /* Resize the panel to the height without the accessory view. This must be done with the special care of not resizing the heights of the other views. */ addedHeight = accessoryViewFrame.size.height + (_SAVE_PANEL_Y_PAD * 2); contentSize = [[self contentView] frame].size; contentSize.height -= addedHeight; // Resize without modifying topView and bottomView height. [_topView setAutoresizingMask: NSViewWidthSizable | NSViewMinYMargin]; [self setContentSize: contentSize]; [_topView setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; } /* Resize the panel to its original size. This resizes freely the heights of the views. NB: minSize *must* come first */ [self setMinSize: _originalMinSize]; [self setContentSize: _originalSize]; /* Set the new accessory view */ _accessoryView = aView; /* If there is a new accessory view, plug it in */ if (_accessoryView != nil) { /* Make sure the new accessory view behaves - its height must be fixed * and its position relative to the bottom of the superview must not * change - so its position rlative to the top must be changable. */ [_accessoryView setAutoresizingMask: NSViewMaxYMargin | ([_accessoryView autoresizingMask] & ~(NSViewHeightSizable | NSViewMinYMargin))]; /* Compute size taken by the new accessory view */ accessoryViewFrame = [_accessoryView frame]; addedHeight = accessoryViewFrame.size.height + (_SAVE_PANEL_Y_PAD * 2); accessoryWidth = accessoryViewFrame.size.width + (_SAVE_PANEL_X_PAD * 2); /* Resize content size accordingly */ contentSize = _originalSize; contentSize.height += addedHeight; if (accessoryWidth > contentSize.width) { contentSize.width = accessoryWidth; } /* Set new content size without resizing heights of topView, bottomView */ // Our views should resize horizontally if needed, but not vertically [_topView setAutoresizingMask: NSViewWidthSizable | NSViewMinYMargin]; [self setContentSize: contentSize]; // Restore the original autoresizing masks [_topView setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; /* Compute new min size */ contentMinSize = _originalMinSize; contentMinSize.height += addedHeight; // width is more delicate tmpRect = NSMakeRect (0, 0, contentMinSize.width, contentMinSize.height); tmpRect = [NSWindow contentRectForFrameRect: tmpRect styleMask: [self styleMask]]; if (accessoryWidth > tmpRect.size.width) { contentMinSize.width += accessoryWidth - tmpRect.size.width; } // Set new min size [self setMinSize: contentMinSize]; /* * Pack the Views */ /* BottomView is ready */ bottomFrame = [_bottomView frame]; /* AccessoryView */ accessoryViewFrame.origin.x = (contentSize.width - accessoryViewFrame.size.width) / 2; accessoryViewFrame.origin.y = NSMaxY (bottomFrame) + _SAVE_PANEL_Y_PAD; [_accessoryView setFrameOrigin: accessoryViewFrame.origin]; /* Add the accessory view */ [[self contentView] addSubview: _accessoryView]; } } /** * Sets the title of the NSSavePanel to title. By default, * 'Save' is the title string. If you adapt the NSSavePanel * for other uses, its title should reflect the user action * that brings it to the screen. */ - (void) setTitle: (NSString*)title { [_titleField setStringValue: title]; // TODO: Improve the following by managing // vertical alignment better. [_titleField sizeToFit]; } /** Returns the title of the save panel */ - (NSString*) title { return [_titleField stringValue]; } /** * Returns the prompt of the Save panel field that holds * the current pathname or file name. By default this * prompt is 'Name: '. */ - (void) setPrompt: (NSString*)prompt { [[_form cellAtIndex: 0] setTitle: prompt]; [_form setNeedsDisplay: YES]; } /** Returns the prompt used in the current path field. */ - (NSString*) prompt { return [[_form cellAtIndex: 0] title]; } /** Returns the accesory view (if any). */ - (NSView*) accessoryView { return _accessoryView; } - (void) setNameFieldLabel: (NSString *)label { // FIXME } - (NSString *) nameFieldLabel { // FIXME return _(@"Save As"); } - (void) setMessage: (NSString *)message { // FIXME } - (NSString *) message { // FIXME return nil; } /** * Sets the current path name in the Save panel's browser. * The path argument must be an absolute path name. */ - (void) setDirectory: (NSString*)path { NSString *standardizedPath = [path stringByStandardizingPath]; BOOL isDir; if (standardizedPath && [_fm fileExistsAtPath: standardizedPath isDirectory: &isDir] && isDir) { ASSIGN (_directory, standardizedPath); [_browser setPath: _directory]; } } /** * Specifies the type, a file name extension to be appended to * any selected files that don't already have that extension; * The argument type should not include the period that begins * the extension. Invoke this method each time the Save panel * is used for another file type within the application. If * you do not invoke it, or set it to empty string or nil, no * extension will be appended, indicated by an empty string * returned from -requiredFileType . */ - (void) setRequiredFileType: (NSString*)fileType { ASSIGN(_requiredFileType, fileType); } /** * Returns the required file type. The default, indicated by empty string, * is no required file type. */ - (NSString*) requiredFileType { return _requiredFileType; } - (void) setAllowedFileTypes: (NSArray *)types { // FIXME } - (void) setAllowsOtherFileTypes: (BOOL)flag { _allowsOtherFileTypes = flag; } - (NSArray *) allowedFileTypes { // FIXME return nil; } - (BOOL) allowsOtherFileTypes { return _allowsOtherFileTypes; } /** Returns YES if file packages are shown as directories. The default is NO. */ - (BOOL) treatsFilePackagesAsDirectories { return _treatsFilePackagesAsDirectories; } /** * Sets the NSSavePanel's behavior for displaying file packages * (for example, MyApp.app) to the user. If flag is YES, the * user is shown files and subdirectories within a file * package. If NO, the NSSavePanel shows each file package as * a file, thereby giving no indication that it is a directory. */ - (void) setTreatsFilePackagesAsDirectories: (BOOL)flag { _treatsFilePackagesAsDirectories = flag; } /** * Validates and possibly reloads the browser columns that are visible * in the Save panel by causing the delegate method * -panel:shouldShowFilename: to be invoked. One situation in * which this method would find use is whey you want the * browser to show only files with certain extensions based on the * selection made in an accessory-view pop-up list. When the * user changes the selection, you would invoke this method to * revalidate the visible columns. */ - (void) validateVisibleColumns { [_browser validateVisibleColumns]; } - (void) setCanCreateDirectories: (BOOL)flag { _canCreateDirectories = flag; } - (BOOL) canCreateDirectories { return _canCreateDirectories; } /** * Shows the save panel for the user. This method invokes * -runModalForDirectory:file: with empty strings for the filename. * Returns NSOKButton (if the user clicks the OK button) or * NSCancelButton (if the user clicks the Cancel button). */ - (int) runModal { return [self runModalForDirectory: nil file: @""]; } /** * Initializes the panel to the directory specified by path and, * optionally, the file specified by filename, then displays it and * begins its modal event loop; path and filename can be empty * strings. The method invokes [NSApplication:-runModalForWindow:] * method with self as the argument. Returns NSOKButton (if the user * clicks the OK button) or NSCancelButton (if the user clicks the * Cancel button). If path is nil then the panel displays the last * selected directory or as a last resort, the current working directory. */ - (int) runModalForDirectory: (NSString*)path file: (NSString*)filename { [self _setupForDirectory: path file: filename]; return [NSApp runModalForWindow: self]; } - (int) runModalForDirectory: (NSString *)path file: (NSString *)filename relativeToWindow: (NSWindow*)window { [self _setupForDirectory: path file: filename]; return [NSApp runModalForWindow: self relativeToWindow: window]; } - (void) beginSheetForDirectory: (NSString *)path file: (NSString *)filename modalForWindow: (NSWindow *)docWindow modalDelegate: (id)delegate didEndSelector: (SEL)didEndSelector contextInfo: (void *)contextInfo { [self _setupForDirectory: path file: filename]; [NSApp beginSheet: self modalForWindow: docWindow modalDelegate: delegate didEndSelector: didEndSelector contextInfo: contextInfo]; } /** * Returns the directory choosen by the user. Do not invoke directory * within a modal loop because the information that these methods * fetch is updated only upon return. */ - (NSString*) directory { if (_directory) return _directory; else return @""; } /** * Returns the absolute filename choosen by the user. Do not invoke * filename within a modal loop because the information that these * methods fetch is updated only upon return. */ - (NSString*) filename { if (_fullFileName == nil) return @""; if (_requiredFileType == nil || [_requiredFileType isEqual: @""] == YES) return _fullFileName; // add filetype extension only if the filename does not include it already if ([[_fullFileName pathExtension] isEqual: _requiredFileType] == YES) return _fullFileName; else return [_fullFileName stringByAppendingPathExtension: _requiredFileType]; } - (NSURL *) URL { return [NSURL fileURLWithPath: [self filename]]; } - (void) cancel: (id)sender { ASSIGN(_directory, [_browser pathToColumn:[_browser lastColumn]]); [NSApp stopModalWithCode: NSCancelButton]; [self close]; } - (void) ok: (id)sender { NSMatrix *matrix; NSBrowserCell *selectedCell; NSString *filename; BOOL isDir = NO; matrix = [_browser matrixInColumn: [_browser lastColumn]]; selectedCell = [matrix selectedCell]; if (selectedCell && [selectedCell isLeaf] == NO) { [[_form cellAtIndex: 0] setStringValue: @""]; [_browser doClick: matrix]; [_form selectTextAtIndex: 0]; [_form setNeedsDisplay: YES]; return; } ASSIGN (_directory, [_browser pathToColumn:[_browser lastColumn]]); filename = [[_form cellAtIndex: 0] stringValue]; if ([filename isAbsolutePath] == YES) { ASSIGN (_fullFileName, filename); } else { ASSIGN (_fullFileName, [_directory stringByAppendingPathComponent: filename]); } if (_delegateHasUserEnteredFilename) { filename = [_delegate panel: self userEnteredFilename: _fullFileName confirmed: YES]; if (!filename) return; else if (![_fullFileName isEqual: filename]) { ASSIGN (_directory, [filename stringByDeletingLastPathComponent]); ASSIGN (_fullFileName, filename); [_browser setPath: _fullFileName]; filename = [_fullFileName lastPathComponent]; [self _selectCellName: filename]; [[_form cellAtIndex: 0] setStringValue: filename]; [_form selectTextAtIndex: 0]; [_form setNeedsDisplay: YES]; } } filename = [_fullFileName stringByDeletingLastPathComponent]; if ([_fm fileExistsAtPath: filename isDirectory: &isDir] == NO) { int result; result = NSRunAlertPanel(_(@"Save"), _(@"The directory '%@' does not exist, do you want to create it?"), _(@"Yes"), _(@"No"), nil, filename ); if (result == NSAlertDefaultReturn) { if ([_fm createDirectoryAtPath: filename attributes: nil] == NO) { NSRunAlertPanel(_(@"Save"), _(@"The directory '%@' could not be created."), _(@"Dismiss"), nil, nil, filename ); return; } } } else if (isDir == NO) { NSRunAlertPanel(_(@"Save"), _(@"The path '%@' is not a directory."), _(@"Dismiss"), nil, nil, filename ); return; } if ([_fm fileExistsAtPath: [self filename] isDirectory: NULL]) { int result; result = NSRunAlertPanel(_(@"Save"), _(@"The file '%@' in '%@' exists. Replace it?"), _(@"Replace"), _(@"Cancel"), nil, [[self filename] lastPathComponent], _directory); if (result != NSAlertDefaultReturn) return; } if (_delegateHasValidNameFilter) if (![_delegate panel: self isValidFilename: [self filename]]) return; [NSApp stopModalWithCode: NSOKButton]; [self close]; } - (void) selectText: (id)sender { NSEvent *theEvent = [self currentEvent]; NSString *characters = [theEvent characters]; unichar character = 0; if ([characters length] > 0) { character = [characters characterAtIndex: 0]; } switch (character) { case NSUpArrowFunctionKey: case NSDownArrowFunctionKey: case NSLeftArrowFunctionKey: case NSRightArrowFunctionKey: [_form abortEditing]; [[_form cellAtIndex:0] setStringValue: @""]; [_browser keyDown:theEvent]; break; } } - (void) setDelegate: (id)aDelegate { if ([aDelegate respondsToSelector: @selector(panel:compareFilename:with:caseSensitive:)]) _delegateHasCompareFilter = YES; else _delegateHasCompareFilter = NO; if ([aDelegate respondsToSelector: @selector(panel:shouldShowFilename:)]) _delegateHasShowFilenameFilter = YES; else _delegateHasShowFilenameFilter = NO; if ([aDelegate respondsToSelector: @selector(panel:isValidFilename:)]) _delegateHasValidNameFilter = YES; else _delegateHasValidNameFilter = NO; if ([aDelegate respondsToSelector: @selector(panel:userEnteredFilename:confirmed:)]) _delegateHasUserEnteredFilename = YES; else _delegateHasUserEnteredFilename = NO; if ([aDelegate respondsToSelector: @selector(panel:directoryDidChange:)]) _delegateHasDirectoryDidChange = YES; else _delegateHasDirectoryDidChange = NO; if ([aDelegate respondsToSelector: @selector(panelSelectionDidChange:)]) _delegateHasSelectionDidChange = YES; else _delegateHasSelectionDidChange = NO; [super setDelegate: aDelegate]; [self validateVisibleColumns]; } - (void) setCanSelectHiddenExtension: (BOOL) flag { _canSelectHiddenExtension = flag; } - (BOOL) canSelectHiddenExtension { return _canSelectHiddenExtension; } - (BOOL) isExtensionHidden { return _isExtensionHidden; } - (void) setExtensionHidden: (BOOL) flag { _isExtensionHidden = flag; } - (BOOL) isExpanded { // FIXME return NO; } // // NSCoding protocol // - (id) initWithCoder: (NSCoder*)aDecoder { self = [super initWithCoder: aDecoder]; // TODO return self; } - (void) encodeWithCoder: (NSCoder*)aCoder { [super encodeWithCoder: aCoder]; // TODO } @end // // SavePanel filename compare // @implementation NSString (_gsSavePanel) - (NSComparisonResult)_gsSavePanelCompare:(NSString *)other { int sLength, oLength; unichar sChar, oChar; NSComparisonResult result; NSRange range; sLength = [self length]; oLength = [other length]; range.location = 0; range.length = sLength; if (sLength == 0) { if (oLength == 0) return NSOrderedSame; else return NSOrderedAscending; } else if (oLength == 0) { return NSOrderedDescending; } sChar = [self characterAtIndex: 0]; oChar = [other characterAtIndex: 0]; if (sChar == '.' && oChar != '.') return NSOrderedDescending; else if (sChar != '.' && oChar == '.') return NSOrderedAscending; if (sLength == oLength) { result = [self compare: other options: NSCaseInsensitiveSearch range: range]; if (result == NSOrderedSame) result = [self compare: other options: 0 range: range]; } else { if (sLength < oLength) { result = [other compare: self options: NSCaseInsensitiveSearch range: range]; if (result == NSOrderedAscending) result = NSOrderedDescending; else if (result == NSOrderedDescending) result = NSOrderedAscending; else { result = [other compare: self options: 0 range: range]; if (result == NSOrderedAscending) result = NSOrderedDescending; else result = NSOrderedAscending; } } else result = [self compare: other options: NSCaseInsensitiveSearch range: range]; if (result == NSOrderedSame) result = [self compare: other options: 0 range: range]; } return result; } @end // // NSSavePanel browser delegate methods // @interface NSSavePanel (_BrowserDelegate) - (void) browser: (NSBrowser*)sender createRowsForColumn: (int)column inMatrix: (NSMatrix*)matrix; - (BOOL) browser: (NSBrowser*)sender isColumnValid: (int)column; - (void) browser: (NSBrowser*)sender willDisplayCell: (id)cell atRow: (int)row column: (int)column; @end struct NSSavePanel_struct { @defs (NSSavePanel) }; static int compareFilenames (id elem1, id elem2, void *context) { /* TODO - use IMP optimization here. */ struct NSSavePanel_struct *s = (struct NSSavePanel_struct *)context; NSSavePanel *self = (NSSavePanel *)context; return (int)[s->_delegate panel: self compareFilename: elem1 with: elem2 caseSensitive: YES]; } @implementation NSSavePanel (_BrowserDelegate) - (void) browser: (NSBrowser*)sender createRowsForColumn: (int)column inMatrix: (NSMatrix*)matrix { NSString *path, *file, *pathAndFile, *extension; NSArray *files; unsigned i, count, addedRows; BOOL exists, isDir; NSBrowserCell *cell; // _gs_display_reading_progress variables unsigned reached_frac = 0; unsigned base_frac = 1; BOOL display_progress = NO; NSString *progressString = nil; /* We create lot of objects in this method, so we use a pool */ NSAutoreleasePool *pool; pool = [NSAutoreleasePool new]; path = [_browser pathToColumn: column]; files = [[NSFileManager defaultManager] directoryContentsAtPath: path]; /* Remove hidden files. */ { NSString *h; NSArray *hiddenFiles = nil; BOOL gsSavePanelHideDotFiles; if ([files containsObject: @".hidden"] == YES) { /* We need to remove files listed in the xxx/.hidden file. */ h = [path stringByAppendingPathComponent: @".hidden"]; h = [NSString stringWithContentsOfFile: h]; hiddenFiles = [h componentsSeparatedByString: @"\n"]; } /* We need to remove files starting with `.' (dot), but only if the user asked for it in the defaults. Perhaps we could add a button turning on/off display of hidden files ? */ /* NB: GWorkspace is using this same user default to determine whether to hide or not dot files. */ gsSavePanelHideDotFiles = [[NSUserDefaults standardUserDefaults] boolForKey: @"GSFileBrowserHideDotFiles"]; /* Now copy the files array into a mutable array - but only if strictly needed. */ if (hiddenFiles != nil || gsSavePanelHideDotFiles) { /* We must make a mutable copy of the array because the API says that NSFileManager -directoryContentsAtPath: return a NSArray, not a NSMutableArray, so we shouldn't expect it to be mutable. */ NSMutableArray *mutableFiles = AUTORELEASE ([files mutableCopy]); /* Ok - now modify the mutable array removing unwanted files. */ if (hiddenFiles != nil) { [mutableFiles removeObjectsInArray: hiddenFiles]; } if (gsSavePanelHideDotFiles) { /* Don't use i which is unsigned. */ int j = [mutableFiles count] - 1; while (j >= 0) { NSString *file = (NSString *)[mutableFiles objectAtIndex: j]; if ([file hasPrefix: @"."]) { /* NSLog (@"Removing dot file %@", file); */ [mutableFiles removeObjectAtIndex: j]; } j--; } } files = mutableFiles; } } count = [files count]; /* If array is empty, just return (nothing to display). */ if (count == 0) { RELEASE (pool); return; } // Prepare Messages on title bar if directory is big and user wants them if (_gs_display_reading_progress && (count > 100)) { display_progress = YES; base_frac = count / 4; progressString = [@"Reading Directory " stringByAppendingString: path]; [super setTitle: progressString]; // Is the following really safe? [GSCurrentContext() flushGraphics]; } //TODO: Sort after creation of matrix so we do not sort // files we are not going to show. Use NSMatrix sorting cells method // Sort list of files to display if (_delegateHasCompareFilter == YES) { files = [files sortedArrayUsingFunction: compareFilenames context: self]; } else files = [files sortedArrayUsingSelector: @selector(_gsSavePanelCompare:)]; addedRows = 0; for (i = 0; i < count; i++) { // Update displayed message if needed if (display_progress && (i > (base_frac * (reached_frac + 1)))) { reached_frac++; progressString = [progressString stringByAppendingString: @"."]; [super setTitle: progressString]; [GSCurrentContext() flushGraphics]; } // Now the real code file = [files objectAtIndex: i]; extension = [file pathExtension]; pathAndFile = [path stringByAppendingPathComponent: file]; exists = [_fm fileExistsAtPath: pathAndFile isDirectory: &isDir]; if (_delegateHasShowFilenameFilter) { exists = [_delegate panel: self shouldShowFilename: pathAndFile]; } if (exists) { exists = [self _shouldShowExtension: extension isDir: &isDir]; } if (exists) { if (addedRows == 0) { [matrix addColumn]; } else // addedRows > 0 { /* Same as [matrix addRow] */ [matrix insertRow: addedRows withCells: nil]; /* Possible TODO: Faster would be to create all the cells at once with a single call instead of resizing the matrix each time a cell is inserted. */ } cell = [matrix cellAtRow: addedRows column: 0]; [cell setStringValue: file]; if (isDir) [cell setLeaf: NO]; else [cell setLeaf: YES]; addedRows++; } } if (display_progress) { [super setTitle: @""]; [GSCurrentContext() flushGraphics]; } RELEASE (pool); } - (BOOL) browser: (NSBrowser*)sender isColumnValid: (int)column { NSArray *cells = [[sender matrixInColumn: column] cells]; unsigned count = [cells count], i; NSString *path = [sender pathToColumn: column]; // iterate through the cells asking the delegate if each filename is valid // if it says no for any filename, the column is not valid if (_delegateHasShowFilenameFilter == YES) for (i = 0; i < count; i++) { if (![_delegate panel: self shouldShowFilename: [path stringByAppendingPathComponent: [[cells objectAtIndex: i] stringValue]]]) return NO; } return YES; } - (void) browser: (NSBrowser*)sender willDisplayCell: (id)cell atRow: (int)row column: (int)column { } @end // // NSForm delegate methods // @interface NSSavePanel (FormDelegate) - (void) controlTextDidChange: (NSNotification *)aNotification; @end @implementation NSSavePanel (FormDelegate) - (void) controlTextDidChange: (NSNotification *)aNotification { NSString *s, *selectedString; NSArray *cells; NSMatrix *matrix; NSCell *selectedCell; int i, sLength, cellLength, selectedRow; NSComparisonResult result; NSRange range; s = [[[aNotification userInfo] objectForKey:@"NSFieldEditor"] string]; /* * If the user typed in an absolute path, display it. */ if ([s isAbsolutePath] == YES) { [self setDirectory: s]; } sLength = [s length]; range.location = 0; range.length = sLength; matrix = [_browser matrixInColumn:[_browser lastColumn]]; if (sLength == 0) { [matrix deselectAllCells]; [_okButton setEnabled:NO]; return; } selectedCell = [matrix selectedCell]; selectedString = [selectedCell stringValue]; selectedRow = [matrix selectedRow]; cells = [matrix cells]; if (selectedString) { result = [s compare:selectedString options:0 range:range]; if (result == NSOrderedSame) return; } else result = NSOrderedDescending; if (result == NSOrderedDescending) { int numberOfCells = [cells count]; for (i = selectedRow+1; i < numberOfCells; i++) { selectedString = [[matrix cellAtRow:i column:0] stringValue]; cellLength = [selectedString length]; if (cellLength != sLength) continue; result = [selectedString compare:s options:0 range:range]; if (result == NSOrderedSame) { [matrix deselectAllCells]; [matrix selectCellAtRow:i column:0]; [matrix scrollCellToVisibleAtRow:i column:0]; [_okButton setEnabled:YES]; return; } } } else { for (i = selectedRow; i >= 0; --i) { selectedString = [[matrix cellAtRow:i column:0] stringValue]; cellLength = [selectedString length]; if (cellLength != sLength) continue; result = [selectedString compare:s options:0 range:range]; if (result == NSOrderedSame) { [matrix deselectAllCells]; [matrix selectCellAtRow:i column:0]; [matrix scrollCellToVisibleAtRow:i column:0]; [_okButton setEnabled:YES]; return; } } } [matrix deselectAllCells]; [_okButton setEnabled:YES]; } @end /* NSSavePanel */