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.