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.