ftp.nice.ch/pub/next/tools/screen/backspace/Fortune.NIHS.bs.tar.gz#/FortuneView.BackModule/FortuneView.m

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

#import "FortuneView.h"
#import <math.h>
#import <ctype.h>
#import "Thinker.h"

// this code is copyright Darcy Brockbank, 1994
//
// You may freely reuse and distribute this code in any way shape or
// form, provided that this notice stays intact.
//
// darcy@hasc.ca, samurai@cs.mcgill.ca
//
// If you do improve this thing, send me a copy!
//
// - darcy

// v1.3 (Joe Reiss) <jreiss@magnus.acs.ohio-state.edu>
// + Converted database selector from radio buttons to popup list
// + Moved list of databases from source code to disk file
// + Added Babylon 5 quotes

#define INCLUDED (char)0
#define EXCLUDED (char)1

#define SHADOW 0
#define HIGHLIGHT 1
#define PLAIN 2

#define SPEED		"FortuneSpeed"
#define USECOLOR	"FortuneUseColor"
#define F_FILE		"FortuneFile"
#define BACKING		"FortuneBacking"
#define CLEARAFTER	"FortuneClearAfter"
#define EXFONTS		"FortuneExcludedFonts"

#define ROT(c) ((isupper(c))? ('A' + (c - 'A' + 13) % 26) : \
((islower(c)) ? ('a' + (c - 'a' + 13) % 26) : c))

@interface ThreeField : TextField
{
    int drawBacking;
}

- setDrawBacking:(int)flag;
- drawSelf:(const NXRect *)r :(int)c;

@end

@implementation ThreeField

- setDrawBacking:(int)flag;
{
    drawBacking = flag;
    return self;
}


- drawSelf:(const NXRect *)r :(int)c;
{
    NXColor realColor = [self textColor];
    NXRect hoffset=*r;
    if (strlen([self stringValue])==0)
      {
	NXSetColor(NX_COLORBLACK);
	NXRectFill(r);
      }
    else if (drawBacking==HIGHLIGHT)
      {
	hoffset.origin.x+=1.0;
	hoffset.origin.y+=1.0;
	[cell setTextColor:NX_COLORWHITE];
	[cell drawInside:&hoffset inView:self];
	[cell setTextColor:realColor];
	[cell drawInside:r inView:self];
      }
    else
      {
	[cell drawInside:r inView:self];
      }
    return self;
}

@end

@interface GrayCell : NXBrowserCell
{
    BOOL isGray;
}
- setGrayed:(BOOL)flag;
@end

@implementation GrayCell
- setGrayed:(BOOL)flag;
{
    isGray = flag;
    return self;
}

- setTextAttributes:sender;
{
    [super setTextAttributes:sender];
    if (isGray) [sender setTextGray:NX_DKGRAY];
    return self;
}

@end

@implementation FortuneView

#define MINX 0.0
#define MINY 0.0
#define SIZE 36.0

typedef struct fortDB {
    char	*title;
    char	*fname;
    int		offend;
    struct fortDB *next;
  } FORTDB;
FORTDB		*fdbs=NULL;


- newDatabase:sender;
{
    FORTDB *walk;
    int sel = [[sender selectedCell] tag],i;

    for (i=0,walk=fdbs; i<sel; i++,walk=walk->next)
	;
    if (walk->offend)
      {
	if (NXRunAlertPanel(
	  "Be sure...", "This option will print fortune entries on the screen "
	  "which some people will find offensive (hence the name). If you "
	  "are offended by offcolor jokes, or sexually explicit remarks, "
	  "or are easily offended at all, then I suggest you not use this option. "
	  "The fact that these fortunes are included as part of this screen "
	  "saver does not imply that I condone or agree with any of them. "
	  "They are included since they were part of the Berkeley 'fortune' package.",
	  "Continue","Cancel Selection",0)==NX_ALERTALTERNATE)
	  {
	    [databaseTrigger
	     setTitle: [[[databaseMatrix itemList]
			 findCellWithTag:fortuneFile] title]];
	    [[databaseMatrix itemList] selectCellWithTag:fortuneFile];
	    return self;
	  }
      }
    fortuneFile = sel;
    [self loadWords:walk->fname];
    [self perform:@selector(writeFileDefault:)
     with:self
     afterDelay:1000
     cancelPrevious:YES];
    [self lockFocus];
    [self doDrawing];
    [self unlockFocus];
    return self;
}


- writeThresholdDefault:sender
{
    char tmp[128];
    sprintf(tmp,"%u",threshold);
    NXWriteDefault([NXApp appName],SPEED,tmp);
    return self;
}

- writeColorDefault:sender
{
    char tmp[128];
    sprintf(tmp,"%s",(useColor)?"YES":"NO");
    NXWriteDefault([NXApp appName],USECOLOR,tmp);
    return self;
}

- writeFileDefault:sender
{
    char tmp[128];
    sprintf(tmp,"%d",fortuneFile);
    NXWriteDefault([NXApp appName],F_FILE,tmp);
    return self;
}

- writeBackingDefault:sender
{
    char tmp[128];
    sprintf(tmp,"%d",gray);
    NXWriteDefault([NXApp appName],BACKING,tmp);
    return self;
}


- setThreshold:sender
{
    threshold = [sender intValue];
    [self perform:@selector(writeThresholdDefault:)
     with:self
     afterDelay:1000
     cancelPrevious:YES];
    return self;
}

- setSpeedSlider:sender;
{
    speedSlider = sender;
    [sender setIntValue:threshold];
    return self;
}


- loadFonts;
{
    fontNames = [[FontManager new] availableFonts];
    if (fontNames)
      {
	for(numFonts = 0;fontNames[numFonts];numFonts++)
	    ;
      }
    return self;
}


- loadWords:(const char *)database;
{
    char buf[MAXPATHLEN];
	
    if (fortuneStream)
      {
	NXCloseMemory(fortuneStream,NX_FREEBUFFER);
      } 
    sprintf(buf,"%s/db/%s",[appowner moduleDirectory:"Fortune"],database);
    fortuneStream = NXMapFile(buf,NX_READONLY);
    if (fortuneStream)
      {
	NXGetMemoryBuffer(fortuneStream,&words,&len,&maxlen);
      }
    else
      {
	NXRunAlertPanel("File not found.",
			"I couldn't locate the fortune database "
			"'%s'",NULL,NULL,"OK",buf);
      }
    return self;
}

- (BOOL)isExcluded:(const char *)fontName
{
    int i,count = numFonts;
    for(i=0;i<count;i++)
      {
	if (strcmp(fontName,fontNames[i])==0)
	  {
	    if (excluded[i]==EXCLUDED)
	      {
		return YES;
	      }
	    else
	      {
		break;
	      }
	  }
      }
    return NO;
}

- prepFont
{
    float size = SIZE;
    int fn;
    fn = ((unsigned)random()) % numFonts;

    while([self isExcluded:fontNames[fn]])
      {
	fn++;
	if (!fontNames[fn]) fn=0;
      }
    fontNum = fn;
    font = [Font newFont:fontNames[fn] size:size matrix:NX_FLIPPEDMATRIX];
    return self;
}

- drawSelf:(const NXRect *)r :(int)c
{
    if (!r || !c || c==2) return self;

    NXSetColor(NX_COLORBLACK);
    NXRectFill(r);
    [self doDrawing];
    return self;
}

- prepColor;
{
    if (useColor)
      {
	blue = ((float)((unsigned)random()))/(float)INT_MAX;
	red = ((float)((unsigned)random()))/(float)INT_MAX;
	green = ((float)((unsigned)random()))/(float)INT_MAX;
      }
    else
      {
	blue = red = green = (((unsigned)random())%3) / 3.0 + (1.0/3.0);
      }
    currentColor = NXConvertRGBToColor(red,blue,green);
    return self;
}


- prepPosition;
{
    xpos = (((unsigned)random()) % (unsigned)((maxCoord.x-MINX))) +MINX;
    ypos = (((unsigned)random()) % (unsigned)((maxCoord.y-MINY))) +MINY;
    return self;
}

- prepWord;
{
    FORTDB *walk;
    unsigned i = ((unsigned)random()) % (maxlen-1);
    int j,n;
    numWords = 0;
    do
      {
	i--;
	for(i=(i)?i:i-1;(words[i]!='\n' && i);i--);
      } while (i && words[i-1]!='%');
    for(j=0,i++;i<maxlen && !(words[i]=='\n' && words[i+1]=='%');i++,j++)
      {
	if (j>=maxWordLen)
	  {
	    if (!maxWordLen) maxWordLen = 256;
	    currentWord = realloc(currentWord,maxWordLen*2);
	    maxWordLen*=2;
	  }
	for (n=0,walk=fdbs; n<fortuneFile; n++,walk=walk->next)
	    ;
	if (walk->offend)
	  {
	    currentWord[j]=ROT(words[i]);
	  }
	else
	  {
	    currentWord[j]=words[i];
	  }
	if (currentWord[j]==' ' || currentWord[j]=='\t')
	  {
	    numWords++;
	  }
      }
    currentWord[j]='\0';
    return self;
}

- prep;
{
    [self prepFont];
    [self prepColor];
    [self prepPosition];
    [self prepWord];
    return self;
}

- setGray:sender
{
    gray = [sender state];
    [fe setDrawBacking:gray];
    [self perform:@selector(writeBackingDefault:)
     with:self
     afterDelay:1000
     cancelPrevious:YES];
    [self lockFocus];
    [self doDrawing];
    [self unlockFocus];
    return self;
}

- useColor:sender;
{
    useColor = [sender state];
    [self perform:@selector(writeColorDefault:)
     with:self afterDelay:1000 cancelPrevious:YES];
    return self;
}

- setHighlightSwitch:sender
{
    highlightSwitch = sender;
    [highlightSwitch setState:gray];
    return self;
}

static void do_size(void)
{
#ifdef TEST1
    system("ps -u | grep BackSpace.app | grep -v grep | awk ' { print \"Size:     V=\"$5\", R=\"$6 } '");
#endif
}
       
- setDatabaseMatrix:sender;
{
    databaseMatrix = sender;
    [[databaseMatrix itemList] selectCellWithTag:fortuneFile];
    [databaseTrigger
     setTitle: [[[databaseMatrix itemList]
		 findCellWithTag:fortuneFile] title]];
    return self;
}

- doDrawing;
{
    static const char * oldCurrent = 0;
    NXRect dr = bounds;

    do_size();
    [self prep];
    [window disableFlushWindow];
    [fe setStringValue:""];
    [fe display];
    dr = bounds;
    dr.size.width-=10.0;
    [fe setFrame:&dr];
    [window disableDisplay];
    [fe setTextColor:currentColor];
    while(1){
      int sz = [font pointSize];
      if (sz<=2) {
	/*
	 * problem in NeXT's stuff it seems. Too many
	 * tabs in the TextFields will make the width
	 * impossible to calculate. If this happens,
	 * then we just try to do our best.
	 */
				
	font = [Font newFont:fontNames[fontNum] size:8 matrix:[font matrix]];
	[fe setFrame:&bounds];
	[fe setFont:font];
#ifdef TEST2
	printf("%s\n",currentWord);
#endif
	break;
      }
#ifdef TESTING
      [fe getFrame:&dr];
      printf("Original font: (%s,%d) \t: {{%.0f,%.0f},{%.0f,%.0f}}\n",
	     [font familyName],sz,dr.origin.x,dr.origin.y,dr.size.width,dr.size.height);
#endif
      [fe setFont:font];
      [fe setStringValueNoCopy:currentWord];
      [fe sizeToFit];
      [fe getFrame:&dr];
#ifdef TESTING
      printf("Sized to fit ================\t: {{%.0f,%.0f},{%.0f,%.0f}}\n",
	     dr.origin.x,dr.origin.y,dr.size.width,dr.size.height);
#endif
      if (dr.size.width>(bounds.size.width-10.0)) {
	font = [Font newFont:fontNames[fontNum] size:sz-2 matrix:[font matrix]];
      } else if (dr.size.height>=bounds.size.height) {
	font = [Font newFont:fontNames[fontNum] size:sz-2 matrix:[font matrix]];
      } else {
	break;
      }
    }
    [window reenableDisplay];
    [fe moveTo:(bounds.size.width-dr.size.width)/2.0
     :(bounds.size.height-dr.size.height)/2.0];
    [fe display];
    [[window reenableFlushWindow] flushWindow];
    oldCurrent = currentWord;
    return self;
}

- oneStep
{
    static unsigned lastTime = 0;
    unsigned thisTime = currentTimeInMs();
	
    if (thisTime-lastTime<(threshold*numWords)) return self;
    lastTime = thisTime;
    if (!appowner) return self;
    [window makeFirstResponder:fe];
    [self doDrawing];
    return self;
}


- (BOOL)useBufferedWindow
{
    return YES;
}

- allocTextObject;
{
    id tf = [[ThreeField alloc] initFrame:&frame];
    [tf allocateGState];
    [tf setClipping:NO];
    [tf setBezeled:NO];
    [tf setBordered:NO];
//	[tf setBackgroundGray:NX_BLACK];
    [tf setBackgroundTransparent:YES];
    [tf setAutodisplay:NO];
    [tf setEditable:NO];
    [tf setSelectable:YES];
    [self addSubview:tf];
    return tf;
}


- setup;
{
    FORTDB *walk;
    char buf[MAXPATHLEN];
    int i;
    
    appowner = BSThinker();
    sprintf(buf,"%s/FortuneView.nib",[appowner moduleDirectory:"Fortune"]);
    [NXApp loadNibFile:buf owner:self withNames:NO];
    if (fdbs == NULL)
	[self loadDBList];
    for (i=0,walk=fdbs; i<fortuneFile; i++,walk=walk->next)
	;
    [self loadWords:walk->fname];
    [speedSlider setIntValue:threshold];
    [highlightSwitch setState:gray];
    fe = [self allocTextObject];
    [fe setDrawBacking:gray];
    [colorSwitch setState:useColor];
    [availableBrowser setCellClass:[GrayCell class]];
    [[databaseMatrix itemList] selectCellWithTag:fortuneFile];
    [databaseTrigger
     setTitle: [[[databaseMatrix itemList]
		 findCellWithTag:fortuneFile] title]];
    [databaseMatrix setAction:@selector(newDatabase:)];
    [databaseMatrix setTarget:self];
    return self;
}

- loadDBList;
{
    char buf[MAXPATHLEN];
    NXStream *dblistStream;
    char *list;
    int	listlen,listmlen;
    FORTDB *walk,*prev;
    int i,n;

    sprintf(buf,"%s/FortuneView.dblist",
	    [appowner moduleDirectory:"Fortune"]);
    dblistStream = NXMapFile(buf,NX_READONLY);
    if (dblistStream)
      {
	NXGetMemoryBuffer(dblistStream,&list,&listlen,&listmlen);
      }
    else
      {
	NXRunAlertPanel("File not found.",
			"I couldn't locate the fortune database list",
			NULL,NULL,"OK",buf);
	return self;
      }
    prev = walk = fdbs;
    n = 0;
    /* FIXME */
    for (i=0; i<listmlen; )
      {
	prev=walk;
	walk=(FORTDB *)malloc(sizeof(FORTDB));
	walk->next = NULL;
	if (prev == NULL)
	    fdbs = walk;
	else
	    prev->next = walk;

	if (walk->offend = (list[i] == '?'))
	    i++;

	walk->fname=list+i;
	while (!isspace(list[i]))	// Get past the file name
	    i++;
	list[i++] = '\0';		// Null terminate filename
	while (isspace(list[i]))	// Skip any whitespace after file
	    i++;
	walk->title=list+i;
	while (i<listmlen && list[i] != '\n')	// Go to end of line
	    i++;
	list[i] = '\0';			// Null terminate title
	i++;
	[[databaseMatrix addItem:walk->title] setTag:n++];
      }
    if (fdbs == NULL)
	[databaseTrigger setEnabled:NO];
    else
	[databaseMatrix removeItemAt:0];
    return self;
}

#define STRDUP(a) ((a)?(strcpy(malloc((strlen(a)+1)*sizeof(char)),a)):0)

- writeExcludedDefault:sender
{
    int i,length,count = numFonts;
    char * def;
    char * out;
    for(length=i=0;i<count;i++){
      if (excluded[i] == EXCLUDED){
	length += (strlen(fontNames[i])+4);
      }
    }
    def = malloc((length+1) * sizeof(char));
    *def='\0';
    for(out=def,length=i=0;i<count;i++){
      if (excluded[i] == EXCLUDED){
	const char * current = fontNames[i];
	length = strlen(current);
	strcat(out,current);
	if (i<(count-1)) strcat(out,"; ");
	out+=length+2;
      }
    }
    NXWriteDefault([NXApp appName],EXFONTS,def);
    free(def);
    return self;
}

- showFonts:sender;
{
    [fontWindow makeKeyAndOrderFront:self];
    return self;
}

- (int)browser:sender fillMatrix:matrix inColumn:(int)col;
{
    int i;
    [matrix renewRows:0 cols:0];
    /*
     * I've had some trouble doing this through the browser.
     */
    [[matrix setPrototype:[[GrayCell alloc] init]] free];
    for(i=0; fontNames[i]; i++){
      id cell;
      [matrix addRow];
      cell = [matrix cellAt:i:0];
      [cell setStringValueNoCopy:fontNames[i]];
      [cell setGrayed:[self isExcluded:fontNames[i]]];
      [cell setLoaded:YES];
      [cell setLeaf:YES];
    }
    return i;
}

- setAvailableBrowser:sender;
{
    availableBrowser = sender;
    [sender setTarget:self];
    [sender setDoubleAction:@selector(toggleFont:)];
    return self;
}

- toggleFont:sender;
{
    id matrix = [availableBrowser matrixInColumn:0];
    id cell = [matrix cellAt:[matrix selectedRow]:0];
    if ([self isExcluded:[cell stringValue]]){
      [self removeFont:self];
    } else {
      [self addFont:self];
    }
    return self;
}

/*
 * ADD to the excluded list!
 */
- addFont:sender;
{
    id matrix = [availableBrowser matrixInColumn:0];
    id cell = [matrix cellAt:[matrix selectedRow]:0];

    int i,count;
    for(count=i=0;i<numFonts;i++) count+=(excluded[i]==INCLUDED);
    if (count==1) {
      NXRunAlertPanel("Hey!", "You have to keep at least one font around!",0,0,0);
    } else {
      excluded[[matrix selectedRow]]=EXCLUDED;
      [[availableBrowser window] disableFlushWindow];
      [cell setGrayed:YES];
      [availableBrowser display];
      [[[availableBrowser window] reenableFlushWindow] flushWindow];
      [self perform:@selector(writeExcludedDefault:) with:self afterDelay:1000 cancelPrevious:YES];
    }
    return self;
}

/*
 * REMOVE from the excluded list!
 */
- removeFont:sender;
{
    int col = [availableBrowser selectedColumn];
    if (col==0) {
      id matrix = [availableBrowser matrixInColumn:col];
      int row = [matrix  selectedRow]; 
      if (row>=0){
	excluded[row]=INCLUDED;
	[self perform:@selector(writeExcludedDefault:) with:self afterDelay:1000 cancelPrevious:YES];
	[[availableBrowser window] disableFlushWindow];
	[[matrix cellAt:row:0] setGrayed:NO];
	[availableBrowser display];
	[[[availableBrowser window] reenableFlushWindow] flushWindow];
      }
    }
    return self;
}

- excludeFont:(const char *)p;
{
    int i;
    for(i=0;fontNames[i];i++){
      if (strcmp(p,fontNames[i])==0){
	excluded[i]=EXCLUDED;
	break;
      }
    }
    return self;
}
		
- setExcludedFonts:(const char *)list;
{
    char array[strlen(list)+1];
    char * p;
    int i;

    excluded = (char *)malloc(sizeof(char)*numFonts);
    for(i=0;i<numFonts;i++) excluded[i]=INCLUDED;
    strcpy(array,list);
    for(i=0,p=array;;i++){
      if (array[i]==';' || array[i]=='\0') {
	int j;
	BOOL shouldBreak = (array[i]=='\0');
	array[i]='\0';
	for(j=i-1;(array[j]=='\t' || array[j]==' ');j--) array[j]='\0';
	[self excludeFont:p];
	if (shouldBreak) break;
	p=array+i+1;
	while(*p==' ' || *p=='\t') p++;
      }
    }
    return self;
}
			
- initFrame:(const NXRect *)frameRect
{
    const char *t;
    [super initFrame:frameRect];
    [self allocateGState];		// For faster lock/unlockFocus
    [self setClipping:NO];// even faster...

    [self loadFonts];
    [self newViewSize];
    srandom(time(0));
    if (t = NXReadDefault([NXApp appName],SPEED)){
      threshold = atoi(t);
    } else {
      threshold = 200;
    }
    if (t= NXReadDefault([NXApp appName],BACKING)){
      gray = atoi(t);
    } else {
      gray = PLAIN;
    }
    if (t= NXReadDefault([NXApp appName],USECOLOR)){
      useColor = (strcmp(t,"YES")==0);
    } else {
      useColor = YES;
    }
    if (t= NXReadDefault([NXApp appName],F_FILE)){
      fortuneFile = (atoi(t));
    } else {
      fortuneFile = 0;
    }
    if (t = NXReadDefault([NXApp appName],EXFONTS)){
      [self setExcludedFonts:t];
    } else {
      [self setExcludedFonts:"Symbol ; Lexi"];
    }
    [self setup];
    return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
    [super sizeTo:width :height];
    [self newViewSize];
    return self;
}

- newViewSize
{
    if (oldSize.width == bounds.size.width &&
	oldSize.height == bounds.size.height)
      {
	return self;
      }else{
	oldSize.width = bounds.size.width;
	oldSize.height = bounds.size.height;
      }
    maxCoord.x = bounds.size.width-150.0;
    maxCoord.y = bounds.size.height;
    if (maxCoord.x < 0) maxCoord.x = 0;
    if (maxCoord.y < 0) maxCoord.y = 0;
    [fe setFrame:&frame];
    if ([self window]){
      [self display];
    }
    return self;
}

- (const char *)windowTitle
{
    return "FortuneView";
}

- inspector:sender
{
    appowner=sender;
    if (!sharedInspectorPanel){
      [self setup];
    }
    return sharedInspectorPanel;
}

@end

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