libs-gui/Source/NSBrowser.m

1731 lines
36 KiB
Mathematica
Raw Normal View History

/*
NSBrowser.m
Control to display and select from hierarchal lists
Copyright (C) 1996, 1997 Free Software Foundation, Inc.
Author: Scott Christley <scottc@net-community.com>
Date: 1996
Author: Felipe A. Rodriguez <far@ix.netcom.com>
Date: August 1998
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 <gnustep/gui/config.h>
#include <AppKit/NSBrowser.h>
#include <AppKit/NSBrowserCell.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSException.h>
#include <AppKit/AppKitExceptions.h>
#include <AppKit/NSScroller.h>
#include <AppKit/NSCell.h>
#include <AppKit/NSColor.h>
#include <AppKit/NSScrollView.h>
#include <AppKit/NSGraphics.h>
#include <AppKit/NSMatrix.h>
#include <AppKit/NSTextFieldCell.h>
#include <AppKit/PSOperators.h>
#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
- (id) init
{
[super init];
_isLoaded = NO;
_emptyView = [[NSView alloc] init];
return self;
}
- (void) dealloc
{
[_columnScrollView release];
[_columnMatrix release];
[_emptyView release];
[_columnTitle release];
[super dealloc];
}
- (void) setIsLoaded: (BOOL)flag
{
_isLoaded = flag;
}
- (BOOL) isLoaded
{
return _isLoaded;
}
- (void) setColumnScrollView: (id)aView
{
ASSIGN(_columnScrollView, aView);
}
- columnScrollView
{
return _columnScrollView;
}
- (void)setColumnMatrix: (id)aMatrix
{
ASSIGN(_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
@interface GSBrowserTitleCell: NSTextFieldCell
@end
@implementation GSBrowserTitleCell
- (id) initTextCell: (NSString *)aString
{
[super initTextCell: aString];
[self setTextColor: [NSColor windowFrameTextColor]];
[self setBackgroundColor: [NSColor controlShadowColor]];
[self setEditable: NO];
[self setBezeled: YES];
[self setAlignment: NSCenterTextAlignment];
draw_background = YES;
return self;
}
- (void) drawWithFrame: (NSRect)cellFrame inView: (NSView*)controlView
{
if (NSIsEmptyRect (cellFrame))
return;
[controlView lockFocus];
NSDrawGrayBezel (cellFrame, NSZeroRect);
[controlView unlockFocus];
[self drawInteriorWithFrame: cellFrame inView: controlView];
}
@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
*/
- (id) initWithFrame: (NSRect)rect
{
NSSize bs;
[super initWithFrame: rect];
_browserCellClass = [NSBrowser cellClass];
_browserCellPrototype = [[_browserCellClass alloc] init];
_browserMatrixClass = [NSMatrix class];
_pathSeparator = @"/";
_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;
_scrollerRect.origin.x = bs.width;
_scrollerRect.origin.y = bs.height;
_scrollerRect.size.width = frame.size.width - (2 * bs.width);
_scrollerRect.size.height = [NSScroller scrollerWidth];
_horizontalScroller = [[NSScroller alloc] initWithFrame: _scrollerRect];
[_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 = [GSBrowserTitleCell new];
// 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];
}
//
// Target-actions stuff
//
- (void) setTarget: (id)t
{
_target = t;
}
- (id) target
{
return _target;
}
- (void) setAction: (SEL)s
{
_action = s;
}
- (SEL) action
{
return _action;
}
//
// 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]]
autorelease];
[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]))
{
if ([o isLoaded])
{
c = [[o columnMatrix] selectedCell];
if (c)
found = YES;
else
--i;
}
else
--i;
}
if (found)
return i;
else
return NSNotFound;
}
- (int) selectedRowInColumn: (int)column
{
return [[self matrixInColumn: column] selectedRow];
}
- (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];
}
[self setNeedsDisplay: YES];
}
//
// Event Handling
//
- (void)mouseDown: (NSEvent *)aEvent
{
}
- (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];
[self setNeedsDisplay: YES];
}
- (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;
NSSize bs = [NSCell sizeForBorderType: NSBezelBorder];
// 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] + (2 * bs.height) + 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
{
NSMutableString *s = [_pathSeparator mutableCopy];
unsigned 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];
[s appendString: [c stringValue]];
}
/*
* 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 s;
}
- (BOOL) setPath: (NSString *)path
{
NSArray *subStrings;
NSString *aStr;
unsigned numberOfSubStrings;
unsigned i;
subStrings = [path componentsSeparatedByString: _pathSeparator];
// ensure that column zero
if (![self isLoaded]) { // is loaded
[self loadColumnZero];
}
else { // and is the last column
[self setLastColumn: 0];
}
numberOfSubStrings = [subStrings count];
if (numberOfSubStrings == 2)
{
/* select root path */
if ([[subStrings objectAtIndex: 1] length] == 0)
{
[self scrollColumnsLeftBy: [_browserColumns count]];
return YES;
}
}
// cycle thru str's array created from path
for (i = 1; i < numberOfSubStrings; i++)
{
NSBrowserColumn *bc = [_browserColumns objectAtIndex: i-1];
NSMatrix *matrix = [bc columnMatrix];
NSArray *cells = [matrix cells];
unsigned j, numOfRows = [cells count];
NSBrowserCell *selectedCell = nil;
aStr = [subStrings objectAtIndex: i];
// find the cell in the browser matrix with the equal to aStr
for (j = 0; j < numOfRows; j++)
{
NSArray *cellRow = [cells objectAtIndex: j];
unsigned numOfCols, k;
numOfCols = [cellRow count];
for (k = 0; k < numOfCols; k++)
{
NSString *cellString;
selectedCell = [cellRow objectAtIndex: k];
cellString = [selectedCell stringValue];
if ([cellString isEqualToString: aStr])
{
int r, c;
k = numOfCols;
j = numOfRows;
if ([matrix getRow: &r column: &c ofCell: selectedCell])
[matrix selectCellAtRow: r column: c];
else
selectedCell = nil;
}
}
}
// if unable to find a cell whose title matches aStr return NO
if (!selectedCell)
{
NSLog(@"NSBrowser: unable to find cell '%@' in column %d\n", aStr, i - 1);
return NO;
}
// if the cell is not a leaf add a column to the browser for it
if (![selectedCell isLeaf])
{
[self addColumn];
[self _performLoadOfColumn: i];
[self setLastColumn: i];
[self _adjustMatrixOfColumn: i];
[self scrollColumnsRightBy: 1];
}
else
break;
}
return YES;
}
- (void)setPathSeparator: (NSString *)aString
{
[aString retain];
[_pathSeparator release];
_pathSeparator = aString;
}
//
// Arranging an NSBrowser's Components
//
- (void)tile
{
int num;
NSSize bs = [NSCell sizeForBorderType: NSBezelBorder];
// 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)
{
_scrollerRect.origin.x = bs.width;
_scrollerRect.origin.y = bs.height;
_scrollerRect.size.width = frame.size.width - (2* bs.width);
_scrollerRect.size.height = [NSScroller scrollerWidth];
_columnSize.height -= _scrollerRect.size.height + 4 + (2 * bs.height);
}
else
_scrollerRect = NSZeroRect;
// Title
if (_isTitled)
_columnSize.height -= [self titleHeight];
if (_columnSize.height < 0)
_columnSize.height = 0;
}
- (void)drawRect: (NSRect)rect
{
int i;
NSRectClip(rect);
// 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];
}
if (_hasHorizontalScroller)
{
NSRect scrollerBorderRect = _scrollerRect;
NSSize bs = [NSCell sizeForBorderType: NSBezelBorder];
scrollerBorderRect.origin.x = 0;
scrollerBorderRect.origin.y = 0;
scrollerBorderRect.size.width += 2 * bs.width;
scrollerBorderRect.size.height += 2 * bs.height;
// Draw the horizontal scroller border
NSDrawGrayBezel (scrollerBorderRect, rect);
}
}
//
// 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: (NSCoder*)aCoder
{
[super encodeWithCoder: aCoder];
}
- (id) initWithCoder: (NSCoder*)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 matrix to fit in scrollview if column has been loaded
if (sc && matrix && [bc isLoaded])
{
cs = [sc contentSize];
ms = [matrix cellSize];
ms.width = cs.width;
[matrix setCellSize: ms];
mr = [matrix frame];
// matrix smaller than scrollview's content
if (mr.size.height < cs.height)
{
// view requires origin adjustment for it to appear at top
mr.origin.y = cs.height;
[matrix setFrame: mr];
}
[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
{
NSBrowserColumn *bc = [_browserColumns objectAtIndex: i];
id sc = [bc columnScrollView];
// Remove it from its superview if it has one
if (sc)
{
if ([sc superview])
{
[sc removeFromSuperview];
}
}
}
}
}
- (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];
NSRect matrixRect = {{0, 0}, {100, 100}};
int i;
// create a new col matrix
matrix = [[[_browserMatrixClass alloc]
initWithFrame: matrixRect
mode: NSListModeMatrix
prototype: _browserCellPrototype
numberOfRows: n
numberOfColumns: 1]
autorelease];
[matrix setAllowsEmptySelection: _allowsEmptySelection];
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];
[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];
NSRect matrixRect = {{0, 0}, {100, 100}};
// create a new col matrix
matrix = [[[_browserMatrixClass alloc]
initWithFrame: matrixRect
mode: NSListModeMatrix
prototype: _browserCellPrototype
numberOfRows: 0
numberOfColumns: 0]
autorelease];
[matrix setAllowsEmptySelection: _allowsEmptySelection];
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];
[sc setDocumentView: matrix];
// Tell the delegate to create the rows
[_browserDelegate browser: self
createRowsForColumn: column
inMatrix: matrix];
}
}
[bc setIsLoaded: YES];
[self setNeedsDisplayInRect: [self frameOfColumn: column]];
}
- (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];
}
}
}
- (void) resizeSubviewsWithOldSize: (NSSize)oldSize
{
[super resizeSubviewsWithOldSize: oldSize];
[self tile];
[self _adjustScrollerFrames: YES];
[_horizontalScroller setFrame: _scrollerRect];
}
@end