/* NSBrowser.m Control to display and select from hierarchal lists Copyright (C) 1996, 1997 Free Software Foundation, Inc. Author: Scott Christley Date: 1996 This file is part of the GNUstep GUI Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #define COLUMN_SEP 6 // // Internal class for maintaining information about columns // @interface NSBrowserColumn : NSObject { BOOL _isLoaded; id _columnScrollView; id _columnMatrix; id _emptyView; int _numberOfRows; NSString *_columnTitle; } - (void)setIsLoaded:(BOOL)flag; - (BOOL)isLoaded; - (void)setColumnScrollView:(id)aView; - columnScrollView; - (void)setColumnMatrix:(id)aMatrix; - columnMatrix; - (void)setNumberOfRows:(int)num; - (int)numberOfRows; - (void)setColumnTitle:(NSString *)aString; - (NSString *)columnTitle; - emptyView; @end @implementation NSBrowserColumn - init { [super init]; _isLoaded = NO; _emptyView = [[NSView alloc] init]; return self; } - (void)dealloc { [_columnTitle release]; [super dealloc]; } - (void)setIsLoaded:(BOOL)flag { _isLoaded = flag; } - (BOOL)isLoaded { return _isLoaded; } - (void)setColumnScrollView:(id)aView { _columnScrollView = aView; } - columnScrollView { return _columnScrollView; } - (void)setColumnMatrix:(id)aMatrix { _columnMatrix = aMatrix; } - columnMatrix { return _columnMatrix; } - (void)setNumberOfRows:(int)num { _numberOfRows = num; } - (int)numberOfRows { return _numberOfRows; } - (void)setColumnTitle:(NSString *)aString { [aString retain]; [_columnTitle release]; _columnTitle = aString; } - (NSString *)columnTitle { return _columnTitle; } - emptyView { return _emptyView; } @end // // Private NSBrowser methods // @interface NSBrowser (Private) - (void)_adjustMatrixOfColumn:(int)column; - (void)_adjustScrollerFrameOfColumn:(int)column force:(BOOL)flag; - (void)_adjustScrollerFrames:(BOOL)flag; - (void)_performLoadOfColumn:(int)column; - (void)_unloadFromColumn:(int)column; @end // // NSBrowser implementation // @implementation NSBrowser // // Class methods // + (void)initialize { if (self == [NSBrowser class]) { // Initial version [self setVersion:1]; } } // // Setting Component Classes // + (Class)cellClass { return [NSBrowserCell class]; } // // Instance methods // - initWithFrame:(NSRect)rect { NSSize bs; NSRect scroller_rect; [super initWithFrame: rect]; _browserCellClass = [NSBrowser cellClass]; _browserCellPrototype = [[_browserCellClass alloc] init]; _browserMatrixClass = [NSMatrix class]; _pathSeparator = [[NSString alloc] initWithCString: "/"]; _isLoaded = NO; _allowsBranchSelection = YES; _allowsEmptySelection = YES; _allowsMultipleSelection = YES; _reusesColumns = NO; _maxVisibleColumns = 1; bs = [NSCell sizeForBorderType: NSBezelBorder]; _minColumnWidth = [NSScroller scrollerWidth] + (2 * bs.width); _separatesColumns = YES; _takesTitleFromPreviousColumn = YES; _isTitled = YES; _hasHorizontalScroller = YES; scroller_rect.origin = NSZeroPoint; scroller_rect.size.width = frame.size.width; scroller_rect.size.height = [NSScroller scrollerWidth]; _horizontalScroller = [[NSScroller alloc] initWithFrame: scroller_rect]; [_horizontalScroller setTarget: self]; [_horizontalScroller setAction: @selector(scrollViaScroller:)]; [self addSubview: _horizontalScroller]; _acceptsArrowKeys = YES; _sendsActionOnArrowKeys = YES; _browserDelegate = nil; _passiveDelegate = YES; _doubleAction = NULL; _browserColumns = [[NSMutableArray alloc] init]; _titleCell = [NSTextFieldCell new]; [_titleCell setEditable: NO]; [_titleCell setTextColor: [NSColor whiteColor]]; [_titleCell setBackgroundColor: [NSColor darkGrayColor]]; //[_titleCell setBordered: YES]; //[_titleCell setBezeled: YES]; [_titleCell setAlignment: NSCenterTextAlignment]; // Calculate geometry [self tile]; // Create a single column _lastColumnLoaded = -1; _firstVisibleColumn = 0; _lastVisibleColumn = 0; [self addColumn]; return self; } - (void)dealloc { [_browserCellPrototype release]; [_pathSeparator release]; [_horizontalScroller release]; [_browserColumns release]; [_titleCell release]; [super dealloc]; } // // Setting the Delegate // - (id)delegate { return _browserDelegate; } - (void)setDelegate:(id)anObject { BOOL flag = NO; BOOL both = NO; if (![anObject respondsToSelector: @selector(browser:willDisplayCell:atRow:column:)]) [NSException raise: NSBrowserIllegalDelegateException format: @"Delegate does not respond to %s\n", "browser:willDisplayCell:atRow:column:"]; if ([anObject respondsToSelector: @selector(browser:numberOfRowsInColumn:)]) { _passiveDelegate = YES; flag = YES; } if ([anObject respondsToSelector: @selector(browser:createRowsForColumn:inMatrix:)]) { _passiveDelegate = NO; // If flag is already set // then delegate must respond to both methods if (flag) both = YES; flag = YES; } if (!flag) [NSException raise: NSBrowserIllegalDelegateException format: @"Delegate does not respond to %s or %s\n", "browser:numberOfRowsInColumn:", "browser:createRowsForColumn:inMatrix:"]; if (both) [NSException raise: NSBrowserIllegalDelegateException format: @"Delegate responds to both %s and %s\n", "browser:numberOfRowsInColumn:", "browser:createRowsForColumn:inMatrix:"]; [anObject retain]; [_browserDelegate release]; _browserDelegate = anObject; } // // Target and Action // - (SEL)doubleAction { return _doubleAction; } - (BOOL)sendAction { return [self sendAction: [self action] to: [self target]]; } - (void)setDoubleAction:(SEL)aSelector { _doubleAction = aSelector; } // // Setting Component Classes // - (id)cellPrototype { return _browserCellPrototype; } - (Class)matrixClass { return _browserMatrixClass; } - (void)setCellClass:(Class)classId { _browserCellClass = classId; // set the prototype for the new class [self setCellPrototype: [[[_browserCellClass alloc] init] autorelease]]; } - (void)setCellPrototype:(NSCell *)aCell { [aCell retain]; [_browserCellPrototype release]; _browserCellPrototype = aCell; } - (void)setMatrixClass:(Class)classId { _browserMatrixClass = classId; } // // Setting NSBrowser Behavior // - (BOOL)reusesColumns { return _reusesColumns; } - (void)setReusesColumns:(BOOL)flag { _reusesColumns = flag; } - (void)setTakesTitleFromPreviousColumn:(BOOL)flag { _takesTitleFromPreviousColumn = flag; } - (BOOL)takesTitleFromPreviousColumn { return _takesTitleFromPreviousColumn; } // // Allowing Different Types of Selection // - (BOOL)allowsBranchSelection { return _allowsBranchSelection; } - (BOOL)allowsEmptySelection { return _allowsEmptySelection; } - (BOOL)allowsMultipleSelection { return _allowsMultipleSelection; } - (void)setAllowsBranchSelection:(BOOL)flag { _allowsBranchSelection = flag; } - (void)setAllowsEmptySelection:(BOOL)flag { _allowsEmptySelection = flag; } - (void)setAllowsMultipleSelection:(BOOL)flag { _allowsMultipleSelection = flag; } // // Setting Arrow Key Behavior // - (BOOL)acceptsArrowKeys { return _acceptsArrowKeys; } - (BOOL)sendsActionOnArrowKeys { return _sendsActionOnArrowKeys; } - (void)setAcceptsArrowKeys:(BOOL)flag { _acceptsArrowKeys = flag; } - (void)setSendsActionOnArrowKeys:(BOOL)flag { _sendsActionOnArrowKeys = flag; } // // Showing a Horizontal Scroller // - (void)setHasHorizontalScroller:(BOOL)flag { _hasHorizontalScroller = flag; if (!flag) [_horizontalScroller removeFromSuperview]; [self tile]; } - (BOOL)hasHorizontalScroller { return _hasHorizontalScroller; } // // Setting the NSBrowser's Appearance // - (int)maxVisibleColumns { return _maxVisibleColumns; } - (int)minColumnWidth { return _minColumnWidth; } - (BOOL)separatesColumns { return _separatesColumns; } - (void)setMaxVisibleColumns:(int)columnCount { int i, count = [_browserColumns count]; _maxVisibleColumns = columnCount; // Create additional columns if necessary for (i = count; i < _maxVisibleColumns; ++i) [self addColumn]; _lastVisibleColumn = _maxVisibleColumns - _firstVisibleColumn - 1; [self tile]; [self _adjustScrollerFrames: NO]; } - (void)setMinColumnWidth:(int)columnWidth { float sw = [NSScroller scrollerWidth]; NSSize bs = [NSCell sizeForBorderType: NSBezelBorder]; float bw = 2 * bs.width; // Take the border into account if (_separatesColumns) sw += bw; // Column width cannot be less than scroller and border if (columnWidth < sw) _minColumnWidth = sw; else _minColumnWidth = columnWidth; [self tile]; } - (void)setSeparatesColumns:(BOOL)flag { _separatesColumns = flag; [self tile]; } // // Manipulating Columns // - (void)addColumn { NSBrowserColumn *bc; NSScrollView *sc; int n = [_browserColumns count]; bc = [[[NSBrowserColumn alloc] init] autorelease]; // Create a scrollview sc = [[NSScrollView alloc] initWithFrame: [self frameOfInsideOfColumn: n]]; [sc setHasHorizontalScroller: NO]; [sc setHasVerticalScroller: YES]; [bc setColumnScrollView: sc]; [self addSubview: sc]; [_browserColumns addObject: bc]; } - (int)columnOfMatrix:(NSMatrix *)matrix { NSBrowserColumn *bc; int i, count = [_browserColumns count]; // Loop through columns and compare matrixes for (i = 0;i < count; ++i) { bc = [_browserColumns objectAtIndex: i]; if (matrix == [bc columnMatrix]) return i; } // Not found return NSNotFound; } - (void)displayAllColumns { int i, count = [_browserColumns count]; for (i = 0; i < count; ++i) { //NSBrowserColumn *bc = [_browserColumns objectAtIndex: i]; // Only display if loaded //if ([bc isLoaded]) [self displayColumn: i]; } } - (void)displayColumn:(int)column { NSBrowserColumn *bc; // If not visible then nothing to display if ((column < _firstVisibleColumn) || (column > _lastVisibleColumn)) return; bc = [_browserColumns objectAtIndex: column]; // Ask the delegate for the column title if ([_browserDelegate respondsToSelector: @selector(browser:titleOfColumn:)]) [self setTitle: [_browserDelegate browser: self titleOfColumn: column] ofColumn: column]; else { // Check if we take title from previous column if ([self takesTitleFromPreviousColumn]) { // If first column then use the path separator if (column == 0) [self setTitle: _pathSeparator ofColumn: 0]; else { // Get the selected cell // Use its string value as the title // Only if it is not a leaf id c = [self selectedCellInColumn: column - 1]; if ([c isLeaf]) [self setTitle: @"" ofColumn: column]; else [self setTitle: [c stringValue] ofColumn: column]; } } else [self setTitle: @"" ofColumn: column]; } // Draw the title [self drawTitle: [bc columnTitle] inRect: [self titleFrameOfColumn: column] ofColumn: column]; } - (int)firstVisibleColumn { return _firstVisibleColumn; } - (BOOL)isLoaded { return _isLoaded; } - (int)lastColumn { return _lastColumnLoaded; } - (int)lastVisibleColumn { return _lastVisibleColumn; } - (void)loadColumnZero { // load column 0 [self _performLoadOfColumn: 0]; // Unload other columns [self _unloadFromColumn: 1]; // set last column loaded [self setLastColumn: 0]; _isLoaded = YES; [self tile]; [self _adjustScrollerFrames: YES]; } - (int)numberOfVisibleColumns { int i, j, count = [_browserColumns count]; BOOL done; // As I interpret it, the number of visible columns // is the total number of scrollviews in the scrolling // region regardless of whether they are actually // visible or not. // We have at least upto the last visible column i = _lastVisibleColumn; // Plus any loaded columns after that done = NO; j = i + 1; while ((!done) && (j < count)) { NSBrowserColumn *bc = [_browserColumns objectAtIndex: j]; if ([bc isLoaded]) { i = j; ++j; } else done = YES; } // Should be at least the max visible columns if (i < _maxVisibleColumns) return _maxVisibleColumns; else return i + 1; } - (void)reloadColumn:(int)column { NSBrowserColumn *bc; // Make sure the column even exists if (column >= (int)[_browserColumns count]) return; bc = [_browserColumns objectAtIndex: column]; // If the column has not be loaded then do not reload if (![bc isLoaded]) return; // Perform the data load [self _performLoadOfColumn: column]; // Unload the columns after [self _unloadFromColumn: column + 1]; // set last column loaded [self setLastColumn: column]; } - (void)selectAll:(id)sender { id matrix = [self matrixInColumn: _lastVisibleColumn]; [matrix selectAll: sender]; } - (int)selectedColumn { id o, e = [_browserColumns reverseObjectEnumerator]; BOOL found = NO; id c = nil; int i = [_browserColumns count] - 1; while ((!found) && (o = [e nextObject])) { id matrix = [o columnMatrix]; c = [matrix selectedCell]; if (c) found = YES; else --i; } if (found) return i; else return NSNotFound; } - (void)setLastColumn:(int)column { _lastColumnLoaded = column; } - (void)validateVisibleColumns { int i; // xxx Should we trigger an exception? 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 BOOL v = [_browserDelegate browser: self isColumnValid: i]; if (!v) [self reloadColumn: i]; } } // // Manipulating Column Titles // - (void)drawTitle:(NSString *)title inRect:(NSRect)aRect ofColumn:(int)column { // Not titled then nothing to draw if (![self isTitled]) return; // If column is not visible then nothing to draw if ((column < _firstVisibleColumn) || (column > _lastVisibleColumn)) return; [_titleCell setStringValue: title]; [_titleCell drawWithFrame: aRect inView: self]; } - (BOOL)isTitled { return _isTitled; } - (void)setTitled:(BOOL)flag { _isTitled = flag; [self tile]; } - (void)setTitle:(NSString *)aString ofColumn:(int)column { NSBrowserColumn *bc = [_browserColumns objectAtIndex: column]; [bc setColumnTitle: aString]; // If column is not visible then nothing to redisplay if ((column < _firstVisibleColumn) || (column > _lastVisibleColumn)) return; else [self setNeedsDisplayInRect: [self titleFrameOfColumn: column]]; } - (NSRect)titleFrameOfColumn:(int)column { NSRect r; int n; // Not titled then no frame if (![self isTitled]) return NSZeroRect; // Number of columns over from the first n = column - _firstVisibleColumn; // Calculate the frame r.origin.x = n * _columnSize.width; r.origin.y = frame.size.height - [self titleHeight] + 2; r.size.width = _columnSize.width; r.size.height = [self titleHeight] - 4; if (_separatesColumns) r.origin.x += n * COLUMN_SEP; return r; } - (float)titleHeight { return 24; } - (NSString *)titleOfColumn:(int)column { NSBrowserColumn *bc = [_browserColumns objectAtIndex: column]; return [bc columnTitle]; } // // Scrolling an NSBrowser // - (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 scrollviews [self _adjustScrollerFrames: YES]; // Update the scroller [self updateScroller]; // Notify the delegate if ([_browserDelegate respondsToSelector: @selector(browserDidScroll:)]) [_browserDelegate browserDidScroll: self]; } - (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 scrollviews [self _adjustScrollerFrames: YES]; // Update the scroller [self updateScroller]; // Notify the delegate if ([_browserDelegate respondsToSelector: @selector(browserDidScroll:)]) [_browserDelegate browserDidScroll: self]; } - (void)scrollColumnToVisible:(int)column { int i; // If its the last visible column then we are there already if (_lastVisibleColumn == column) return; // If there are not enough columns to scroll with // then the column must be visible if ([self numberOfVisibleColumns] <= _maxVisibleColumns) return; i = _lastVisibleColumn - column; if (i > 0) [self scrollColumnsLeftBy: i]; else [self scrollColumnsRightBy: (-i)]; } - (void)scrollViaScroller:(NSScroller *)sender { NSScrollerPart hit = [sender hitPart]; // Scroll to the left if ((hit == NSScrollerDecrementLine) || (hit == NSScrollerDecrementPage)) { [self scrollColumnsLeftBy: 1]; return; } // Scroll to the right if ((hit == NSScrollerIncrementLine) || (hit == NSScrollerIncrementPage)) { [self scrollColumnsRightBy: 1]; return; } // The knob or knob slot if ((hit == NSScrollerKnob) || (hit == NSScrollerKnobSlot)) { float f = [sender floatValue]; int c = [self numberOfVisibleColumns] - 1; float i; // If the number of loaded columns has shrunk then // there will be more columns displayed then loaded // so use that value instead if (c > _lastColumnLoaded) i = c - _maxVisibleColumns + 1; else i = _lastColumnLoaded - _maxVisibleColumns + 1; if (_lastColumnLoaded != 0) { f = (f - 1) * i; f += _lastColumnLoaded; [self scrollColumnToVisible: (int)f]; } else [self updateScroller]; return; } } - (void)updateScroller { // If there are not enough columns to scroll with // then the column must be visible if ((_lastColumnLoaded == 0) || (_lastColumnLoaded <= (_maxVisibleColumns - 1))) [_horizontalScroller setEnabled: NO]; else { float prop = (float)_maxVisibleColumns / (float)(_lastColumnLoaded + 1); float i = _lastColumnLoaded - _maxVisibleColumns + 1; float f = 1 + ((_lastVisibleColumn - _lastColumnLoaded) / i); [_horizontalScroller setFloatValue: f knobProportion: prop]; [_horizontalScroller setEnabled: YES]; } return; } // // Event Handling // - (void)doClick:(id)sender { int column = [self columnOfMatrix: sender]; NSArray *a; BOOL shouldSelect = YES; // If the matrix isn't ours then just return if (column == NSNotFound) return; // Ask delegate if selection is ok if ([_browserDelegate respondsToSelector: @selector(browser:selectRow:inColumn:)]) { int row = [sender selectedRow]; shouldSelect = [_browserDelegate browser: self selectRow: row inColumn: column]; } else { // Try the other method if ([_browserDelegate respondsToSelector: @selector(browser:selectCellWithString:inColumn:)]) { id c = [sender selectedCell]; shouldSelect = [_browserDelegate browser: self selectCellWithString: [c stringValue] inColumn: column]; } } // If we should not select the cell // then select it and return if (!shouldSelect) { [sender deselectSelectedCell]; return; } a = [sender selectedCells]; // If only one cell is selected if ([a count] == 1) { id c = [a objectAtIndex: 0]; // If the cell is a leaf // then unload the columns after if ([c isLeaf]) { [self _unloadFromColumn: column + 1]; [self setLastColumn: column]; } else { // The cell is not a leaf so we need to load a column // If last column then add a column if (column == (int)([_browserColumns count] - 1)) [self addColumn]; // Load column [self _performLoadOfColumn: column + 1]; [self setLastColumn: column + 1]; [self _adjustMatrixOfColumn: column + 1]; [self _unloadFromColumn: column + 2]; // If this column is the last visible // then scroll right by one column if (column == _lastVisibleColumn) [self scrollColumnsRightBy: 1]; } } else { // If multiple selection // then we unload the columns after [self _unloadFromColumn: column + 1]; [self setLastColumn: column]; } // Send the action to target [self sendAction]; } - (void)doDoubleClick:(id)sender { // We have already handled the single click // so send the double action [self sendAction: _doubleAction to: [self target]]; } // // Getting Matrices and Cells // - (id)loadedCellAtRow:(int)row column:(int)column { NSBrowserColumn *bc; NSArray *columnCells; id matrix; int count = [_browserColumns count]; id aCell; // column range check if (column >= count) return nil; bc = [_browserColumns objectAtIndex: column]; matrix = [bc columnMatrix]; columnCells = [matrix cells]; count = [columnCells count]; // row range check if (row >= count) return nil; // Get the cell aCell = [matrix cellAtRow: row column: 0]; // Load if not already loaded if ([aCell isLoaded]) return aCell; else { [_browserDelegate browser: self willDisplayCell: aCell atRow: row column: column]; [aCell setLoaded: YES]; } return aCell; } - (NSMatrix *)matrixInColumn:(int)column { NSBrowserColumn *bc = [_browserColumns objectAtIndex: column]; return [bc columnMatrix]; } - (id)selectedCell { int i = [self selectedColumn]; id matrix; // Nothing selected if (i == NSNotFound) return nil; matrix = [self matrixInColumn: i]; return [matrix selectedCell]; } - (id)selectedCellInColumn:(int)column { id matrix = [self matrixInColumn: column]; return [matrix selectedCell]; } - (NSArray *)selectedCells { int i = [self selectedColumn]; id matrix; // Nothing selected if (i == NSNotFound) return nil; matrix = [self matrixInColumn: i]; return [matrix selectedCells]; } // // Getting Column Frames // - (NSRect)frameOfColumn:(int)column { NSRect r = NSZeroRect; 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 * COLUMN_SEP; // Adjust for horizontal scroller if (_hasHorizontalScroller) r.origin.y = [NSScroller scrollerWidth] + 4; return r; } - (NSRect)frameOfInsideOfColumn:(int)column { // xxx what does this one do? return [self frameOfColumn: column]; } // // Manipulating Paths // - (NSString *)path { return [self pathToColumn: _lastColumnLoaded + 1]; } - (NSString *)pathSeparator { return _pathSeparator; } - (NSString *)pathToColumn:(int)column { int i; NSMutableString *s = [NSMutableString stringWithCString: ""]; // Cannot go past the number of loaded columns if (column > _lastColumnLoaded) column = _lastColumnLoaded + 1; [s appendString: _pathSeparator]; for (i = 0;i < column; ++i) { id c = [self selectedCellInColumn: i]; if (i != 0) [s appendString: _pathSeparator]; [s appendString: [c stringValue]]; } return [[[NSString alloc] initWithString: s] autorelease]; } - (BOOL)setPath:(NSString *)path { return NO; } - (void)setPathSeparator:(NSString *)aString { [aString retain]; [_pathSeparator release]; _pathSeparator = aString; } // // Arranging an NSBrowser's Components // - (void)tile { NSRect scroller_rect; int num; // It is assumed the frame/bounds are set appropriately // before calling this method // Determine number of columns and their widths num = _maxVisibleColumns; if (_separatesColumns) _columnSize.width = (frame.size.width - ((num - 1) * COLUMN_SEP)) / (float)num; else _columnSize.width = frame.size.width / (float)num; _columnSize.height = frame.size.height; // Horizontal scroller if (_hasHorizontalScroller) { scroller_rect.origin = frame.origin; scroller_rect.size.width = frame.size.width; scroller_rect.size.height = [NSScroller scrollerWidth] + 4; _columnSize.height -= scroller_rect.size.height; } else scroller_rect = NSZeroRect; // Title if (_isTitled) _columnSize.height -= [self titleHeight]; if (_columnSize.height < 0) _columnSize.height = 0; } - (void)drawRect:(NSRect)rect { int i; // Load the first column if not already done if (!_isLoaded) { [self loadColumnZero]; [self displayAllColumns]; } // Loop through the visible columns for (i = _firstVisibleColumn; i <= _lastVisibleColumn; ++i) { // If the column title intersects with the rect to be drawn // then draw that column NSRect r = NSIntersectionRect([self titleFrameOfColumn: i], rect); if (! NSIsEmptyRect(r)) [self displayColumn: i]; } } // // Displaying the Control and Cell // - (void)drawCell:(NSCell *)aCell { } - (void)drawCellInside:(NSCell *)aCell { } - (void)selectCell:(NSCell *)aCell { } - (void)updateCell:(NSCell *)aCell { } - (void)updateCellInside:(NSCell *)aCell { } // // NSCoding protocol // - (void)encodeWithCoder:aCoder { [super encodeWithCoder:aCoder]; } - initWithCoder:aDecoder { [super initWithCoder:aDecoder]; return self; } @end // // Private NSBrowser methods // @implementation NSBrowser (Private) - (void)_adjustMatrixOfColumn:(int)column { NSBrowserColumn *bc; NSScrollView *sc; id matrix; NSSize cs, ms; NSRect mr; if (column >= (int)[_browserColumns count]) return; bc = [_browserColumns objectAtIndex: column]; sc = [bc columnScrollView]; matrix = [bc columnMatrix]; // Adjust matrixes to fit in the scrollview // Only set if column has been loaded if (sc && matrix && [bc isLoaded]) { cs = [sc contentSize]; [matrix sizeToCells]; ms = [matrix cellSize]; ms.width = cs.width; [matrix setCellSize: ms]; mr = [matrix frame]; #if 0 // Adjust because NSMatrix has incorrect origin if (mr.size.height < cs.height) { mr.origin.y = cs.height - mr.size.height; [matrix setFrame: mr]; } #endif [sc setDocumentView: matrix]; } } - (void)_adjustScrollerFrameOfColumn:(int)column force:(BOOL)flag { // Only if we've loaded the first column if ((_isLoaded) || (flag)) { NSBrowserColumn *bc; NSScrollView *sc; if (column >= (int)[_browserColumns count]) return; bc = [_browserColumns objectAtIndex: column]; sc = [bc columnScrollView]; // Set the scrollview frame // Only set before the column has been loaded // Or we are being forced if (sc && ((![bc isLoaded]) || flag)) [sc setFrame: [self frameOfInsideOfColumn: column]]; } } - (void)_adjustScrollerFrames:(BOOL)force { int i, count = [_browserColumns count]; // Loop through the columns for (i = 0;i < count; ++i) { // If its a visible column if ((i >= _firstVisibleColumn) && (i <= _lastVisibleColumn)) { NSBrowserColumn *bc = [_browserColumns objectAtIndex: i]; id sc = [bc columnScrollView]; // Add as subview if necessary if (sc) if (![sc superview]) [self addSubview: sc]; [self _adjustScrollerFrameOfColumn: i force: force]; [self _adjustMatrixOfColumn: i]; } else { // If it is not visible NSBrowserColumn *bc = [_browserColumns objectAtIndex: i]; id sc = [bc columnScrollView]; // Remove it from its superview if it has one if (sc) if ([sc superview]) { NSLog(@"before: %d\n", [sc retainCount]); [sc removeFromSuperview]; NSLog(@"after: %d\n", [sc retainCount]); } } } } - (void)_performLoadOfColumn:(int)column { NSBrowserColumn *bc = [_browserColumns objectAtIndex: column]; // Loading is different based upon passive/active delegate if (_passiveDelegate) { // Ask the delegate for the number of rows int n = [_browserDelegate browser: self numberOfRowsInColumn: column]; if (_reusesColumns) { } else { id matrix; id sc = [bc columnScrollView]; id oldm = [bc columnMatrix]; NSRect matrixRect = {{0, 0}, {100, 100}}; int i; // If we are not reusing matrixes // then delete the old matrix and create a new one [oldm release]; matrix = [[_browserMatrixClass alloc] initWithFrame: matrixRect mode: NSListModeMatrix prototype: _browserCellPrototype numberOfRows: n numberOfColumns: 1]; [matrix setAllowsEmptySelection: _allowsEmptySelection]; if (!_allowsMultipleSelection) [matrix setMode: NSRadioModeMatrix]; [matrix setTarget: self]; [matrix setAction: @selector(doClick:)]; [matrix setDoubleAction: @selector(doDoubleClick:)]; [bc setColumnMatrix: matrix]; [sc setDocumentView: matrix]; // Now loop through the cells and load each one for (i = 0;i < n; ++i) [self loadedCellAtRow: i column: column]; } } else { if (_reusesColumns) { } else { id matrix; id sc = [bc columnScrollView]; id oldm = [bc columnMatrix]; NSRect matrixRect = {{0, 0}, {100, 100}}; // If we are not reusing matrixes // then delete the old matrix and create a new one [oldm release]; matrix = [[_browserMatrixClass alloc] initWithFrame: matrixRect mode: NSListModeMatrix prototype: _browserCellPrototype numberOfRows: 0 numberOfColumns: 0]; [matrix setAllowsEmptySelection: _allowsEmptySelection]; if (!_allowsMultipleSelection) [matrix setMode: NSRadioModeMatrix]; [matrix setTarget: self]; [matrix setAction: @selector(doClick:)]; [matrix setDoubleAction: @selector(doDoubleClick:)]; [bc setColumnMatrix: matrix]; [sc setDocumentView: matrix]; // Tell the delegate to create the rows [_browserDelegate browser: self createRowsForColumn: column inMatrix: matrix]; } } [bc setIsLoaded: YES]; } - (void)_unloadFromColumn:(int)column { int i, count = [_browserColumns count]; for (i = column; i < count; ++i) { NSBrowserColumn *bc = [_browserColumns objectAtIndex: i]; if ([bc isLoaded]) { id sc = [bc columnScrollView]; // Make the column appear empty // by removing the matrix [sc setDocumentView: [bc emptyView]]; [bc setIsLoaded: NO]; } } } @end