/**
Returns the NSBrowserCell class (regardless of whether a -setCellClass: message has been sent to a particular instance). This method is not meant to be used by applications.
See Also: -setCellClass:
*/ + (Class) cellClass { return [NSBrowserCell class]; } /**Sets the class of NSCell used in the columns of the NSBrowser.
*See Also: -setCellPrototype: -cellPrototype +cellClass
*/ - (void) setCellClass: (Class)classId { NSCell *aCell; aCell = [[classId alloc] init]; // set the prototype for the new class [self setCellPrototype: aCell]; RELEASE(aCell); } /**Returns the NSBrowser's prototype NSCell instance.
See Also: -setCellPrototype:
*/ - (id) cellPrototype { return _browserCellPrototype; } /**Sets the NSCell instance copied to display items in the columns of NSBrowser.
See Also: -cellPrototype
*/ - (void) setCellPrototype: (NSCell *)aCell { ASSIGN(_browserCellPrototype, aCell); } /**Returns the class of NSMatrix used in the NSBrowser's columns.
*See Also: -setMatrixClass:
*/ - (Class) matrixClass { return _browserMatrixClass; } /**Sets the matrix class (NSMatrix or an NSMatrix subclass) used in the NSBrowser's columns.
See Also: -matrixClass
*/ - (void) setMatrixClass: (Class)classId { _browserMatrixClass = classId; } /* * Getting matrices, cells, and rows */ /**Returns the last (rightmost and lowest) selected NSCell. Returns nil if * no cell is selected
*See Also: -selectedCells -selectedCellInColumn:
*/ - (id) selectedCell { int i; id matrix; // Nothing selected if ((i = [self selectedColumn]) == -1) { return nil; } if (!(matrix = [self matrixInColumn: i])) { return nil; } return [matrix selectedCell]; } /**Returns the last (lowest) NSCell that's selected in column. *Returns nil if no cell is selected
See Also: -selectedCell * -selectedCells
*/ - (id) selectedCellInColumn: (int)column { id matrix; if (!(matrix = [self matrixInColumn: column])) { return nil; } return [matrix selectedCell]; } /**Returns a NSArray of selected cells in the rightmost column. Returns *nil if no cell is selected
See Also: -selectedCell -selectedCellInColumn:
*/ - (NSArray *) selectedCells { int i; id matrix; // Nothing selected if ((i = [self selectedColumn]) == -1) { return nil; } if (!(matrix = [self matrixInColumn: i])) { return nil; } return [matrix selectedCells]; } /** Selects all NSCells in the last column of the NSBrowser. */ - (void) selectAll: (id)sender { id matrix; if (!(matrix = [self matrixInColumn: _lastColumnLoaded])) { return; } [matrix selectAll: sender]; } /** Returns the row index of the selected cell in the column specified by index column. */ - (int) selectedRowInColumn: (int)column { id matrix; if (!(matrix = [self matrixInColumn: column])) { return -1; } return [matrix selectedRow]; } /** Selects the cell at index row in the column identified by index column. */ - (void) selectRow: (int)row inColumn: (int)column { id matrix; id cell; BOOL didSelect; if ((matrix = [self matrixInColumn: column]) == nil) { return; } if ((cell = [matrix cellAtRow: row column: 0]) == nil) { return; } [self setLastColumn: column]; if (_allowsMultipleSelection == NO) { [matrix deselectAllCells]; } if ([_browserDelegate respondsToSelector: @selector(browser:selectRow:inColumn:)]) { didSelect = [_browserDelegate browser: self selectRow: row inColumn: column]; } else { [matrix selectCellAtRow: row column: 0]; didSelect = YES; } if (didSelect && [cell isLeaf] == NO) { [self addColumn]; } } /** Loads if necessary and returns the NSCell at row in column. */ /* if you change this code, you may want to look at the _loadColumn method in which the following code is integrated (for speed) */ - (id) loadedCellAtRow: (int)row column: (int)column { NSMatrix *matrix; id cell; if ((matrix = [self matrixInColumn: column]) == nil) { return nil; } // Get the cell if ((cell = [matrix cellAtRow: row column: 0]) == nil) { return nil; } // Load if not already loaded if ([cell isLoaded]) { return cell; } else { if (_passiveDelegate || [_browserDelegate respondsToSelector: @selector(browser:willDisplayCell:atRow:column:)]) { [_browserDelegate browser: self willDisplayCell: cell atRow: row column: column]; } [cell setLoaded: YES]; } return cell; } /** Returns the matrix located in the column identified by index column. */ - (NSMatrix *) matrixInColumn: (int)column { NSBrowserColumn *bc; if (column < 0 || column > _lastColumnLoaded) { return nil; } bc = [_browserColumns objectAtIndex: column]; if ((bc == nil) || !(bc->_isLoaded)) { return nil; } return bc->_columnMatrix; } /* * Getting and setting paths */ /** Returns the browser's current path. */ - (NSString *) path { return [self pathToColumn: _lastColumnLoaded + 1]; } /** *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 { NSMutableArray *subStrings; unsigned numberOfSubStrings; unsigned indexOfSubStrings; int column; BOOL useDelegate = NO; if ([_browserDelegate respondsToSelector: @selector(browser:selectCellWithString:inColumn:)]) { useDelegate = YES; } /* * Ensure that our starting column is loaded. */ if (_lastColumnLoaded < 0) { [self loadColumnZero]; } /* * Decompose the path. */ subStrings = [[path componentsSeparatedByString: _pathSeparator] mutableCopy]; [subStrings removeObject: @""]; numberOfSubStrings = [subStrings count]; if ([path hasPrefix: _pathSeparator]) { int i; /* * If the path begins with a separator, start at column 0. * Otherwise start at the currently selected column. */ column = 0; /* * 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. */ for (i = 0; i <= _lastColumnLoaded && (unsigned)i < numberOfSubStrings; i++) { NSString *c = [[self selectedCellInColumn: i] stringValue]; if ([c isEqualToString: [subStrings objectAtIndex: i]]) { column = i; } else { // Actually it's always called at 0 column [[self matrixInColumn: i] deselectAllCells]; break; } } [self setLastColumn: column]; indexOfSubStrings = column; } else { column = _lastColumnLoaded; indexOfSubStrings = 0; } // cycle thru str's array created from path while (indexOfSubStrings < numberOfSubStrings) { NSString *aStr = [subStrings objectAtIndex: indexOfSubStrings]; 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 { int numOfRows = [matrix numberOfRows]; int row; // find the cell in the browser matrix which is equal to aStr for (row = 0; row < numOfRows; row++) { selectedCell = [matrix cellAtRow: row column: 0]; if ([[selectedCell stringValue] isEqualToString: aStr]) { [matrix selectCellAtRow: row column: 0]; found = YES; break; } } } if (found) { indexOfSubStrings++; } else { // if unable to find a cell whose title matches aStr return 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++; } // Clean up local memory usage RELEASE(subStrings); if (indexOfSubStrings == 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]; NSString *string; int i; /* * 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 = {{-110, -110}, {100, 100}}; bc = [[NSBrowserColumn alloc] init]; // Create a scrollview sc = [[NSScrollView alloc] initWithFrame: rect]; [sc setHasHorizontalScroller: NO]; [sc setHasVerticalScroller: YES]; if (_separatesColumns) { [sc setBorderType: NSBezelBorder]; } else { [sc setBorderType: NSNoBorder]; } [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 ((unsigned)(_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 > _lastColumnLoaded) { return; } if (column < 0) { 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]; } [bc setIsLoaded: NO]; [self setTitle: nil ofColumn: i]; } if (!_reusesColumns && i > _lastVisibleColumn) { [sc removeFromSuperview]; [_browserColumns removeObject: bc]; count--; i--; } } [self scrollColumnToVisible:column]; } /** 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. Reselects previously selected cells, if they remain. */ - (void) reloadColumn: (int)column { NSArray *selectedCells; NSEnumerator *selectedCellsEnumerator; NSMatrix *matrix; NSCell *cell; matrix = [self matrixInColumn: column]; if (matrix == nil) { return; } // Get the previously selected cells selectedCells = [[matrix selectedCells] copy]; // Perform the data load [self _performLoadOfColumn: column]; // set last column loaded [self setLastColumn: column]; // Restore the selected cells matrix = [self matrixInColumn: column]; selectedCellsEnumerator = [selectedCells objectEnumerator]; while ((cell = [selectedCellsEnumerator nextObject]) != nil) { int sRow, sColumn; if ([matrix getRow: &sRow column: &sColumn ofCell: cell]) { [matrix selectCellAtRow: sRow column: sColumn]; } } RELEASE(selectedCells); } /* * Setting selection characteristics */ /**Returns whether the user can select branch items when multiple selection is enabled.
See Also: -setAllowsBranchSelection:
*/ - (BOOL) allowsBranchSelection { return _allowsBranchSelection; } /**Sets whether the user can select branch items when multiple selection is enabled.
See Also: -allowsBranchSelection
*/ - (void) setAllowsBranchSelection: (BOOL)flag { _allowsBranchSelection = flag; } /**Returns whether there can be nothing selected.
See Also: -setAllowsEmptySelection:
*/ - (BOOL) allowsEmptySelection { return _allowsEmptySelection; } /**Sets whether there can be nothing selected.
See Also: -allowsEmptySelection
*/ - (void) setAllowsEmptySelection: (BOOL)flag { _allowsEmptySelection = flag; } /**Returns whether the user can select multiple items.
See Also: -allowsMultipleSelection
*/ - (BOOL) allowsMultipleSelection { return _allowsMultipleSelection; } /**Sets whether the user can select multiple items.
See Also: -allowsMultipleSelection
*/ - (void) setAllowsMultipleSelection: (BOOL)flag { _allowsMultipleSelection = flag; } /* * Setting column characteristics */ /**Returns YES if NSMatrix objects aren't freed when their columns are unloaded.
See Also: -setReusesColumns:
*/ - (BOOL) reusesColumns { return _reusesColumns; } /**If flag is YES, prevents NSMatrix objects from being freed when their columns are unloaded, so they can be reused.
See Also: -reusesColumns
*/ - (void) setReusesColumns: (BOOL)flag { _reusesColumns = flag; } /**Returns the maximum number of visible columns.
See Also: -setMaxVisibleColumns:
*/ - (int) maxVisibleColumns { return _maxVisibleColumns; } /**Sets the maximum number of columns displayed.
See Also: -maxVisibleColumns
*/ - (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.
See Also: -setSeparatesColumns:
*/ - (BOOL) separatesColumns { return _separatesColumns; } /**Sets whether to separate columns with bezeled borders.
See Also: -separatesColumns
*/ - (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.
See Also: -setTitled:
*/ - (BOOL) isTitled { return _isTitled; } /**Sets whether columns display titles.
See Also: -isTitled
*/ - (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 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 = [self numberOfVisibleColumns]; float prop = (float)num / (float)(_lastColumnLoaded + 1); int uc = ((_lastColumnLoaded + 1) - num); // Unvisible columns float f_step = 1.0; // Knob moving step float fv = 0.0; if (uc > 0.0) { f_step = 1.0 / (float)uc; } fv = (float)(_firstVisibleColumn * f_step); if (_lastVisibleColumn > _lastColumnLoaded) { prop = (float)num / (float)(_lastVisibleColumn + 1); } [_horizontalScroller setFloatValue: fv knobProportion: prop]; } /** 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]; [self scrollColumnToVisible: rintf(f * _lastColumnLoaded)]; } break; // NSScrollerNoPart ??? default: break; } } /* * Showing a horizontal scroller */ /**Returns whether an NSScroller is used to scroll horizontally.
See Also: -setHasHorizontalScroller:
*/ - (BOOL) hasHorizontalScroller { return _hasHorizontalScroller; } /**Sets whether an NSScroller is used to scroll horizontally.
See Also: -hasHorizontalScroller
*/ - (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.
See Also: -setAcceptsArrowKeys:
*/ - (BOOL) acceptsArrowKeys { return _acceptsArrowKeys; } /**Enables or disables the arrow keys as used for navigating within and between browsers.
See Also: -acceptsArrowKeys
*/ - (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).
See Also: -sendsActionOnArrowKeys
*/ - (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; } else { r.origin.y += 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 + bs.width); } 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)); _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; _columnSize.height -= 2 * bs.width; } num = _lastVisibleColumn - _firstVisibleColumn + 1; if (_minColumnWidth > 0) { float colWidth = _minColumnWidth + scrollerWidth; if (_separatesColumns) colWidth += NSBR_COLUMN_SEP; if (_frame.size.width > colWidth) { 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 - 1) + (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; // FIXME: in some cases the column is not loaded while (i >= [_browserColumns count]) [self _createColumn]; 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]; } } /** Override from NSControl. Don't do anything to change the size of the browser. */ - (void) sizeToFit { } /* * Setting the delegate */ /**Returns the NSBrowser's delegate.
*See Also: -setDelegate:
*/ - (id) delegate { return _browserDelegate; } /** *Sets the delegate of the receiver. * If not nil, the delegate must either be passive and respond to * [NSObject-browser:numberOfRowsInColumn:] or be active and respond to * [NSObject-browser:createRowsForColumn:inMatrix:] but not both. * If the delegate is active it must also respond to * [NSObject-browser:willDisplayCell:atRow:column:]. * If the delegate is not nil but does not meet these conditions, * an NSBrowserIllegalDelegateException will be raised.
*See Also: -delegate
*/ - (void) setDelegate: (id)anObject { BOOL flag = NO; /* Default to YES for nil delegate. */ _passiveDelegate = YES; if ([anObject respondsToSelector: @selector(browser:numberOfRowsInColumn:)]) { flag = YES; if (![anObject respondsToSelector: @selector(browser:willDisplayCell:atRow:column:)]) [NSException raise: NSBrowserIllegalDelegateException format: @"(Passive) Delegate does not respond to %s\n", GSNameFromSelector (@selector(browser:willDisplayCell:atRow:column:))]; } if ([anObject respondsToSelector: @selector(browser:createRowsForColumn:inMatrix:)]) { _passiveDelegate = NO; /* If flag is already set then the delegate must respond to both methods. */ if (flag) { [NSException raise: NSBrowserIllegalDelegateException format: @"Delegate responds to both %s and %s\n", GSNameFromSelector (@selector(browser:numberOfRowsInColumn:)), GSNameFromSelector (@selector(browser:createRowsForColumn:inMatrix:))]; } flag = YES; } if (!flag && anObject) [NSException raise: NSBrowserIllegalDelegateException format: @"Delegate does not respond to %s or %s\n", GSNameFromSelector (@selector(browser:numberOfRowsInColumn:)), GSNameFromSelector (@selector(browser:createRowsForColumn:inMatrix:))]; _browserDelegate = anObject; } /* * Target and action */ /**Returns the NSBrowser's double-click action method.
*See Also: -setDoubleAction:
*/ - (SEL) doubleAction { return _doubleAction; } /**Sets the NSBrowser's double-click action to aSelector.
*See Also: -doubleAction
*/ - (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 column, aCount, selectedCellsCount; if ([sender class] != _browserMatrixClass) return; column = [self columnOfMatrix: sender]; // If the matrix isn't ours then just return if (column < 0 || column > _lastColumnLoaded) 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 && [sender selectedCell] != nil) [selectedCells addObject: [sender selectedCell]]; selectedCellsCount = [selectedCells count]; // Select cells that should be selected if (selectedCellsCount > 0) { enumerator = [selectedCells objectEnumerator]; while ((cell = [enumerator nextObject])) [sender selectCell: cell]; } [self setLastColumn: column]; // Single selection if (selectedCellsCount == 1) { cell = [selectedCells objectAtIndex: 0]; // If the cell is not a leaf we need to load a column if (![cell isLeaf]) { [self addColumn]; } [sender scrollCellToVisibleAtRow: [sender selectedRow] column: 0]; } [self updateScroller]; // 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]; } } /* * Override superclass methods */ /** Setups browser with frame 'rect'. */ - (id) initWithFrame: (NSRect)rect { NSSize bs; //NSScroller *hs; /* Created the shared titleCell if it hasn't been created already. */ if (!titleCell) { titleCell = [GSBrowserTitleCell new]; } 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 && _separatesColumns) { NSRect scrollerBorderRect = _scrollerRect; NSSize bs = _sizeForBorderType (NSBezelBorder); scrollerBorderRect.origin.x = 0; scrollerBorderRect.origin.y = 0; scrollerBorderRect.size.width += 2 * bs.width; scrollerBorderRect.size.height += (2 * bs.height) - 1; if ((NSIntersectsRect (scrollerBorderRect, rect) == YES) && _window) { [GSDrawFunctions drawGrayBezel: scrollerBorderRect : rect]; } } if (!_separatesColumns) { NSPoint p1,p2; int i, visibleColumns; float hScrollerWidth = _hasHorizontalScroller ? scrollerWidth : 0; // Columns borders [GSDrawFunctions drawGrayBezel: _bounds : rect]; [[NSColor blackColor] set]; visibleColumns = [self numberOfVisibleColumns]; for (i = 1; i < visibleColumns; i++) { p1 = NSMakePoint((_columnSize.width * i) + 2 + (i-1), _columnSize.height + hScrollerWidth + 2); p2 = NSMakePoint((_columnSize.width * i) + 2 + (i-1), hScrollerWidth + 2); [NSBezierPath strokeLineFromPoint: p1 toPoint: p2]; } // Horizontal scroller border if (_hasHorizontalScroller) { p1 = NSMakePoint(2, hScrollerWidth + 2); p2 = NSMakePoint(rect.size.width - 2, hScrollerWidth + 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; int selectedColumn; matrix = (NSMatrix *)[_window firstResponder]; selectedColumn = [self columnOfMatrix:matrix]; if (selectedColumn == -1) { selectedColumn = [self selectedColumn]; matrix = [self matrixInColumn: selectedColumn]; } if (selectedColumn > 0) { [matrix deselectAllCells]; [matrix scrollCellToVisibleAtRow:0 column:0]; [self setLastColumn: selectedColumn]; selectedColumn--; [self scrollColumnToVisible: selectedColumn]; matrix = [self matrixInColumn: selectedColumn]; [_window makeFirstResponder: matrix]; if (_sendsActionOnArrowKeys == YES) { [super sendAction: _action to: _target]; } } } } - (void) moveRight: (id)sender { if (_acceptsArrowKeys) { NSMatrix *matrix; int selectedColumn; matrix = (NSMatrix *)[_window firstResponder]; selectedColumn = [self columnOfMatrix:matrix]; if (selectedColumn == -1) { selectedColumn = [self selectedColumn]; matrix = [self matrixInColumn: selectedColumn]; } if (selectedColumn == -1) { selectedColumn = 0; matrix = [self matrixInColumn: 0]; if ([[matrix cells] count]) { [matrix selectCellAtRow: 0 column: 0]; } } else { // if there is one selected cell and it is a leaf, move right // (column is already loaded) if (![[matrix selectedCell] isLeaf] && [[matrix selectedCells] count] == 1) { selectedColumn++; matrix = [self matrixInColumn: selectedColumn]; if ([[matrix cells] count] && [matrix selectedCell] == nil) { [matrix selectCellAtRow: 0 column: 0]; } // if selected cell is a leaf, we need to add a column if (![[matrix selectedCell] isLeaf] && [[matrix selectedCells] count] == 1) { [self addColumn]; } } } [_window makeFirstResponder: 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 match; 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; match = -1; for (i = s + 1; i < n; i++) { sv = [((*lcarc)(self, lcarcSel, i, selectedColumn)) stringValue]; if (([sv length] > 0) && ([sv hasPrefix: _charBuffer])) { match = i; break; } } if (i == n) { for (i = 0; i < s; i++) { sv = [((*lcarc)(self, lcarcSel, i, selectedColumn)) stringValue]; if (([sv length] > 0) && ([sv hasPrefix: _charBuffer])) { match = i; break; } } } if (match != -1) { [matrix deselectAllCells]; [self selectRow: match inColumn: selectedColumn]; [matrix scrollCellToVisibleAtRow: match 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 { self = [super initWithCoder: aDecoder]; if ([aDecoder allowsKeyedCoding]) { NSCell *proto = [aDecoder decodeObjectForKey: @"NSCellPrototype"]; NSString *title = [aDecoder decodeObjectForKey: @"NSFirstColumnTitle"]; NSString *sep = [aDecoder decodeObjectForKey: @"NSPathSeparator"]; int flags; self = [super initWithCoder: aDecoder]; [self setCellPrototype: proto]; [self setPathSeparator: sep]; [self setTitle: title ofColumn: 0]; if ([aDecoder containsValueForKey: @"NSBrFlags"]) { flags = [aDecoder decodeIntForKey: @"NSBrFlags"]; [self setHasHorizontalScroller: (flags & 0x10000)]; [self setAllowsEmptySelection: !(flags & 0x20000)]; [self setSendsActionOnArrowKeys: (flags & 0x40000)]; [self setAcceptsArrowKeys: (flags & 0x100000)]; [self setSeparatesColumns: (flags & 0x4000000)]; [self setTakesTitleFromPreviousColumn: (flags & 0x8000000)]; [self setTitled: (flags & 0x10000000)]; [self setReusesColumns: (flags & 0x20000000)]; [self setAllowsBranchSelection: (flags & 0x40000000)]; [self setAllowsMultipleSelection: (flags & 0x80000000)]; } if ([aDecoder containsValueForKey: @"NSNumberOfVisibleColumns"]) { [self setMaxVisibleColumns: [aDecoder decodeIntForKey: @"NSNumberOfVisibleColumns"]]; } if ([aDecoder containsValueForKey: @"NSMinColumnWidth"]) { [self setMinColumnWidth: [aDecoder decodeIntForKey: @"NSMinColumnWidth"]]; } if ([aDecoder containsValueForKey: @"NSColumnResizingType"]) { //[self setColumnResizingType: [aDecoder decodeIntForKey: @"NSColumnResizingType"]]; } if ([aDecoder containsValueForKey: @"NSPreferedColumnWidth"]) { //int prefWidth = [aDecoder decodeIntForKey: @"NSPreferedColumnWidth"]; } } else { int colCount; id dummy; // 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]; if (column > _lastColumnLoaded) { _lastColumnLoaded = column; } /* 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 untitled, 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