ftp.nice.ch/users/felix/FileSpy.1.1.src/Controller.m

This is Controller.m in view mode; [Download] [Up]

/*
 *   This sourcecode is part of FileSpy, a logfile observing utility.
 *   Copyright (C) 1996  Felix Rauch
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *   
 *   Notice that this program may not be possessed, used, copied,
 *   distributed or modified by people having to do with nuclear
 *   weapons. See the file CONDITIONS for details.
 *
 *   This program 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 General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *   To contact the original author, try:
 *   e-mail: Felix.Rauch@nice.ch
 *   Traditional mail:	Felix Rauch
 *			Sempacherstrasse 33
 *			8032 Zurich
 *			Switzerland
 */

#import "Controller.h"

#define SHORTESTTIME 1		/* minimal time between two tests on a file */
#define LONGESTTIME 60		/* maximal time    -       "      -        */

#define WIN_DELTA_X 25.0	/* coord-difference of new windows */
#define WIN_DELTA_Y 25.0	/* remember: Y comes down! */
#define WIN_START_X 100.0
#define WIN_START_Y 500.0
#define WIN_MAX_X 500.0
#define WIN_MAX_Y 100.0

#define MAXFILELEN MAXNAMLEN

#ifdef DEBUG
    #define DEFOWNER "FileSpyDebug"
#else
    #define DEFOWNER "FileSpy"
#endif

int wildmat(char *text, char *p);	// still uses wildmat, should use GNU-regexp in the near future

@implementation Controller

- appWillInit:sender
{
    whileInit = YES;
    return self;
}

- appDidInit:sender
{
    NXRect	matrixRect, scrollRect;
    NXSize	interCellSpacing = {0.0, 0.0}, cellSize;
    const char *fileType[] = {NXFilenamePboardType};

    winx = WIN_START_X;
    winy = WIN_START_Y;
    fileCount = 0;		// no files yet
    secs = 2.0;		// this one should probably be in the defaults database
			    // now set some defaults
    saveFileList = YES;
    prefPopup = YES;
    prefBeepOnChange = YES;
    prefSuperlog = NO;
    prefUseFilter = NO;
    prefFileMode = RADIO_ENTIRE;
    prefColor = NX_COLORBLACK;	// defined in <appkit/color.h>
    prefTimeStamp = NO;		// no timestamps is default
    prefSuperCopyFilename = ONCE;
    prefSpytype = SPY_CONTENTS;	// only spy for contents
    superPopup = YES;
    superBeepOnChange = YES;
    superUseFilter = NO;
    useFilter = NO;
    filterMode = FILTER_DONTBEEP;
    filterIsUpToDate = YES;
    displayCurWindow = YES;		// only used when loading preferences
    becomeKey = NO;
    saveState = NO;
    maxLineState = NO;
    maxLines = 100;
    inspectorFrame = (char *)0;
    myWindowIsVisible = NO;
    nrOpenWindows = 0;
//    lastSuperlogString = (char *)0;
    tmpList = [[List allocFromZone:[self zone]] init];	// temporary List used by all objects
    [SpyTextFieldCell setSharedTmpList:tmpList];
    existenceSeen = YES;
    mySound = nil;			// why do I need this???
    swapfilename = NULL;
    swapfilesize = -1;
    swapfileTooBig = swapfileError = NO;

    [[NXApp appListener] setServicesDelegate:self];
    [self registerServices:self];
    if(system("/bin/stty pass8 pass8out nl > /dev/console") == 127)		// proposed by mwa
	    printf("filespy: couldn't open shell for 'stty pass8 pass8out nl > /dev/console\n");
    [[[myScrollView setHorizScrollerRequired: NO]		// setup mainwindow
	    setVertScrollerRequired: YES]
	    setBorderType: NX_BEZEL];
    [myScrollView getFrame:&scrollRect];
    [ScrollView getContentSize:&(matrixRect.size)
	    forFrameSize:&(scrollRect.size)
	    horizScroller: NO
	    vertScroller: YES
	    borderType:NX_BEZEL];
    myMatrix = [[MyMatrix alloc] initFrame: &matrixRect
	    mode: NX_LISTMODE
	    cellClass: [SpyTextFieldCell class]
	    numRows: 0
	    numCols: 1];
    [myMatrix setIntercell:&interCellSpacing];
    [myMatrix getCellSize:&cellSize];
    cellSize.width = NX_WIDTH(&matrixRect);
    [myMatrix setCellSize:&cellSize];
    [myMatrix sizeToCells];
    [myMatrix setAutosizeCells:YES];
    [myMatrix setAutoscroll:YES];
    [myScrollView setDocView:myMatrix];
    [[myMatrix superview] setAutoresizeSubviews:YES];
    [myMatrix setAutosizing:NX_WIDTHSIZABLE];
    [myMatrix setTarget:self];
    [myMatrix setAction:@selector(singleClick:)];
    [myMatrix setDoubleAction:@selector(doubleClick:)];
    [myScrollView setController:self];
    [myScrollView registerForDraggedTypes:fileType count:1];

    [[[myExistenceScrollView setHorizScrollerRequired: NO]		// setup existencelog
	    setVertScrollerRequired: YES]
	    setBorderType: NX_BEZEL];
    [myExistenceScrollView getFrame:&scrollRect];
    [ScrollView getContentSize:&(matrixRect.size)
	    forFrameSize:&(scrollRect.size)
	    horizScroller: NO
	    vertScroller: YES
	    borderType:NX_BEZEL];
    myExistenceMatrix = [[SimpleMatrix alloc] initFrame: &matrixRect
	    mode: NX_TRACKMODE
	    cellClass: [ExistenceTextFieldCell class]
	    numRows: 0
	    numCols: 1];
    [myExistenceMatrix setIntercell:&interCellSpacing];
    [myExistenceMatrix getCellSize:&cellSize];
    cellSize.width = NX_WIDTH(&matrixRect);
    [myExistenceMatrix setCellSize:&cellSize];
    [myExistenceMatrix sizeToCells];
    [myExistenceMatrix setAutosizeCells:YES];
    [myExistenceMatrix setAutoscroll:YES];
    [myExistenceScrollView setDocView:myExistenceMatrix];
    [[myExistenceMatrix superview] setAutoresizeSubviews:YES];
    [myExistenceMatrix setAutosizing:NX_WIDTHSIZABLE];
    [myExistenceMatrix setTarget:self];
    [myExistenceScrollView setController:self];
    [myExistenceScrollView registerForDraggedTypes:fileType count:1];

    finder = [[Finder alloc] init];
    [finder setDelegate:self];
    [self setupFilter];
    [self setupSuperlog];
    [self loadPreferences:self];
    if(firstFileStore) {		// load files dragged on icon before start
	unsigned int max = [firstFileStore count], i;
	newFileOffset = YES;
	displayCurWindow = NO;
	for(i = 1; i != max; i++)
	    [self addFileWithName:[firstFileStore elementAt:i]];
	[firstFileStore free];
    }
    if(fileCount == 0) {	// set the main window correctly
	[radioMatrix selectCellAt:0:prefFileMode];
	[popupSwitch setState:(prefPopup ? 1 : 0)];
	[soundButton setState:(prefBeepOnChange ? 1 : 0)];
	[filterSwitch setState:(prefUseFilter ? 1 : 0)];
	[superlogSwitch setState:(prefSuperlog ? 1 : 0)];
    }

    [self startSpyIfNeeded];
#ifdef DEBUG
    [myWindow setTitle:"Debug"];
#endif
    whileInit = NO;
    
    return self;
}

- appWillTerminate:sender
{
    if(saveState)
	[self savePreferences:self];
    [self stopSpy];
    return self;
}

- app:sender powerOffIn:(int)ms andSave:(int)aFlag
{
    return [self appWillTerminate:sender];
}

- free			// hmm.. I'd say there're some more things to free, aren't there?
{
    [myWindow free];
    return [super free];
}

- (BOOL)appAcceptsAnotherFile:sender
{
    return YES;
}

- (int)app:sender openFile:(const char *)filename type:(const char *)aType;
{
    BOOL tmpdcw = displayCurWindow;		// tmpDisplayCurWindow
    displayCurWindow = NO;
    if(whileInit) {
	if(!firstFileStore)
	    firstFileStore = [[Storage alloc] initCount:1 elementSize:MAXFILELEN description:"[MAXFILELENc]"];
	[firstFileStore addElement:(char *)filename];
    } else {
	newFileOffset = YES;	// place window at a new position (its position is not in the defaults database)
	displayCurWindow = NO;
	[self addFileWithName:filename];
	displayCurWindow = YES;
    }
    displayCurWindow = tmpdcw;
    return YES;
}

- addFileWithName:(const char *)name
{
    [myTextField setStringValue:name];
    [self addFile:self];
    return self;
}

- addFile:sender
{
    const char *str;
    int rowCount, colCount, button, i = 0;
    SpyTextFieldCell *newCell;
    BOOL fileExists = NO;	// is the file already in the list?

    str = [myTextField stringValue];
    if(*str == '\000') {
	[self chooseFile:self];
	return self;
    }
    [myTextField selectText:self];
    [myMatrix getNumRows:&rowCount numCols:&colCount];
    while((i < rowCount) && !fileExists) {		// only one window per file
	if(strcmp([[myMatrix cellAt:i :0] stringValue], str) == 0)
	    fileExists = TRUE;
	else
	    i++;
    }

    if(!fileExists) {
	BOOL newFile = YES;
	struct stat st;
	int actRowNr, actColNr, statRet;

	[myMatrix getRow:&actRowNr andCol:&actColNr ofCell:[myMatrix selectedCell]];
	[myMatrix selectCellAt:-1:-1];
	[myMatrix addRow];
	newCell = [myMatrix cellAt:rowCount :0];
	[newCell setTag:rowCount];
	[newCell setStringValue: str];
						// now set all the default-values
	[newCell setFileMode:prefFileMode];
	[newCell setAutoPopUp:prefPopup];
	[newCell setBeepOnChange:prefBeepOnChange];
	[newCell setMyDelegate:self];
	[newCell setLogToSuperlog:prefSuperlog];
	[newCell setUseFilter:prefUseFilter];
	[newCell setDontCopy:prefDontCopySuperlog];
	[newCell setSuperCopyFilename:prefSuperCopyFilename];
	[newCell setColor:prefColor];
	[newCell setTimeStamp:prefTimeStamp];
	[newCell setMaxLines:(maxLineState ? maxLines : 0)];
	[newCell setSpytype:prefSpytype];
	[newCell setShowTime:showTime];
	if(prefFont)
	    [newCell setSpyFont:prefFont];
	if(((statRet = stat(str, &st)) < 0) && !whileInit) {
	    if(errno == ENOENT) {
		displayCurWindow = NO;
		button = NXRunAlertPanel("Alert",
			"File %s doesn't exist.\nAre you sure you want to spy it?",  "Ok", "Cancel", NULL, str);
		if(button != NX_ALERTDEFAULT) {
		    [myMatrix selectCellAt:rowCount :0];
		    [self deleteFile:self];
		    newFile = NO;
		}
	    } else if(errno == EACCES) {
		displayCurWindow = NO;
		button = NXRunAlertPanel("Alert",
			"No permission for file %s.\nAre you sure you want to spy it?", "Ok", "Cancel", NULL, str);
		if(button != NX_ALERTDEFAULT) {
		    [myMatrix selectCellAt:rowCount :0];
		    [self deleteFile:self];
		    newFile = NO;
		}
	    }
	}
	if(newFile) {
	    if(newFileOffset) {
		winx = winx + WIN_DELTA_X;
		winy = winy - WIN_DELTA_Y;
		if((winx > WIN_MAX_X) || (winy < WIN_MAX_Y)) {
		    winx = WIN_START_X;
		    winy = WIN_START_Y;
		}
		[newCell moveWindowTo:winx:winy];
	    } else {
		[[newCell window] setFrameFromString:frameStr];
	    }
	    [newCell updateFile];
	}
	[myMatrix sizeToCells];
	[myMatrix selectCellAt:(newFile ? rowCount : actRowNr) :0];
	[myMatrix scrollCellToVisible:rowCount :0];
	fileCount++;
	[self startSpyIfNeeded];
    } else {		// file is already beeing spyed, so beep and select it
	[self nxbeep];
	[myMatrix selectCellAt:i :0];
	[myMatrix scrollCellToVisible:i :0];
    }
    [self singleClick:self];
    return self;
}

- deleteFile:sender
{
    id selectedCellsList;
    int i, rowCount, colCount;

    [myTextField setStringValue:""];
    selectedCellsList = [tmpList empty];
    [myMatrix getSelectedCells:selectedCellsList];
    [myMatrix getNumRows:&rowCount numCols:&colCount];
    for(i=0; i<rowCount; i++) {
	if([selectedCellsList indexOf:[myMatrix cellAt:i :0]] != NX_NOT_IN_LIST) {
	    [[myMatrix cellAt:i :0] delete];
	    [myMatrix removeRowAt:i-- andFree:YES];
	    fileCount--;
	}
    }
    [[myMatrix sizeToCells] display];
    [myExistenceMatrix sizeToCells];
    [myExistenceScrollView display];

    if((fileCount == 0) || ([myMatrix selectedCell] == nil)) {
	[self setInspectorToView:[inspectorNoView contentView]];
    }
    [self stopSpyIfNeeded];
    return self;
}

- chooseFile:sender
{
    id openPanel;
    const char *const fileType[1] = {NULL};
    const char *const *files;
    char tmpfile[MAXFILENAMELENGHT];
    int i = 0;

    openPanel = [OpenPanel new];
    [openPanel setDelegate:self];
    [openPanel chooseDirectories:NO];
    [openPanel allowMultipleFiles:YES];
    if([openPanel runModalForDirectory:[myTextField stringValue] file:NULL types:fileType] == NX_OKTAG) {
	if((files = [openPanel filenames]) != (const char *const *)0) {
	    while(files[i]) {
		sprintf(tmpfile, "%s/%s", [openPanel directory], files[i++]);
		[self addFileWithName:tmpfile];
	    }
	}
    }
    [self singleClick:self];
    return self;
}

- singleClick:sender
{
    PopUpList *popupList = [[superFilenameButton cell] target], *spytypePopuplist = [[spytypeButton cell] target];
    Matrix *popupMatrix = [popupList itemList], *spytypeMatrix = [spytypePopuplist itemList];
    id selectedCell = [myMatrix selectedCell];

    if(selectedCell != nil) {						// this does not work correctly because of a bug in NS!
    									// tempi passati
	[myTextField setStringValue:[selectedCell stringValue]];
	[myTextField selectText:self];
	if(inspectorPanel) {		// if the inspectorPanel is open, then set all the values
	    [radioMatrix selectCellAt:0 :[selectedCell fileMode]];
	    [soundButton setState:[selectedCell beepOnChange]];
	    [popupSwitch setState:[selectedCell autoPopUp]];
	    [superlogSwitch setState:[selectedCell logToSuperlog]];
	    [filterSwitch setState:[selectedCell useFilter]];
	    [dontCopySwitch setState:[selectedCell dontCopy]];
	    [timeSwitch setState:[selectedCell timeStamp]];
	    [popupMatrix selectCellWithTag:[selectedCell superCopyFilename]];
	    [superFilenameButton setTitle:
		    [[popupMatrix findCellWithTag:[selectedCell superCopyFilename]] title]];
	    [superColorWell setColor:[selectedCell color]];
	    [fontTextField setFont:[selectedCell spyFont]];
	    [myFontManager setSelFont:[selectedCell spyFont] isMultiple:NO];
	    [spytypeMatrix selectCellWithTag:(int)[selectedCell spytype]];
	    [spytypeButton setTitle:
		    [[spytypeMatrix findCellWithTag:(int)[selectedCell spytype]] title]];
	    if([[myMatrix getSelectedCells:[tmpList empty]] count] > 1) {		// if multiple selection...
		[inspectorFile setStringValue:"Files: "];
		[inspectorName setTextGray:NX_DKGRAY];
	    } else {
		[inspectorFile setStringValue:"File: "];
		[inspectorName setTextGray:NX_BLACK];
	    }
	    [inspectorName setStringValue:[selectedCell stringValue]];
	    [self setInspectorToView:[inspectorNormalView contentView]];
	}
    } else {		// if no cell is selected
#ifdef DEBUG
//	puts("selectedCell == nil");	// for testing if the bug still exists // it doesn't...
#endif
	[myTextField setStringValue:""];
	[myTextField selectText:self];
	[self setInspectorToView:[inspectorNoView contentView]];
    }
    return self;
}

- doubleClick:sender
{
    [[myMatrix selectedCell] displayWindow];
    return self;
}

- radioClick:sender		// if user selects the radio-button for entire file/only new text
{
    List *selectedCellsList = [tmpList empty];
    unsigned int i;
    int mode = [[radioMatrix selectedCell] tag];

    [myMatrix getSelectedCells:selectedCellsList];
    for(i = 0; i < [selectedCellsList count]; i++)
	[[selectedCellsList objectAt:i] setFileMode:mode];
    return self;
}

- autoPopUpClick:sender
{
    List *selectedCellsList = [tmpList empty];
    unsigned int i;
    BOOL state = [popupSwitch state];

    [myMatrix getSelectedCells:selectedCellsList];
    for(i = 0; i < [selectedCellsList count]; i++)
	[[selectedCellsList objectAt:i] setAutoPopUp:state];
    return self;
}

- beepOnChangeClick:sender
{
    List *selectedCellsList = [tmpList empty];
    unsigned int i;
    BOOL state = [soundButton state];

    [myMatrix getSelectedCells:selectedCellsList];
    for(i = 0; i < [selectedCellsList count]; i++)
	[[selectedCellsList objectAt:i] setBeepOnChange:state];
    return self;
}

- superlogClick:sender
{
    List *selectedCellsList = [tmpList empty];
    unsigned int i, max;
    BOOL state = [sender state];

    [myMatrix getSelectedCells:selectedCellsList];
    max = [selectedCellsList count];
    for(i = 0; i < max; i++)
	[[selectedCellsList objectAt:i] setLogToSuperlog:state];
    return self;
}

- useFilterClick:sender
{
    List *selectedCellsList = [tmpList empty];
    unsigned int i, max;
    BOOL state = [sender state];

    [myMatrix getSelectedCells:selectedCellsList];
    max = [selectedCellsList count];
    for(i = 0; i < max; i++)
	[[selectedCellsList objectAt:i] setUseFilter:state];
    return self;
}

- copySuperlogClick:sender
{
    List *selectedCellsList = [tmpList empty];
    unsigned int i, max;
    BOOL state = [sender state];

    selectedCellsList = [tmpList empty];
    [myMatrix getSelectedCells:selectedCellsList];
    max = [selectedCellsList count];
    for(i = 0; i < max; i++)
	    [[selectedCellsList objectAt:i] setDontCopy:state];
    return self;

}

- filterModeClick:sender
{
    switch([[sender selectedCell] tag]) {
	    case 0: filterMode = FILTER_DONTCOPY; break;
	    case 1: filterMode = FILTER_DONTBEEP; break;
#ifdef DEBUG
	    default: printf("filespy: unknown filtermode %d\n", [sender state]);
#endif
    }
    return self;
}

// if user changes something in the preferences-window
- prefRadioClick:sender
{
    prefFileMode = [[prefRadioMatrix selectedCell] tag];
    return self;
}

- prefSuperlogClick:sender
{
    prefSuperlog = [sender state];
    return self;
}

- prefAutoPopUpClick:sender
{
    prefPopup = [sender state];
    return self;
}

- prefBeepOnChangeClick:sender
{
    prefBeepOnChange = [sender state];
    return self;
}

- prefUseFilterClick:sender
{
    prefUseFilter = [sender state];
    return self;
}

- prefCopySuperlogClick:sender
{
    prefDontCopySuperlog = [sender state];
    return self;
}

- superAutoPopUpClick:sender
{
    superPopup = [sender state];
    return self;
}

- superBeepOnChangeClick:sender
{
    superBeepOnChange = [sender state];
    return self;
}

- superUseFilterClick:sender
{
    superUseFilter = [sender state];
    return self;
}

- superDupLinesClick:sender
{
//    BOOL oldState = noDuplicatedLines;

    noDuplicatedLines = [sender state];
#if 0
    if((noDuplicatedLines == YES) && (oldState == NO))
	lastSuperlogString = (char *)malloc(MAXSTRLEN * sizeof(char));
    else if((noDuplicatedLines == NO) && (oldState == YES))
	free(lastSuperlogString);
#endif
    return self;
}

- saveFileListClick:sender
{
    saveFileList = [sender state];
    return self;
}

- keyClick:sender
{
    becomeKey = [sender state];
    return self;
}

- stateClick:sender
{
    saveState = [sender state];
    return self;
}

- setShowTimeClick:sender
{
    unsigned int i, max;
    List *selectedCellsList = [myMatrix cellList];

    showTime = [sender state];
    max = [selectedCellsList count];
    for(i = 0; i < max; i++)
	[[selectedCellsList objectAt:i] setShowTime:showTime];
    if(existencelogWindow)
	[existencelogWindow display];
    return self;
}

- (BOOL)showTime
{
    return showTime;
}

- startSpy
{
    if(!running) {
	alarmTimedEntry = DPSAddTimedEntry(secs, &alarmHandler, self, NX_BASETHRESHOLD);
	running = YES;
    }
    return self;
}

- startSpyIfNeeded
{
    if(fileCount > 0 || (swapfilename != NULL && swapfilesize >= 0))
	[self startSpy];
    return self;
}

- stopSpy
{
    if(running) {
	DPSRemoveTimedEntry(alarmTimedEntry);
	NXPing();
	running = NO;
    }
    return self;
}

- stopSpyIfNeeded
{
    if(fileCount == 0 && (swapfilename == NULL || swapfilesize < 0))
	[self stopSpy];
    return self;
}

- checkFiles	// update all the files
{
    unsigned int i, max;
    int row, col;
    SpyTextFieldCell *actualCell;
    BOOL exMatrixUpdate = NO;

    [tmpList empty];
    for(i=0; i<fileCount; i++) {
	actualCell = [myMatrix cellAt:i :0];
	[actualCell updateFile];
	if([actualCell changedState])
	    [actualCell updateText];
	if([actualCell askUpdateTime] && showTime)
	    exMatrixUpdate = YES;
    }
    max = [tmpList count];
    if(max > 0) {
	if(existenceSeen) {
	    existenceSeen = NO;
	    [myExistenceMatrix clearSelectedCell];
	}
	for(i=0; i<max; i++) {		// select all cell that changed existence in existencelog
	    [myExistenceMatrix getRow:&row andCol:&col ofCell:[tmpList objectAt:i]];
	    [myExistenceMatrix setSelectionFrom:row to:row anchor:row lit:YES];
	}
	[myExistenceMatrix getRow:&row andCol:&col ofCell:[tmpList objectAt:0]];	// get first selected cell
	[myExistenceMatrix scrollCellToVisible:row :col];				// and scroll it to visible
    } 
    if((max > 0) || (exMatrixUpdate)) {
	[existencelogWindow display];
    }
    if((swapfilename != NULL) && (swapfilesize >= 0)) {
	[self updateSwapfile];
    }
    return self;
}

- changeUpdatePeriod:sender
{
    int newPeriod = [sender intValue];

    if(newPeriod > LONGESTTIME)
	newPeriod = LONGESTTIME;
    if(newPeriod < SHORTESTTIME)
	newPeriod = SHORTESTTIME;
    [secsTextField setIntValue:newPeriod];
    [secsScroller setIntValue:newPeriod];
    secs = newPeriod;
    [self stopSpy];
    [self startSpyIfNeeded];
    return self;
}

- showPreferences:sender	// shows preferences panel
{
    PopUpList *popupList;
    Matrix *popupMatrix;

    if(!preferencesPanel) {
	if(![NXApp loadNibSection:"Preferences.nib" owner:self withNames:NO fromZone:[self zone]]) {
	    NXLogError("Can't open Preferences.nib!");
	}
	[self setPrefToView:[prefGenView contentView]];
    }
    popupList = [[prefSuperFilenameButton cell] target];
    popupMatrix = [popupList itemList];
    [secsScroller setDoubleValue:secs];
    [secsTextField setDoubleValue:secs];
    [prefPopupSwitch setState:prefPopup];
    [prefSoundButton setState:prefBeepOnChange];
    [prefFilterSwitch setState:prefUseFilter];
    [prefSuperlogSwitch setState:prefSuperlog];
    [superPopupSwitch setState:superPopup];
    [superSoundSwitch setState:superBeepOnChange];
    [superFilterSwitch setState:superUseFilter];
    [superDupSwitch setState:noDuplicatedLines];
    [prefRadioMatrix selectCellAt:0:prefFileMode];
    [keySwitch setState:becomeKey];
    [stateSwitch setState:saveState];
    [fileListButton setState:saveFileList];
    [prefDontCopySwitch setState:prefDontCopySuperlog];
    [showTimeSwitch setState:showTime];
    [prefSuperFilenameButton setTitle:
	    [[popupMatrix findCellWithTag:prefSuperCopyFilename] title]];
    [prefColorWell setColor:prefColor];

    popupList = [[prefSpytypeButton cell] target];
    popupMatrix = [popupList itemList];
    [prefSpytypeButton setTitle:
	    [[popupMatrix findCellWithTag:prefSpytype] title]];
    [prefFontTextField setFont:prefFont];
    [myFontManager setSelFont:prefFont isMultiple:NO];
    [prefTimeSwitch setState:prefTimeStamp];
    [[maxLinesScroller setIntValue:maxLines] setEnabled:maxLineState];
    [[maxLinesTextField setIntValue:maxLines] setEnabled:maxLineState];
    [maxLinesMatrix selectCellWithTag:maxLineState];
    if(mySound != nil)
	[soundTextField setStringValue:[mySound name]];
    if(swapfilename != NULL) {
	[prefSwapfileTextField setStringValue:swapfilename];
	[prefSwapfileSizeTextField setIntValue:swapfilesize/(1024*1024)];
    } else {
	[prefSwapfileTextField setStringValue:""];
	[prefSwapfileSizeTextField setStringValue:""];
    }
    [preferencesPanel makeKeyAndOrderFront:sender];
    return self;
}

- setPref:sender
{
    id newView = nil;
    int tag = [[sender selectedCell] tag];
    
    switch(tag) {
	case 0:	newView = [prefGenView contentView];
		break;
	case 1: newView = [prefNewView contentView];
		break;
	case 2: newView = [prefSuperView contentView];
		break;
	case 3: newView = [prefSwapfileView contentView];
		break;
#ifdef DEBUG
	default: printf("filespy: unknown tag in setPref:sender\n");	// this shouldn't happen
		break;
#endif
    }
    [self setPrefToView:newView];
    [popButton setTitle:[[sender selectedCell] title]];

    return self;
}

- setPrefToView:theView
{
    NXRect boxRect, viewRect;
    
    [multiView getFrame:&boxRect];
    [theView getFrame:&viewRect];
    [multiView setContentView:theView];
    NX_X(&viewRect) = (NX_WIDTH(&boxRect) - NX_WIDTH(&viewRect)) / 2.0;
    NX_Y(&viewRect) = (NX_HEIGHT(&boxRect) - NX_HEIGHT(&viewRect)) / 2.0;
    [theView setFrame:&viewRect];
    [multiView display];
    return self;
}

- savePreferences:sender	// saves preferences to defaults-database
{
    unsigned int i, oldCount = 0;
    char *str;
    id cellList, actualCell;
    NXDefaultsVector vec = {						// contains global defaults
	{"filterMode", ""},						// 0
	{"superUseFilter", (superUseFilter ? "YES" : "NO")},		// 1
	{"prefUseFilter", (prefUseFilter ? "YES" : "NO")},		// 2
	{"secs",""},							// 3
	{"prefPopup", (prefPopup ? "YES" : "NO")},			// 4
	{"prefBeepOnChange", (prefBeepOnChange ? "YES" : "NO")},	// 5
	{"prefSuperlog", (prefSuperlog ? "YES" : "NO")},		// 6
	{"superPopup", (superPopup ? "YES" : "NO")},			// 7
	{"superBeepOnChange", (superBeepOnChange ? "YES" : "NO")},	// 8
	{"prefFileMode", ""},						// 9
	{"saveFileList", (saveFileList ? "YES" : "NO")},		// 10
	{"fileCount",""},						// 11
	{"myWindowFrame", ""},						// 12
	{"myWindowVisible", [myWindow isVisible] ? "YES" : "NO"},	// 13
	{"superWindowFrame", ""},					// 14
	{"superWindowVisible", [superWindow isVisible] ? "YES" : "NO"},	// 15
	{"becomeKey", becomeKey ? "YES" : "NO"},			// 16
	{"saveState", saveState ? "YES" : "NO"},			// 17
	{"prefDontCopySuperlog", prefDontCopySuperlog ? "YES" : "NO"},	// 18
	{"prefSuperCopyFilename", ""},					// 19
	{"prefColor", ""},						// 20
	{"prefTimeStamp", prefTimeStamp ? "YES" : "NO"},		// 21
	{"prefFont", ""},						// 22
	{"maxLines", ""},						// 23
	{"inspectorFrame", ""},						// 24
	{"superNoDupLines", noDuplicatedLines ? "YES" : "NO"},		// 25
	{"prefSpytype", ""},						// 26
	{"exWindowFrame", ""},						// 27
	{"exWindowVisible",[existencelogWindow isVisible]? "YES":"NO"},	// 28
	{"soundfile", ""},						// 29
	{"showTime", "NO"},						// 30
	{"swapFileName", ""},						// 31
	{"swapFileSize", "-1"},						// 32
	{NULL, NULL}							// <- enter this number in VECLEN
    };
#define VECLEN 33

    NXDefaultsVector fileVec = {					// contains defaults for each file
	{"", ""},	// 0
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},	// 13
	{NULL, NULL},	// <- enter this number in FILEVECLEN!
    };
#define FILEVECLEN 14

    oldCount = atoi(NXGetDefaultValue(DEFOWNER, "fileCount"));		// need to remember how many files were in the database
    									// so that I can remove thouse which now longer belong there
    vec[0].value = (char *)alloca(16);
    sprintf(vec[0].value, "%d", filterMode);
    vec[3].value = (char *)alloca(16);
    sprintf(vec[3].value, "%f", secs);
    vec[9].value = (char *)alloca(16);
    sprintf(vec[9].value, "%d", prefFileMode);
    vec[19].value = (char *)alloca(16);
    sprintf(vec[19].value, "%d", prefSuperCopyFilename);
    vec[20].value = (char *)alloca(32);
    sprintf(vec[20].value, "%f,%f,%f", NXRedComponent(prefColor), NXGreenComponent(prefColor), NXBlueComponent(prefColor));
    vec[22].value = (char *)alloca(32);		// hope this is enough for a fontname, is there some constant somewhere?
    sprintf(vec[22].value, "%s %f", [prefFont name], [prefFont pointSize]);
    vec[23].value = (char *)alloca(16);
    sprintf(vec[23].value, "%d", maxLineState ? maxLines : -maxLines);
    vec[26].value = (char *)alloca(16);
    sprintf(vec[26].value, "%hu", prefSpytype);
    vec[29].value = (char *)alloca(MAXNAMLEN);
    sprintf(vec[29].value, [self soundName]);
    vec[30].value = (char *)alloca(4);
    sprintf(vec[30].value, showTime ? "YES" : "NO");
    if(swapfilename != NULL) {
	vec[31].value = (char *)alloca(strlen(swapfilename) + 1);
	strcpy(vec[31].value, swapfilename);
	vec[32].value = (char *)alloca(sizeof(char)*11);
	sprintf(vec[32].value, "%ld", swapfilesize);
    }

    vec[11].value = (char *)alloca(16);
    if(saveFileList)
	sprintf(vec[11].value, "%d", fileCount);
    else
	sprintf(vec[11].value, "%d", oldFileCount);
    vec[12].value = (char *)alloca(NX_MAXFRAMESTRINGLENGTH);
    [myWindow saveFrameToString:vec[12].value];
    vec[14].value = (char *)alloca(NX_MAXFRAMESTRINGLENGTH);
    [superWindow saveFrameToString:vec[14].value];
    vec[24].value = (char *)alloca(NX_MAXFRAMESTRINGLENGTH);
    if(inspectorPanel) {			// remember the inspector panel's position
	[inspectorPanel saveFrameToString:vec[24].value];
    } else if(inspectorFrame != (char *)0) {
	strcpy(vec[24].value, inspectorFrame);
    }
    vec[27].value = (char *)alloca(NX_MAXFRAMESTRINGLENGTH);
    [existencelogWindow saveFrameToString:vec[27].value];

    if(NXWriteDefaults(DEFOWNER, vec) != VECLEN)
	NXLogError("error while writing to defaults database");

    [self saveFilter:self];

    if(saveFileList) {		// save defaults for each file
	NXColor col;
	for(i = 0; i < FILEVECLEN; i++) {
	    fileVec[i].name = (char *)alloca(32);
	    fileVec[i].value = (char *)alloca(NX_MAXFRAMESTRINGLENGTH);
	}
	cellList = [myMatrix cellList];
	for(i = 0; i < fileCount; i++) {
	    actualCell = [cellList objectAt:i];
	    sprintf(fileVec[0].name, "fileName%d", i);
	    strcpy(fileVec[0].value, [actualCell stringValue]);
	    sprintf(fileVec[1].name, "fileFrame%d", i);
	    [[actualCell window] saveFrameToString:fileVec[1].value];
	    sprintf(fileVec[2].name, "fileMode%d", i);
	    sprintf(fileVec[2].value, "%d", [actualCell fileMode]);
	    sprintf(fileVec[3].name, "filePop%d", i);
	    strcpy(fileVec[3].value, [actualCell autoPopUp] ? "YES" : "NO");
	    sprintf(fileVec[4].name, "fileBeep%d", i);
	    strcpy(fileVec[4].value, [actualCell beepOnChange] ? "YES" : "NO");
	    sprintf(fileVec[5].name, "fileLog%d", i);
	    strcpy(fileVec[5].value, [actualCell logToSuperlog] ? "YES" : "NO");
	    sprintf(fileVec[6].name, "fileFilter%d", i);
	    strcpy(fileVec[6].value, [actualCell useFilter] ? "YES" : "NO");
	    sprintf(fileVec[7].name, "fileVisible%d", i);
	    strcpy(fileVec[7].value, [[actualCell window] isVisible] ? "YES" : "NO");
	    sprintf(fileVec[8].name, "fileDontCopy%d", i);
	    strcpy(fileVec[8].value, [actualCell dontCopy] ? "YES" : "NO");
	    sprintf(fileVec[9].name, "fileSuperCopyName%d", i);
	    sprintf(fileVec[9].value, "%d", [actualCell superCopyFilename]);
	    col = [actualCell color];
	    sprintf(fileVec[10].name, "fileColor%d", i);
	    sprintf(fileVec[10].value, "%f,%f,%f", NXRedComponent(col), NXGreenComponent(col), NXBlueComponent(col));
	    sprintf(fileVec[11].name, "fileTimeStamp%d", i);
	    sprintf(fileVec[11].value, [actualCell timeStamp] ? "YES" : "NO");
	    sprintf(fileVec[12].name, "fileFont%d", i);
	    sprintf(fileVec[12].value, "%s %f", [[actualCell spyFont] name], [[actualCell spyFont] pointSize]);
	    sprintf(fileVec[13].name, "fileSpytype%d", i);
	    sprintf(fileVec[13].value, "%hu", [actualCell spytype]);

	    if(NXWriteDefaults(DEFOWNER, fileVec) != FILEVECLEN)
		NXLogError("error while writing filevector to defaults database");
	}
	if(oldCount > fileCount) {	// remove old entries which sould no longer be there
	    str = (char *)alloca(32);
	    for(i = fileCount; i < oldCount; i++) {
		sprintf(str, "fileName%d", i);
		NXRemoveDefault(DEFOWNER, str);
		sprintf(str, "fileFrame%d", i);
		NXRemoveDefault(DEFOWNER, str);
		sprintf(str, "fileMode%d", i);
		NXRemoveDefault(DEFOWNER, str);
		sprintf(str, "filePop%d", i);
		NXRemoveDefault(DEFOWNER, str);
		sprintf(str, "fileBeep%d", i);
		NXRemoveDefault(DEFOWNER, str);
		sprintf(str, "fileLog%d", i);
		NXRemoveDefault(DEFOWNER, str);
		sprintf(str, "fileFilter%d", i);
		NXRemoveDefault(DEFOWNER, str);
		sprintf(str, "fileVisible%d", i);
		NXRemoveDefault(DEFOWNER, str);
		sprintf(str, "fileDontCopy%d", i);
		NXRemoveDefault(DEFOWNER, str);
		sprintf(str, "fileSuperCopyName%d", i);
		NXRemoveDefault(DEFOWNER, str);
		sprintf(str, "fileColor%d", i);
		NXRemoveDefault(DEFOWNER, str);
		sprintf(str, "fileTimeStamp%d", i);
		NXRemoveDefault(DEFOWNER, str);
		sprintf(str, "fileFont%d", i);
		NXRemoveDefault(DEFOWNER, str);
		sprintf(str, "fileSpytype%d", i);
		NXRemoveDefault(DEFOWNER, str);
	    }
	}
    }
    return self;
}

- saveFilter:sender		// save filter-patterns to defaults database
{
    char *tmp = (char *)alloca(16);
    const char *tmpStr;
    unsigned int i, maxFilter, oldCount = 0;

    maxFilter = [filterStore count];
    tmpStr = NXGetDefaultValue(DEFOWNER, "filterStoreCount");
    if(tmpStr != (const char *)0)
	oldCount = atoi(tmpStr);
    sprintf(tmp, "%d", maxFilter);
    if(!NXWriteDefault(DEFOWNER, "filterStoreCount", tmp))
	NXLogError("error while writing filterStoreCount to defaults database");
    for(i = 0; i < maxFilter; i++) {				// save all filter strings
	sprintf(tmp, "filterLine%d", i);
	if(!NXWriteDefault(DEFOWNER, tmp, (char *)[filterStore elementAt:i]))
	    NXLogError("error while writing filterLine to defaults database");
    }
    for(i = maxFilter; i < oldCount; i++) {		// remove remaining old entries
	sprintf(tmp, "filterLine%d", i);
	NXRemoveDefault(DEFOWNER, tmp);
    }
    if([filterWindow isDocEdited])
	[filterWindow setDocEdited:NO];
    return self;
}

- loadPreferences:sender		// load preferences from defaults database
{
    NXDefaultsVector vec = {						// contains global defaults
	{"filterMode", "1"},						// 0
	{"superUseFilter", "NO"},					// 1
	{"prefUseFilter", "NO"},					// 2
	{"secs", "2.000000"},						// 3
	{"prefPopup", "YES"},						// 4
	{"prefBeepOnChange", "YES"},					// 5
	{"prefSuperlog", "NO"},						// 6
	{"superPopup", "YES"},						// 7
	{"superBeepOnChange", "YES"},					// 8
	{"prefFileMode", "0"},						// 9
	{"saveFileList", "YES"},					// 10
	{"fileCount", "0"},						// 11
	{"myWindowFrame", ""},						// 12
	{"myWindowVisible", "YES"},					// 13
	{"superWindowFrame", ""},					// 14
	{"superWindowVisible", "NO"},					// 15
	{"becomeKey", "NO"},						// 16
	{"saveState", "NO"},						// 17
	{"prefDontCopySuperlog", "YES"},				// 18
	{"filterStoreCount", "0"},					// 19
	{"prefSuperCopyFilename", "0"},					// 20
	{"prefColor", "0,0,0"},						// 21
	{"prefTimeStamp", "NO"},					// 22
	{"prefFont", "Ohlfs 10.000000"},				// 23
	{"maxLines", "-100"},						// 24
	{"inspectorFrame", ""},						// 25
	{"superNoDupLines", "NO"},					// 26
	{"prefSpytype", "1"},						// 27
	{"exWindowFrame", ""},						// 28
	{"exWindowVisible", "NO"},					// 29
	{"soundfile", ""},						// 30
	{"showTime", "NO"},						// 31
	{"swapFileName", ""},						// 32
	{"swapFileSize", "-1"},						// 33
	{NULL, NULL}
    };
    NXDefaultsVector fileVec = {					// contains defaults for individual files
	{"", ""},	// 0
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},
	{"", ""},	// 13
	{NULL, NULL},	// <- enter this number in NRFILEVEC
    };
#define NRFILEVEC 14

    unsigned int max, i;
    BOOL tmpPopup, tmpBeepOnChange, tmpLogToSuperlog, tmpUseFilter;
    unsigned short int tmpSpytype;
    int tmpFileMode;
    float red, green, blue;
    char str[MAXFILELEN], fontStr[48];
    const char *tmpStr;

    NXRegisterDefaults(DEFOWNER, vec);
    [self setSound:NXGetDefaultValue(DEFOWNER, "soundfile")];		// set 'beep'
    filterMode = atoi(NXGetDefaultValue(DEFOWNER, "filterMode"));
    superUseFilter = (strncmp(NXGetDefaultValue(DEFOWNER, "superUseFilter"), "NO", 2) == 0) ? NO : YES;
    tmpUseFilter = (strncmp(NXGetDefaultValue(DEFOWNER, "prefUseFilter"), "NO", 2) == 0) ? NO : YES;
    secs = atof(NXGetDefaultValue(DEFOWNER, "secs"));
    tmpPopup = (strncmp(NXGetDefaultValue(DEFOWNER, "prefPopup"), "NO", 2) == 0) ? NO : YES;
    tmpBeepOnChange = (strncmp(NXGetDefaultValue(DEFOWNER, "prefBeepOnChange"), "NO", 2) == 0) ? NO : YES;
    tmpLogToSuperlog = (strncmp(NXGetDefaultValue(DEFOWNER, "prefSuperlog"), "NO", 2) == 0) ? NO : YES;
    superPopup = (strncmp(NXGetDefaultValue(DEFOWNER, "superPopup"), "NO", 2) == 0) ? NO : YES;
    superBeepOnChange = (strncmp(NXGetDefaultValue(DEFOWNER, "superBeepOnChange"), "NO", 2) == 0) ? NO : YES;
    noDuplicatedLines = (strncmp(NXGetDefaultValue(DEFOWNER, "superNoDupLines"), "NO", 2) == 0) ? NO : YES;
#if 0
    if(noDuplicatedLines)
	lastSuperlogString = (char *)malloc(MAXSTRLEN * sizeof(char));
    else
	lastSuperlogString = (char *)0;
#endif
    tmpFileMode = atoi(NXGetDefaultValue(DEFOWNER, "prefFileMode"));
    tmpSpytype = (unsigned short int)atoi(NXGetDefaultValue(DEFOWNER, "prefSpytype"));
    saveFileList = (strncmp(NXGetDefaultValue(DEFOWNER, "saveFileList"), "NO", 2) == 0) ? NO : YES;
    maxLines = atoi(NXGetDefaultValue(DEFOWNER, "maxLines"));
    if(maxLines < 0) {
	maxLineState = NO;
	maxLines = -maxLines;
    } else
	maxLineState = YES;
    showTime = (strncmp(NXGetDefaultValue(DEFOWNER, "showTime"), "NO", 2) == 0) ? NO : YES;
    tmpStr = NXGetDefaultValue(DEFOWNER, "swapFileName");
    if(tmpStr != NULL && *tmpStr != '\000') {
	swapfilename = (char *)malloc(strlen(tmpStr)+1);
	strcpy(swapfilename, tmpStr);
	swapfilesize = atoi(NXGetDefaultValue(DEFOWNER, "swapFileSize"));
    }

    [superWindow setFrameFromString:NXGetDefaultValue(DEFOWNER, "superWindowFrame")];
    max = atoi(NXGetDefaultValue(DEFOWNER, "filterStoreCount"));
    for(i = 0; i < max; i++) {
	const char *ts;
	sprintf(str, "filterLine%d", i);
	ts = NXGetDefaultValue(DEFOWNER, str);
	if(ts != (const char *)0)
	    [filterStore addElement:(char *)ts];
    }
    filterIsUpToDate = NO;

    for(i = 0; i < NRFILEVEC; i++) {
	fileVec[i].name = (char *)alloca(32);
	fileVec[i].value = (char *)alloca(NX_MAXFRAMESTRINGLENGTH);
    }

    max = oldFileCount= atoi(NXGetDefaultValue(DEFOWNER, "fileCount"));
    displayCurWindow = NO;
    newFileOffset = NO;

    for(i = 0; i < max; i++) {
	sprintf(fileVec[0].name, "fileName%d", i);
	sprintf(fileVec[1].name, "fileFrame%d", i);
	sprintf(fileVec[2].name, "fileMode%d", i);
	sprintf(fileVec[3].name, "filePop%d", i);
	sprintf(fileVec[4].name, "fileBeep%d", i);
	sprintf(fileVec[5].name, "fileLog%d", i);
	sprintf(fileVec[6].name, "fileDontCopy%d", i);
	sprintf(fileVec[7].name, "fileFilter%d", i);
	sprintf(fileVec[8].name, "fileVisible%d", i);
	sprintf(fileVec[9].name, "fileSuperCopyName%d", i);
	sprintf(fileVec[10].name, "fileColor%d", i);
	sprintf(fileVec[11].name, "fileTimeStamp%d", i);
	sprintf(fileVec[12].name, "fileFont%d", i);
	sprintf(fileVec[13].name, "fileSpytype%d", i);

	strcpy(fileVec[0].value, "/");
	strcpy(fileVec[1].value, "");
	strcpy(fileVec[2].value, "0");
	strcpy(fileVec[3].value, "YES");
	strcpy(fileVec[4].value, "YES");
	strcpy(fileVec[5].value, "NO");
	strcpy(fileVec[6].value, "YES");
	strcpy(fileVec[7].value, "NO");
	strcpy(fileVec[8].value, "YES");
	strcpy(fileVec[9].value, "0");
	strcpy(fileVec[10].value, "0,0,0");
	strcpy(fileVec[11].value, "NO");
	strcpy(fileVec[12].value, "Ohlfs 10.000000");
	strcpy(fileVec[13].value, "1");
	
	NXRegisterDefaults(DEFOWNER, fileVec);
	
	[myTextField setStringValue:NXGetDefaultValue(DEFOWNER, fileVec[0].name)];
	strcpy(frameStr, NXGetDefaultValue(DEFOWNER, fileVec[1].name));
	prefFileMode = atoi(NXGetDefaultValue(DEFOWNER, fileVec[2].name));
	prefPopup = (strncmp(NXGetDefaultValue(DEFOWNER, fileVec[3].name), "NO", 2) == 0) ? NO : YES;
	prefBeepOnChange = (strncmp(NXGetDefaultValue(DEFOWNER, fileVec[4].name), "NO", 2) == 0) ? NO : YES;
	prefSuperlog = (strncmp(NXGetDefaultValue(DEFOWNER, fileVec[5].name), "NO", 2) == 0) ? NO : YES;
	prefDontCopySuperlog = (strncmp(NXGetDefaultValue(DEFOWNER, fileVec[6].name), "NO", 2) == 0) ? NO : YES;
	prefUseFilter = (strncmp(NXGetDefaultValue(DEFOWNER, fileVec[7].name), "NO", 2) == 0) ? NO : YES;
	prefSuperCopyFilename = atoi(NXGetDefaultValue(DEFOWNER, fileVec[9].name));
	strcpy(str, NXGetDefaultValue(DEFOWNER, fileVec[10].name));
	sscanf(str, "%f,%f,%f", &red, &green, &blue);
	prefColor = NXConvertRGBToColor(red, green, blue);
	prefTimeStamp = (strncmp(NXGetDefaultValue(DEFOWNER, fileVec[11].name), "NO", 2) == 0) ? NO : YES;
	strcpy(str, NXGetDefaultValue(DEFOWNER, fileVec[12].name));
	sscanf(str, "%s %f", fontStr, &red);
	prefFont = [Font newFont:fontStr size:red];
	prefSpytype = atoi(NXGetDefaultValue(DEFOWNER, fileVec[13].name));

	[self addFile:self];
	if(strncmp(NXGetDefaultValue(DEFOWNER, fileVec[8].name), "YES", 3) == 0) {
	    [[myMatrix selectedCell] displayWindow];
	    [self addWindow:self];
	}

    }
    displayCurWindow = YES;
    newFileOffset = YES;
    
    [myWindow setFrameFromString:NXGetDefaultValue(DEFOWNER, "myWindowFrame")];
    if(strncmp(NXGetDefaultValue(DEFOWNER, "myWindowVisible"), "YES", 3) == 0) {
	myWindowIsVisible = YES;
	[myWindow makeKeyAndOrderFront:self];
    } else
	myWindowIsVisible = NO;

    [existencelogWindow setFrameFromString:NXGetDefaultValue(DEFOWNER, "exWindowFrame")];
    if(strncmp(NXGetDefaultValue(DEFOWNER, "exWindowVisible"), "YES", 3) == 0) {
	[existencelogWindow makeKeyAndOrderFront:self];
    }

    prefPopup = tmpPopup;
    prefBeepOnChange = tmpBeepOnChange;
    prefSuperlog = tmpLogToSuperlog;
    prefUseFilter = tmpUseFilter;
    prefFileMode = tmpFileMode;
    prefDontCopySuperlog = (strncmp(NXGetDefaultValue(DEFOWNER, "prefDontCopySuperlog"), "NO", 2) == 0) ? NO : YES;
    prefSuperCopyFilename = atoi(NXGetDefaultValue(DEFOWNER, "prefSuperCopyFilename"));
    strcpy(str, NXGetDefaultValue(DEFOWNER, "prefColor"));
    sscanf(str, "%f,%f,%f", &red, &green, &blue);
    prefColor = NXConvertRGBToColor(red, green, blue);
    prefTimeStamp = (strncmp(NXGetDefaultValue(DEFOWNER, "prefTimeStamp"), "NO", 2) == 0) ? NO : YES;
    strcpy(str, NXGetDefaultValue(DEFOWNER, "prefFont"));
    sscanf(str, "%s %f", fontStr, &red);
    prefFont = [Font newFont:fontStr size:red];
    prefSpytype = tmpSpytype;

    if(strncmp(NXGetDefaultValue(DEFOWNER, "superWindowVisible"), "YES", 3) == 0)
	[superWindow makeKeyAndOrderFront:self];
    becomeKey = strncmp(NXGetDefaultValue(DEFOWNER, "becomeKey"), "NO", 2) == 0 ? NO : YES;
    saveState = strncmp(NXGetDefaultValue(DEFOWNER, "saveState"), "NO", 2) == 0 ? NO : YES;
    if(NXGetDefaultValue(DEFOWNER, "inspectorFrame") != (const char *)0) {
	inspectorFrame = (char *)malloc(NX_MAXFRAMESTRINGLENGTH*sizeof(char));
	strcpy(inspectorFrame, NXGetDefaultValue(DEFOWNER, "inspectorFrame"));
    }
    [self singleClick:self];	// update the filelist with all buttons

    return self;
}

- changeBeepState:sender;
{
    BOOL beepOnChange;
    unsigned int i, max;
    List *selectedCellsList = [tmpList empty];

    if([sender state] == 0)
	beepOnChange = NO;
    else
	beepOnChange = YES;
    [myMatrix getSelectedCells:selectedCellsList];
    max = [selectedCellsList count];
    for(i = 0; i < max; i++)
	[[selectedCellsList objectAt:i] setBeepOnChange:beepOnChange];
    return self;
}

- showSuperlog:sender
{
    [superWindow orderFront:self];
    return self;
}

- updateSuperlog:sender :(char *)newText :(BOOL)changed :(BOOL)beepThis :(BOOL)popThis
{
    long int tmpPos, superLen;
    char myText[strlen(newText) + MAXFILELEN], senderName[MAXFILELEN];

    if(*newText == '\000')	// if there's no new text then forget it
	return self;

    if(maxLineState)
	[self shortSuperText];
    if(noDuplicatedLines && (sender != lastSuperlogUser)) {	// if user doesn't want the same line from multiple log-files
    								// logged more than once, then don't append the line to superlog
	unsigned int len, len2, oldLineNumber = 0 , newLineNumber = 0, i ,j, k;
	NXSelPt start, end;
	char *oldText, *pos;
	char **oldLines, **newLines;
	
	[superText getSel:&start :&end];
	len = end.cp - start.cp;
	if(len != 0) {
	    oldText = (char *)alloca(sizeof(char) * (len + 1));
	    [superText getSubstring:oldText start:start.cp length:len];
	    oldText[len] = '\000';
	    for(pos = oldText; *pos != '\000'; ) {	// remove all \r from oldText
		if(*pos == '\n') {			// count number of lines
		    oldLineNumber++;
		    pos++;
		} else if(*pos == '\r')			// (they are inserted in some logfiles and in some not)
		    strcpy(pos, pos+1);
		else
		    pos++;
	    }
	    for(pos = newText; *pos != '\000'; ) {	// same for newtext
		if(*pos == '\n') {
		    newLineNumber++;
		    pos++;
		} else if(*pos == '\r')
		    strcpy(pos, pos+1);
		else
		    pos++;
	    }
	    	// get the starting positions of the lines
	    oldLines = (char **)malloc(oldLineNumber * sizeof(char *));
	    newLines = (char **)malloc(newLineNumber * sizeof(char *));
	    oldLineNumber = newLineNumber = 1;
	    oldLines[0] = oldText;
	    newLines[0] = newText;
	    for(pos = oldText; *pos != '\000'; pos++) {
		if((pos[0] == '\n') && (pos[1] != '\000'))
		    oldLines[oldLineNumber++] = pos+1;
	    }
	    for(pos = newText; *pos != '\000'; pos++) {
		if((pos[0] == '\n') && (pos[1] != '\000'))
		    newLines[newLineNumber++] = pos+1;
	    }
	    	// compare all the new lines with the old ones
	    for(i = 0; i < newLineNumber; i++) {	// for each line in the new text
		for(len = 0; (newLines[i][len] != '\n') && (newLines[i][len] != '\000'); len++) ;
		for(j = 0; j < oldLineNumber; j++) {
		    for(len2 = 0; (oldLines[j][len2] != '\n') && (oldLines[j][len2] != '\000'); len2++) ;
		    if((len == len2) && (strncmp(newLines[i], oldLines[j], len) == 0)) {
		    		// in case of a match, remove the new line
			for(k = i + 1; k < newLineNumber; k++)
			    strcpy(newLines[k-1], newLines[k]);
			len = newLines[i+1] - newLines[i];
			for(k = i+1; k < newLineNumber; k++)
			    newLines[k] -= len;
			newLineNumber--;
			i--;				// will be increased in the for loop, so adjust here
			if(i < newLineNumber)
			    break;
		    }
		}
	    }
	    if(newLineNumber == 0)	// if there are no new lines left, we don't have to add any text
		return self;
	}
    }
    if(changed || (filterMode == FILTER_DONTBEEP)) {
	if(changed) {		// if something passed the filter
	    if(superBeepOnChange && beepThis)	// and we prefer beeps in general and in this very special case
		[self nxbeep];			// then beep
	    if(superPopup && popThis) {		// if we prefer popups uf the superlogwindow and especially in this case...
		[superWindow orderFrontRegardless];
		if(becomeKey) {
		    [NXApp unhide:self];
		    [superWindow makeKeyWindow];
		}
	    }
	}
	if(![superWindow isKeyWindow] && ![superWindow isDocEdited])
	    [superWindow setDocEdited:YES];
	myText[0] = '\000';

	strcpy(senderName, [sender stringValue]);		// sender's filename
	superLen = [superText textLength];
	[superText setAutodisplay:NO];
	[superText setSel:superLen :superLen];

	switch([sender superCopyFilename]) {
	    case ONCE:
		if(sender != lastSuperlogUser) {
		    strcpy(myText, senderName);
		    strcat(myText, ":\n");
		} else
		    *myText = '\000';
		tmpPos = superLen + strlen(myText);
		strcat(myText, newText);
		[superText replaceSel :myText];
		[[superText setSel :superLen :tmpPos] underline:self];
		[superText setSel :superLen :superLen + strlen(myText) + 1];
		break;
	    case EACHLINE:{
		char tmpText[strlen(newText) + MAXFILELEN], *p, *cp, tmp;

		tmpPos = superLen;		// remember current length of superText to select new text afterwards
		strcpy(myText, senderName);
		strcat(myText, ":\t");		// tab better than space (Tremblin), maybe use rulers?
		cp = newText;
		while(*cp != '\000') {		// start each line with myText
		    strcpy(tmpText, myText);
		    p = cp;
		    while((*p != '\000') && (*p != '\n'))	// search end of line/string
			p++;
		    tmp = *p;
		    *p = '\000';
		    strcat(tmpText, cp);
		    strcat(tmpText, "\n");			// copy text up to there and append newline
		    if(tmp == '\n')				// go to next line if we haven't found end of string yet
			cp = p + 1;
		    else
			cp = p;
		    superLen = [superText textLength];
		    [superText setSel:superLen :superLen];
		    [superText replaceSel :tmpText];		// append this line to superText
		}
		[superText setSel :tmpPos :[superText textLength]];
		break;}
	    case NONE:
		[superText replaceSel :newText];
		[superText setSel :superLen :[superText textLength]];
		break;
#ifdef DEBUG
	    default:			// that doesn't happen anyway...
		printf("unmatched case-value\n");
#endif
	}
	[superText setSelColor:[sender color]];
	if([sender spyFont])
	    [superText setSelFont:[sender spyFont]];
	if([superText needsDisplay])
	    [superText display];
	[superText setAutodisplay:YES];
	[[[superText sizeToFit] scrollSelToVisible] display];
	if(!lastSuperlogUser && inspectorPanel && [inspectorPanel isVisible] && [superWindow isKeyWindow] && !myWindowIsVisible)
	    [self setInspectorToView:[inspectorNormalView contentView]];
	lastSuperlogUser = sender;
	    
    }
    return self;
}

- setupSuperlog
{
    superFont = [Font newFont:"Ohlfs"
		    size:10
		    style:0
		    matrix:NX_FLIPPEDMATRIX];
    [Text setDefaultFont:superFont];
    superDoc = [Document new:self];
    superWindow = [[superDoc scrollView] window];
    superScrollView = [superDoc scrollView];
    superText = [[superDoc scrollView] docView];
    [superWindow setFreeWhenClosed:NO];
    [superWindow setTitle:"Multilog"];
    [superWindow setDelegate:self];
    [superText setMonoFont:NO];

    return self;
}

- setupFilter
{
    filterStore = [[Storage alloc] initCount:0		// contains filter-lines
				    elementSize:128
				    description:"[128c]"];
    return self;
}

- showFilter:sender
{
    if(!filterWindow) {
	if(![NXApp loadNibSection:"Filter.nib" owner:self withNames:NO fromZone:[self zone]]) {
	    NXLogError("Can't open Filter.nib!");
	    return self;
	}
	[filterText setDelegate:self];
    }
    [filterModeMatrix selectCellAt:filterMode:0];
    if(!filterIsUpToDate) {
	unsigned int i, max;

	[filterText setSel:0 :[filterText textLength]];
	[filterText replaceSel:""];
	max = [filterStore count];
	for(i = 0; i < max; i++) {		// read lines from filterStore and insert them in the text in the filter-panel
	    char str[128];
	    int filterTextLen;

	    filterTextLen = [filterText textLength];
	    [filterText setSel:filterTextLen :filterTextLen];
	    strcpy(str, (char *)[filterStore elementAt:i]);
	    strcat(str, "\n");
	    [filterText replaceSel:str];
	}
	filterIsUpToDate = YES;
    }
    [filterWindow makeKeyAndOrderFront:self];
    return self;
}

- textDidGetKeys:sender isEmpty:(BOOL)flag
{
    if(sender == filterText) {
	if(![filterWindow isDocEdited])
	    [filterWindow setDocEdited:YES];
    }
    return self;
}

- applyFilter:sender		// copy all the filter-lines from the text-object to the filterStore
{
    int max,i, s, l;
    char str[128];

    [filterStore empty];
    max = [filterText lineFromPosition:[filterText textLength]];
    for(i = 1; i < max; i++) {
	s = [filterText positionFromLine:i];
	l = [filterText positionFromLine:i+1] - s;
	if(l > 127) {
	    [[[filterText setSel:s :127] scrollSelToVisible] display];
	    NXRunAlertPanel("Warning", "Filterline to long, string is truncated to 127 characters.", "Ok", NULL, NULL);
	    l = 127;
	}
	[filterText getSubstring:str
			start: s
			length: l];
	str[l-1] = '\000';	// without '\n' please
	if((str[0] == '|') && (strchr(str+1, '|') == NULL)) {	// illegal syntax for file-sensitive filterstrings
	    [[[filterText setSel:s :l] scrollSelToVisible] display];
	    NXRunAlertPanel("Alert", "Filename for file-sensitive filterstring must be between two '|'.", "Ok", NULL, NULL);
	} else {
	    [filterStore addElement:str];
	}
    }
    return self;
}


- (BOOL)filterString:(char *)str andRemove:(BOOL)rm who:sender	// returns YES if something passes the filter
{
    unsigned int i, max;
    char *from, *to, *ptr, ch;
    BOOL matched = NO, pass = NO;	// pass: TRUE if something passed the filter
    char *senderName = (char *)[sender stringValue];

    from = to = str;
    max = [filterStore count];
    while(*from != '\000') {
	ptr = from;
	while((*ptr != '\n') && (*ptr != '\000'))
		ptr++;
	ch = *ptr;	// in case that this line has no NL at the end
	*ptr = '\000';
	for(i = 0; i < max; i++) {		// go through all filter-lines
	    char *filtStr = (char *)[filterStore elementAt:i], *tmpStr = filtStr;
	    
	    if(((filtStr = strchr(filtStr, '|')) != (char *)0) &&
			(filtStr[1] != '|')) {	// file-sensitive filterstring
		*filtStr = '\000';
		if(!wildmat(senderName, tmpStr)) {
		    *filtStr = '|';
		    break;	// if we're doing file-sensitive filtering and this is the wrong file, then skip filterline
		}
		*filtStr++ = '|';	// restore filterstring and advance to real filterstring (the one after the filename)
	    } else
		filtStr = tmpStr;
	    if(wildmat(from, filtStr)) {
		matched = TRUE;
		break;
	    }
	}
	if(!matched && rm) {	// if we didn't match anything but are removing matches line, we have to copy the whole line
	    while(*from != '\000')
		*to++ = *from++;
	    *to++ = ch;
	    from++;
	    while(*from == '\r')
		from++;
	} else {	
	    from = (char *)(ptr + 1);
	    *ptr = ch;
	    while(*from == '\r') {		// remove '\r's from begin of line
		ptr = from;
		while(ptr[1] != '\000') {
		    *ptr++ = ptr[1];
		}
		*ptr = ptr[1];
	    }
	}
	if(!matched)
	    pass = YES;
    }
    if(rm)
	*to = '\000';
    return pass;
}

- (int)filterMode
{
    return filterMode;
}

- (BOOL)superUsesFilter
{
    return superUseFilter;
}

- (BOOL)becomeKey
{
    return becomeKey;
}

- unhideApp:sender
{
    [NXApp unhide:self];
    return self;
}

- clearBuffer:sender
{
    id keyWindow = [NXApp keyWindow];
    
    if((keyWindow != nil) && (keyWindow != myWindow) && (keyWindow != superWindow)) {
	[[keyWindow delegate] clearBuffer:self];
    } else if(keyWindow == myWindow) {
	List *selectedCellsList = [tmpList empty];
	unsigned int i;

	[myMatrix getSelectedCells:selectedCellsList];
	for(i = 0; i < [selectedCellsList count]; i++)
	    [[selectedCellsList objectAt:i] clearBuffer:self];
    } else if(keyWindow == superWindow) {
	[superText setSel :0 :[superText textLength]];
	[superText replaceSel:""];
	[[superText sizeToFit] scrollSelToVisible];
	if(lastSuperlogUser && inspectorPanel && [inspectorPanel isVisible] && !myWindowIsVisible && [superWindow isKeyWindow]) {
	    // if there is already something in the superlog AND the inspectorPanel is already loaded AND it is visible
	    // AND the mainwindow is not visible AND the (now empty) superlogwindow is the keywindow
	    // THEN the inspectorpanel doesn't know which file it should inspect...
	    [self setInspectorToView:[inspectorNoView contentView]];
	}
	lastSuperlogUser = nil;
    }

    return self;
}

- windowDidBecomeKey:sender
{
    if(sender == myWindow) {
	myWindowIsVisible = YES;
	if([myMatrix selectedCell] != nil) {
	    [self setInspectorToView:[inspectorNormalView contentView]];
	    [self singleClick:self];
	}
	return self;
    } else if(sender == superWindow) {
	if([superWindow isDocEdited])
	    [superWindow setDocEdited:NO];
	if(!myWindowIsVisible && inspectorPanel && [inspectorPanel isVisible]) {
	    if(lastSuperlogUser) {
	    	// if the superwindow just became the keywindow and the mainwindow is not visible but the
		// inspectorpanel is, then set the inspector to the last file that logged something in the superlog
		[self setInspectorToView:[inspectorNormalView contentView]];
		[myMatrix selectCell:lastSuperlogUser];
		[self singleClick:self];
	    } else {
		// if nothing is in the superlog, then set the inspector to the empty view
	    	[self setInspectorToView:[inspectorNoView contentView]];
	    }
	}
    } else if(sender == inspectorPanel) {
	if(myWindowIsVisible) {
	    if([myMatrix cellCount] > 0) {
		[myFontManager setSelFont:[[myMatrix selectedCell] spyFont] isMultiple:NO];
		[self setInspectorToView:[inspectorNormalView contentView]];
	    } else {
		[self setInspectorToView:[inspectorNoView contentView]];
	    }
	} else if((nrOpenWindows > 0) || ([superWindow isVisible] && lastSuperlogUser)) {
	    [self setInspectorToView:[inspectorNormalView contentView]];
	} else {
	    [self setInspectorToView:[inspectorNoView contentView]];
	}
    } else if(sender == preferencesPanel) {
	[myFontManager setSelFont:prefFont isMultiple:NO];
    } else if(sender == existencelogWindow) {
	existenceSeen = YES;			// existenceWindow has been seen by the user
    } else if(!myWindowIsVisible && inspectorPanel && [inspectorPanel isVisible]
		&& ([[sender delegate] class] == [SpyTextFieldCell class])) {
	// if the mainwindow is not visible but the inspectorpanel is and the window which just became the
	// keywindow is a normal window, then update the inspector the the right file
	[myMatrix selectCell:[sender delegate]];
	[self singleClick:self];
	[self setInspectorToView:[inspectorNormalView contentView]];
    }
    return self;
}

- windowWillClose:sender
{
    Window *keyWindow;
    if(sender == myWindow) {
	myWindowIsVisible = NO;
	keyWindow = [NXApp keyWindow];
	if((keyWindow != myWindow) && (keyWindow != nil)
		    && (([[keyWindow delegate] class] == [SpyTextFieldCell class]) || (keyWindow == superWindow))) {
	    [self windowDidBecomeKey:keyWindow];
	} else if(nrOpenWindows == 0) {
	    [self setInspectorToView:[inspectorNoView contentView]];
	}
    } else if(sender == existencelogWindow) {
	existenceSeen = YES;		// we assume that the user has noticed eventual changes in the existencelogWindow
    } else if([[sender delegate] class] == [SpyTextFieldCell class]) {
	if(nrOpenWindows > 0) {
	    if((--nrOpenWindows == 0) && !myWindowIsVisible && ![superWindow isVisible]) {
		// if this was tha last open window (except the panels) then clear the inspectorpanel
		[self setInspectorToView:[inspectorNoView contentView]];
	    }
	}
    } else if((sender == superWindow) && (nrOpenWindows == 0)) {
	[self setInspectorToView:[inspectorNoView contentView]];
    }
    return self;
}

- showInspectorPanel:sender
{
    if(!inspectorPanel) {
	if(![NXApp loadNibSection:"Inspector.nib" owner:self withNames:NO fromZone:[self zone]]) {
	    NXLogError("Can't open Inspector.nib!");
	}
	if(inspectorFrame != (char *)0) {	// if we read a window-position for the inspectorpanel from the defaults database,
						// then set the window accordingly
	    [inspectorPanel setFrameFromString:inspectorFrame];
	    free(inspectorFrame);
	}
	if((myWindowIsVisible && ([myMatrix cellCount] > 0) && ([myMatrix selectedCell] != nil))
			|| (nrOpenWindows > 0) || ([superWindow isVisible] && lastSuperlogUser)) {
	    [self setInspectorToView:[inspectorNormalView contentView]];
	} else {
	    [self setInspectorToView:[inspectorNoView contentView]];
	}
    }
    [self singleClick:self];
    [inspectorPanel makeKeyAndOrderFront:self];
    return self;
}

- setInspectorToView:theView
{
    NXRect boxRect, viewRect;
    
    if([inspectorMultiView contentView] != theView) {
	[inspectorMultiView getFrame:&boxRect];
	[theView getFrame:&viewRect];
	[inspectorMultiView setContentView:theView];
	NX_X(&viewRect) = (NX_WIDTH(&boxRect) - NX_WIDTH(&viewRect)) / 2.0;
	NX_Y(&viewRect) = (NX_HEIGHT(&boxRect) - NX_HEIGHT(&viewRect)) / 2.0;
	[theView setFrame:&viewRect];
	[inspectorMultiView display];
    }
    return self;
}

- showFindPanel:sender
{
    [finder showFindPanel:self];
    return self;
}

- findNext:sender
{
    [finder findNext:self];
    return self;
}

- findPrevious:sender
{
    [finder findPrevious:self];
    return self;
}

- enterSelection:sender
{
    [finder enterSelection:self];
    return self;
}

- jumpToSelection:sender
{
    const id mainWindow = [NXApp mainWindow];

    if(mainWindow == superWindow)
	[superText makeSelectionVisible];
    else if(mainWindow == filterWindow)
	[filterText makeSelectionVisible];
    else if([[mainWindow delegate] class] == [SpyTextFieldCell class])
	[[[mainWindow delegate] text] makeSelectionVisible];
    return self;
}

- findTexts:(BOOL)all
{
    List *textList;
    const id mainWindow = [NXApp mainWindow];

    textList = [[List alloc] init];
    if(all) {			// return ALL text objects you have
	unsigned int i;
	List *cellList;

	cellList = [myMatrix cellList];
	if(mainWindow == superWindow)			// if the superlogWindow is main, search there first
	    [textList addObject:superText];
	else if(mainWindow == filterWindow)
	    [textList addObject:filterText];
	else if(mainWindow == helpWindow)
	    [textList addObject:helpText];
	else if((mainWindow != nil) && ([[mainWindow delegate] class] == [SpyTextFieldCell class]))
	    [textList addObject:[[mainWindow delegate] text]];	// then search in the mainWindows text
	for(i = 0; i < [cellList count]; i++)			// add all other texts
	    [textList addObjectIfAbsent:[[cellList objectAt:i] text]];
	[textList addObjectIfAbsent:superText];			// if the superWindow is not opened, add it at the end
	[textList addObjectIfAbsent:filterText];
	return textList;
    } else if(mainWindow == myWindow) {		// return only the text objects currently selected in the matrix
	[myMatrix getSelectedCells:textList];
	return textList;
    } else if(mainWindow == superWindow) {
	[textList addObject:superText];
	return textList;
    } else if(mainWindow == filterWindow) {
	[textList addObject:filterText];
	return textList;
    } else if(mainWindow == helpWindow) {
	[textList addObject:helpText];
	return textList;
    } else if((mainWindow != nil) && ([[mainWindow delegate] class] == [SpyTextFieldCell class])) {
	[textList addObject:[[mainWindow delegate] text]];
	return textList;
    } else {
	[textList free];
	return nil;
    }
}

- superFilenameClick:sender
{
    List *selectedCellsList = [tmpList empty];
    unsigned int i, max, state = [[sender selectedCell] tag]; 
    PopUpList *popupList = [[superFilenameButton cell] target];
    Matrix *popupMatrix = [popupList itemList];

    [myMatrix getSelectedCells:selectedCellsList];
    max = [selectedCellsList count];
    for(i = 0; i < max; i++)
	[[selectedCellsList objectAt:i] setSuperCopyFilename:state];
    
    [popupMatrix selectCellWithTag:state];
    [superFilenameButton setTitle:[[popupMatrix findCellWithTag:state] title]];

    return self;
}

- prefSuperFilenameClick:sender
{
    PopUpList *popupList = [[prefSuperFilenameButton cell] target];
    Matrix *popupMatrix = [popupList itemList];
    unsigned int state = [[sender selectedCell] tag];

    prefSuperCopyFilename = state;
    [popupMatrix selectCellWithTag:state];
    [prefSuperFilenameButton setTitle:[[popupMatrix findCellWithTag:state] title]];
    return self;
}

- timeClick:sender
{
    List *selectedCellsList;
    unsigned int i, max;
    BOOL state = [sender state];

    selectedCellsList = [tmpList empty];
    [myMatrix getSelectedCells:selectedCellsList];
    max = [selectedCellsList count];
    for(i = 0; i < max; i++)
	[[selectedCellsList objectAt:i] setTimeStamp:state];
    return self;
}

- prefTimeClick:sender
{
    prefTimeStamp = [sender state];
    return self;
}

- spytypeClick:sender
{
    unsigned int i, max;
    int tag = [[sender selectedCell] tag];
    
    [tmpList empty];
    [myMatrix getSelectedCells:tmpList];
    max = [tmpList count];
    for(i = 0; i < max; i++)
	[[tmpList objectAt:i] setSpytype:(unsigned short int)tag];
    [myMatrix display];
    return self;
}

- prefSpytypeClick:sender
{
    prefSpytype = (unsigned short int)[[sender selectedCell] tag];
    return self;
}

- showExistenceLog:sender
{
    return self;
}

- colorChanged:sender
{
    List *selectedCellsList = [tmpList empty];
    unsigned int i, max;
    NXColor color = [superColorWell color];

    [myMatrix getSelectedCells:selectedCellsList];
    max = [selectedCellsList count];
    for(i = 0; i < max; i++) {
	[[selectedCellsList objectAt:i] setColor:color];
    }
    [myMatrix display];
    return self;
}

- prefColorChanged:sender
{
    prefColor = [sender color];
    return self;
}

- (NXColor)prefColor
{
    return prefColor;
}

- changeFont:sender
{
    List *selectedCellsList = [tmpList empty];
    unsigned int i, max;
    id actCell;

    if([NXApp keyWindow] != preferencesPanel) {
	[fontTextField setFont:[sender convertFont:[fontTextField font]]];
	[myMatrix getSelectedCells:selectedCellsList];
	max = [selectedCellsList count];
	for(i = 0; i < max; i++) {
	    actCell = [selectedCellsList objectAt:i];
	    [actCell setSpyFont:[sender convertFont:[actCell spyFont]]];
	}
    } else {
	[prefFontTextField setFont:[sender convertFont:[prefFontTextField font]]];
	prefFont = [prefFontTextField font];
    }
    return self;
}

- showFontPanel:sender
{
    return [myFontManager orderFrontFontPanel:sender];
}

- setMaxLines:sender
{
    int oldMaxLines = maxLines;
    if((sender == maxLinesScroller) || (sender == maxLinesTextField)) {
	maxLines = [sender intValue];
	if(maxLines < 10)
	    maxLines = 10;
	if(maxLines > INT_MAX)
	    maxLines = INT_MAX;
	if(sender == maxLinesScroller)			// set the other one to the correct value
	    [maxLinesTextField setIntValue:maxLines];
	else
	    [maxLinesScroller setIntValue:maxLines];
    } else {						// so it must be the matrix of ButtonCells
	maxLineState = [[sender selectedCell] tag];
	[maxLinesScroller setEnabled:maxLineState];
	[maxLinesTextField setEnabled:maxLineState];
    }
    if(oldMaxLines != maxLines) {
	List *selectedCellsList = [tmpList empty];
	unsigned int i, max;
	int state = (maxLineState ? maxLines : 0);

	[myMatrix getSelectedCells:selectedCellsList];
	max = [selectedCellsList count];
	for(i = 0; i < max; i++)
	    [(SpyTextFieldCell *)[selectedCellsList objectAt:i] setMaxLines:state];
    }
    return self;
}

- (int)maxLines;
{
    return maxLines;
}

- shortSuperText
{
    int textLen = [superText textLength];
    int textLines = [superText lineFromPosition:textLen];
#ifdef DEBUG
    assert(maxLines != 0);
#endif
    if(textLines > maxLines)
	[[superText setSel:0 :[superText positionFromLine:textLines - maxLines]] replaceSel:""];
    return self;
}

- addWindow:sender;
{
    if(nrOpenWindows++ == 0) {
	if(!myWindowIsVisible && inspectorPanel && [inspectorPanel isVisible]) {
	    [self setInspectorToView:[inspectorNormalView contentView]];
	    [myMatrix selectCell:sender];
	    [self singleClick:self];
	}
    }
    return self;
}

- existenceMatrix
{
    return myExistenceMatrix;
}

- showExistencelogRegardless
{
    [existencelogWindow orderFrontRegardless];
    return self;
}

- window
{
    return [NXApp mainWindow];
}

@end

void alarmHandler(DPSTimedEntry te, double timeNow, void *data)
{
    [(id)data checkFiles];
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.