This is BrowserPane.m in view mode; [Download] [Up]
// BrowserPane.m
//
// Free software created 1 Feb 1992
// by Paul Burchard <burchard@math.utah.edu>.
#import "BrowserPane.h"
#import <objc/Storage.h>
#import <appkit/appkit.h>
#import <string.h>
#import <sys/types.h>
#import <sys/stat.h>
#import <sys/file.h>
@implementation BrowserPane
// Init, freeing, archiving.
- initFrame:(const NXRect *)frameRect
{
return [self initFrame:frameRect cellClass:[TextFieldCell class]];
}
- initFrame:(const NXRect *)frameRect cellClass:factoryId
{
id matrix;
NXRect subframe;
NXSize cellsize;
[super initFrame:frameRect];
[self setBorderType:NX_BEZEL];
[self setBackgroundGray:NX_LTGRAY];
[self setVertScrollerRequired:YES];
[self setAutoresizeSubviews:YES];
action = @selector(takeStringValueFrom:);
doubleAction = @selector(takeStringValueFrom:);
stringValue = [[Storage alloc] initCount:0 elementSize:sizeof(char) description:"c"];
stringList = [[List alloc] initCount:0];
separator = '\t';
// Create Matrix of full-width Cells of class factoryId,
// and place in our scrollView as its docView.
[contentView getFrame:&subframe];
matrix = [[Matrix alloc] initFrame:&subframe mode:NX_LISTMODE cellClass:factoryId numRows:0 numCols:1];
if(!matrix) return nil;
cellsize.width = cellsize.height = 0.0;
[matrix setIntercell:&cellsize];
[matrix getCellSize:&cellsize];
cellsize.width = NX_WIDTH(&subframe);
[matrix setCellSize:&cellsize];
[matrix setAutosizeCells:YES];
[matrix setAutoscroll:YES];
[[matrix setAction:@selector(sendAction)] setTarget:self];
[matrix setDoubleAction:@selector(sendDoubleAction)];
[self setDocView:matrix];
[matrix sizeToCells];
[self setAutodisplay:YES];
[self setNeedsDisplay:YES];
return self;
}
- resizeSubviews:(const NXSize *)oldSize
{
NXRect contentFrame, docFrame;
[super resizeSubviews:oldSize];
// Keep matrix at full width of contentView when size changes.
[contentView getFrame:&contentFrame];
[contentView convertRect:&contentFrame toView:contentView];//paranoia
[[self docView] getFrame:&docFrame];
[[self docView] sizeTo:NX_WIDTH(&contentFrame) :NX_HEIGHT(&docFrame)];
return self;
}
- read:(NXTypedStream *)stream
{
[super read:stream];
stringValue = NXReadObject(stream);
stringList = NXReadObject(stream);
delegate = NXReadObject(stream);
target = NXReadObject(stream);
// Assumes BOOL is size of char.
NXReadTypes(stream, "::ccccc", &action, &doubleAction,
&isAlphabetized, &isAbbreviated, &isEditable, &isDisabledOnEntry,
&separator);
return self;
}
- write:(NXTypedStream *)stream
{
[super write:stream];
NXWriteObject(stream, stringValue);
NXWriteObject(stream, stringList);
NXWriteObjectReference(stream, delegate);
NXWriteObjectReference(stream, target);
// Assumes BOOL is size of char.
NXWriteTypes(stream, "::ccccc", &action, &doubleAction,
&isAlphabetized, &isAbbreviated, &isEditable, &isDisabledOnEntry,
&separator);
return self;
}
- free
{
[stringValue free];
[[stringList freeObjects] free];
return [super free];
}
// Adding and removing entries.
// Internal use only.
- (int)indexAddEntryStorage:stringStorage
{
int row, nrows, ncols, len;
id matrix;
char *newName, *abbrevName = 0;
const char *name;
// Don't enter again if already in here.
if(!stringStorage) return (-1);
if(!(newName = [stringStorage elementAt:0])) return (-1);
if((row=[self indexOfEntry:newName]) >= 0)
{ [stringStorage free]; return row; }
// Get abbreviation if necessary.
if(isAbbreviated)
{
len = strlen(newName);
[stringStorage setNumSlots:2*(len+1)];
abbrevName = (char *)[stringStorage elementAt:(len+1)];
newName = (char *)[stringStorage elementAt:0];
if(!abbrevName || !newName) return (-1);
*abbrevName = 0;
[self abbreviate:newName to:abbrevName];
}
// Find slot for new entry (sort by abbreviated forms).
matrix = [self docView];
[matrix getNumRows:&nrows numCols:&ncols];
if(isAlphabetized) for(row=0; row<nrows; row++)
{
name = [[matrix cellAt:row :0] stringValue];
if(isAbbreviated && [self isFirst:abbrevName second:name]) break;
if(!isAbbreviated && [self isFirst:newName second:name]) break;
}
else row = nrows;
// Insert new entry into matrix and string list, disabling if req'd.
[matrix insertRowAt:row];
[matrix sizeToCells];
[[matrix cellAt:row :0]
setStringValue:(isAbbreviated ? abbrevName : newName)];
[stringList insertObject:stringStorage at:row];
if(isDisabledOnEntry) [self setEntryEnabled:NO at:row];
return row;
}
- (int)indexAddEntry:(const char *)aString
{
int row, len;
id stringStorage;
if(!aString) return (-1);
len = strlen(aString);
stringStorage = [[Storage alloc] initCount:(len+1) elementSize:sizeof(char) description:"c"];
[stringStorage setNumSlots:(len+1)];
strcpy((char *)[stringStorage elementAt:0], aString);
row = [self indexAddEntryStorage:stringStorage];
if(row < 0) { [stringStorage free]; return (-1); }
return row;
}
- addEntry:(const char *)aString
{
if([self indexAddEntry:aString] < 0) return nil;
else return self;
}
- (const char *)entryAt:(int)row
{
return (const char *)[[stringList objectAt:row] elementAt:0];
}
- cellAt:(int)row
{
return [[self docView] cellAt:row :0];
}
- (int)indexOfEntry:(const char *)aString
{
int row, nrows, ncols;
id matrix;
if(!aString) return (-1);
matrix = [self docView];
[matrix getNumRows:&nrows numCols:&ncols];
for(row=0; row<nrows; row++)
if(strcmp([self entryAt:row], aString) == 0) break;
if(row >= nrows) return (-1);
return row;
}
- (unsigned)count
{
int nrows = (-1), ncols = (-1);
[[self docView] getNumRows:&nrows numCols:&ncols];
if(nrows < 0) return 0;
else return nrows;
}
- removeEntryAt:(int)row
{
int nrows, ncols;
id matrix;
// Removing a selected entry clears the selection.
matrix = [self docView];
if([self isEntrySelectedAt:row]) [self clearSelection];
// Remove entry and resize Matrix.
[matrix getNumRows:&nrows numCols:&ncols];
if(row<0 || row>=nrows) return nil;
[matrix removeRowAt:row andFree:YES];
[matrix sizeToCells];
[[stringList removeObjectAt:row] free];
return self;
}
- removeSelection
{
int row, nrows, ncols;
id matrix;
// Remove selected entries (starting from last) and resize Matrix.
matrix = [self docView];
[matrix getNumRows:&nrows numCols:&ncols];
for(row=nrows-1; row>=0; row--) if([self isEntrySelectedAt:row])
{
[matrix removeRowAt:row andFree:YES];
[[stringList removeObjectAt:row] free];
}
[matrix sizeToCells];
[self clearSelection];
return self;
}
- clear
{
int row, nrows, ncols;
id matrix;
[self clearSelection];
matrix = [self docView];
[matrix getNumRows:&nrows numCols:&ncols];
for(row=nrows-1; row>=0; row--)
{
[matrix removeRowAt:row andFree:YES];
[[stringList removeObjectAt:row] free];
}
[matrix sizeToCells];
return self;
}
- addFiles:(const char *)dir suffix:(const char *)sfx;
{
char command[2048], *fgets(), *name;
FILE *pipe;
struct stat statbuf;
sprintf(command, "/bin/ls -1d 2>/dev/null %s/*", dir);
if(sfx != NULL) { strcat(command, "."); strcat(command, sfx); }
pipe = popen(command, "r");
while(fgets(command, 2048, pipe) != NULL)
{
command[strlen (command) - 1] = '\0';
stat(command, &statbuf);
if((name = strrchr(command, '/')) != NULL) name++;
else name = command;
if(!(statbuf.st_mode & S_IFDIR)) [self addEntry:name];
}
pclose(pipe);
return self;
}
// Adding and selecting entries via stringValue.
- (const char *)grabStringFrom:sender
{
if([sender respondsTo:@selector(stringValue)])
return [sender stringValue];
else if([sender respondsTo:@selector(stringValueAt:)])
return [sender stringValueAt:0];
else return (const char *)0;
}
- (const char *)stringValue
{
if([stringValue count] <= 0) return 0;
return (const char *)[stringValue elementAt:0];
}
- setStringValue:(const char *)aString
{
return [self setStringValue:aString append:NO];
}
- setStringValue:(const char *)aString append:(BOOL)yn
{
int row, len;
const char *nextString;
char *endOfEntry;
id nextEntry;
// Clear selection first if not appending to stringValue.
if(!yn || !aString) [self clearSelection];
if(!aString) return self;
// Add TAB-separated items to selection.
// If not already in matrix and list, add them.
for(nextString=aString; nextString;
nextString=strchr(nextString, separator))
{
// Create Storage string to hold unabbrev next entry.
if(*nextString == separator) nextString++;
len = strlen(nextString);
nextEntry = [[Storage alloc] initCount:(len+1) elementSize:sizeof(char) description:"c"];
[nextEntry setNumSlots:(len+1)];
strcpy((char *)[nextEntry elementAt:0], nextString);
endOfEntry = strchr((char *)[nextEntry elementAt:0], separator);
if(endOfEntry) *endOfEntry = 0;
// Add to matrix and list if necessary, disabled if req'd.
row = [self indexAddEntryStorage:nextEntry];
if(row < 0) { [nextEntry free]; return nil; }
if(isDisabledOnEntry) [self setEntryEnabled:NO at:row];
// Add entry to selection.
if(!isDisabledOnEntry) [self selectEntryAt:row append:YES];
}
[self update];
return self;
}
- takeStringValueFrom:sender
{
id oldTarget = nil;
id rtn;
// If sender is target, don't send action (to avoid circularity).
if(sender == target) { oldTarget = target; [self setTarget:nil]; }
rtn = [self setStringValue:[self grabStringFrom:sender] append:NO];
if(oldTarget) [self setTarget:oldTarget];
// Notify delegate of user-induced change in list.
// (Actually, due to non-duplication no change may have taken place.)
if([delegate respondsTo:@selector(textDidChange:)])
[delegate textDidChange:self];
return rtn;
}
- appendStringValueFrom:sender
{
id oldTarget = nil;
id rtn;
// If sender is target, don't send action (to avoid circularity).
if(sender == target) { oldTarget = target; [self setTarget:nil]; }
rtn = [self setStringValue:[self grabStringFrom:sender] append:YES];
if(oldTarget) [self setTarget:oldTarget];
// Notify delegate of user-induced change in list.
// (Actually, due to non-duplication no change may have taken place.)
if([delegate respondsTo:@selector(textDidChange:)])
[delegate textDidChange:self];
return rtn;
}
- (char)separator
{
return separator;
}
- setSeparator:(char)c
{
if(!c) return nil;
separator = c;
return self;
}
// Selecting.
- selectEntryAt:(int)row append:(BOOL)yn
{
int nrows, ncols, oldlen;
const char *entryString;
id matrix;
matrix = [self docView];
[matrix getNumRows:&nrows numCols:&ncols];
if(row<0 || row>=nrows) return nil;
if(![self isEntryEnabledAt:row]) return nil;
entryString = (char *)[[stringList objectAt:row] elementAt:0];
if(yn)
{
// Append to selection.
// Selection indicated by cell state and highlighting.
[matrix lockFocus];
[matrix highlightCellAt:row :0 lit:YES];
[matrix setState:1 at:row :0];
[matrix unlockFocus];
if([stringValue count] <= 0) oldlen = 0;
else oldlen = strlen((char *)[stringValue elementAt:0]);
[stringValue setNumSlots:(oldlen + 1 + strlen(entryString) + 1)];
sprintf((char *)[stringValue elementAt:0] + oldlen,
(oldlen>0 ? "\t%s" : "%s"), entryString);
}
else
{
// Create new selection.
// Selection indicated by cell state and highlighting.
[self clearSelection];
[matrix lockFocus];
[matrix highlightCellAt:row :0 lit:YES];
[matrix setState:1 at:row :0];
[matrix unlockFocus];
[stringValue setNumSlots:(strlen(entryString)+1)];
strcpy((char *)[stringValue elementAt:0], entryString);
}
// Notify target of new selection.
if(action) [target perform:action with:self];
return self;
}
- (BOOL)isEntrySelectedAt:(int)row
{
int nrows, ncols;
id matrix;
// Selection indicated by cell state and highlighting.
// Make sure state is consistent with highlighting and enabling.
matrix = [self docView];
[matrix getNumRows:&nrows numCols:&ncols];
if(row<0 || row>=nrows) return NO;
if(![self isEntryEnabledAt:row] || ![[matrix cellAt:row :0] isHighlighted])
{
[matrix setState:0 at:row :0];
return NO;
}
[matrix setState:1 at:row :0];
return YES;
}
- clearSelection
{
int row, nrows, ncols;
id matrix;
matrix = [self docView];
[matrix selectCellAt:(-1) :(-1)];
[matrix getNumRows:&nrows numCols:&ncols];
[matrix lockFocus];
for(row=0; row<nrows; row++)
{
// Selection indicated by cell state and highlighting.
[matrix highlightCellAt:row :0 lit:NO];
[matrix setState:0 at:row :0];
}
[matrix unlockFocus];
[stringValue setNumSlots:0];
if(action) [target perform:action with:self];
return self;
}
// Target and action.
// For internal use only.
- (BOOL)appendSelStringFrom:sender
{
int row, oldlen;
const char *nextString;
// Find (enabled) cell and its unabbreviated stringValue.
if((row=[[[self docView] cellList] indexOf:sender]) == NX_NOT_IN_LIST)
return NO;
if(![self isEntryEnabledAt:row]) return YES; // continue looping thru cells
nextString = (char *)[[stringList objectAt:row] elementAt:0];
// Selection indicated by cell state and highlighting.
// Make sure state is consistent with highlighting.
if(![sender isHighlighted])
{
[sender setState:0];
return YES; // continue looping thru cells
}
[sender setState:1];
// Append sender's unabbrev stringValue onto ours (with TAB separation).
if([stringValue count] <= 0) oldlen = 0;
else oldlen = strlen((char *)[stringValue elementAt:0]);
[stringValue setNumSlots:(oldlen + 1 + strlen(nextString) + 1)];
sprintf((char *)[stringValue elementAt:0] + oldlen,
(oldlen>0 ? "\t%s" : "%s"), nextString);
return YES; // continue looping thru cells
}
// For internal use only.
- recomputeStringValueFromSelection
{
[stringValue setNumSlots:0];
[[self docView] sendAction:@selector(appendSelStringFrom:) to:self forAllCells:NO];
return self;
}
- sendAction
{
// Sent by newly selected cells in matrix.
// So recompute stringValue and make self firstResponder.
[self recomputeStringValueFromSelection];
[window makeFirstResponder:self];
if(!action) return self;
return [target perform:action with:self];
}
- sendDoubleAction
{
char *fileName, *nxt;
int ok;
// Sent by newly selected cell in matrix.
// So recompute stringValue and make self firstResponder.
[self recomputeStringValueFromSelection];
[window makeFirstResponder:self];
// Double-click means open files (if delegate exists and opens files).
// Open files one by one (there may be many if msg sent by outside obj).
if([stringValue count]>0 && [delegate respondsTo:@selector(openFile:ok:)])
{
fileName = (char *)[stringValue elementAt:0];
nxt = strchr(fileName, separator);
while(fileName)
{
if(nxt) *nxt = 0;
[delegate openFile:fileName ok:&ok];
if(nxt)
{
*nxt = separator;
fileName = nxt+1;
nxt = strchr(fileName, separator);
}
else fileName = nxt = 0;
}
}
// Send doubleAction to target.
if(!doubleAction) return self;
return [target perform:doubleAction with:self];
}
- setTarget:anObject
{
target = anObject;
[[self docView] setTarget:self];
return self;
}
- setAction:(SEL)aSelector
{
action = aSelector;
[[self docView] setAction:@selector(sendAction)];
return self;
}
- setDoubleAction:(SEL)aSelector
{
doubleAction = aSelector;
[[self docView] setDoubleAction:@selector(sendDoubleAction)];
return self;
}
- target
{
return target;
}
- (SEL)action
{
return action;
}
- (SEL)doubleAction
{
return doubleAction;
}
// Editing.
- setEditable:(BOOL)yn
{
isEditable = yn;
return self;
}
- (BOOL)isEditable
{
return isEditable;
}
- (BOOL)acceptsFirstResponder
{
return YES;
}
- becomeFirstResponder
{
[self setBackgroundGray:NX_WHITE];
[self update];
return self;
}
- resignFirstResponder
{
[self setBackgroundGray:NX_LTGRAY];
[self update];
return self;
}
- keyDown:(NXEvent *)theEvent
{
if(theEvent->type!=NX_KEYDOWN) return self;
if(!isEditable) return nil;
switch(theEvent->data.key.charCode)
{
case NX_DELETE:
[self delete:self];
break;
default:
return nil;
}
return self;
}
- mouseDown:(NXEvent *)theEvent
{
// In case matrix is empty or does not fill view, capture mouse clicks.
[super mouseDown:theEvent];
if([window firstResponder] != self) [window makeFirstResponder:self];
return self;
}
- delete:sender
{
char *fileName, *nxt;
int ok;
if(!isEditable) return self;
// Let delegate delete selected "files" if it can.
if([stringValue count]>0
&& [delegate respondsTo:@selector(removeFile:ok:)])
{
fileName = (char *)[stringValue elementAt:0];
nxt = strchr(fileName, separator);
while(fileName)
{
if(nxt) *nxt = 0;
[delegate removeFile:fileName ok:&ok];
if(nxt)
{
*nxt = separator;
fileName = nxt+1;
nxt = strchr(fileName, separator);
}
else fileName = nxt = 0;
}
}
// Now remove selected names from list.
[self removeSelection];
[self update];
// Notify delegate of user-induced change in list.
if([delegate respondsTo:@selector(textDidChange:)])
[delegate textDidChange:self];
return self;
}
- cut:sender
{
const char *sval;
if(!isEditable) return self;
// Copy out string value...
if(!(sval = [self stringValue])) return self;
[[Pasteboard new] declareTypes:&NXAsciiPboard num:1 owner:self];
[[Pasteboard new] writeType:NXAsciiPboard data:sval length:strlen(sval)];
// Delete selection (and associated "files" if any).
return [self delete:sender];
}
- copy:sender
{
char *fileName, *nxt;
int ok;
const char *sval;
if(!(sval = [self stringValue])) return self;
// Let delegate "prepare" selected "files" if it can.
if([delegate respondsTo:@selector(prepFile:ok:)])
{
fileName = sval;
nxt = strchr(fileName, separator);
while(fileName)
{
if(nxt) *nxt = 0;
[delegate prepFile:fileName ok:&ok];
if(nxt)
{
*nxt = separator;
fileName = nxt+1;
nxt = strchr(fileName, separator);
}
else fileName = nxt = 0;
}
}
// Copy out string value.
[[Pasteboard new] declareTypes:&NXAsciiPboard num:1 owner:self];
[[Pasteboard new] writeType:NXAsciiPboard data:sval length:strlen(sval)];
[self update];
return self;
}
- paste:sender
{
char *sval;
int length;
if(!isEditable) return self;
[[Pasteboard new] readType:NXAsciiPboard data:&sval length:&length];
[self setStringValue:sval]; //!!! is the data null-terminated?
vm_deallocate(task_self(), (vm_address_t)sval, sizeof(char)*length);
[self update];
// Notify delegate of user-induced change in list.
if([delegate respondsTo:@selector(textDidChange:)])
[delegate textDidChange:self];
return self;
}
- selectAll:sender
{
int row, nrows, ncols;
id matrix;
BOOL autodisplay;
//!!! Hideous in action (autoDisplay isn't getting shut off?).
matrix = [self docView];
autodisplay = [matrix isAutodisplay];
[matrix setAutodisplay:NO];
[matrix getNumRows:&nrows numCols:&ncols];
for(row=0; row<nrows; row++) [self selectEntryAt:row append:YES];
[matrix setAutodisplay:autodisplay];
[self update];
return self;
}
- (int)openFile:(const char *)fileName ok:(int *)flag
{
// Default does nothing.
return 0;
}
- (int)prepFile:(const char *)fileName ok:(int *)flag
{
// Default does nothing.
return 0;
}
- (int)removeFile:(const char *)fileName ok:(int *)flag
{
// Default does nothing.
return 0;
}
- textDidChange:sender
{
return self;
}
// String display control.
- setEntryEnabled:(BOOL)yn at:(int)row
{
id matrix, cell;
// Disabling a selected entry clears the selection.
if(!yn && [self isEntrySelectedAt:row]) [self clearSelection];
matrix = [self docView]; cell = [matrix cellAt:row :0];
[cell setEnabled:yn];
//!!! Note: textGray seems to be relative to the background??
if(yn) [matrix drawCell:[cell setTextGray:NX_BLACK]];
else [matrix drawCell:[cell setTextGray:NX_LTGRAY]];
return self;
}
- (BOOL)isEntryEnabledAt:(int)row
{
return [[[self docView] cellAt:row :0] isEnabled];
}
- setDisabledOnEntry:(BOOL)yn
{
isDisabledOnEntry = yn;
return self;
}
- (BOOL)isDisabledOnEntry
{
return isDisabledOnEntry;
}
- setAlphabetized:(BOOL)yn
{
//!!! Does not retroactively sort yet! Sorry!
isAlphabetized = yn;
return self;
}
- (BOOL)isAlphabetized
{
return isAlphabetized;
}
- (BOOL)isFirst:(const char *)firstString second:(const char *)secondString
{
// Standard NeXTstep string comparison.
if(!secondString || !firstString) return NO;
if(NXOrderStrings(secondString, firstString, NO, -1, NULL) > 0)
return YES;
else return NO;
}
- setAbbreviated:(BOOL)yn
{
int len, row, nrows, ncols;
char *fullName, *abbrevName;
id matrix, stringStorage;
isAbbreviated = yn;
matrix = [self docView];
[matrix getNumRows:&nrows numCols:&ncols];
for(row=0; row<nrows; row++)
{
stringStorage = [stringList objectAt:row];
if(!(fullName = (char *)[stringStorage elementAt:0])) continue;
len = strlen(fullName);
[stringStorage setNumSlots:2*(len+1)];
abbrevName = (char *)[stringStorage elementAt:(len+1)];
fullName = (char *)[stringStorage elementAt:0];
if(!abbrevName || !fullName) continue;
*abbrevName = 0;
[self abbreviate:fullName to:abbrevName];
[[matrix cellAt:row :0] setStringValue:abbrevName];
}
[self update];
return self;
}
- (BOOL)isAbbreviated
{
return isAbbreviated;
}
- abbreviate:(const char *)srcString to:(char *)destString
{
const char *abbr;
// Show part after last '/'.
if(!destString) return nil;
if(!srcString) { *destString = 0; return self; }
abbr = strrchr(srcString, '/');
if(!abbr) abbr = srcString;
else abbr++;
strcpy(destString, abbr);
return self;
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.