/**
Parses path and selects corresponding items in the NSBrowser columns. *
*This is the primary mechanism for programmatically updating the
* selection of a browser. It should result in the browser cells
* corresponding to the components being selected, and the
* browser columns up to the end of path (and just beyond if the
* last selected cell's [NSBrowserCell-isLeaf] returns YES).
* It does not result in the browsers action being sent to its
* target, just in a change to the browser selection and display.
*
If path begins with the -pathSeparator then it is taken to be absolute * and the first component in it is expected to denote a cell in column * zero. Otherwise it is taken to be relative to the currently selected * column. *
*Empty components (ie where a -pathSeparator occurs immediately * after another or at the end of path) are simply ignored. *
*The receivers delegate is asked to select each cell in turn
* using the -browser:selectCellWithString:inColumn: method (if it
* implements it). If this call to the delegate returns NO then
* the attempt to set the path fails.
* If the delegate does not implement the method, the browser attempts
* to locate and select the cell itsself, and the method fails if it
* is unable to locate the cell by matching its [NSCell-stringValue] with
* the component of the path.
*
The method returns YES if path contains no components or if a cell * corresponding to the path was found. Otherwise it returns NO. *
*/ - (BOOL) setPath: (NSString *)path { NSArray *subStrings; unsigned numberOfSubStrings; unsigned i, j; int column = _lastColumnLoaded; BOOL useDelegate = NO; if ([_browserDelegate respondsToSelector: @selector(browser:selectCellWithString:inColumn:)]) { useDelegate = YES; } /* * Decompose the path. */ subStrings = [path componentsSeparatedByString: _pathSeparator]; numberOfSubStrings = [subStrings count]; i = [subStrings indexOfObject: @""]; if (i != NSNotFound) { NSMutableArray *subs = AUTORELEASE([subStrings mutableCopy]); /* * If there is a leading empty component, the path began with a * separator and is therefore absolute. Otherwise it begins from * the currently selected column. */ if (i == 0 && column > 0) { column = 0; } [subs removeObject: @""]; subStrings = subs; numberOfSubStrings = [subStrings count]; /* * Optimisation. If there are columns loaded, it may be that the * specified path is already partially selected. If this is the * case, we can avoid redrawing those columns. */ if (column == 0) { for (i = 0; i <= _lastColumnLoaded && numberOfSubStrings > 0; i++) { NSString *c = [[self selectedCellInColumn: i] stringValue]; if ([c isEqualToString: [subs objectAtIndex: 0]]) { [subs removeObjectAtIndex: 0]; numberOfSubStrings--; column++; } } } } /* * Ensure that our starting column is loaded. */ if (column < 0) { column = 0; [self loadColumnZero]; } else { [self setLastColumn: column]; } // cycle thru str's array created from path for (i = 0; i < numberOfSubStrings; i++) { NSString *aStr = [subStrings objectAtIndex: i]; NSBrowserColumn *bc = [_browserColumns objectAtIndex: column]; NSMatrix *matrix = [bc columnMatrix]; NSBrowserCell *selectedCell = nil; BOOL found = NO; if (useDelegate == YES) { if ([_browserDelegate browser: self selectCellWithString: aStr inColumn: column]) { found = YES; selectedCell = [matrix selectedCell]; } } else { NSArray *cells = [matrix cells]; unsigned numOfRows = [cells count]; // find the cell in the browser matrix which is equal to aStr for (j = 0; j < numOfRows; j++) { selectedCell = [cells objectAtIndex: j]; if ([[selectedCell stringValue] isEqualToString: aStr]) { [matrix selectCellAtRow: j column: 0]; found = YES; break; } } } // if unable to find a cell whose title matches aStr return NO if (found == NO) { NSDebugLLog (@"NSBrowser", @"unable to find cell '%@' in column %d\n", aStr, column); break; } // if the cell is a leaf, we are finished setting the path if ([selectedCell isLeaf]) { break; } // else, it is not a leaf: get a column in the browser for it [self addColumn]; column++; } [self setNeedsDisplay: YES]; if (i == numberOfSubStrings) { return YES; } else { return NO; } } /** Returns a string representing the path from the first column up to, but not including, the column at index column. */ - (NSString *) pathToColumn: (int)column { NSMutableString *s = [_pathSeparator mutableCopy]; unsigned i; NSString *string; /* * Cannot go past the number of loaded columns */ if (column > _lastColumnLoaded) { column = _lastColumnLoaded + 1; } for (i = 0; i < column; ++i) { id c = [self selectedCellInColumn: i]; if (i != 0) { [s appendString: _pathSeparator]; } string = [c stringValue]; if (string == nil) { /* This should happen only when c == nil, in which case it doesn't make sense to go with the path */ break; } else { [s appendString: string]; } } /* * We actually return a mutable string, but that's ok since a mutable * string is a string and the documentation specifically says that * people should not depend on methods that return strings to return * immutable strings. */ return AUTORELEASE (s); } /** Returns the path separator. The default is "/". */ - (NSString *) pathSeparator { return _pathSeparator; } /** Sets the path separator to newString. */ - (void) setPathSeparator: (NSString *)aString { ASSIGN(_pathSeparator, aString); } /* * Manipulating columns */ - (NSBrowserColumn *) _createColumn { NSBrowserColumn *bc; NSScrollView *sc; NSRect rect = {{0, 0}, {100, 100}}; bc = [[NSBrowserColumn alloc] init]; // Create a scrollview sc = [[NSScrollView alloc] initWithFrame: rect]; [sc setHasHorizontalScroller: NO]; [sc setHasVerticalScroller: YES]; [sc setBorderType: NSBezelBorder]; [bc setColumnScrollView: sc]; [self addSubview: sc]; RELEASE(sc); [_browserColumns addObject: bc]; RELEASE(bc); return bc; } /** Adds a column to the right of the last column. */ - (void) addColumn { int i; if (_lastColumnLoaded + 1 >= [_browserColumns count]) { i = [_browserColumns indexOfObject: [self _createColumn]]; } else { i = _lastColumnLoaded + 1; } if (i < 0) { i = 0; } [self _performLoadOfColumn: i]; [self setLastColumn: i]; _isLoaded = YES; [self tile]; if (i > 0 && i - 1 == _lastVisibleColumn) { [self scrollColumnsRightBy: 1]; } } - (BOOL) acceptsFirstResponder { return YES; } - (BOOL) becomeFirstResponder { NSMatrix *matrix; int selectedColumn; selectedColumn = [self selectedColumn]; if (selectedColumn == -1) matrix = [self matrixInColumn: 0]; else matrix = [self matrixInColumn: selectedColumn]; if (matrix) [_window makeFirstResponder: matrix]; return YES; } /** Updates the NSBrowser to display all loaded columns. */ - (void) displayAllColumns { [self tile]; } /** Updates the NSBrowser to display the column with the given index. */ - (void) displayColumn: (int)column { id bc, sc; // If not visible then nothing to display if ((column < _firstVisibleColumn) || (column > _lastVisibleColumn)) { return; } [self tile]; // Update and display title of column if (_isTitled) { [self lockFocus]; [self drawTitleOfColumn: column inRect: [self titleFrameOfColumn: column]]; [self unlockFocus]; } // Display column if (!(bc = [_browserColumns objectAtIndex: column])) return; if (!(sc = [bc columnScrollView])) return; /* FIXME: why the following ? Are we displaying now, or marking for * later display ?? Given the name, I think we are displaying * now. */ [sc setNeedsDisplay: YES]; } /** Returns the column number in which matrix is located. */ - (int) columnOfMatrix: (NSMatrix *)matrix { int i, count; // Loop through columns and compare matrixes count = [_browserColumns count]; for (i = 0; i < count; ++i) { if (matrix == [self matrixInColumn: i]) return i; } // Not found return -1; } /** Returns the index of the last column with a selected item. */ - (int) selectedColumn { int i; id matrix; for (i = _lastColumnLoaded; i >= 0; i--) { if (!(matrix = [self matrixInColumn: i])) continue; if ([matrix selectedCell]) return i; } return -1; } /** Returns the index of the last column loaded. */ - (int) lastColumn { return _lastColumnLoaded; } /** Sets the last column to column. */ - (void) setLastColumn: (int)column { int i, count, num; id bc, sc; if (column <= -1) { column = -1; _isLoaded = NO; } _lastColumnLoaded = column; // Unloads columns. count = [_browserColumns count]; num = [self numberOfVisibleColumns]; for (i = column + 1; i < count; ++i) { bc = [_browserColumns objectAtIndex: i]; sc = [bc columnScrollView]; if ([bc isLoaded]) { // Make the column appear empty by removing the matrix if (sc) { [sc setDocumentView: nil]; [sc setNeedsDisplay: YES]; } [bc setIsLoaded: NO]; [bc setColumnTitle: nil]; } if (!_reusesColumns && i >= num) { [sc removeFromSuperview]; [_browserColumns removeObject: bc]; count--; i--; } } // Scroll if needed. if ((column < _lastVisibleColumn) && (_firstVisibleColumn > 0)) { [self scrollColumnsLeftBy: _lastVisibleColumn - column]; } else { [self updateScroller]; [self _setColumnTitlesNeedDisplay]; } } /** Returns the index of the first visible column. */ - (int) firstVisibleColumn { return _firstVisibleColumn; } /** Returns the number of columns visible. */ - (int) numberOfVisibleColumns { int num; num = _lastVisibleColumn - _firstVisibleColumn + 1; return (num > 0 ? num : 1); } /** Returns the index of the last visible column. */ - (int) lastVisibleColumn { return _lastVisibleColumn; } /** Invokes delegate method browser:isColumnValid: for visible columns. */ - (void) validateVisibleColumns { int i; // If delegate doesn't care, just return if (![_browserDelegate respondsToSelector: @selector(browser:isColumnValid:)]) { return; } // Loop through the visible columns for (i = _firstVisibleColumn; i <= _lastVisibleColumn; ++i) { // Ask delegate if the column is valid and if not // then reload the column if (![_browserDelegate browser: self isColumnValid: i]) { [self reloadColumn: i]; } } } /* * Loading columns */ /** Returns whether column zero is loaded. */ - (BOOL) isLoaded { return _isLoaded; } /** Loads column zero; unloads previously loaded columns. */ - (void) loadColumnZero { // set last column loaded [self setLastColumn: -1]; // load column 0 [self addColumn]; [self _remapColumnSubviews: YES]; [self _setColumnTitlesNeedDisplay]; } /** Reloads column if it is loaded; sets it as the last column. */ - (void) reloadColumn: (int)column { NSArray *selectedCells; NSMatrix *matrix; int i, count, max; int *selectedIndexes = NULL; // Make sure the column even exists if (column > _lastColumnLoaded) return; // Save the index of the previously selected cells matrix = [self matrixInColumn: column]; selectedCells = [matrix selectedCells]; count = [selectedCells count]; if (count > 0) { selectedIndexes = NSZoneMalloc (NSDefaultMallocZone (), sizeof (int) * count); for (i = 0; i < count; i++) { NSCell *cell = [selectedCells objectAtIndex: i]; int sRow, sColumn; [matrix getRow: &sRow column: &sColumn ofCell: cell]; selectedIndexes[i] = sRow; } } // Perform the data load [self _performLoadOfColumn: column]; // set last column loaded [self setLastColumn: column]; // Restore the selected cells if (count > 0) { matrix = [self matrixInColumn: column]; max = [matrix numberOfRows]; for (i = 0; i < count; i++) { // Abort when it stops making sense if (selectedIndexes[i] > max) { break; } [matrix selectCellAtRow: selectedIndexes[i] column: 0]; } NSZoneFree (NSDefaultMallocZone (), selectedIndexes); } } /* * Setting selection characteristics */ /** Returns whether the user can select branch items when multiple selection is enabled. */ - (BOOL) allowsBranchSelection { return _allowsBranchSelection; } /** Sets whether the user can select branch items when multiple selection is enabled. */ - (void) setAllowsBranchSelection: (BOOL)flag { _allowsBranchSelection = flag; } /** Returns whether there can be nothing selected. */ - (BOOL) allowsEmptySelection { return _allowsEmptySelection; } /** Sets whether there can be nothing selected. */ - (void) setAllowsEmptySelection: (BOOL)flag { _allowsEmptySelection = flag; } /** Returns whether the user can select multiple items. */ - (BOOL) allowsMultipleSelection { return _allowsMultipleSelection; } /** Sets whether the user can select multiple items. */ - (void) setAllowsMultipleSelection: (BOOL)flag { _allowsMultipleSelection = flag; } /* * Setting column characteristics */ /** Returns YES if NSMatrix objects aren't freed when their columns are unloaded. */ - (BOOL) reusesColumns { return _reusesColumns; } /** If flag is YES, prevents NSMatrix objects from being freed when their columns are unloaded, so they can be reused. */ - (void) setReusesColumns: (BOOL)flag { _reusesColumns = flag; } /** Returns the maximum number of visible columns. */ - (int) maxVisibleColumns { return _maxVisibleColumns; } /** Sets the maximum number of columns displayed. */ - (void) setMaxVisibleColumns: (int)columnCount { if ((columnCount < 1) || (_maxVisibleColumns == columnCount)) return; _maxVisibleColumns = columnCount; // Redisplay [self tile]; } /** Returns the minimum column width in pixels. */ - (int) minColumnWidth { return _minColumnWidth; } /** Sets the minimum column width in pixels. */ - (void) setMinColumnWidth: (int)columnWidth { float sw; sw = scrollerWidth; // Take the border into account if (_separatesColumns) sw += 2 * (_sizeForBorderType (NSBezelBorder)).width; // Column width cannot be less than scroller and border if (columnWidth < sw) _minColumnWidth = sw; else _minColumnWidth = columnWidth; [self tile]; } /** Returns whether columns are separated by bezeled borders. */ - (BOOL) separatesColumns { return _separatesColumns; } /** Sets whether to separate columns with bezeled borders. */ - (void) setSeparatesColumns: (BOOL)flag { NSBrowserColumn *bc; NSScrollView *sc; NSBorderType bt; int i, columnCount; // if this flag already set or browser is titled -- do nothing if (_separatesColumns == flag || _isTitled) return; columnCount = [_browserColumns count]; bt = flag ? NSBezelBorder : NSNoBorder; for (i = 0; i < columnCount; i++) { bc = [_browserColumns objectAtIndex: i]; sc = [bc columnScrollView]; [sc setBorderType:bt]; } _separatesColumns = flag; [self setNeedsDisplay:YES]; [self tile]; } /** Returns YES if the title of a column is set to the string value of the selected NSCell in the previous column.*/ - (BOOL) takesTitleFromPreviousColumn { return _takesTitleFromPreviousColumn; } /** Sets whether the title of a column is set to the string value of the selected NSCell in the previous column. */ - (void) setTakesTitleFromPreviousColumn: (BOOL)flag { if (_takesTitleFromPreviousColumn != flag) { _takesTitleFromPreviousColumn = flag; [self setNeedsDisplay: YES]; } } /* * Manipulating column titles */ /** Returns the title displayed for the column at index column. */ - (NSString *) titleOfColumn: (int)column { NSBrowserColumn *bc; bc = [_browserColumns objectAtIndex: column]; return bc->_columnTitle; } /** Sets the title of the column at index column to aString. */ - (void) setTitle: (NSString *)aString ofColumn: (int)column { NSBrowserColumn *bc; bc = [_browserColumns objectAtIndex: column]; [bc setColumnTitle: aString]; // If column is not visible then nothing to redisplay if (!_isTitled || !NSBR_COLUMN_IS_VISIBLE(column)) return; [self setNeedsDisplayInRect: [self titleFrameOfColumn: column]]; } /** Returns whether columns display titles. */ - (BOOL) isTitled { return _isTitled; } /** Sets whether columns display titles. */ - (void) setTitled: (BOOL)flag { if (_isTitled == flag || !_separatesColumns) return; _isTitled = flag; [self tile]; [self setNeedsDisplay: YES]; } - (void) drawTitleOfColumn: (int)column inRect: (NSRect)aRect { [self drawTitle: [self titleOfColumn: column] inRect: aRect ofColumn: column]; } /** Draws the title for the column at index column within the rectangle defined by aRect. */ - (void) drawTitle: (NSString *)title inRect: (NSRect)aRect ofColumn: (int)column { if (!_isTitled || !NSBR_COLUMN_IS_VISIBLE(column)) return; [titleCell setStringValue: title]; [titleCell drawWithFrame: aRect inView: self]; } /** Returns the height of column titles. */ - (float) titleHeight { // Nextish look requires 21 here return 21; } /** Returns the bounds of the title frame for the column at index column. */ - (NSRect) titleFrameOfColumn: (int)column { // Not titled then no frame if (!_isTitled) { return NSZeroRect; } else { // Number of columns over from the first int n = column - _firstVisibleColumn; int h = [self titleHeight]; NSRect r; // Calculate origin if (_separatesColumns) { r.origin.x = n * (_columnSize.width + NSBR_COLUMN_SEP); } else { r.origin.x = n * _columnSize.width; } r.origin.y = _frame.size.height - h; // Calculate size if (column == _lastVisibleColumn) { r.size.width = _frame.size.width - r.origin.x; } else { r.size.width = _columnSize.width; } r.size.height = h; return r; } } /* * Scrolling an NSBrowser */ /** Scrolls to make the column at index column visible. */ - (void) scrollColumnToVisible: (int)column { // If there are not enough columns to scroll with // then the column must be visible if (_lastColumnLoaded + 1 <= [self numberOfVisibleColumns]) return; // If its the last visible column then we are there already if (_lastVisibleColumn < column) { [self scrollColumnsRightBy: (column - _lastVisibleColumn)]; } else if (_firstVisibleColumn > column) { [self scrollColumnsLeftBy: (_firstVisibleColumn - column)]; } } /** Scrolls columns left by shiftAmount columns. */ - (void) scrollColumnsLeftBy: (int)shiftAmount { // Cannot shift past the zero column if ((_firstVisibleColumn - shiftAmount) < 0) shiftAmount = _firstVisibleColumn; // No amount to shift then nothing to do if (shiftAmount <= 0) return; // Notify the delegate if ([_browserDelegate respondsToSelector: @selector(browserWillScroll:)]) [_browserDelegate browserWillScroll: self]; // Shift _firstVisibleColumn = _firstVisibleColumn - shiftAmount; _lastVisibleColumn = _lastVisibleColumn - shiftAmount; // Update the scroller [self updateScroller]; // Update the scrollviews [self tile]; [self _remapColumnSubviews: YES]; [self _setColumnTitlesNeedDisplay]; // Notify the delegate if ([_browserDelegate respondsToSelector: @selector(browserDidScroll:)]) [_browserDelegate browserDidScroll: self]; } /** Scrolls columns right by shiftAmount columns. */ - (void) scrollColumnsRightBy: (int)shiftAmount { // Cannot shift past the last loaded column if ((shiftAmount + _lastVisibleColumn) > _lastColumnLoaded) shiftAmount = _lastColumnLoaded - _lastVisibleColumn; // No amount to shift then nothing to do if (shiftAmount <= 0) return; // Notify the delegate if ([_browserDelegate respondsToSelector: @selector(browserWillScroll:)]) [_browserDelegate browserWillScroll: self]; // Shift _firstVisibleColumn = _firstVisibleColumn + shiftAmount; _lastVisibleColumn = _lastVisibleColumn + shiftAmount; // Update the scroller [self updateScroller]; // Update the scrollviews [self tile]; [self _remapColumnSubviews: NO]; [self _setColumnTitlesNeedDisplay]; // Notify the delegate if ([_browserDelegate respondsToSelector: @selector(browserDidScroll:)]) [_browserDelegate browserDidScroll: self]; } /** Updates the horizontal scroller to reflect column positions. */ - (void) updateScroller { int num; num = [self numberOfVisibleColumns]; // If there are not enough columns to scroll with // then the column must be visible if ((_lastColumnLoaded == 0) || (_lastColumnLoaded <= (num - 1))) { [_horizontalScroller setEnabled: NO]; } else { if (!_skipUpdateScroller) { float prop = (float)num / (float)(_lastColumnLoaded + 1); float i = _lastColumnLoaded - num + 1; float f = 1 + ((_lastVisibleColumn - _lastColumnLoaded) / i); [_horizontalScroller setFloatValue: f knobProportion: prop]; } [_horizontalScroller setEnabled: YES]; } [_horizontalScroller setNeedsDisplay: YES]; } /** Scrolls columns left or right based on an NSScroller. */ - (void) scrollViaScroller: (NSScroller *)sender { NSScrollerPart hit; if ([sender class] != [NSScroller class]) return; hit = [sender hitPart]; switch (hit) { // Scroll to the left case NSScrollerDecrementLine: case NSScrollerDecrementPage: [self scrollColumnsLeftBy: 1]; break; // Scroll to the right case NSScrollerIncrementLine: case NSScrollerIncrementPage: [self scrollColumnsRightBy: 1]; break; // The knob or knob slot case NSScrollerKnob: case NSScrollerKnobSlot: { float f = [sender floatValue]; _skipUpdateScroller = YES; [self scrollColumnToVisible: rintf(f * _lastColumnLoaded)]; _skipUpdateScroller = NO; } break; // NSScrollerNoPart ??? default: break; } } /* * Showing a horizontal scroller */ /** Returns whether an NSScroller is used to scroll horizontally. */ - (BOOL) hasHorizontalScroller { return _hasHorizontalScroller; } /** Sets whether an NSScroller is used to scroll horizontally. */ - (void) setHasHorizontalScroller: (BOOL)flag { if (_hasHorizontalScroller != flag) { _hasHorizontalScroller = flag; if (!flag) [_horizontalScroller removeFromSuperview]; else [self addSubview: _horizontalScroller]; [self tile]; [self setNeedsDisplay: YES]; } } /* * Setting the behavior of arrow keys */ /** Returns YES if the arrow keys are enabled. */ - (BOOL) acceptsArrowKeys { return _acceptsArrowKeys; } /** Enables or disables the arrow keys as used for navigating within and between browsers. */ - (void) setAcceptsArrowKeys: (BOOL)flag { _acceptsArrowKeys = flag; } /** Returns NO if pressing an arrow key only scrolls the browser, YES if it also sends the action message specified by setAction:. */ - (BOOL) sendsActionOnArrowKeys { return _sendsActionOnArrowKeys; } /** Sets whether pressing an arrow key will cause the action message to be sent (in addition to causing scrolling). */ - (void) setSendsActionOnArrowKeys: (BOOL)flag { _sendsActionOnArrowKeys = flag; } /* * Getting column frames */ /** Returns the rectangle containing the column at index column. */ - (NSRect) frameOfColumn: (int)column { NSRect r = NSZeroRect; NSSize bs = _sizeForBorderType (NSBezelBorder); int n; // Number of columns over from the first n = column - _firstVisibleColumn; // Calculate the frame r.size = _columnSize; r.origin.x = n * _columnSize.width; if (_separatesColumns) { r.origin.x += n * NSBR_COLUMN_SEP; } else { if (column == _firstVisibleColumn) r.origin.x = (n * _columnSize.width) + 2; else r.origin.x = (n * _columnSize.width) + (n + 2); } // Adjust for horizontal scroller if (_hasHorizontalScroller) { if (_separatesColumns) r.origin.y = (scrollerWidth - 1) + (2 * bs.height) + NSBR_VOFFSET; else r.origin.y = scrollerWidth + bs.width; } // Padding : _columnSize.width is rounded in "tile" method if (column == _lastVisibleColumn) { if (_separatesColumns) r.size.width = _frame.size.width - r.origin.x; else r.size.width = _frame.size.width - (r.origin.x + (2 * bs.width) + ([self numberOfVisibleColumns] - 1)); } if (r.size.width < 0) { r.size.width = 0; } if (r.size.height < 0) { r.size.height = 0; } return r; } /** Returns the rectangle containing the column at index column, */ // not including borders. - (NSRect) frameOfInsideOfColumn: (int)column { // xxx what does this one do? return [self frameOfColumn: column]; } /* * Arranging browser components */ /** Adjusts the various subviews of NSBrowser-scrollers, columns, titles, and so on-without redrawing. Your code shouldn't send this message. It's invoked any time the appearance of the NSBrowser changes. */ - (void) tile { NSSize bs = _sizeForBorderType (NSBezelBorder); int i, num, columnCount, delta; float frameWidth; _columnSize.height = _frame.size.height; // Titles (there is no real frames to resize) if (_isTitled) { _columnSize.height -= [self titleHeight] + NSBR_VOFFSET; } // Horizontal scroller if (_hasHorizontalScroller) { _scrollerRect.origin.x = bs.width; _scrollerRect.origin.y = bs.height - 1; _scrollerRect.size.width = (_frame.size.width - (2 * bs.width)) + 1; _scrollerRect.size.height = scrollerWidth; if (_separatesColumns) _columnSize.height -= (scrollerWidth - 1) + (2 * bs.height) + NSBR_VOFFSET; else _columnSize.height -= scrollerWidth + (2 * bs.height); if (!NSEqualRects(_scrollerRect, [_horizontalScroller frame])) { [_horizontalScroller setFrame: _scrollerRect]; } } else { _scrollerRect = NSZeroRect; } num = _lastVisibleColumn - _firstVisibleColumn + 1; if (_minColumnWidth > 0) { float colWidth = _minColumnWidth + scrollerWidth; if ((int)(_frame.size.width > _minColumnWidth)) { if (_separatesColumns) colWidth += NSBR_COLUMN_SEP; columnCount = (int)(_frame.size.width / colWidth); } else columnCount = 1; } else columnCount = num; if (_maxVisibleColumns > 0 && columnCount > _maxVisibleColumns) columnCount = _maxVisibleColumns; if (columnCount != num) { if (num > 0) delta = columnCount - num; else delta = columnCount - 1; if ((delta > 0) && (_lastVisibleColumn <= _lastColumnLoaded)) { _firstVisibleColumn = (_firstVisibleColumn - delta > 0) ? _firstVisibleColumn - delta : 0; } for (i = [_browserColumns count]; i < columnCount; i++) [self _createColumn]; _lastVisibleColumn = _firstVisibleColumn + columnCount - 1; } // Columns if (_separatesColumns) frameWidth = _frame.size.width - ((columnCount - 1) * NSBR_COLUMN_SEP); else frameWidth = _frame.size.width - (columnCount + (2 * bs.width)); _columnSize.width = (int)(frameWidth / (float)columnCount); if (_columnSize.height < 0) _columnSize.height = 0; for (i = _firstVisibleColumn; i <= _lastVisibleColumn; i++) { id bc, sc; id matrix; bc = [_browserColumns objectAtIndex: i]; if (!(sc = [bc columnScrollView])) { NSLog(@"NSBrowser error, sc != [bc columnScrollView]"); return; } [sc setFrame: [self frameOfColumn: i]]; matrix = [bc columnMatrix]; // Adjust matrix to fit in scrollview if column has been loaded if (matrix && [bc isLoaded]) { NSSize cs, ms; cs = [sc contentSize]; ms = [matrix cellSize]; ms.width = cs.width; [matrix setCellSize: ms]; [sc setDocumentView: matrix]; } } if (columnCount != num) { [self updateScroller]; [self _remapColumnSubviews: YES]; // [self _setColumnTitlesNeedDisplay]; [self setNeedsDisplay: YES]; } } /* * Setting the delegate */ /** Returns the NSBrowser's delegate. */ - (id) delegate { return _browserDelegate; } /** Sets the NSBrowser's delegate to anObject. Raises NSBrowserIllegalDelegateException if the delegate specified by anObject doesn't respond to browser:willDisplayCell:atRow:column: (if passive) and either of the methods browser:numberOfRowsInColumn: or browser:createRowsForColumn:inMatrix:. */ - (void) setDelegate: (id)anObject { BOOL flag = NO; if ([anObject respondsToSelector: @selector(browser:numberOfRowsInColumn:)]) { _passiveDelegate = YES; flag = YES; if (![anObject respondsToSelector: @selector(browser:willDisplayCell:atRow:column:)]) [NSException raise: NSBrowserIllegalDelegateException format: @"(Passive) Delegate does not respond to %s\n", "browser: willDisplayCell: atRow: column: "]; } if ([anObject respondsToSelector: @selector(browser:createRowsForColumn:inMatrix:)]) { _passiveDelegate = NO; // If flag is already set // then delegate must respond to both methods if (flag) { [NSException raise: NSBrowserIllegalDelegateException format: @"Delegate responds to both %s and %s\n", "browser: numberOfRowsInColumn: ", "browser: createRowsForColumn: inMatrix: "]; } flag = YES; } if (!flag) [NSException raise: NSBrowserIllegalDelegateException format: @"Delegate does not respond to %s or %s\n", "browser: numberOfRowsInColumn: ", "browser: createRowsForColumn: inMatrix: "]; _browserDelegate = anObject; } /* * Target and action */ /** Returns the NSBrowser's double-click action method. */ - (SEL) doubleAction { return _doubleAction; } /** Sets the NSBrowser's double-click action to aSelector. */ - (void) setDoubleAction: (SEL)aSelector { _doubleAction = aSelector; } /** Sends the action message to the target. Returns YES upon success, NO if no target for the message could be found. */ - (BOOL) sendAction { return [self sendAction: [self action] to: [self target]]; } /* * Event handling */ /** Responds to (single) mouse clicks in a column of the NSBrowser. */ - (void) doClick: (id)sender { NSArray *a; NSMutableArray *selectedCells; NSEnumerator *enumerator; NSBrowserCell *cell; int row, column, aCount, selectedCellsCount; if ([sender class] != _browserMatrixClass) return; column = [self columnOfMatrix: sender]; // If the matrix isn't ours then just return if (column == -1) return; a = [sender selectedCells]; aCount = [a count]; if(aCount == 0) return; selectedCells = [a mutableCopy]; enumerator = [a objectEnumerator]; while ((cell = [enumerator nextObject])) { if (_allowsBranchSelection == NO && [cell isLeaf] == NO) { [selectedCells removeObject: cell]; } } if ([selectedCells count] == 0) [selectedCells addObject: [sender selectedCell]]; selectedCellsCount = [selectedCells count]; if (selectedCellsCount == 0) { // If we should not select the cell // then deselect it and return [sender deselectAllCells]; RELEASE(selectedCells); return; } else if (selectedCellsCount < aCount) { [sender deselectSelectedCell]; enumerator = [selectedCells objectEnumerator]; while ((cell = [enumerator nextObject])) [sender selectCell: cell]; // FIXME: shouldn't be locking focus on another object // probably all this loop is wrong, because deselectSelectedCell // above may have changed array a. [sender lockFocus]; enumerator = [a objectEnumerator]; while ((cell = [enumerator nextObject])) { if ([selectedCells containsObject: cell] == NO) { if (![sender getRow: &row column: NULL ofCell: cell]) continue; if ([cell isHighlighted]) [sender highlightCell: NO atRow: row column: 0]; else [sender drawCellAtRow: row column: 0]; } } [sender unlockFocus]; [self displayIfNeeded]; [_window flushWindow]; } if (selectedCellsCount > 0) { // Single selection if (selectedCellsCount == 1) { cell = [selectedCells objectAtIndex: 0]; // If the cell is a leaf // then unload the columns after if ([cell isLeaf]) [self setLastColumn: column]; // The cell is not a leaf so we need to load a column else { int count = [_browserColumns count]; if (column < count - 1) [self setLastColumn: column]; [self addColumn]; } [sender scrollCellToVisibleAtRow: [sender selectedRow] column: column]; } // Multiple selection else { [self setLastColumn: column]; } } // Send the action to target [self sendAction]; RELEASE(selectedCells); } /** Responds to double-clicks in a column of the NSBrowser. */ - (void) doDoubleClick: (id)sender { // We have already handled the single click // so send the double action [self sendAction: _doubleAction to: [self target]]; } + (void) initialize { if (self == [NSBrowser class]) { // Initial version [self setVersion: 1]; scrollerWidth = [NSScroller scrollerWidth]; titleCell = [GSBrowserTitleCell new]; } } /* * Override superclass methods */ /** Setups browser with frame 'rect'. */ - (id) initWithFrame: (NSRect)rect { NSSize bs; //NSScroller *hs; self = [super initWithFrame: rect]; // Class setting _browserCellPrototype = [[[NSBrowser cellClass] alloc] init]; _browserMatrixClass = [NSMatrix class]; // Default values _pathSeparator = @"/"; _allowsBranchSelection = YES; _allowsEmptySelection = YES; _allowsMultipleSelection = YES; _reusesColumns = NO; _separatesColumns = YES; _isTitled = YES; _takesTitleFromPreviousColumn = YES; _hasHorizontalScroller = YES; _isLoaded = NO; _acceptsArrowKeys = YES; _acceptsAlphaNumericalKeys = YES; _lastKeyPressed = 0.; _charBuffer = nil; _sendsActionOnArrowKeys = YES; _sendsActionOnAlphaNumericalKeys = YES; _browserDelegate = nil; _passiveDelegate = YES; _doubleAction = NULL; bs = _sizeForBorderType (NSBezelBorder); _minColumnWidth = scrollerWidth + (2 * bs.width); if (_minColumnWidth < 100.0) _minColumnWidth = 100.0; // Horizontal scroller _scrollerRect.origin.x = bs.width; _scrollerRect.origin.y = bs.height; _scrollerRect.size.width = _frame.size.width - (2 * bs.width); _scrollerRect.size.height = scrollerWidth; _horizontalScroller = [[NSScroller alloc] initWithFrame: _scrollerRect]; [_horizontalScroller setTarget: self]; [_horizontalScroller setAction: @selector(scrollViaScroller:)]; [self addSubview: _horizontalScroller]; _skipUpdateScroller = NO; // Columns _browserColumns = [[NSMutableArray alloc] init]; // Create a single column _lastColumnLoaded = -1; _firstVisibleColumn = 0; _lastVisibleColumn = 0; _maxVisibleColumns = 3; [self _createColumn]; return self; } - (void) dealloc { RELEASE(_browserCellPrototype); RELEASE(_pathSeparator); RELEASE(_horizontalScroller); RELEASE(_browserColumns); TEST_RELEASE(_charBuffer); [super dealloc]; } /* * Target-actions */ /** Set target to 'target' */ - (void) setTarget: (id)target { _target = target; } /** Return current target. */ - (id) target { return _target; } /** Set action to 's'. */ - (void) setAction: (SEL)s { _action = s; } /** Return current action. */ - (SEL) action { return _action; } /* * Events handling */ - (void) drawRect: (NSRect)rect { NSRectClip(rect); [[_window backgroundColor] set]; NSRectFill(rect); // Load the first column if not already done if (!_isLoaded) { [self loadColumnZero]; } // Draws titles if (_isTitled) { int i; for (i = _firstVisibleColumn; i <= _lastVisibleColumn; ++i) { NSRect titleRect = [self titleFrameOfColumn: i]; if (NSIntersectsRect (titleRect, rect) == YES) { [self drawTitleOfColumn: i inRect: titleRect]; } } } // Draws scroller border if (_hasHorizontalScroller) { NSRect scrollerBorderRect = _scrollerRect; NSSize bs = _sizeForBorderType (NSBezelBorder); scrollerBorderRect.origin.x = 0; scrollerBorderRect.origin.y = 0; scrollerBorderRect.size.width += 2 * bs.width - 1; scrollerBorderRect.size.height += (2 * bs.height) - 1; if ((NSIntersectsRect (scrollerBorderRect, rect) == YES) && _window) { NSDrawGrayBezel (scrollerBorderRect, rect); } } if (!_separatesColumns) { NSPoint p1,p2; NSRect browserRect; int i, visibleColumns; // Columns borders browserRect = NSMakeRect(0, 0, rect.size.width, rect.size.height); NSDrawGrayBezel (browserRect, rect); [[NSColor blackColor] set]; visibleColumns = [self numberOfVisibleColumns]; for (i = 1; i < visibleColumns; i++) { p1 = NSMakePoint((_columnSize.width * i) + 2 + (i-1), _columnSize.height + scrollerWidth + 2); p2 = NSMakePoint((_columnSize.width * i) + 2 + (i-1), scrollerWidth + 3); [NSBezierPath strokeLineFromPoint: p1 toPoint: p2]; } // Horizontal scroller border p1 = NSMakePoint(2, scrollerWidth + 2); p2 = NSMakePoint(rect.size.width - 2, scrollerWidth + 2); [NSBezierPath strokeLineFromPoint: p1 toPoint: p2]; } } /* Informs the receivers's subviews that the receiver's bounds rectangle size has changed from oldFrameSize. */ - (void) resizeSubviewsWithOldSize: (NSSize)oldSize { [self tile]; } /* Override NSControl handler (prevents highlighting). */ - (void) mouseDown: (NSEvent *)theEvent { } - (void) moveLeft: (id)sender { if (_acceptsArrowKeys) { NSMatrix *matrix; NSCell *selectedCell; int selectedRow, selectedColumn; selectedColumn = [self selectedColumn]; if (selectedColumn > 0) { matrix = [self matrixInColumn: selectedColumn]; selectedCell = [matrix selectedCell]; selectedRow = [matrix selectedRow]; [matrix deselectAllCells]; if(selectedColumn+1 <= [self lastColumn]) [self setLastColumn: selectedColumn]; matrix = [self matrixInColumn: [self selectedColumn]]; [_window makeFirstResponder: matrix]; if (_sendsActionOnArrowKeys == YES) [super sendAction: _action to: _target]; } } } - (void) moveRight: (id)sender { if (_acceptsArrowKeys) { NSMatrix *matrix; BOOL selectFirstRow = NO; int selectedColumn; selectedColumn = [self selectedColumn]; if (selectedColumn == -1) { matrix = [self matrixInColumn: 0]; if ([[matrix cells] count]) { [matrix selectCellAtRow: 0 column: 0]; [_window makeFirstResponder: matrix]; [self doClick: matrix]; selectedColumn = 0; } } else { matrix = [self matrixInColumn: selectedColumn]; if (![[matrix selectedCell] isLeaf] && [[matrix selectedCells] count] == 1) selectFirstRow = YES; } if(selectFirstRow == YES) { matrix = [self matrixInColumn: [self lastColumn]]; if ([[matrix cells] count]) { [matrix selectCellAtRow: 0 column: 0]; [_window makeFirstResponder: matrix]; [self doClick: matrix]; } } if (_sendsActionOnArrowKeys == YES) [super sendAction: _action to: _target]; } } - (void) keyDown: (NSEvent *)theEvent { NSString *characters = [theEvent characters]; unichar character = 0; if ([characters length] > 0) { character = [characters characterAtIndex: 0]; } if (_acceptsArrowKeys) { switch (character) { case NSUpArrowFunctionKey: case NSDownArrowFunctionKey: return; case NSLeftArrowFunctionKey: [self moveLeft:self]; return; case NSRightArrowFunctionKey: [self moveRight:self]; return; case NSTabCharacter: { if ([theEvent modifierFlags] & NSShiftKeyMask) { [_window selectKeyViewPrecedingView: self]; } else { [_window selectKeyViewFollowingView: self]; } } return; break; } } if (_acceptsAlphaNumericalKeys && (character < 0xF700) && ([characters length] > 0)) { NSMatrix *matrix; NSString *sv; int i, n, s; int selectedColumn; SEL lcarcSel = @selector(loadedCellAtRow:column:); IMP lcarc = [self methodForSelector: lcarcSel]; selectedColumn = [self selectedColumn]; if(selectedColumn != -1) { matrix = [self matrixInColumn: selectedColumn]; n = [matrix numberOfRows]; s = [matrix selectedRow]; if (!_charBuffer) { _charBuffer = [characters substringToIndex: 1]; RETAIN(_charBuffer); } else { if (([theEvent timestamp] - _lastKeyPressed < 2000.0) && (_alphaNumericalLastColumn == selectedColumn)) { NSString *transition; transition = [_charBuffer stringByAppendingString: [characters substringToIndex: 1]]; RELEASE(_charBuffer); _charBuffer = transition; RETAIN(_charBuffer); } else { RELEASE(_charBuffer); _charBuffer = [characters substringToIndex: 1]; RETAIN(_charBuffer); } } _alphaNumericalLastColumn = selectedColumn; _lastKeyPressed = [theEvent timestamp]; sv = [((*lcarc)(self, lcarcSel, s, selectedColumn)) stringValue]; if (([sv length] > 0) && ([sv hasPrefix: _charBuffer])) return; for (i = s+1; i < n; i++) { sv = [((*lcarc)(self, lcarcSel, i, selectedColumn)) stringValue]; if (([sv length] > 0) && ([sv hasPrefix: _charBuffer])) { [self selectRow: i inColumn: selectedColumn]; [matrix scrollCellToVisibleAtRow: i column: 0]; [matrix performClick: self]; return; } } for (i = 0; i < s; i++) { sv = [((*lcarc)(self, lcarcSel, i, selectedColumn)) stringValue]; if (([sv length] > 0) && ([sv hasPrefix: _charBuffer])) { [self selectRow: i inColumn: selectedColumn]; [matrix scrollCellToVisibleAtRow: i column: 0]; [matrix performClick: self]; return; } } } _lastKeyPressed = 0.; } [super keyDown: theEvent]; } /* * NSCoding protocol * * We do not encode most of the instance variables except the Browser columns * because they are internal objects (though not transportable). So we just * encode enoguh information to rebuild identical columns on the decoder * side. Same for the Horizontal Scroller */ - (void) encodeWithCoder: (NSCoder*)aCoder { [super encodeWithCoder: aCoder]; // Here to keep compatibility with old version [aCoder encodeObject: nil]; [aCoder encodeObject:_browserCellPrototype]; [aCoder encodeObject: NSStringFromClass (_browserMatrixClass)]; [aCoder encodeObject:_pathSeparator]; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_isLoaded]; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_allowsBranchSelection]; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_allowsEmptySelection]; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_allowsMultipleSelection]; [aCoder encodeValueOfObjCType: @encode(int) at: &_maxVisibleColumns]; [aCoder encodeValueOfObjCType: @encode(float) at: &_minColumnWidth]; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_reusesColumns]; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_separatesColumns]; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_takesTitleFromPreviousColumn]; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_isTitled]; [aCoder encodeObject:_horizontalScroller]; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_hasHorizontalScroller]; [aCoder encodeRect: _scrollerRect]; [aCoder encodeSize: _columnSize]; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_acceptsArrowKeys]; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_sendsActionOnArrowKeys]; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_acceptsAlphaNumericalKeys]; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_sendsActionOnAlphaNumericalKeys]; [aCoder encodeConditionalObject:_browserDelegate]; [aCoder encodeValueOfObjCType: @encode(SEL) at: &_doubleAction]; [aCoder encodeConditionalObject: _target]; [aCoder encodeValueOfObjCType: @encode(SEL) at: &_action]; [aCoder encodeObject: _browserColumns]; // Just encode the number of columns and the first visible // and rebuild the browser columns on the decoding side { int colCount = [_browserColumns count]; [aCoder encodeValueOfObjCType: @encode(int) at: &colCount]; [aCoder encodeValueOfObjCType: @encode(int) at: &_firstVisibleColumn]; } } - (id) initWithCoder: (NSCoder*)aDecoder { int colCount; id dummy; [super initWithCoder: aDecoder]; // Here to keep compatibility with old version dummy = [aDecoder decodeObject]; _browserCellPrototype = RETAIN([aDecoder decodeObject]); _browserMatrixClass = NSClassFromString ((NSString *)[aDecoder decodeObject]); [self setPathSeparator: [aDecoder decodeObject]]; [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_isLoaded]; [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_allowsBranchSelection]; [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_allowsEmptySelection]; [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_allowsMultipleSelection]; [aDecoder decodeValueOfObjCType: @encode(int) at: &_maxVisibleColumns]; [aDecoder decodeValueOfObjCType: @encode(float) at: &_minColumnWidth]; [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_reusesColumns]; [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_separatesColumns]; [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_takesTitleFromPreviousColumn]; [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_isTitled]; //NSBox *_horizontalScrollerBox; _horizontalScroller = RETAIN([aDecoder decodeObject]); [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_hasHorizontalScroller]; _scrollerRect = [aDecoder decodeRect]; _columnSize = [aDecoder decodeSize]; _skipUpdateScroller = NO; /* _horizontalScroller = [[NSScroller alloc] initWithFrame: _scrollerRect]; [_horizontalScroller setTarget: self]; [_horizontalScroller setAction: @selector(scrollViaScroller:)]; */ [self setHasHorizontalScroller: _hasHorizontalScroller]; [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_acceptsArrowKeys]; [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_sendsActionOnArrowKeys]; [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_acceptsAlphaNumericalKeys]; [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_sendsActionOnAlphaNumericalKeys]; _lastKeyPressed = 0; _charBuffer = nil; // Skip: int _alphaNumericalLastColumn; _browserDelegate = [aDecoder decodeObject]; if (_browserDelegate != nil) [self setDelegate:_browserDelegate]; else _passiveDelegate = YES; [aDecoder decodeValueOfObjCType: @encode(SEL) at: &_doubleAction]; _target = [aDecoder decodeObject]; [aDecoder decodeValueOfObjCType: @encode(SEL) at: &_action]; // Do the minimal thing to initiate the browser... /* _lastColumnLoaded = -1; _firstVisibleColumn = 0; _lastVisibleColumn = 0; [self _createColumn]; */ _browserColumns = RETAIN([aDecoder decodeObject]); // ..and rebuild any existing browser columns [aDecoder decodeValueOfObjCType: @encode(int) at: &colCount]; [aDecoder decodeValueOfObjCType: @encode(int) at: &_firstVisibleColumn]; // Display even if there isn't any column _isLoaded = NO; [self tile]; return self; } /* * Div. */ - (BOOL) isOpaque { return YES; // See drawRect. } @end @implementation NSBrowser (GNUstepExtensions) /* * Setting the behavior of arrow keys */ /** Returns YES if the alphanumerical keys are enabled. */ - (BOOL) acceptsAlphaNumericalKeys { return _acceptsAlphaNumericalKeys; } /** Enables or disables the arrow keys as used for navigating within and between browsers. */ - (void) setAcceptsAlphaNumericalKeys: (BOOL)flag { _acceptsAlphaNumericalKeys = flag; } /** Returns NO if pressing an arrow key only scrolls the browser, YES if it also sends the action message specified by setAction:. */ - (BOOL) sendsActionOnAlphaNumericalKeys { return _sendsActionOnAlphaNumericalKeys; } /** Sets whether pressing an arrow key will cause the action message to be sent (in addition to causing scrolling). */ - (void) setSendsActionOnAlphaNumericalKeys: (BOOL)flag { _sendsActionOnAlphaNumericalKeys = flag; } @end /* * * PRIVATE METHODS * */ @implementation NSBrowser (Private) - (void) _remapColumnSubviews: (BOOL)fromFirst { id bc, sc; int i, count; id firstResponder = nil; BOOL setFirstResponder = NO; // Removes all column subviews. count = [_browserColumns count]; for (i = 0; i < count; i++) { bc = [_browserColumns objectAtIndex: i]; sc = [bc columnScrollView]; if (!firstResponder && [bc columnMatrix] == [_window firstResponder]) { firstResponder = [bc columnMatrix]; } if (sc) { [sc removeFromSuperviewWithoutNeedingDisplay]; } } if (_firstVisibleColumn > _lastVisibleColumn) return; // Sets columns subviews order according to fromFirst (display order...). // All added subviews are automaticaly marked as needing display (-> // NSView). if (fromFirst) { for (i = _firstVisibleColumn; i <= _lastVisibleColumn; i++) { bc = [_browserColumns objectAtIndex: i]; sc = [bc columnScrollView]; [self addSubview: sc]; if ([bc columnMatrix] == firstResponder) { [_window makeFirstResponder: firstResponder]; setFirstResponder = YES; } } if (firstResponder && setFirstResponder == NO) { [_window makeFirstResponder: [[_browserColumns objectAtIndex: _firstVisibleColumn] columnMatrix]]; } } else { for (i = _lastVisibleColumn; i >= _firstVisibleColumn; i--) { bc = [_browserColumns objectAtIndex: i]; sc = [bc columnScrollView]; [self addSubview: sc]; if ([bc columnMatrix] == firstResponder) { [_window makeFirstResponder: firstResponder]; setFirstResponder = YES; } } if (firstResponder && setFirstResponder == NO) { [_window makeFirstResponder: [[_browserColumns objectAtIndex: _lastVisibleColumn] columnMatrix]]; } } } /* Loads column 'column' (asking the delegate). */ - (void) _performLoadOfColumn: (int)column { id bc, sc, matrix; int i, rows, cols; if (_passiveDelegate) { // Ask the delegate for the number of rows rows = [_browserDelegate browser: self numberOfRowsInColumn: column]; cols = 1; } else { rows = 0; cols = 0; } bc = [_browserColumns objectAtIndex: column]; if (!(sc = [bc columnScrollView])) return; matrix = [bc columnMatrix]; if (_reusesColumns && matrix) { [matrix renewRows: rows columns: cols]; // Mark all the cells as unloaded for (i = 0; i < rows; i++) { [[matrix cellAtRow: i column: 0] setLoaded: NO]; } } else { NSRect matrixRect = {{0, 0}, {100, 100}}; NSSize matrixIntercellSpace = {0, 0}; // create a new col matrix matrix = [[_browserMatrixClass alloc] initWithFrame: matrixRect mode: NSListModeMatrix prototype: _browserCellPrototype numberOfRows: rows numberOfColumns: cols]; [matrix setIntercellSpacing: matrixIntercellSpace]; [matrix setAllowsEmptySelection: _allowsEmptySelection]; [matrix setAutoscroll: YES]; if (!_allowsMultipleSelection) { [matrix setMode: NSRadioModeMatrix]; } [matrix setTarget: self]; [matrix setAction: @selector(doClick:)]; [matrix setDoubleAction: @selector(doDoubleClick:)]; // set new col matrix and release old [bc setColumnMatrix: matrix]; RELEASE (matrix); } [sc setDocumentView: matrix]; // Loading is different based upon passive/active delegate if (_passiveDelegate) { // Now loop through the cells and load each one id aCell; SEL sel1 = @selector(browser:willDisplayCell:atRow:column:); IMP imp1 = [_browserDelegate methodForSelector: sel1]; SEL sel2 = @selector(cellAtRow:column:); IMP imp2 = [matrix methodForSelector: sel2]; for (i = 0; i < rows; i++) { aCell = (*imp2)(matrix, sel2, i, 0); if (![aCell isLoaded]) { (*imp1)(_browserDelegate, sel1, self, aCell, i, column); [aCell setLoaded: YES]; } } } else { // Tell the delegate to create the rows [_browserDelegate browser: self createRowsForColumn: column inMatrix: matrix]; } [sc setNeedsDisplay: YES]; [bc setIsLoaded: YES]; /* Determine the height of a cell in the matrix, and set that as the cellSize of the matrix. */ { NSSize cs, ms; NSBrowserCell *b = [matrix cellAtRow: 0 column: 0]; if (b != nil) { ms = [b cellSize]; } else { ms = [matrix cellSize]; } cs = [sc contentSize]; ms.width = cs.width; [matrix setCellSize: ms]; } // Get the title even when untiteled, as this may change later. [self setTitle: [self _getTitleOfColumn: column] ofColumn: column]; } /* Get the title of a column. */ - (NSString *) _getTitleOfColumn: (int)column { // Ask the delegate for the column title if ([_browserDelegate respondsToSelector: @selector(browser:titleOfColumn:)]) { return [_browserDelegate browser: self titleOfColumn: column]; } // Check if we take title from previous column if (_takesTitleFromPreviousColumn) { id c; // If first column then use the path separator if (column == 0) { return _pathSeparator; } // Get the selected cell // Use its string value as the title // Only if it is not a leaf if(_allowsMultipleSelection == NO) { c = [self selectedCellInColumn: column - 1]; } else { NSMatrix *matrix; NSArray *selectedCells; if (!(matrix = [self matrixInColumn: column - 1])) return @""; selectedCells = [matrix selectedCells]; if([selectedCells count] == 1) { c = [selectedCells objectAtIndex:0]; } else { return @""; } } if ([c isLeaf]) { return @""; } else { NSString *value = [c stringValue]; if (value != nil) { return value; } else { return @""; } } } return @""; } /* Marks all titles as needing to be redrawn. */ - (void) _setColumnTitlesNeedDisplay { if (_isTitled) { NSRect r = [self titleFrameOfColumn: _firstVisibleColumn]; r.size.width = _frame.size.width; [self setNeedsDisplayInRect: r]; } } @end