This is MiscTreeBrowser.m in view mode; [Download] [Up]
/* MiscTreeBrowser
*
*/
// IMPORTS
#import <ansi/assert.h>
#import <appkit/Text.h>
#import <appkit/publicWraps.h> // NXBeep()
#import <misckit/MiscTreeBrowser.h>
#import <misckit/MiscTreeBrowserCell.h>
#import <misckit/MiscTreeBrowserMatrix.h>
#import <misckit/MiscTreeBrowserProtocols.h>
// DEFINES
#define DEFAULT_BG_RED 0.80079
#define DEFAULT_BG_GRN 0.73438
#define DEFAULT_BG_BLU 0.66797
#define DEFAULT_BG_COLOR \
NXConvertRGBToColor(DEFAULT_BG_RED, DEFAULT_BG_GRN, DEFAULT_BG_BLU)
// CLASS VARIABLES
static NXColor defaultBackgroundColor;
// IMPLEMENTATION
@implementation MiscTreeBrowser
/*"
To Be Described
"*/
// PRIVATE CLASS METHODS
+ initialize
/*"
Initialises the class; sets the default background color for instances.
FUTURE: perhaps should set default cell icons as well.
"*/
{
if ([MiscTreeBrowser class] == self)
{
defaultBackgroundColor = DEFAULT_BG_COLOR;
}
return self;
}
// PRIVATE METHODS
- (BOOL)_node:(id<MiscTreeBrowserNodes>)aNode
isDescendantOf:(id<MiscTreeBrowserNodes>)parentNode
/*"
Returns YES if parentNode == [...[aNode parent]... parent] or parentNode is aNode.
"*/
{
id node = aNode;
while (node)
{
if (node == parentNode)
{
return YES;
}
node = [node parent];
}
return NO;
}
- (int)_indexOfNode:aNode
/*
Returns index of cell containing aNode, or -1 if none does.
*/
{
unsigned int row, cellCount = [cellMatrix cellCount];
for (row = 0; row < cellCount; row++)
{
id cell = [cellMatrix cellAt:row :0];
if ([cell node] == aNode)
{
return row;
}
}
return -1; // no cells contain aNode
}
- _cellForNode:aNode
/*"
Returns the cell containing aNode (if any), otherwise returns nil.
"*/
{
int idx = [self _indexOfNode:aNode];
return (idx >= 0) ? [cellMatrix cellAt:idx :0] : nil;
}
- _insertCellAt:(unsigned int)index withNode:aNode
/*"
Inserts a cell into the matrix at index, containing node aNode. Does not call -sizeToCells or -display to redisplay the matrix, allowing rapid insertion of more than one cell e.g. opening a node with children. The cell is closed and unselected. Returns the new cell.
"*/
{
unsigned int cellCount = [cellMatrix cellCount];
id cell;
if (index > cellCount)
{ // if cell beyond end of matrix, change
index = cellCount; // to just at end of the matrix
}
[cellMatrix endEditing];
// add cell to matrix
[cellMatrix insertRowAt:index];
cell = [cellMatrix cellAt:index :0];
// initialise cell
[cell setState:0];
[cell setOpen:NO];
[cell setNode:aNode];
return self;
}
- _removeCellAt:(unsigned int)index
/*"
Removes the cell at index. Does nothing to the node it contains. Does not call -sizeToCells or -display to redisplay the matrix, allowing rapid deletion of more than one cell e.g. closing a node with children
"*/
{
[[cellMatrix cellAt:index :0] setNode:nil];
[cellMatrix removeRowAt:index andFree:NO];
return self;
}
- (int)_nextNonDescendantOf:cell after:(int)index
/*"
Returns the row index of the next Cell after index which is %{not} a descendant of cell. If all Cells following cell are descendants, it returns a row index one beyond the end of the matrix.
"*/
{
int cellCount = [cellMatrix cellCount];
id parentNode = [cell node];
while (++index < cellCount)
{
id node = [[cellMatrix cellAt:index :0] node];
if (![self _node:node isDescendantOf:parentNode])
{
return index;
}
}
return index;
}
// Opening and closing cells
- _openCell:aCell
/*"
Opens the node in cell aCell. If aCell is nil or not one of our cells, do nothing.
"*/
{
int row, col;
if (!aCell || [aCell isOpen])
{ // do nothing if already open
return self;
}
[cellMatrix getRow:&row andCol:&col ofCell:aCell];
if (row >= 0)
{
id node = [aCell node];
int numChildren = [node childCount];
[aCell setOpen:YES];
if (numChildren > 0)
{
int i;
for (i = 0; i < numChildren; i++)
{
row++;
[self _insertCellAt:row withNode:[node childAt:i]];
}
[cellMatrix sizeToCells];
}
[cellMatrix display];
}
return self;
}
- _closeCell:aCell
/*"
Closes the node in cell aCell. If aCell is nil or not one of our cells, do nothing.
"*/{
int row, col;
if (!aCell || ![aCell isOpen])
{ // do nothing if already closed
return self;
}
[cellMatrix getRow:&row andCol:&col ofCell:aCell];
if (row >= 0)
{
int cellCount = [cellMatrix cellCount];
id thisNode = [aCell node];
row++;
while ((row < cellCount) &&
([self _node:[[cellMatrix cellAt:row :0] node]
isDescendantOf:thisNode]))
{
[self _removeCellAt:row];
cellCount--;
}
[aCell setOpen:NO];
[cellMatrix sizeToCells];
[cellMatrix display];
}
return self;
}
// PUBLIC METHODS
#if 0
- mouseDragged:(NXEvent *)theEvent
/*"
Should not be called; left in to catch clicks if something breaks whilst I'm
hacking.
"*/
{
printf("MTB:dr\n");
return [super mouseDragged:theEvent];
}
#endif
- mouseUp:(NXEvent *)theEvent
/*"
Should not be called; left in to catch clicks if something breaks whilst I'm
hacking.
"*/
{
printf("MTB:up\n");
return [super mouseUp:theEvent];
}
- doubleClick:sender
/*"
Should not be called; left in to catch double-clicks if something breaks whilst I'm hacking. IMPLEMENTATION NOTE. %will be called if the user clicks on a part of the cell not covered by icons or text...
"*/
{
printf("MTB:edit!\n");
return self;
}
- init
/*"
Should not be called; Initialises self with a NULL frame.
"*/
{
return [self initFrame:NULL];
}
- initFrame:(const NXRect *)frameRect
/*"
Designated initialiser; creates the MiscTreeBrowser and initialises other instance variables. Returns self.
"*/
{
NXSize interCellSpacing = {0.0, 0.0}, docSize;
[super initFrame:frameRect];
cellMatrix = [[MiscTreeBrowserMatrix alloc] initFrame:frameRect
mode:NX_RADIOMODE
cellClass:[MiscTreeBrowserCell class]
numRows:0
numCols:1];
[self setBackgroundColor:defaultBackgroundColor];
/* we don't want any space between the matrix's cells */
[cellMatrix setIntercell:&interCellSpacing];
/* resize the matrix to contain the cells */
[cellMatrix sizeToCells];
[cellMatrix setAutosizeCells:YES];
/* let the matrix stretch horizontally */
[cellMatrix setAutosizing:NX_WIDTHSIZABLE];
/* Install the matrix as the docview of the scrollview */
[[self setDocView:cellMatrix] free];
/* set up the visible attributes of the scrollview */
[self setVertScrollerRequired:YES];
[self setBorderType:NX_BEZEL];
/* tell the subviews to resize along with the scrollview */
[self setAutoresizeSubviews:YES];
/* This is the only way to get the clipview to resize too */
[[cellMatrix superview] setAutoresizeSubviews:YES];
/* Allow the scrollview to stretch both horizontally and vertically */
[self setAutosizing:NX_WIDTHSIZABLE|NX_HEIGHTSIZABLE];
/* Resize the matrix to fill the inside of the scrollview */
[self getContentSize:&docSize];
[cellMatrix sizeTo:docSize.width :docSize.height - 10.0];
rootNodes = [[List alloc] init];
return self;
}
- free
/*"
Frees the receiver - any nodes in the cell matrix are %not freed. Returns nil.
"*/
{
[cellMatrix free];
[rootNodes free];
return [super free];
}
- cellMatrix
/*"
Returns the receiver MiscTreeBrowserMatrix instance.
"*/
{
return cellMatrix;
}
- setFont:aFont
/*"
Sets the receiver's font to aFont.
"*/
{
[cellMatrix setFont:aFont];
return self;
}
- font
/*"
Returns the receiver's font.
"*/
{
return [cellMatrix font];
}
// accessing nodes
- nodeAt:(int)index
/*"
if a cell exists at row index, return its node otherwise return nil;
"*/
{
id cell = [cellMatrix cellAt:index :0];
return cell ? [cell node] : nil;
}
- selectedNode
/*"
If a cell is selected, return its node otherwise return nil;
"*/
{
id cell = [cellMatrix selectedCell];
return cell ? [cell node] : nil;
}
// accessing node information
- (BOOL)nodeIsOpen:aNode
/*"
Returns YES if aNode is "open" (displaying its children, if any).
"*/
{
id cell = [self _cellForNode:aNode];
return cell ? [cell isOpen] : NO;
}
// creating and deleting nodes
- removeNodeAt:(int)index
/*"
Removes the node at cell index from the node tree and the MiscTreeBrowserMatrix, but does not free it.
"*/
{
id cell = [cellMatrix cellAt:index :0];
if (cell)
{
id<MiscTreeBrowserNodes> node = [cell node];
id<MiscTreeBrowserNodes> parent = [node parent];
if (!parent || [parent canRemoveChild:node])
{
if ([cell isOpen] && ![cell isLeaf])
{
[self _closeCell:cell];
}
if (parent)
{
[parent removeChild:node];
}
else
{
[rootNodes removeObject:node];
}
[cell setNode:nil];
[cellMatrix removeRowAt:index andFree:NO];
[cellMatrix sizeToCells];
[cellMatrix display];
}
else
{
NXBeep();
}
}
return self;
}
- removeNode:aNode
/*"
Removes (if possible) aNode from the node tree. Returns aNode if successfully removed, otherwise nil.
"*/
{
if (aNode)
{
id next, root = aNode;
while ((next = [root parent]))
{
root = next;
}
if (!root || ([rootNodes indexOf:root] != NX_NOT_IN_LIST))
{
int row = [self _indexOfNode:aNode];
id parent;
if (row >= 0)
{
return [self removeNodeAt:row];
}
// else
parent = [aNode parent];
if (parent && [parent canRemoveChild:aNode])
{
return [parent removeChild:aNode];
}
else if (!parent)
{
return [rootNodes removeObject:aNode];
}
}
}
return nil;
}
- addNode:aNode toParent:newParent after:sibling
/*"
Adds aNode to (or moves it within) the node tree, to parent newParent
(nil if aNode is to be a new root node) and after sibling (nil if
aNode is to be the first child). Returns self for success, nil if
failed to add/move aNode.
"*/
{
id siblingCell;
int nodeRow;
BOOL nodeWasOpen = NO;
assert(aNode);
assert((sibling == nil) ||
(([sibling parent] == newParent) || ([sibling parent] == nil)));
/* cannot make a node into a child of one of its descendants! */
if (newParent && [self _node:newParent isDescendantOf:aNode])
{
NXBeep();
return nil;
}
if (newParent && ![newParent canAddChild:aNode after:sibling])
{
NXBeep();
return nil;
}
// printf("Node: %s parent:%s after:%s\n", aNode ? [aNode stringValue] : "<>", newParent ? [newParent stringValue] : "<>", sibling ? [sibling stringValue] : "<>");
[window disableFlushWindow];
nodeRow = [self _indexOfNode:aNode];
if (nodeRow >= 0)
{ // node already in browser; note state and remove it
id cell = [self _cellForNode:aNode];
nodeWasOpen = [cell isOpen];
[self removeNode:aNode];
}
siblingCell = [self _cellForNode:sibling];
if (newParent)
{
id parentCell = [self _cellForNode:newParent];
int row;
[newParent addChild:aNode after:sibling];
// choose where to display new node; after sib, or parent if sib == nil
//
if (sibling && siblingCell)
{
row = [self _nextNonDescendantOf:siblingCell
after:[self _indexOfNode:sibling]];
[self _insertCellAt:row withNode:aNode];
}
else if ([parentCell isOpen])
{
row = [self _indexOfNode:newParent] + 1;
[self _insertCellAt:row withNode:aNode];
}
}
else // another "root" object
{
if (sibling)
{ // display after sibling
int siblingRow = [self _indexOfNode:sibling];
if (siblingRow < 0)
{ // invalid sibling, put on end
[self _insertCellAt:[cellMatrix cellCount] + 1 withNode:aNode];
[rootNodes addObject:aNode];
}
else
{
int nextRow = [self _nextNonDescendantOf:siblingCell
after:siblingRow];
[self _insertCellAt:nextRow withNode:aNode];
[rootNodes insertObject:aNode
at:([rootNodes indexOf:sibling] + 1)];
}
}
else // or at top if sibling == nil
{
[self _insertCellAt:0 withNode:aNode];
[rootNodes insertObject:aNode at:0];
}
}
if (nodeWasOpen)
{
[self openNode:aNode];
}
[cellMatrix sizeToCells];
[cellMatrix scrollNodeToVisible:aNode];
[cellMatrix selectNode:aNode];
[cellMatrix display];
[[window reenableFlushWindow] flushWindow];
return self;
}
- addChild:aNode
/*"
Add aNode as a child of the node in the selected cell. Returns self for
success, nil for failure.
"*/
{
id scell = [cellMatrix selectedCell];
if (scell)
{
id par = [scell node];
return [self addNode:aNode toParent:par after:MTB_LASTCHILD(par)];
}
return nil;
}
- addSibling:aNode
/*"
Add aNode as a sibling of the node in the selected cell. Returns self for
success, nil for failure.
"*/
{
id scell = [cellMatrix selectedCell];
if (scell)
{
id sibling = [scell node];
[self addNode:aNode toParent:[sibling parent] after:sibling];
return self;
}
return nil;
}
// opening and closing nodes
- openNode:aNode
/*"
If aNode is in a cell and is closed, "open" the node (display all of aNode's
children in cells below it).
"*/
{
return [self _openCell:[self _cellForNode:aNode]];
}
- closeNode:aNode
/*"
If aNode is in a cell and is open, "close" the node (remove all the cells below aNode's containing its children).
"*/
{
return [self _closeCell:[self _cellForNode:aNode]];
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.