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 - mouseDragged:(NXEvent *)theEvent /*" Should not be called; left in to catch clicks if something breaks whilst I'm hacking. "*/ { printf("TB:dr\n"); return self; } - 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("TB: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_TRACKMODE 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]; /* * when the user clicks in the matrix and then drags the mouse out of * scrollView's contentView, we want the matrix to scroll */ [cellMatrix setAutoscroll: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; } 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 display]; 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]; return [self addNode:aNode toParent:[sibling parent] after:sibling]; } 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.