shithub: fc

ref: c5d556434652b8f00fb0ec89fe2d1fc162f5e4c5
dir: /fc.c/

View raw version
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
#include <keyboard.h>
#include <bio.h>
#include <ctype.h>

enum {
	MAXBOXES = 300,
	BOXWIDTH = 128,
	BOXHEIGHT = 32,
	MAXFORMULA = 256,
	MAXCONTENT = 256,
};

enum {
	T_TEXT = 0,
	T_NUMBER,
	T_FORMULA,
	T_LABEL,
	MAXBOXTYPES,
};

enum {
	TOK_NUM = 0,
	TOK_CELL,
	TOK_RANGE,
	TOK_OP,
	TOK_FUNC,
	TOK_LPAREN,
	TOK_RPAREN,
	TOK_COMMA,
	TOK_STRING,
	TOK_END,
	
	OP_ADD = '+',
	OP_SUB = '-',
	OP_MUL = '*',
	OP_DIV = '/',
	OP_MOD = '%',
	OP_POW = '^',
	OP_EQ = '=',
	OP_NE = '!',
	OP_LT = '<',
	OP_GT = '>',
	OP_LE = '[',  /* Using [ for <= */
	OP_GE = ']',  /* Using ] for >= */
	
	FN_SUM = 0,
	FN_AVG,
	FN_MIN,
	FN_MAX,
	FN_COUNT,
	FN_ABS,
	FN_SQRT,
	FN_POW,
	FN_ROUND,
	FN_FLOOR,
	FN_CEIL,
	FN_IF,
	FN_AND,
	FN_OR,
	FN_NOT,
	FN_CONCAT,
	FN_LEN,
	FN_UPPER,
	FN_LOWER,
	FN_LOOKUP,
	MAXFUNCS,
};

typedef struct Box Box;
struct Box {
	Point		pos;			
	Rectangle	r;					
	char		content[MAXCONTENT];
	char		formula[MAXFORMULA];
	char		label[32];	
	int			type;				
	double		value;				
	int			selected;			
	int			dirty;				
	int			refs[10];
	int			nrefs;
};

struct {
	Box		boxes[MAXBOXES];
	int		nboxes;
	int		selected;
	int		editing;	
	char	editbuf[MAXCONTENT];
	int		editpos;
	int		editing_label;	
	char	labelbuf[32];		
	int		labelpos;
	int		entering_filename;	
	char	filenamebuf[256];
	int		filenamepos;		
	int		save_mode;		/* 1=save, 2=load */
	int		current_mode;
	Point	offset;	
	int		needredraw;
	int		gridsnap;
	int		gridsize;
} sheet;

typedef struct BoxType BoxType;
struct BoxType {
	char	*name;
	void	(*parse)(Box*);
	void	(*eval)(Box*);
	void	(*draw)(Box*, Image*);
};

typedef struct Token Token;
struct Token {
	int	type;
	union {
		double	num;
		int	cell;			/* box index */
		struct {
			int start, end;	/* range of boxes */
		} range;
		int		op;
		int		func;
		char	str[MAXCONTENT];
	};
};

typedef struct Function Function;
struct Function {
	char	*name;
	int	minargs;
	int	maxargs;
	double	(*eval)(Token *args, int nargs);
};

typedef struct Eval Eval;
struct Eval {
	Token	tokens[256];
	int	ntokens;
	int	pos;
	Box	*current;	
	int	depth;		
};

typedef void (*KeyHandler)(int key);
typedef void (*MouseHandler)(Mouse m); /* For data-oriented mouse handling */

typedef struct InputMode InputMode;
struct InputMode {
	char		*name;
	KeyHandler	handler;
	MouseHandler mouse_handler; /* Added mouse handler */
	void		(*draw)(void);
	char		*status;
};

typedef struct Command Command;
struct Command {
	int		key;
	void	(*action)(void);
};

typedef struct EditAction EditAction;
struct EditAction {
	int		key;
	int		(*action)(char *buf, int *pos, int maxlen);  /* Returns 1 to exit mode */
};

typedef struct DrawStep DrawStep;
struct DrawStep {
	void (*draw)(void);
	int  condition;  /* 0=always, 1=if grid, etc */
};

Image	*colors[6];
Image	*boxbg;
Image	*boxselected;
Image	*boxediting;
Image	*gridcolor;
void parse_text(Box*);
void parse_number(Box*);
void parse_formula(Box*);
void eval_text(Box*);
void eval_number(Box*);
void eval_formula(Box*);
void draw_box_generic(Box*, Image*);
void recalc_all(void);
void handle_normal_mode(int key);
void handle_cell_edit(int key);
void handle_label_edit(int key);
void handle_filename_input(int key);
void handle_normal_mouse(Mouse m);
void handle_edit_mouse(Mouse m);
void handle_label_mouse(Mouse m);
void handle_filename_mouse(Mouse m);
void draw_normal_overlay(void);
void draw_cell_edit_overlay(void);
void draw_label_edit_overlay(void);
void draw_filename_overlay(void);
void cmd_quit(void);
void cmd_save(void);
void cmd_save_as(void);
void cmd_open(void);
void cmd_open_file(void);
void cmd_start_label(void);
void cmd_toggle_grid(void);
void cmd_delete_box(void);
void draw_background(void);
void draw_grid_lines(void);
void draw_all_boxes(void);
void draw_status_line(void);
int cistrcmp(char *s1, char *s2);
int tokenize_formula(char *formula, Token *tokens, int maxtokens);
int cellref_lookup(char *ref);
int edit_finish(char *buf, int *pos, int maxlen);
int edit_cancel(char *buf, int *pos, int maxlen);
int edit_backspace(char *buf, int *pos, int maxlen);
int edit_add_char(char *buf, int *pos, int maxlen);
double round(double nargs);
double fn_sum(Token *args, int nargs);
double fn_avg(Token *args, int nargs);
double fn_min(Token *args, int nargs);
double fn_max(Token *args, int nargs);
double fn_count(Token *args, int nargs);
double fn_abs(Token *args, int nargs);
double fn_sqrt(Token *args, int nargs);
double fn_pow(Token *args, int nargs);
double fn_round(Token *args, int nargs);
double fn_floor(Token *args, int nargs);
double fn_ceil(Token *args, int nargs);
double fn_if(Token *args, int nargs);
double fn_and(Token *args, int nargs);
double fn_or(Token *args, int nargs);
double fn_not(Token *args, int nargs);
double fn_lookup(Token *args, int nargs);
double eval_expr(Eval *e);
double eval_term(Eval *e);
double eval_factor(Eval *e);
double eval_primary(Eval *e);

BoxType boxtypes[] = {
	[T_TEXT]    = {"text",    parse_text,    eval_text,    draw_box_generic},
	[T_NUMBER]  = {"number",  parse_number,  eval_number,  draw_box_generic},
	[T_FORMULA] = {"formula", parse_formula, eval_formula, draw_box_generic},
	[T_LABEL]   = {"label",   parse_text,    eval_text,    draw_box_generic},
};

Function functions[MAXFUNCS] = {
	[FN_SUM]    = {"SUM",    1, 100, fn_sum},
	[FN_AVG]    = {"AVG",    1, 100, fn_avg},
	[FN_MIN]    = {"MIN",    1, 100, fn_min},
	[FN_MAX]    = {"MAX",    1, 100, fn_max},
	[FN_COUNT]  = {"COUNT",  1, 100, fn_count},
	[FN_ABS]    = {"ABS",    1, 1,   fn_abs},
	[FN_SQRT]   = {"SQRT",   1, 1,   fn_sqrt},
	[FN_POW]    = {"POW",    2, 2,   fn_pow},
	[FN_ROUND]  = {"ROUND",  1, 2,   fn_round},
	[FN_FLOOR]  = {"FLOOR",  1, 1,   fn_floor},
	[FN_CEIL]   = {"CEIL",   1, 1,   fn_ceil},
	[FN_IF]     = {"IF",     3, 3,   fn_if},
	[FN_AND]    = {"AND",    2, 100, fn_and},
	[FN_OR]     = {"OR",     2, 100, fn_or},
	[FN_NOT]    = {"NOT",    1, 1,   fn_not},
	[FN_LOOKUP] = {"LOOKUP", 2, 3,   fn_lookup},
};

InputMode input_modes[] = {
	[0] = {"normal",    handle_normal_mode,    handle_normal_mouse,    draw_normal_overlay,    "S:save  O:open  L:label  G:grid"},
	[1] = {"edit",      handle_cell_edit,      handle_edit_mouse,      draw_cell_edit_overlay, "Enter:save  Esc:cancel"},
	[2] = {"label",     handle_label_edit,     handle_label_mouse,     draw_label_edit_overlay,"Enter:save  Esc:cancel"},
	[3] = {"filename",  handle_filename_input, handle_filename_mouse,  draw_filename_overlay,  "Tab:.spr ` Enter:confirm  `Esc:cancel"},
};

Command commands[] = {
	{'q',	cmd_quit},
	{Kdel,	cmd_quit},
	{'s',	cmd_save},
	{'S',	cmd_save_as},
	{'o',	cmd_open},
	{'O',	cmd_open_file},
	{'l',	cmd_start_label},
	{'g',	cmd_toggle_grid},
	{'d',	cmd_delete_box},
	{0, nil}
};

EditAction edit_actions[] = {
	{'\n',	edit_finish},
	{Kesc,	edit_cancel},
	{Kbs,	edit_backspace},
	{-1,	edit_add_char},  /* Default for any other key */
	{0, nil}
};

DrawStep draw_steps[] = {
	{draw_background,	0},  /* Always */
	{draw_grid_lines,	1},  /* If gridsnap */
	{draw_all_boxes,	0},  /* Always */
	{draw_status_line,	0},  /* Always */
	{nil, 0}
};

void
handlekey(int key)
{
	InputMode *mode = &input_modes[sheet.current_mode];
	if(mode->handler)
		mode->handler(key);
}

void
handlemouse(Mouse m)
{
	InputMode *mode = &input_modes[sheet.current_mode];
	if(mode->mouse_handler)
		mode->mouse_handler(m);
}

void
initcolors(void)
{
	colors[0] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x000000FF);
	colors[1] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xFFFFFFFF);
	colors[2] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xEEEEEEFF);
	colors[3] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x4444FFFF);
	colors[4] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xFF4444FF);
	colors[5] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xCCCC88FF);
	
	boxbg = colors[1];
	boxselected = colors[3];
	boxediting = colors[5];
	gridcolor = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xCCCCCCFF);
}

double
round(double x)
{
	if(x >= 0)
		return floor(x + 0.5);
	else
		return ceil(x - 0.5);
}

/* Helper for case-insensitive string comparison */
int
cistrcmp(char *s1, char *s2)
{
	while(*s1 && *s2) {
		int c1 = toupper(*s1);
		int c2 = toupper(*s2);
		if(c1 != c2)
			return c1 - c2;
		s1++;
		s2++;
	}
	return *s1 - *s2;
}

int
cellref_lookup(char *ref)
{
	int i;
	
	/* First, check if any box has this exact label */
	for(i = 0; i < sheet.nboxes; i++) {
		if(sheet.boxes[i].label[0] && 
		   cistrcmp(sheet.boxes[i].label, ref) == 0) {
			return i;
		}
	}
	
	/* If no labeled box found, try index-based (A=0, B=1, etc.) */
	/* Handle single letters first */
	if(strlen(ref) == 1 && isalpha(ref[0])) {
		int idx = toupper(ref[0]) - 'A';
		if(idx >= 0 && idx < sheet.nboxes)
			return idx;
	}
	
	/* Handle double letters (AA, AB, etc.) */
	if(strlen(ref) == 2 && isalpha(ref[0]) && isalpha(ref[1])) {
		int idx = (toupper(ref[0]) - 'A' + 1) * 26 + (toupper(ref[1]) - 'A');
		if(idx >= 0 && idx < sheet.nboxes)
			return idx;
	}
	
	/* Try traditional Excel-style (A1, B2) - just use the letter part */
	if(isalpha(ref[0])) {
		int col = 0;
		char *p = ref;
		while(*p && isalpha(*p)) {
			col = col * 26 + (toupper(*p) - 'A');
			p++;
		}
		if(col >= 0 && col < sheet.nboxes)
			return col;
	}
	
	return -1;	/* not found */
}

int
tokenize_formula(char *formula, Token *tokens, int maxtokens)
{
	char *p = formula;
	int ntok = 0;
	char buf[256];
	int i;
	
	if(*p == '=')
		p++;
	
	while(*p && ntok < maxtokens) {
		Token *t = &tokens[ntok];
		
		while(*p && (*p == ' ' || *p == '\t'))
			p++;
		
		if(!*p)
			break;
		
		if(isdigit(*p) || (*p == '.' && isdigit(*(p+1)))) {
			char *endp;
			t->type = TOK_NUM;
			t->num = strtod(p, &endp);
			p = endp;
			ntok++;
			continue;
		}
		
		if(isalpha(*p)) {
			i = 0;
			while(*p && (isalnum(*p) || *p == '_') && i < 255)
				buf[i++] = *p++;
			buf[i] = '\0';
			
			if(*p == ':') {
				p++;	/* skip : */
				char buf2[256];
				i = 0;
				while(*p && (isalnum(*p) || *p == '_'))
					buf2[i++] = *p++;
				buf2[i] = '\0';
				
				t->type = TOK_RANGE;
				t->range.start = cellref_lookup(buf);
				t->range.end = cellref_lookup(buf2);
				ntok++;
				continue;
			}
			
			int found = 0;
			for(i = 0; i < MAXFUNCS; i++) {
				if(functions[i].name && cistrcmp(buf, functions[i].name) == 0) {
					t->type = TOK_FUNC;
					t->func = i;
					found = 1;
					break;
				}
			}
			
			if(!found) {
				/* It's a cell reference */
				t->type = TOK_CELL;
				t->cell = cellref_lookup(buf);
			}
			ntok++;
			continue;
		}
		
		if(*p == '"') {
			p++;
			i = 0;
			while(*p && *p != '"' && i < MAXCONTENT-1)
				t->str[i++] = *p++;
			t->str[i] = '\0';
			if(*p == '"')
				p++;
			t->type = TOK_STRING;
			ntok++;
			continue;
		}
		
		switch(*p) {
		case '+': case '-': case '*': case '/': case '%': case '^':
		case '=': case '<': case '>':
			t->type = TOK_OP;
			t->op = *p++;
			
			/* Check for two-char operators */
			if(t->op == '<' && *p == '=') {
				t->op = OP_LE;
				p++;
			} else if(t->op == '>' && *p == '=') {
				t->op = OP_GE;
				p++;
			} else if(t->op == '!' && *p == '=') {
				t->op = OP_NE;
				p++;
			}
			ntok++;
			break;
			
		case '(':
			t->type = TOK_LPAREN;
			p++;
			ntok++;
			break;
			
		case ')':
			t->type = TOK_RPAREN;
			p++;
			ntok++;
			break;
			
		case ',':
			t->type = TOK_COMMA;
			p++;
			ntok++;
			break;
			
		default:
			p++;
			break;
		}
	}
	
	if(ntok < maxtokens) {
		tokens[ntok].type = TOK_END;
		ntok++;
	}
	
	return ntok;
}

double
token_value(Token *t, Eval *e)
{
	Box *b;
	
	switch(t->type) {
	case TOK_NUM:
		return t->num;
		
	case TOK_CELL:
		if(t->cell >= 0 && t->cell < sheet.nboxes) {
			b = &sheet.boxes[t->cell];
			
			if(b == e->current) {
				return 0.0;	/* circular ref */
			}
			
			if(b->type == T_FORMULA && e->depth < 10) {
				Eval subeval;
				subeval.current = b;
				subeval.depth = e->depth + 1;
				subeval.pos = 0;
				subeval.ntokens = tokenize_formula(b->formula, 
					subeval.tokens, nelem(subeval.tokens));
				return eval_expr(&subeval);
			}
			
			return b->value;
		}
		return 0.0;
		
	case TOK_STRING:
		return atof(t->str);
		
	default:
		return 0.0;
	}
}

/* Evaluate primary expression (number, cell, function call, parentheses) */
double
eval_primary(Eval *e)
{
	Token *t;
	double result = 0.0;
	
	if(e->pos >= e->ntokens)
		return 0.0;
	
	t = &e->tokens[e->pos];
	
	switch(t->type) {
	case TOK_NUM:
	case TOK_CELL:
	case TOK_STRING:
		result = token_value(t, e);
		e->pos++;
		break;
		
	case TOK_FUNC: {
		int func = t->func;
		e->pos++;	/* skip function name */
		
		/* Expect ( */
		if(e->pos >= e->ntokens || e->tokens[e->pos].type != TOK_LPAREN)
			return 0.0;
		e->pos++;	/* skip ( */
		
		/* Collect arguments */
		Token args[100];
		int nargs = 0;
		
		while(e->pos < e->ntokens && e->tokens[e->pos].type != TOK_RPAREN) {
			if(nargs > 0) {
				/* Expect comma */
				if(e->tokens[e->pos].type != TOK_COMMA)
					break;
				e->pos++;	/* skip comma */
			}
			
			/* Check for range */
			if(e->tokens[e->pos].type == TOK_RANGE) {
				/* Expand range into individual cells */
				int start = e->tokens[e->pos].range.start;
				int end = e->tokens[e->pos].range.end;
				e->pos++;
				
				/* Add all cells in range */
				int i;
				for(i = start; i <= end && i >= 0 && nargs < 100; i++) {
					args[nargs].type = TOK_CELL;
					args[nargs].cell = i;
					nargs++;
				}
			} else {
				/* Regular argument - evaluate it */
				args[nargs].type = TOK_NUM;
				args[nargs].num = eval_expr(e);
				nargs++;
			}
		}
		
		/* Skip ) */
		if(e->pos < e->ntokens && e->tokens[e->pos].type == TOK_RPAREN)
			e->pos++;
		
		/* Call function */
		if(func >= 0 && func < MAXFUNCS && functions[func].eval) {
			if(nargs >= functions[func].minargs && nargs <= functions[func].maxargs) {
				result = functions[func].eval(args, nargs);
			}
		}
		break;
	}
		
	case TOK_LPAREN:
		e->pos++;	/* skip ( */
		result = eval_expr(e);
		/* Skip ) */
		if(e->pos < e->ntokens && e->tokens[e->pos].type == TOK_RPAREN)
			e->pos++;
		break;
		
	case TOK_OP:
		/* Unary minus */
		if(t->op == OP_SUB) {
			e->pos++;
			result = -eval_primary(e);
		}
		break;
	}
	
	return result;
}

/* Evaluate factor (handle power) */
double
eval_factor(Eval *e)
{
	double left = eval_primary(e);
	
	while(e->pos < e->ntokens) {
		Token *t = &e->tokens[e->pos];
		if(t->type != TOK_OP || t->op != OP_POW)
			break;
		
		e->pos++;	/* skip operator */
		double right = eval_primary(e);
		left = pow(left, right);
	}
	
	return left;
}

/* Evaluate term (multiplication, division, modulo) */
double
eval_term(Eval *e)
{
	double left = eval_factor(e);
	
	while(e->pos < e->ntokens) {
		Token *t = &e->tokens[e->pos];
		if(t->type != TOK_OP)
			break;
		
		switch(t->op) {
		case OP_MUL:
			e->pos++;
			left *= eval_factor(e);
			break;
		case OP_DIV:
			e->pos++;
			{
				double right = eval_factor(e);
				if(right != 0.0)
					left /= right;
				else
					left = 0.0;	/* div by zero */
			}
			break;
		case OP_MOD:
			e->pos++;
			{
				double right = eval_factor(e);
				if(right != 0.0)
					left = fmod(left, right);
			}
			break;
		default:
			return left;
		}
	}
	
	return left;
}

/* Evaluate expression (addition, subtraction, comparison) */
double
eval_expr(Eval *e)
{
	double left = eval_term(e);
	
	while(e->pos < e->ntokens) {
		Token *t = &e->tokens[e->pos];
		if(t->type != TOK_OP)
			break;
		
		switch(t->op) {
		case OP_ADD:
			e->pos++;
			left += eval_term(e);
			break;
		case OP_SUB:
			e->pos++;
			left -= eval_term(e);
			break;
		case OP_LT:
			e->pos++;
			left = left < eval_term(e) ? 1.0 : 0.0;
			break;
		case OP_GT:
			e->pos++;
			left = left > eval_term(e) ? 1.0 : 0.0;
			break;
		case OP_LE:
			e->pos++;
			left = left <= eval_term(e) ? 1.0 : 0.0;
			break;
		case OP_GE:
			e->pos++;
			left = left >= eval_term(e) ? 1.0 : 0.0;
			break;
		case OP_EQ:
			e->pos++;
			left = fabs(left - eval_term(e)) < 0.000001 ? 1.0 : 0.0;
			break;
		case OP_NE:
			e->pos++;
			left = fabs(left - eval_term(e)) >= 0.000001 ? 1.0 : 0.0;
			break;
		default:
			return left;
		}
	}
	
	return left;
}

double
fn_sum(Token *args, int nargs)
{
	double sum = 0.0;
	int i;
	Eval e;
	
	for(i = 0; i < nargs; i++) {
		if(args[i].type == TOK_CELL) {
			e.current = nil;
			e.depth = 0;
			sum += token_value(&args[i], &e);
		} else {
			sum += args[i].num;
		}
	}
	return sum;
}

double
fn_avg(Token *args, int nargs)
{
	if(nargs == 0)
		return 0.0;
	return fn_sum(args, nargs) / nargs;
}

double
fn_min(Token *args, int nargs)
{
	if(nargs == 0)
		return 0.0;
	
	double min = 1e308;	/* large number */
	int i;
	Eval e;
	
	for(i = 0; i < nargs; i++) {
		double val;
		if(args[i].type == TOK_CELL) {
			e.current = nil;
			e.depth = 0;
			val = token_value(&args[i], &e);
		} else {
			val = args[i].num;
		}
		if(val < min)
			min = val;
	}
	return min;
}

double
fn_max(Token *args, int nargs)
{
	if(nargs == 0)
		return 0.0;
	
	double max = -1e308;	/* small number */
	int i;
	Eval e;
	
	for(i = 0; i < nargs; i++) {
		double val;
		if(args[i].type == TOK_CELL) {
			e.current = nil;
			e.depth = 0;
			val = token_value(&args[i], &e);
		} else {
			val = args[i].num;
		}
		if(val > max)
			max = val;
	}
	return max;
}

double
fn_count(Token *args, int nargs)
{
	int count = 0;
	int i;
	
	for(i = 0; i < nargs; i++) {
		if(args[i].type == TOK_CELL) {
			if(args[i].cell >= 0 && args[i].cell < sheet.nboxes) {
				Box *b = &sheet.boxes[args[i].cell];
				if(b->type == T_NUMBER || b->type == T_FORMULA)
					count++;
			}
		} else {
			count++;	/* direct number */
		}
	}
	return (double)count;
}

double
fn_abs(Token *args, int nargs)
{
	USED(nargs);
	return fabs(args[0].num);
}

double
fn_sqrt(Token *args, int nargs)
{
	USED(nargs);
	return sqrt(args[0].num);
}

double
fn_pow(Token *args, int nargs)
{
	USED(nargs);
	return pow(args[0].num, args[1].num);
}

double
fn_round(Token *args, int nargs)
{
	if(nargs == 1) {
		return round(args[0].num);
	} else {
		/* Round to N decimal places */
		double mult = pow(10, args[1].num);
		return round(args[0].num * mult) / mult;
	}
}

double
fn_floor(Token *args, int nargs)
{
	USED(nargs);
	return floor(args[0].num);
}

double
fn_ceil(Token *args, int nargs)
{
	USED(nargs);
	return ceil(args[0].num);
}

double
fn_if(Token *args, int nargs)
{
	USED(nargs);
	return args[0].num != 0.0 ? args[1].num : args[2].num;
}

double
fn_and(Token *args, int nargs)
{
	int i;
	for(i = 0; i < nargs; i++) {
		if(args[i].num == 0.0)
			return 0.0;
	}
	return 1.0;
}

double
fn_or(Token *args, int nargs)
{
	int i;
	for(i = 0; i < nargs; i++) {
		if(args[i].num != 0.0)
			return 1.0;
	}
	return 0.0;
}

double
fn_not(Token *args, int nargs)
{
	USED(nargs);
	return args[0].num == 0.0 ? 1.0 : 0.0;
}

double
fn_lookup(Token *args, int nargs)
{
	/* LOOKUP(value, search_range, result_range) */
	/* Simplified VLOOKUP - finds value in search range, returns corresponding from result range */
	if(nargs < 2)
		return 0.0;
	
	/* For now, just return the value */
	/* Full implementation would search through cells */
	return args[0].num;
}

void
parse_formula(Box *b)
{
	Eval e;
	
	if(b->formula[0] != '=') {
		char *endp;
		double val = strtod(b->formula, &endp);
		if(*endp == '\0') {
			b->type = T_NUMBER;
			b->value = val;
			snprint(b->content, MAXCONTENT, "%.2f", val);
		} else {
			b->type = T_TEXT;
			strncpy(b->content, b->formula, MAXCONTENT);
		}
		return;
	}
	
	/* It's a formula */
	b->type = T_FORMULA;
	
	/* Tokenize */
	e.current = b;
	e.depth = 0;
	e.pos = 0;
	e.ntokens = tokenize_formula(b->formula, e.tokens, nelem(e.tokens));	
	strncpy(b->content, b->formula, MAXCONTENT);
	b->dirty = 1;
	
	/* Extract cell references for dependency tracking */
	b->nrefs = 0;
	int i;
	for(i = 0; i < e.ntokens && b->nrefs < 10; i++) {
		if(e.tokens[i].type == TOK_CELL && e.tokens[i].cell >= 0) {
			b->refs[b->nrefs++] = e.tokens[i].cell;
		} else if(e.tokens[i].type == TOK_RANGE) {
			/* Add range endpoints */
			if(b->nrefs < 10 && e.tokens[i].range.start >= 0)
				b->refs[b->nrefs++] = e.tokens[i].range.start;
			if(b->nrefs < 10 && e.tokens[i].range.end >= 0)
				b->refs[b->nrefs++] = e.tokens[i].range.end;
		}
	}
}

void
eval_formula(Box *b)
{
	Eval e;
	
	if(b->type != T_FORMULA || !b->dirty)
		return;
	
	e.current = b;
	e.depth = 0;
	e.pos = 0;
	e.ntokens = tokenize_formula(b->formula, e.tokens, nelem(e.tokens));
	
	b->value = eval_expr(&e);
	
	if(b->formula[0] == '=' && b->formula[1] == '"') {
		/* String formula - show as text */
		strncpy(b->content, b->formula + 2, MAXCONTENT);
		char *p = strchr(b->content, '"');
		if(p) *p = '\0';
	} else {
		snprint(b->content, MAXCONTENT, "%.2f", b->value);
	}
	
	b->dirty = 0;
	
	/* Mark dependent cells as dirty */
	int i, j;
	for(i = 0; i < sheet.nboxes; i++) {
		Box *other = &sheet.boxes[i];
		if(other->type == T_FORMULA) {
			for(j = 0; j < other->nrefs; j++) {
				if(other->refs[j] == b - sheet.boxes) {
					other->dirty = 1;
					break;
				}
			}
		}
	}
}

/* Recalculate all formulas in dependency order */
void
recalc_all(void)
{
	int i, changed;
	int passes = 0;
	
	/* Mark all formulas as dirty */
	for(i = 0; i < sheet.nboxes; i++) {
		if(sheet.boxes[i].type == T_FORMULA)
			sheet.boxes[i].dirty = 1;
	}
	
	/* Iteratively evaluate until no changes (or max passes) */
	do {
		changed = 0;
		for(i = 0; i < sheet.nboxes; i++) {
			Box *b = &sheet.boxes[i];
			if(b->type == T_FORMULA && b->dirty) {
				eval_formula(b);
				changed = 1;
			}
		}
		passes++;
	} while(changed && passes < 10);
}

int
boxat(Point p)
{
	int i;
	Box *b;
	
	for(i = sheet.nboxes - 1; i >= 0; i--){
		b = &sheet.boxes[i];
		if(ptinrect(p, b->r))
			return i;
	}
	return -1;
}

int
addbox(Point p)
{
	Box *b;
	
	if(sheet.nboxes >= MAXBOXES)
		return -1;
	
	b = &sheet.boxes[sheet.nboxes];
	memset(b, 0, sizeof(Box));
	
	/* Snap to grid if enabled */
	if(sheet.gridsnap){
		p.x = (p.x / sheet.gridsize) * sheet.gridsize;
		p.y = (p.y / sheet.gridsize) * sheet.gridsize;
	}
	
	b->pos = p;
	b->r = Rect(p.x, p.y, p.x + BOXWIDTH, p.y + BOXHEIGHT);
	b->type = T_TEXT;
	strcpy(b->content, "");
	
	return sheet.nboxes++;
}

void
delbox(int i)
{
	if(i < 0 || i >= sheet.nboxes)
		return;
	
	/* Shift boxes down */
	memmove(&sheet.boxes[i], &sheet.boxes[i+1], 
		(sheet.nboxes - i - 1) * sizeof(Box));
	sheet.nboxes--;
	
	/* Update references in other boxes */
	int j, k;
	for(j = 0; j < sheet.nboxes; j++){
		Box *b = &sheet.boxes[j];
		for(k = 0; k < b->nrefs; k++){
			if(b->refs[k] > i)
				b->refs[k]--;
			else if(b->refs[k] == i)
				b->refs[k] = -1;  /* invalidate */
		}
	}
}

void
parse_text(Box *b)
{
	/* copy content as is */
	strncpy(b->content, b->formula, MAXCONTENT);
}

void
parse_number(Box *b)
{
	char *endp;
	b->value = strtod(b->formula, &endp);
	snprint(b->content, MAXCONTENT, "%.2f", b->value);
}

void
eval_text(Box *b)
{
	/* Text doesn't evaluate */
	USED(b);
}

void
eval_number(Box *b)
{
	/* Numbers are already evaluated */
	USED(b);
}

void
draw_box_generic(Box *b, Image *dst)
{
	Image *bg = boxbg;
	int idx = b - sheet.boxes;  /* Get box index */
	
	if(sheet.editing == idx)
		bg = boxediting;
	else if(sheet.editing_label == idx)
		bg = colors[5];
	else if(b->selected)
		bg = boxselected;
	
	draw(dst, b->r, bg, nil, ZP);
	border(dst, b->r, 1, colors[0], ZP);

	char cellname[32];
	if(sheet.editing_label == idx){
		snprint(cellname, sizeof(cellname), "%s", sheet.labelbuf);
		string(dst, Pt(b->r.min.x + 2, b->r.min.y + 2), colors[4], ZP, font, cellname);
	} else if(b->label[0]) {
		/* Has custom label */
		snprint(cellname, sizeof(cellname), "%s", b->label);
		string(dst, Pt(b->r.min.x + 2, b->r.min.y + 2), colors[4], ZP, font, cellname);
	} else {
		/* Show default index (A, B, C...) */
		if(idx < 26) {
			snprint(cellname, sizeof(cellname), "%c", 'A' + idx);
		} else {
			snprint(cellname, sizeof(cellname), "%c%c", 
				'A' + (idx/26)-1, 'A' + (idx%26));
		}
		string(dst, Pt(b->r.min.x + 2, b->r.min.y + 2), colors[3], ZP, font, cellname);
	}
	
	Point p = addpt(b->r.min, Pt(5, 16));  /* Offset down to avoid label */
	
	if(sheet.editing == idx){
		string(dst, p, colors[0], ZP, font, sheet.editbuf);
		
	} else {
		string(dst, p, colors[0], ZP, font, b->content);
	}
	if (b->type == T_FORMULA){
		string(dst, Pt(b->r.max.x - 10, b->r.min.y + 2), colors[3], ZP, font, "=");
	}
}

void
drawgrid(Image *dst)
{
    int x, y;
    Rectangle r = screen->r;
    
    // Start from the first grid line that's visible in the window
    int startx = (r.min.x / sheet.gridsize) * sheet.gridsize;
    int starty = (r.min.y / sheet.gridsize) * sheet.gridsize;
    
    // Draw vertical lines
    for(x = startx; x <= r.max.x; x += sheet.gridsize){
        line(dst, Pt(x, r.min.y), Pt(x, r.max.y), 0, 0, 0, gridcolor, ZP);
    }
    
    // Draw horizontal lines
    for(y = starty; y <= r.max.y; y += sheet.gridsize){
        line(dst, Pt(r.min.x, y), Pt(r.max.x, y), 0, 0, 0, gridcolor, ZP);
    }
}

void
draw_normal_overlay(void)
{
	/* Nothing special for normal mode */
}

void
draw_cell_edit_overlay(void)
{
	/* Could draw edit indicator */
}

void
draw_label_edit_overlay(void)
{
	/* Could highlight the box being labeled */
}

void
draw_filename_overlay(void)
{
	Rectangle r;
	int w = 256;
	int h = 48;
	Point center = Pt(screen->r.min.x + Dx(screen->r)/2,
			  screen->r.min.y + Dy(screen->r)/2);
	
	r = Rect(center.x - w/2, center.y - h/2,
		 center.x + w/2, center.y + h/2);
	
	/* Draw dialog */
	draw(screen, r, colors[1], nil, ZP);
	border(screen, r, 2, colors[0], ZP);
	
	/* Title */
	char *title = sheet.save_mode == 1 ? "Save As:" : "Open File:";
	string(screen, Pt(r.min.x + 10, r.min.y + 5), 
		colors[0], ZP, font, title);
	
	/* Filename with cursor */
	char display[256];
	snprint(display, sizeof(display), "%s_", sheet.filenamebuf);
	string(screen, Pt(r.min.x + 10, r.min.y + 20), 
		colors[0], ZP, font, display);
	
}

void
draw_background(void)
{
	draw(screen, screen->r, colors[2], nil, ZP);
}

void
draw_grid_lines(void)
{
	if(!sheet.gridsnap)
		return;
	drawgrid(screen);
}

void
draw_all_boxes(void)
{
	int i;
	for(i = 0; i < sheet.nboxes; i++) {
		Box *b = &sheet.boxes[i];
		if (b->type >= 0 && b->type < MAXBOXTYPES) {
			BoxType *bt = &boxtypes[b->type];
			if (bt->draw) {
				bt->draw(b, screen);
			}
		}
	}
}

void
draw_status_line(void)
{
	char buf[256];
	InputMode *mode = &input_modes[sheet.current_mode];
	
	snprint(buf, sizeof(buf), "Selected: %d | Mode: %s | Boxes: %d | %s", 
			sheet.selected, mode->name, sheet.nboxes, mode->status);
	
	string(screen, Pt(screen->r.min.x + 10, screen->r.max.y - 20), 
			colors[0], ZP, font, buf);
}

void
redraw(void)
{
	DrawStep *step;
	
	/* Execute drawing steps from table */
	for(step = draw_steps; step->draw; step++) {
		if(step->condition == 0 || 
		   (step->condition == 1 && sheet.gridsnap)) {
			step->draw();
		}
	}
	
	/* Draw mode-specific overlay */
	InputMode *mode = &input_modes[sheet.current_mode];
	if(mode->draw)
		mode->draw();
	
	flushimage(display, 1);
}

int
edit_finish(char *buf, int *pos, int maxlen)
{
	USED(buf); USED(pos); USED(maxlen);
	return 1;  /* Signal to exit edit mode */
}

int
edit_cancel(char *buf, int *pos, int maxlen)
{
	USED(maxlen);
	buf[0] = '\0';
	*pos = 0;
	return -1;  /* Signal cancellation */
}

int
edit_backspace(char *buf, int *pos, int maxlen)
{
	USED(maxlen);
	if(*pos > 0) {
		(*pos)--;
		buf[*pos] = '\0';
		sheet.needredraw = 1;
	}
	return 0;
}

int
edit_add_char(char *buf, int *pos, int maxlen)
{
	/* Note: key is passed through global or parameter */
	/* For this example, we'll use a global current_key */
	return 0;
}

void
cmd_quit(void)
{
    int i;
    for(i = 0; i < 6; i++)
        if(colors[i]) freeimage(colors[i]);
    if(gridcolor) freeimage(gridcolor);
    
    closedisplay(display); 
    exits(nil);
}

void
cmd_save(void)
{
	sheet.current_mode = 3;  /* Enter filename mode */
	sheet.entering_filename = 1;
	sheet.save_mode = 1;
	strcpy(sheet.filenamebuf, "/tmp/sheet.spr");
	sheet.filenamepos = strlen(sheet.filenamebuf);
	sheet.needredraw = 1;
}

void
cmd_save_as(void)
{
	sheet.current_mode = 3;
	sheet.entering_filename = 1;
	sheet.save_mode = 1;
	sheet.filenamebuf[0] = '\0';
	sheet.filenamepos = 0;
	sheet.needredraw = 1;
}

void
cmd_open(void)
{
	sheet.current_mode = 3;
	sheet.entering_filename = 1;
	sheet.save_mode = 2;
	strcpy(sheet.filenamebuf, "/tmp/sheet.spr");
	sheet.filenamepos = strlen(sheet.filenamebuf);
	sheet.needredraw = 1;
}

void
cmd_open_file(void)
{
	sheet.current_mode = 3;
	sheet.entering_filename = 1;
	sheet.save_mode = 2;
	sheet.filenamebuf[0] = '\0';
	sheet.filenamepos = 0;
	sheet.needredraw = 1;
}

void
cmd_start_label(void)
{
	if(sheet.selected >= 0) {
		sheet.current_mode = 2;  /* Label edit mode */
		sheet.editing_label = sheet.selected;
		strncpy(sheet.labelbuf, sheet.boxes[sheet.selected].label, 31);
		sheet.labelbuf[31] = '\0';
		sheet.labelpos = strlen(sheet.labelbuf);
		sheet.needredraw = 1;
	}
}

void
cmd_toggle_grid(void)
{
	sheet.gridsnap = !sheet.gridsnap;
	sheet.needredraw = 1;
}

void
cmd_delete_box(void)
{
	if(sheet.selected >= 0) {
		delbox(sheet.selected);
		sheet.selected = -1;
		sheet.needredraw = 1;
	}
}

void
handle_normal_mode(int key)
{
	Command *cmd;
	
	/* Look up command in table */
	for(cmd = commands; cmd->action; cmd++) {
		if(cmd->key == key) {
			cmd->action();
			return;
		}
	}
	
	/* No match - could show help or ignore */
}

void
handle_cell_edit(int key)
{
	Box *b = &sheet.boxes[sheet.editing];
	
	if(key == '\n') {
		/* Save the edit */
		strcpy(b->formula, sheet.editbuf);
		
		/* Determine type */
		if(sheet.editbuf[0] == '=') {
			b->type = T_FORMULA;
		} else {
			char *endp;
			strtod(sheet.editbuf, &endp);
			b->type = (*endp == '\0') ? T_NUMBER : T_TEXT;
		}
		
		/* Parse and evaluate */
		if (b->type >= 0 && b->type < MAXBOXTYPES) {
			BoxType *bt = &boxtypes[b->type];
			if (bt->parse) bt->parse(b);
			if (bt->eval) bt->eval(b);
		}
		recalc_all();
		
		/* Return to normal mode */
		sheet.editing = -1;
		sheet.current_mode = 0;
		sheet.needredraw = 1;
	} else if(key == Kesc) {
		/* Cancel */
		sheet.editing = -1;
		sheet.current_mode = 0;
		sheet.needredraw = 1;
	} else if(key == Kbs) {
		/* Backspace */
		if(sheet.editpos > 0) {
			sheet.editpos--;
			sheet.editbuf[sheet.editpos] = '\0';
			sheet.needredraw = 1;
		}
	} else if(key >= 32 && key < 127 && sheet.editpos < MAXCONTENT-1) {
		/* Add character */
		sheet.editbuf[sheet.editpos++] = key;
		sheet.editbuf[sheet.editpos] = '\0';
		sheet.needredraw = 1;
	}
}

void
handle_label_edit(int key)
{
	Box *b = &sheet.boxes[sheet.editing_label];
	
	if(key == '\n') {
		/* Save label */
		strncpy(b->label, sheet.labelbuf, 31);
		b->label[31] = '\0';
		sheet.editing_label = -1;
		sheet.current_mode = 0;
		sheet.needredraw = 1;
	} else if(key == Kesc) {
		/* Cancel */
		sheet.editing_label = -1;
		sheet.current_mode = 0;
		sheet.needredraw = 1;
	} else if(key == Kbs) {
		/* Backspace */
		if(sheet.labelpos > 0) {
			sheet.labelpos--;
			sheet.labelbuf[sheet.labelpos] = '\0';
			sheet.needredraw = 1;
		}
	} else if(key >= 32 && key < 127 && sheet.labelpos < 30) {
		/* Add character */
		sheet.labelbuf[sheet.labelpos++] = key;
		sheet.labelbuf[sheet.labelpos] = '\0';
		sheet.needredraw = 1;
	}
}

void
save(char *file)
{
	int fd;
	Biobuf *b;
	int i;
	Box *box;
	
	fd = create(file, OWRITE, 0644);
	if(fd < 0){
		fprint(2, "cannot create %s: %r\n", file);
		return;
	}
	
	b = Bfdopen(fd, OWRITE);
	
	for(i = 0; i < sheet.nboxes; i++){
		box = &sheet.boxes[i];
		Bprint(b, "box %d\n", i);
		Bprint(b, "  pos %d %d\n", box->pos.x, box->pos.y);
		Bprint(b, "  type %d\n", box->type);
		Bprint(b, "  formula %s\n", box->formula);
		Bprint(b, "  value %g\n", box->value);
		Bprint(b, "  label %s\n", box->label);

		if(box->nrefs > 0){
			Bprint(b, "  refs");
			int j;
			for(j = 0; j < box->nrefs; j++)
				Bprint(b, " %d", box->refs[j]);
				Bprint(b, "\n");
		}
	}
	
	Bterm(b);
	close(fd);
}

void
load(char *file)
{
	Biobuf *b;
	char *line;
	char *fields[10];
	int nf;
	
	b = Bopen(file, OREAD);
	if(b == nil){
		fprint(2, "cannot open %s: %r\n", file);
		return;
	}
	
	sheet.nboxes = 0;
	
	while((line = Brdline(b, '\n')) != nil){
		line[Blinelen(b)-1] = '\0';
		
		if(line[0] == '#' || line[0] == '\0')
			continue;
		
		nf = tokenize(line, fields, nelem(fields));
		

		if(nf >= 2 && strcmp(fields[0], "box") == 0){
			if(sheet.nboxes < MAXBOXES)
				sheet.nboxes++;
		} else if(nf >= 3 && strcmp(fields[0], "pos") == 0){
			Box *box = &sheet.boxes[sheet.nboxes-1];
			box->pos.x = atoi(fields[1]);
			box->pos.y = atoi(fields[2]);
			box->r = Rect(box->pos.x, box->pos.y, 
				box->pos.x + BOXWIDTH, box->pos.y + BOXHEIGHT);
		} else if(nf >= 2 && strcmp(fields[0], "type") == 0){
			int type = atoi(fields[1]);
			if (type >= 0 && type < MAXBOXTYPES) {
				sheet.boxes[sheet.nboxes-1].type = type;
			} else {
				/* Default to text type if file is corrupted */
				sheet.boxes[sheet.nboxes-1].type = T_TEXT;
			}
		} else if(nf >= 2 && strcmp(fields[0], "formula") == 0){
			strcpy(sheet.boxes[sheet.nboxes-1].formula, fields[1]);
		} else if(nf >= 2 && strcmp(fields[0], "value") == 0){
			sheet.boxes[sheet.nboxes-1].value = atof(fields[1]);
		} else if(nf >= 2 && strcmp(fields[0], "label") == 0){
			strncpy(sheet.boxes[sheet.nboxes-1].label, fields[1], 31);
		}
	}
	
	Bterm(b);
	
	int i;
	for(i = 0; i < sheet.nboxes; i++){
		Box *box = &sheet.boxes[i];
		if (box->type >= 0 && box->type < MAXBOXTYPES) {
			BoxType *bt = &boxtypes[box->type];
			if (bt->parse) bt->parse(box);
			if (bt->eval) bt->eval(box);
		}
	}
	redraw();
}

void
handle_filename_input(int key)
{
	if(key == '\n') {
		/* Execute save/load */
		if(sheet.filenamebuf[0] == '\0') {
			strcpy(sheet.filenamebuf, "/tmp/sheet.spr");
		}
		
		if(sheet.save_mode == 1) {
			save(sheet.filenamebuf);
		} else {
			load(sheet.filenamebuf);
			recalc_all();
		}
		
		sheet.entering_filename = 0;
		sheet.current_mode = 0;
		sheet.needredraw = 1;
	} else if(key == Kesc) {
		/* Cancel */
		sheet.entering_filename = 0;
		sheet.current_mode = 0;
		sheet.needredraw = 1;
	} else if(key == '\t') {
		/* Tab completion */
		if(!strstr(sheet.filenamebuf, ".spr")) {
			strcat(sheet.filenamebuf, ".spr");
			sheet.filenamepos = strlen(sheet.filenamebuf);
			sheet.needredraw = 1;
		}
	} else if(key == Kbs) {
		/* Backspace */
		if(sheet.filenamepos > 0) {
			sheet.filenamepos--;
			sheet.filenamebuf[sheet.filenamepos] = '\0';
			sheet.needredraw = 1;
		}
	} else if(key >= 32 && key < 127 && sheet.filenamepos < 250) {
		/* Add character */
		sheet.filenamebuf[sheet.filenamepos++] = key;
		sheet.filenamebuf[sheet.filenamepos] = '\0';
		sheet.needredraw = 1;
	}
}

void
handle_normal_mouse(Mouse m)
{
	int i;

	if(m.buttons & 1){
		/* Left click */
		i = boxat(m.xy);
		if(i >= 0){
			/* Select existing box */
			sheet.selected = i;
			
			/* Drag box with button held */
			while(m.buttons & 1){
				sheet.boxes[i].pos = subpt(m.xy, Pt(BOXWIDTH/2, BOXHEIGHT/2));
				if(sheet.gridsnap){
					sheet.boxes[i].pos.x = (sheet.boxes[i].pos.x / sheet.gridsize) * sheet.gridsize;
					sheet.boxes[i].pos.y = (sheet.boxes[i].pos.y / sheet.gridsize) * sheet.gridsize;
				}
				sheet.boxes[i].r = Rect(sheet.boxes[i].pos.x, sheet.boxes[i].pos.y,
					sheet.boxes[i].pos.x + BOXWIDTH, sheet.boxes[i].pos.y + BOXHEIGHT);
				redraw();
				m = emouse();
			}
		} else {
			/* Create new box */
			i = addbox(m.xy);
			if(i >= 0){
				sheet.selected = i;
				sheet.editing = i;
				sheet.current_mode = 1;
				sheet.editbuf[0] = '\0';
				sheet.editpos = 0;
			}
		}
		sheet.needredraw = 1;
	}
	
	if(m.buttons & 2){
		/* Middle click - edit box */
		i = boxat(m.xy);
		if(i >= 0){
			sheet.selected = i;
			sheet.editing = i;
			sheet.current_mode = 1;
			strcpy(sheet.editbuf, sheet.boxes[i].formula);
			sheet.editpos = strlen(sheet.editbuf);
			sheet.needredraw = 1;
		}
	}
}
void
handle_edit_mouse(Mouse m)
{
	if(m.buttons & 4){
		Box *b = &sheet.boxes[sheet.editing];
		
		/* Save the edit */
		strcpy(b->formula, sheet.editbuf);
		
		/* Determine type */
		if(sheet.editbuf[0] == '=') {
			b->type = T_FORMULA;
		} else {
			char *endp;
			strtod(sheet.editbuf, &endp);
			b->type = (*endp == '\0') ? T_NUMBER : T_TEXT;
		}
		
		/* Parse and evaluate */
		if (b->type >= 0 && b->type < MAXBOXTYPES) {
			BoxType *bt = &boxtypes[b->type];
			if (bt->parse) bt->parse(b);
			if (bt->eval) bt->eval(b);
		}
		recalc_all();
		
		/* Return to normal mode */
		sheet.editing = -1;
		sheet.current_mode = 0;
		sheet.needredraw = 1;
	}

	/* Clicking outside the box could cancel the edit */
	int i = boxat(m.xy);
	if(i != sheet.editing && (m.buttons & 1)){
		sheet.editing = -1;
		sheet.current_mode = 0;
		sheet.needredraw = 1;
	}
}

void
handle_label_mouse(Mouse m)
{
	/* Clicking outside the box could cancel the label edit */
	int i = boxat(m.xy);
	if(i != sheet.editing_label && (m.buttons & 1)){
		sheet.editing_label = -1;
		sheet.current_mode = 0;
		sheet.needredraw = 1;
	}
}

void
handle_filename_mouse(Mouse m)
{
	/* Clicks do nothing in this mode */
	USED(m);
}


void 
eresized(int new) 
{
	if(new && getwindow(display, Refnone) < 0)
		sysfatal("can't reattach to window");
		
}

void
main(int argc, char *argv[])
{
	Event e;
	
	USED(argc);
	USED(argv);
	
	if(initdraw(nil, nil, "freecell") < 0)
		sysfatal("initdraw: %r");
	
	initcolors();
	einit(Emouse | Ekeyboard);
	eresized(0);

	memset(&sheet, 0, sizeof(sheet));
	sheet.selected = -1;
	sheet.editing = -1;
	sheet.editing_label = -1; 
	sheet.entering_filename = 0;
	sheet.current_mode = 0;
	sheet.gridsize = 32;
	sheet.gridsnap = 1;
	
	redraw();
	
	for(;;){
		switch(event(&e)){
		case Emouse:
			handlemouse(e.mouse);
			break;
			
		case Ekeyboard:
			handlekey(e.kbdc);
			break;
		}
		
		if(sheet.needredraw){
			redraw();
			sheet.needredraw = 0;
		}
	}
}