shithub: fc

Download patch

ref: 52bfaf7faba06ab40941fd01f97a9e7edebcef83
parent: b9ac29a8e5985f63ec1f20e1fe2b3bc027c02fb9
author: glenda <glenda@krsna>
date: Sun Aug 17 09:48:25 EDT 2025

conf-add

--- a/fc.c
+++ b/fc.c
@@ -5,16 +5,35 @@
 #include <keyboard.h>
 #include <bio.h>
 #include <ctype.h>
-#define CTLFILE "/tmp/fc.ctl"
 
-enum {
-	MAXBOXES = 300,
-	BOXWIDTH = 128,
-	BOXHEIGHT = 32,
-	MAXFORMULA = 256,
-	MAXCONTENT = 256,
-};
+/* Configuration Defaults */
+#define CONFIG_FILE "/tmp/fc.conf"
+#define DEFAULT_CTL_FILE "/tmp/fc.ctl"
+#define DEFAULT_SAVE_PATH "/tmp/sheet.spr"
+#define DEFAULT_BANNER_HEIGHT 25
+#define DEFAULT_STATUS_HEIGHT 20
+#define DEFAULT_STATUS_MARGIN 10
+#define DEFAULT_BOX_LABEL_OFFSET_Y 16
+#define DEFAULT_BOX_TEXT_MARGIN 5
+#define DEFAULT_FORMULA_INDICATOR_OFFSET 10
+#define DEFAULT_DIALOG_WIDTH 256
+#define DEFAULT_DIALOG_HEIGHT 48
+#define DEFAULT_DIALOG_PADDING 10
+#define DEFAULT_EMOJI_SPEED 3
+#define DEFAULT_EMOJI_FRAME_DELAY 10
+#define DEFAULT_CTL_CHECK_INTERVAL 200
+#define DEFAULT_REDRAW_INTERVAL 30
+#define DEFAULT_MAX_EVAL_DEPTH 10
+#define DEFAULT_MAX_RECALC_PASSES 10
+#define DEFAULT_FORMULA_PRECISION 2
 
+/* Constants */
+#define MAXBOXES 300
+#define BOXWIDTH 128
+#define BOXHEIGHT 32
+#define MAXFORMULA 256
+#define MAXCONTENT 256
+
 enum {
 	T_TEXT = 0,
 	T_NUMBER,
@@ -34,7 +53,7 @@
 	TOK_COMMA,
 	TOK_STRING,
 	TOK_END,
-	
+
 	OP_ADD = '+',
 	OP_SUB = '-',
 	OP_MUL = '*',
@@ -45,9 +64,9 @@
 	OP_NE = '!',
 	OP_LT = '<',
 	OP_GT = '>',
-	OP_LE = '[',  /* for <= */
-	OP_GE = ']',  /* for >= */
-	
+	OP_LE = '[',
+	OP_GE = ']',
+
 	FN_SUM = 0,
 	FN_AVG,
 	FN_MIN,
@@ -71,46 +90,97 @@
 	MAXFUNCS,
 };
 
+typedef struct Config Config;
+struct Config {
+	/* Visual */
+	int banner_height;
+	int status_height;
+	int status_margin;
+	int box_label_offset_y;
+	int box_text_margin;
+	int formula_indicator_offset;
+
+	/* Dialog */
+	int dialog_width;
+	int dialog_height;
+	int dialog_padding;
+
+	/* Animation */
+	int emoji_enabled;
+	int emoji_speed;
+	int emoji_frame_delay;
+	int ctl_check_interval;
+	int redraw_interval;
+
+	/* Grid */
+	int gridsnap;
+	int gridsize;
+
+	/* Behavior */
+	int max_eval_depth;
+	int max_recalc_passes;
+	int formula_precision;
+	int show_formula_indicator;
+
+	/* Paths */
+	char ctl_file[256];
+	char default_save_path[256];
+
+	/* Colors */
+	ulong color_bg;
+	ulong color_fg;
+	ulong color_box_bg;
+	ulong color_selected;
+	ulong color_editing;
+	ulong color_grid;
+	ulong color_label;
+	ulong color_formula;
+
+	/* Formula format string */
+	char formula_format[32];
+};
+
 typedef struct Box Box;
 struct Box {
-	Point		pos;			
-	Rectangle	r;					
+	Point		pos;
+	Rectangle	r;
 	char		content[MAXCONTENT];
 	char		formula[MAXFORMULA];
-	char		label[32];	
-	int			type;				
-	double		value;				
-	int			selected;			
-	int			dirty;				
+	char		label[32];
+	int			type;
+	double		value;
+	int			selected;
+	int			dirty;
 	int			refs[10];
 	int			nrefs;
 };
 
-struct {
+typedef struct Sheet Sheet;
+struct Sheet {
 	Box		boxes[MAXBOXES];
 	int		nboxes;
 	int		selected;
-	int		editing;	
+	int		editing;
 	char	editbuf[MAXCONTENT];
 	int		editpos;
-	int		editing_label;	
-	char	labelbuf[32];		
+	int		editing_label;
+	char	labelbuf[32];
 	int		labelpos;
-	int		entering_filename;	
+	int		entering_filename;
 	char	filenamebuf[256];
-	int		filenamepos;		
-	int		save_mode;	
+	int		filenamepos;
+	int		save_mode;
 	int		current_mode;
-	Point	offset;	
+	Point	offset;
 	int		needredraw;
 	int		gridsnap;
 	int		gridsize;
-    int     emoji_pos;         
-    int     emoji_frame;       
-    int     emoji_dir;          /* Direction: 1=right, -1=left */
-    char    *emoji_frames[4];  
-    int     emoji_enabled;     
-} sheet;
+	int		emoji_pos;
+	int		emoji_frame;
+	int		emoji_dir;
+	char	*emoji_frames[4];
+	int		emoji_enabled;
+};
 
 typedef struct BoxType BoxType;
 struct BoxType {
@@ -125,7 +195,7 @@
 	int	type;
 	union {
 		double	num;
-		int	cell;		
+		int	cell;
 		struct {
 			int start, end;
 		} range;
@@ -148,8 +218,8 @@
 	Token	tokens[256];
 	int	ntokens;
 	int	pos;
-	Box	*current;	
-	int	depth;		
+	Box	*current;
+	int	depth;
 };
 
 typedef void (*KeyHandler)(int key);
@@ -173,13 +243,13 @@
 typedef struct EditAction EditAction;
 struct EditAction {
 	int		key;
-	int		(*action)(char *buf, int *pos, int maxlen); 
+	int		(*action)(char *buf, int *pos, int maxlen);
 };
 
 typedef struct DrawStep DrawStep;
 struct DrawStep {
 	void (*draw)(void);
-	int  condition; 
+	int  condition;
 };
 
 typedef struct CommandHandler CommandHandler;
@@ -189,11 +259,36 @@
 	void	(*execute)(char **args, int nargs);
 };
 
+typedef enum {
+	CFG_INT,
+	CFG_STRING,
+	CFG_COLOR,
+	CFG_BOOL
+} ConfigType;
+
+typedef struct ConfigField ConfigField;
+struct ConfigField {
+	char		*name;
+	ConfigType	type;
+	void		*ptr;
+	int			maxlen;				/* for strings */
+	void		(*callback)(void);	/* optional callback after setting */
+};
+
+/* Global Variables */
+Config config;
+Sheet sheet;
 Image	*colors[6];
 Image	*boxbg;
 Image	*boxselected;
 Image	*boxediting;
 Image	*gridcolor;
+
+
+/* Function Declarations */
+void load_config(char *path);
+void save_config(char *path);
+void apply_config(void);
 void parse_text(Box*);
 void parse_number(Box*);
 void parse_formula(Box*);
@@ -229,6 +324,7 @@
 void cmd_delete_box(void);
 void cmd_cycle_emoji(void);
 void cmd_toggle_emoji(void);
+void cmd_reload_config(void);
 void ctl_addbox(char**, int);
 void ctl_load(char**, int);
 void ctl_save(char**, int);
@@ -235,6 +331,8 @@
 void ctl_quit(char**, int);
 void init_emoji(void);
 void initcolors(void);
+void update_formula_format(void);
+void validate_config(void);
 int cistrcmp(char *s1, char *s2);
 int tokenize_formula(char *formula, Token *tokens, int maxtokens);
 int cellref_lookup(char *ref);
@@ -264,6 +362,44 @@
 double eval_factor(Eval *e);
 double eval_primary(Eval *e);
 
+ConfigField config_fields[] = {
+	{"banner_height",				CFG_INT,	&config.banner_height,				0,	nil},
+	{"status_height",				CFG_INT,	&config.status_height,				0,	nil},
+	{"box_label_offset_y",			CFG_INT,	&config.box_label_offset_y,			0,	nil},
+	{"box_text_margin",				CFG_INT,	&config.box_text_margin,			0,	nil},
+	{"formula_indicator_offset",	CFG_INT,	&config.formula_indicator_offset,	0,	nil},
+	{"dialog_width",				CFG_INT,	&config.dialog_width,				0,	nil},
+	{"dialog_height",				CFG_INT,	&config.dialog_height,				0,	nil},
+	{"dialog_padding",				CFG_INT,	&config.dialog_padding,				0,	nil},
+
+	{"emoji_enabled",			CFG_BOOL,	&config.emoji_enabled,					0,	nil},
+	{"emoji_speed",				CFG_INT,	&config.emoji_speed,					0,	nil},
+	{"emoji_frame_delay",		CFG_INT,	&config.emoji_frame_delay,				0,	nil},
+	{"ctl_check_interval",		CFG_INT,	&config.ctl_check_interval,				0,	nil},
+	{"redraw_interval",			CFG_INT,	&config.redraw_interval,				0,	nil},
+
+	{"gridsize",				CFG_INT,	&config.gridsize,						0,	nil},
+	{"gridsnap",				CFG_BOOL,	&config.gridsnap,						0,	nil},
+
+	{"max_eval_depth",			CFG_INT,	&config.max_eval_depth,					0,	nil},
+	{"max_recalc_passes",		CFG_INT,	&config.max_recalc_passes,				0,	nil},
+	{"formula_precision",		CFG_INT,	&config.formula_precision,				0,	update_formula_format},
+	{"show_formula_indicator",	CFG_BOOL,	&config.show_formula_indicator,			0,	nil},
+
+	{"ctl_file",			CFG_STRING,	config.ctl_file,						256,	nil},
+	{"default_save",		CFG_STRING,	config.default_save_path,				256,	nil},
+	{"color_bg",			CFG_COLOR,	&config.color_bg,							0,	nil},
+	{"color_fg",			CFG_COLOR,	&config.color_fg,							0,	nil},
+	{"color_box_bg",		CFG_COLOR,	&config.color_box_bg,						0,	nil},
+	{"color_selected",		CFG_COLOR,	&config.color_selected,						0,	nil},
+	{"color_editing",		CFG_COLOR,	&config.color_editing,						0,	nil},
+	{"color_grid",			CFG_COLOR,	&config.color_grid,							0,	nil},
+	{"color_label",			CFG_COLOR,	&config.color_label,						0,	nil},
+	{"color_formula",		CFG_COLOR,	&config.color_formula,						0,	nil},
+
+	{nil, 0, nil, 0, nil}
+};
+
 BoxType boxtypes[] = {
 	[T_TEXT]    = {"text",    parse_text,    eval_text,    draw_box_generic},
 	[T_NUMBER]  = {"number",  parse_number,  eval_number,  draw_box_generic},
@@ -291,10 +427,10 @@
 };
 
 InputMode input_modes[] = {
-	[0] = {"normal",    handle_normal_mode,    handle_normal_mouse,    draw_normal_overlay,    "E:moji-off  S:ave  O:pen  l:abel  g:rid  e:next-emoji"},
+	[0] = {"normal",    handle_normal_mode,    handle_normal_mouse,    draw_normal_overlay,    "E:moji  S:ave  O:pen  l:abel  g:rid  e:cycle  r:eload-cfg "},
 	[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"},
+	[3] = {"filename",  handle_filename_input, handle_filename_mouse,  draw_filename_overlay,  "Tab:.spr  Enter:confirm  Esc:cancel"},
 };
 
 Command commands[] = {
@@ -307,8 +443,9 @@
 	{'l',	cmd_start_label},
 	{'g',	cmd_toggle_grid},
 	{'d',	cmd_delete_box},
-    {'E',   cmd_toggle_emoji},
-    {'e',   cmd_cycle_emoji}, 
+	{'E',   cmd_toggle_emoji},
+	{'e',   cmd_cycle_emoji},
+	{'r',   cmd_reload_config},
 	{0, nil}
 };
 
@@ -316,15 +453,15 @@
 	{'\n',	edit_finish},
 	{Kesc,	edit_cancel},
 	{Kbs,	edit_backspace},
-	{-1,	edit_add_char}, 
+	{-1,	edit_add_char},
 	{0, nil}
 };
 
 DrawStep draw_steps[] = {
-	{draw_background,	0}, 
-	{draw_grid_lines,	1}, 
-	{draw_all_boxes,	0},  
-	{draw_status_line,	0}, 
+	{draw_background,	0},
+	{draw_grid_lines,	1},
+	{draw_all_boxes,	0},
+	{draw_status_line,	0},
 	{nil, 0}
 };
 
@@ -337,55 +474,334 @@
 };
 
 char *happy_faces[] = {
-    "^_^",
-    "^o^", 
-    "^_^",
-    "^-^"
+	"^_^",
+	"^o^",
+	"^_^",
+	"^-^"
 };
 
 char *dancing_guy[] = {
-    "\\o/",
-    "_o_",
-    "/o\\",
-    "_o_"
+	"\\o/",
+	"_o_",
+	"/o\\",
+	"_o_"
 };
 
 char *kirby_dance[] = {
-    "<('.')>",
-    "<('.')<",
-    "^('.')^",
-    "v('.')v"
+	"<('.')>",
+	"<('.')<",
+	"^('.')^",
+	"v('.')v"
 };
 
 char *lambda_dance[] = {
-    "L(^_^)L",
-    "L(>_<)L",
-    "L(o_o)L",
-    "L(*_*)L"
+	"L(^_^)L",
+	"L(>_<)L",
+	"L(o_o)L",
+	"L(*_*)L"
 };
 
 char *rcc_style[] = {
-    "(-(-_-(-_(-_-)_-)_-)-)",
-    "[~o-o]~",
-    "~(o_o)~",
-    "*(^o^)/*"
+	"(-(-_-(-_(-_-)_-)_-)-)",
+	"[~o-o]~",
+	"~(o_o)~",
+	"*(^o^)/*"
 };
 
 char *cat_faces[] = {
-    "=^.^=",
-    "=^.o=",
-    "=o.^=",
-    "=o.o="
+	"=^.^=",
+	"=^.o=",
+	"=o.^=",
+	"=o.o="
 };
 
 char *shrug_guys[] = {
-    "~\\_('.')_/~",
-    "~\\_(o.o)_/~", 
-    "~\\_(-.-)_/~",
-    "~\\_(^.^)_/~"
+	"~\\_('.')_/~",
+	"~\\_(o.o)_/~",
+	"~\\_(-.-)_/~",
+	"~\\_(^.^)_/~"
 };
 
 void
+save_config(char *path)
+{
+	int fd;
+	Biobuf *b;
+	
+	fd = create(path, OWRITE, 0644);
+	if(fd < 0) {
+		fprint(2, "cannot create config %s: %r\n", path);
+		return;
+	}
+	
+	b = Bfdopen(fd, OWRITE);
+	
+	Bprint(b, "# FreeBox Configuration File\n");
+	Bprint(b, "# Generated automatically - edit to customize\n");
+	Bprint(b, "# Reload with 'r' key while running\n\n");
+	
+	Bprint(b, "# Visual Settings\n");
+	Bprint(b, "banner_height %d\n", config.banner_height);
+	Bprint(b, "status_height %d\n", config.status_height);
+	Bprint(b, "box_label_offset_y %d\n", config.box_label_offset_y);
+	Bprint(b, "box_text_margin %d\n", config.box_text_margin);
+	Bprint(b, "formula_indicator_offset %d\n", config.formula_indicator_offset);
+	
+	Bprint(b, "\n# Dialog Settings\n");
+	Bprint(b, "dialog_width %d\n", config.dialog_width);
+	Bprint(b, "dialog_height %d\n", config.dialog_height);
+	Bprint(b, "dialog_padding %d\n", config.dialog_padding);
+	
+	Bprint(b, "\n# Animation Settings\n");
+	Bprint(b, "emoji_enabled %d\n", config.emoji_enabled);
+	Bprint(b, "emoji_speed %d\n", config.emoji_speed);
+	Bprint(b, "emoji_frame_delay %d\n", config.emoji_frame_delay);
+	Bprint(b, "ctl_check_interval %d\n", config.ctl_check_interval);
+	Bprint(b, "redraw_interval %d\n", config.redraw_interval);
+	
+	Bprint(b, "\n# Grid Settings\n");
+	Bprint(b, "gridsize %d\n", config.gridsize);
+	Bprint(b, "gridsnap %d\n", config.gridsnap);
+	
+	Bprint(b, "\n# Behavior Settings\n");
+	Bprint(b, "max_eval_depth %d\n", config.max_eval_depth);
+	Bprint(b, "max_recalc_passes %d\n", config.max_recalc_passes);
+	Bprint(b, "formula_precision %d\n", config.formula_precision);
+	Bprint(b, "show_formula_indicator %d\n", config.show_formula_indicator);
+	
+	Bprint(b, "\n# Paths\n");
+	Bprint(b, "ctl_file %s\n", config.ctl_file);
+	Bprint(b, "default_save %s\n", config.default_save_path);
+	
+	Bprint(b, "\n# Colors (RGBA hex)\n");
+	Bprint(b, "color_bg %08lux\n", config.color_bg);
+	Bprint(b, "color_fg %08lux\n", config.color_fg);
+	Bprint(b, "color_box_bg %08lux\n", config.color_box_bg);
+	Bprint(b, "color_selected %08lux\n", config.color_selected);
+	Bprint(b, "color_editing %08lux\n", config.color_editing);
+	Bprint(b, "color_grid %08lux\n", config.color_grid);
+	Bprint(b, "color_label %08lux\n", config.color_label);
+	Bprint(b, "color_formula %08lux\n", config.color_formula);
+	
+	Bterm(b);
+	close(fd);
+}
+
+void
+init_config_defaults(void)
+{
+	/* Visual */
+	config.banner_height = DEFAULT_BANNER_HEIGHT;
+	config.status_height = DEFAULT_STATUS_HEIGHT;
+	config.status_margin = DEFAULT_STATUS_MARGIN;
+	config.box_label_offset_y = DEFAULT_BOX_LABEL_OFFSET_Y;
+	config.box_text_margin = DEFAULT_BOX_TEXT_MARGIN;
+	config.formula_indicator_offset = DEFAULT_FORMULA_INDICATOR_OFFSET;
+
+	/* Dialog */
+	config.dialog_width = DEFAULT_DIALOG_WIDTH;
+	config.dialog_height = DEFAULT_DIALOG_HEIGHT;
+	config.dialog_padding = DEFAULT_DIALOG_PADDING;
+
+	/* Animation */
+	config.emoji_enabled = 1;
+	config.emoji_speed = DEFAULT_EMOJI_SPEED;
+	config.emoji_frame_delay = DEFAULT_EMOJI_FRAME_DELAY;
+	config.ctl_check_interval = DEFAULT_CTL_CHECK_INTERVAL;
+	config.redraw_interval = DEFAULT_REDRAW_INTERVAL;
+
+	/* Grid */
+	config.gridsnap = 1;
+	config.gridsize = 32;
+
+	/* Behavior */
+	config.max_eval_depth = DEFAULT_MAX_EVAL_DEPTH;
+	config.max_recalc_passes = DEFAULT_MAX_RECALC_PASSES;
+	config.formula_precision = DEFAULT_FORMULA_PRECISION;
+	config.show_formula_indicator = 1;
+
+	/* Paths */
+	strcpy(config.ctl_file, DEFAULT_CTL_FILE);
+	strcpy(config.default_save_path, DEFAULT_SAVE_PATH);
+
+	/* Colors */
+	config.color_bg = 0xEEEEEEFF;
+	config.color_fg = 0x000000FF;
+	config.color_box_bg = 0xFFFFFFFF;
+	config.color_selected = 0x4444FFFF;
+	config.color_editing = 0xCCCC88FF;
+	config.color_grid = 0xCCCCCCFF;
+	config.color_label = 0xFF4444FF;
+	config.color_formula = 0x4444FFFF;
+
+	update_formula_format();
+}
+
+void
+update_formula_format(void)
+{
+	snprint(config.formula_format, sizeof(config.formula_format),
+		"%%.%df", config.formula_precision);
+}
+
+void
+load_config(char *path)
+{
+	Biobuf *b;
+	char *line;
+	char *fields[3];
+	int nf;
+	ConfigField *cf;
+
+	/* Initialize with defaults first */
+	init_config_defaults();
+
+	b = Bopen(path, OREAD);
+	if(b == nil) {
+		/* Config file doesn't exist, use defaults */
+		return;
+	}
+
+	while((line = Brdline(b, '\n')) != nil) {
+		line[Blinelen(b)-1] = '\0';
+
+		/* Skip comments and empty lines */
+		if(line[0] == '#' || line[0] == '\0')
+			continue;
+
+		nf = tokenize(line, fields, nelem(fields));
+		if(nf < 2)
+			continue;
+
+		/* Look up field in configuration table */
+		for(cf = config_fields; cf->name != nil; cf++) {
+			if(strcmp(fields[0], cf->name) == 0) {
+				/* Process based on type */
+				switch(cf->type) {
+				case CFG_INT:
+					*(int*)cf->ptr = atoi(fields[1]);
+					break;
+
+				case CFG_BOOL:
+					*(int*)cf->ptr = atoi(fields[1]) ? 1 : 0;
+					break;
+
+				case CFG_STRING:
+					strncpy((char*)cf->ptr, fields[1], cf->maxlen - 1);
+					((char*)cf->ptr)[cf->maxlen - 1] = '\0';
+					break;
+
+				case CFG_COLOR:
+					*(ulong*)cf->ptr = strtoul(fields[1], nil, 16);
+					break;
+				}
+
+				/* Call callback if present */
+				if(cf->callback)
+					cf->callback();
+
+				break;	/* Found field, move to next line */
+			}
+		}
+
+		/* Optional: warn about unknown fields */
+		if(cf->name == nil) {
+			fprint(2, "Warning: unknown config field '%s'\n", fields[0]);
+		}
+	}
+
+	Bterm(b);
+}
+
+void
+validate_config(void)
+{
+	/* Ensure sane values */
+	if(config.banner_height < 0) config.banner_height = 0;
+	if(config.banner_height > 100) config.banner_height = 100;
+
+	if(config.gridsize < 8) config.gridsize = 8;
+	if(config.gridsize > 256) config.gridsize = 256;
+
+	if(config.emoji_speed < 1) config.emoji_speed = 1;
+	if(config.emoji_speed > 20) config.emoji_speed = 20;
+
+	if(config.formula_precision < 0) config.formula_precision = 0;
+	if(config.formula_precision > 10) config.formula_precision = 10;
+
+	if(config.max_eval_depth < 1) config.max_eval_depth = 1;
+	if(config.max_eval_depth > 100) config.max_eval_depth = 100;
+
+	if(config.max_recalc_passes < 1) config.max_recalc_passes = 1;
+	if(config.max_recalc_passes > 100) config.max_recalc_passes = 100;
+
+	/* Ensure paths are not empty */
+	if(config.ctl_file[0] == '\0')
+		strcpy(config.ctl_file, DEFAULT_CTL_FILE);
+	if(config.default_save_path[0] == '\0')
+		strcpy(config.default_save_path, DEFAULT_SAVE_PATH);
+
+	update_formula_format();
+}
+void
+apply_config(void)
+{
+	/* Apply config to sheet */
+	sheet.emoji_enabled = config.emoji_enabled;
+	sheet.gridsize = config.gridsize;
+	sheet.gridsnap = config.gridsnap;
+
+	/* Re-allocate colors if they exist */
+	if(colors[0]) {
+		freeimage(colors[0]);
+		colors[0] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_fg);
+	}
+	if(colors[1]) {
+		freeimage(colors[1]);
+		colors[1] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_box_bg);
+	}
+	if(colors[2]) {
+		freeimage(colors[2]);
+		colors[2] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_bg);
+	}
+	if(colors[3]) {
+		freeimage(colors[3]);
+		colors[3] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_formula);
+	}
+	if(colors[4]) {
+		freeimage(colors[4]);
+		colors[4] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_label);
+	}
+	if(colors[5]) {
+		freeimage(colors[5]);
+		colors[5] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_editing);
+	}
+
+	if(boxselected) {
+		freeimage(boxselected);
+		boxselected = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_selected);
+	}
+	if(boxediting) {
+		freeimage(boxediting);
+		boxediting = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_editing);
+	}
+	if(gridcolor) {
+		freeimage(gridcolor);
+		gridcolor = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_grid);
+	}
+}
+
+void
+cmd_reload_config(void)
+{
+	load_config(CONFIG_FILE);
+	validate_config();
+	apply_config();
+	sheet.needredraw = 1;
+	fprint(2, "Configuration reloaded from %s\n", CONFIG_FILE);
+}
+
+void
 handlekey(int key)
 {
 	InputMode *mode = &input_modes[sheet.current_mode];
@@ -404,17 +820,17 @@
 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);
-	
+	colors[0] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_fg);
+	colors[1] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_box_bg);
+	colors[2] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_bg);
+	colors[3] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_formula);
+	colors[4] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_label);
+	colors[5] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_editing);
+
 	boxbg = colors[1];
-	boxselected = colors[3];
-	boxediting = colors[5];
-	gridcolor = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xCCCCCCFF);
+	boxselected = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_selected);
+	boxediting = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_editing);
+	gridcolor = allocimage(display, Rect(0,0,1,1), screen->chan, 1, config.color_grid);
 }
 
 double
@@ -426,7 +842,6 @@
 		return ceil(x - 0.5);
 }
 
-/* Helper for case-insensitive string comparison */
 int
 cistrcmp(char *s1, char *s2)
 {
@@ -445,31 +860,26 @@
 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] && 
+		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;
@@ -480,8 +890,8 @@
 		if(col >= 0 && col < sheet.nboxes)
 			return col;
 	}
-	
-	return -1;	/* not found */
+
+	return -1;
 }
 
 int
@@ -491,19 +901,19 @@
 	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;
@@ -512,21 +922,21 @@
 			ntok++;
 			continue;
 		}
-		
+
 		if(isalpha(*p)) {
 			i = 0;
 			while(*p && (isalnum(*p) || *p == '_') && i < 255)
 				buf[i++] = *p++;
 			buf[i] = '\0';
-			
+
 			if(*p == ':') {
-				p++;	/* skip : */
+				p++;
 				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);
@@ -533,7 +943,7 @@
 				ntok++;
 				continue;
 			}
-			
+
 			int found = 0;
 			for(i = 0; i < MAXFUNCS; i++) {
 				if(functions[i].name && cistrcmp(buf, functions[i].name) == 0) {
@@ -543,9 +953,8 @@
 					break;
 				}
 			}
-			
+
 			if(!found) {
-				/* It's a cell reference */
 				t->type = TOK_CELL;
 				t->cell = cellref_lookup(buf);
 			}
@@ -552,7 +961,7 @@
 			ntok++;
 			continue;
 		}
-		
+
 		if(*p == '"') {
 			p++;
 			i = 0;
@@ -565,14 +974,13 @@
 			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++;
@@ -585,36 +993,36 @@
 			}
 			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;
 }
 
@@ -622,36 +1030,36 @@
 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 */
+				return 0.0;
 			}
-			
-			if(b->type == T_FORMULA && e->depth < 10) {
+
+			if(b->type == T_FORMULA && e->depth < config.max_eval_depth) {
 				Eval subeval;
 				subeval.current = b;
 				subeval.depth = e->depth + 1;
 				subeval.pos = 0;
-				subeval.ntokens = tokenize_formula(b->formula, 
+				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;
 	}
@@ -662,12 +1070,12 @@
 {
 	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:
@@ -675,36 +1083,30 @@
 		result = token_value(t, e);
 		e->pos++;
 		break;
-		
+
 	case TOK_FUNC: {
 		int func = t->func;
-		e->pos++;	/* skip function name */
-		
-		/* Expect ( */
+		e->pos++;
+
 		if(e->pos >= e->ntokens || e->tokens[e->pos].type != TOK_LPAREN)
 			return 0.0;
-		e->pos++;	/* skip ( */
-		
-		/* Collect arguments */
+		e->pos++;
+
 		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 */
+				e->pos++;
 			}
-			
-			/* 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;
@@ -712,18 +1114,15 @@
 					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);
@@ -731,17 +1130,15 @@
 		}
 		break;
 	}
-		
+
 	case TOK_LPAREN:
-		e->pos++;	/* skip ( */
+		e->pos++;
 		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);
@@ -748,40 +1145,38 @@
 		}
 		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 */
+
+		e->pos++;
 		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++;
@@ -794,7 +1189,7 @@
 				if(right != 0.0)
 					left /= right;
 				else
-					left = 0.0;	/* div by zero */
+					left = 0.0;
 			}
 			break;
 		case OP_MOD:
@@ -809,21 +1204,20 @@
 			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++;
@@ -861,7 +1255,7 @@
 			return left;
 		}
 	}
-	
+
 	return left;
 }
 
@@ -871,7 +1265,7 @@
 	double sum = 0.0;
 	int i;
 	Eval e;
-	
+
 	for(i = 0; i < nargs; i++) {
 		if(args[i].type == TOK_CELL) {
 			e.current = nil;
@@ -897,11 +1291,11 @@
 {
 	if(nargs == 0)
 		return 0.0;
-	
-	double min = 1e308;	/* large number */
+
+	double min = 1e308;
 	int i;
 	Eval e;
-	
+
 	for(i = 0; i < nargs; i++) {
 		double val;
 		if(args[i].type == TOK_CELL) {
@@ -922,11 +1316,11 @@
 {
 	if(nargs == 0)
 		return 0.0;
-	
-	double max = -1e308;	/* small number */
+
+	double max = -1e308;
 	int i;
 	Eval e;
-	
+
 	for(i = 0; i < nargs; i++) {
 		double val;
 		if(args[i].type == TOK_CELL) {
@@ -947,7 +1341,7 @@
 {
 	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) {
@@ -956,7 +1350,7 @@
 					count++;
 			}
 		} else {
-			count++;	/* direct number */
+			count++;
 		}
 	}
 	return (double)count;
@@ -989,7 +1383,6 @@
 	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;
 	}
@@ -1048,13 +1441,8 @@
 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;
 }
 
@@ -1062,7 +1450,7 @@
 parse_formula(Box *b)
 {
 	Eval e;
-	
+
 	if(b->formula[0] != '=') {
 		char *endp;
 		double val = strtod(b->formula, &endp);
@@ -1069,7 +1457,7 @@
 		if(*endp == '\0') {
 			b->type = T_NUMBER;
 			b->value = val;
-			snprint(b->content, MAXCONTENT, "%.2f", val);
+			snprint(b->content, MAXCONTENT, config.formula_format, val);
 		} else {
 			b->type = T_TEXT;
 			strncpy(b->content, b->formula, MAXCONTENT);
@@ -1076,19 +1464,16 @@
 		}
 		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));	
+	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++) {
@@ -1095,7 +1480,6 @@
 		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)
@@ -1108,29 +1492,27 @@
 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);
+		snprint(b->content, MAXCONTENT, config.formula_format, 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];
@@ -1150,14 +1532,12 @@
 {
 	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++) {
@@ -1168,7 +1548,7 @@
 			}
 		}
 		passes++;
-	} while(changed && passes < 10);
+	} while(changed && passes < config.max_recalc_passes);
 }
 
 int
@@ -1176,7 +1556,7 @@
 {
 	int i;
 	Box *b;
-	
+
 	for(i = sheet.nboxes - 1; i >= 0; i--){
 		b = &sheet.boxes[i];
 		if(ptinrect(p, b->r))
@@ -1189,24 +1569,23 @@
 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++;
 }
 
@@ -1215,13 +1594,11 @@
 {
 	if(i < 0 || i >= sheet.nboxes)
 		return;
-	
-	/* Shift boxes down */
-	memmove(&sheet.boxes[i], &sheet.boxes[i+1], 
+
+	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];
@@ -1229,7 +1606,7 @@
 			if(b->refs[k] > i)
 				b->refs[k]--;
 			else if(b->refs[k] == i)
-				b->refs[k] = -1;  /* invalidate */
+				b->refs[k] = -1;
 		}
 	}
 }
@@ -1237,7 +1614,6 @@
 void
 parse_text(Box *b)
 {
-	/* copy content as is */
 	strncpy(b->content, b->formula, MAXCONTENT);
 }
 
@@ -1246,13 +1622,12 @@
 {
 	char *endp;
 	b->value = strtod(b->formula, &endp);
-	snprint(b->content, MAXCONTENT, "%.2f", b->value);
+	snprint(b->content, MAXCONTENT, config.formula_format, b->value);
 }
 
 void
 eval_text(Box *b)
 {
-	/* Text doesn't evaluate */
 	USED(b);
 }
 
@@ -1259,7 +1634,6 @@
 void
 eval_number(Box *b)
 {
-	/* Numbers are already evaluated */
 	USED(b);
 }
 
@@ -1267,8 +1641,8 @@
 draw_box_generic(Box *b, Image *dst)
 {
 	Image *bg = boxbg;
-	int idx = b - sheet.boxes;  /* Get box index */
-	
+	int idx = b - sheet.boxes;
+
 	if(sheet.editing == idx)
 		bg = boxediting;
 	else if(sheet.editing_label == idx)
@@ -1275,7 +1649,7 @@
 		bg = colors[5];
 	else if(b->selected)
 		bg = boxselected;
-	
+
 	draw(dst, b->r, bg, nil, ZP);
 	border(dst, b->r, 1, colors[0], ZP);
 
@@ -1284,30 +1658,29 @@
 		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", 
+			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 */
-	
+
+	Point p = addpt(b->r.min, Pt(config.box_text_margin, config.box_label_offset_y));
+
 	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, "=");
+
+	if (b->type == T_FORMULA && config.show_formula_indicator){
+		string(dst, Pt(b->r.max.x - config.formula_indicator_offset, b->r.min.y + 2),
+			colors[3], ZP, font, "=");
 	}
 }
 
@@ -1314,40 +1687,34 @@
 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);
-    }
+	int x, y;
+	Rectangle r = screen->r;
+
+	int startx = (r.min.x / sheet.gridsize) * sheet.gridsize;
+	int starty = (r.min.y / sheet.gridsize) * sheet.gridsize;
+
+	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);
+	}
+
+	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
@@ -1354,29 +1721,25 @@
 draw_filename_overlay(void)
 {
 	Rectangle r;
-	int w = 256;
-	int h = 48;
+	int w = config.dialog_width;
+	int h = config.dialog_height;
 	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), 
+	string(screen, Pt(r.min.x + config.dialog_padding, 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), 
+	string(screen, Pt(r.min.x + config.dialog_padding, r.min.y + 20),
 		colors[0], ZP, font, display);
-	
 }
 
 void
@@ -1413,12 +1776,13 @@
 {
 	char buf[256];
 	InputMode *mode = &input_modes[sheet.current_mode];
-	
-	snprint(buf, sizeof(buf), "Selected: %d | Mode: %s | Boxes: %d | %s", 
+
+	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);
+
+	string(screen, Pt(screen->r.min.x + config.status_margin,
+		screen->r.max.y - config.status_height),
+		colors[0], ZP, font, buf);
 }
 
 void
@@ -1426,14 +1790,12 @@
 {
 	DrawStep *step;
 	draw_emoji_banner();
-    /* Then draw everything else with offset */
-    Rectangle clip = screen->r;
-    clip.min.y += 25; 
-    replclipr(screen, 0, clip);
+	Rectangle clip = screen->r;
+	clip.min.y += config.banner_height;
+	replclipr(screen, 0, clip);
 
-	/* Execute drawing steps from table */
 	for(step = draw_steps; step->draw; step++) {
-		if(step->condition == 0 || 
+		if(step->condition == 0 ||
 		   (step->condition == 1 && sheet.gridsnap)) {
 			step->draw();
 		}
@@ -1440,11 +1802,10 @@
 	}
 	replclipr(screen, 0, screen->r);
 
-	/* Draw mode-specific overlay */
 	InputMode *mode = &input_modes[sheet.current_mode];
 	if(mode->draw)
 		mode->draw();
-	
+
 	flushimage(display, 1);
 }
 
@@ -1452,116 +1813,109 @@
 edit_finish(char *buf, int *pos, int maxlen)
 {
 	USED(buf); USED(pos); USED(maxlen);
-	return 1;  /* Signal to exit edit mode */
+	return 1;
 }
 
 void
 init_emoji(void)
 {
-    sheet.emoji_pos = 0;
-    sheet.emoji_frame = 0;
-    sheet.emoji_dir = 1;
-    sheet.emoji_enabled = 1;
-    
-    /* Choose your emoji set here */
-    int i;
-    for(i = 0; i < 4; i++)
-        sheet.emoji_frames[i] = rcc_style[i]; 
+	sheet.emoji_pos = 0;
+	sheet.emoji_frame = 0;
+	sheet.emoji_dir = 1;
+	sheet.emoji_enabled = config.emoji_enabled;
+
+	int i;
+	for(i = 0; i < 4; i++)
+		sheet.emoji_frames[i] = rcc_style[i];
 }
 
 void
 draw_emoji_banner(void)
 {
-    if(!sheet.emoji_enabled)
-        return;
-    
-    /* Safety check */
-    if(sheet.emoji_frame < 0 || sheet.emoji_frame >= 4)
-        sheet.emoji_frame = 0;
-    
-    /* Get current emoji - with null check */
-    char *emoji = sheet.emoji_frames[sheet.emoji_frame];
-    if(emoji == nil)
-        return;
-    
-    int emoji_width = strlen(emoji) * font->width;
-    
-    /* Draw background bar */
-    Rectangle banner = Rect(screen->r.min.x, screen->r.min.y,
-                           screen->r.max.x, screen->r.min.y + 25);
-    draw(screen, banner, colors[2], nil, ZP);  /* Using tan color */
-    
-    /* Draw the emoji */
-    Point pos = Pt(sheet.emoji_pos, screen->r.min.y + 5);
-    string(screen, pos, colors[0], ZP, font, emoji);
-    
-    /* Update position */
-    sheet.emoji_pos += sheet.emoji_dir * 3;
-    
-    /* Bounce at edges */
-    if(sheet.emoji_pos > screen->r.max.x - emoji_width) {
-        sheet.emoji_pos = screen->r.max.x - emoji_width;
-        sheet.emoji_dir = -1;
-    }
-    if(sheet.emoji_pos < screen->r.min.x) {
-        sheet.emoji_pos = screen->r.min.x;
-        sheet.emoji_dir = 1;
-    }
-    
-    /* Animate frames every few redraws */
-    static int frame_counter = 0;
-    frame_counter++;
-    if(frame_counter % 10 == 0) {
-        sheet.emoji_frame = (sheet.emoji_frame + 1) % 4;
-    }
+	if(!sheet.emoji_enabled)
+		return;
+
+	if(sheet.emoji_frame < 0 || sheet.emoji_frame >= 4)
+		sheet.emoji_frame = 0;
+
+	char *emoji = sheet.emoji_frames[sheet.emoji_frame];
+	if(emoji == nil)
+		return;
+
+	int emoji_width = strlen(emoji) * font->width;
+
+	Rectangle banner = Rect(screen->r.min.x, screen->r.min.y,
+						   screen->r.max.x, screen->r.min.y + config.banner_height);
+	draw(screen, banner, colors[2], nil, ZP);
+
+	Point pos = Pt(sheet.emoji_pos, screen->r.min.y + 5);
+	string(screen, pos, colors[0], ZP, font, emoji);
+
+	sheet.emoji_pos += sheet.emoji_dir * config.emoji_speed;
+
+	if(sheet.emoji_pos > screen->r.max.x - emoji_width) {
+		sheet.emoji_pos = screen->r.max.x - emoji_width;
+		sheet.emoji_dir = -1;
+	}
+	if(sheet.emoji_pos < screen->r.min.x) {
+		sheet.emoji_pos = screen->r.min.x;
+		sheet.emoji_dir = 1;
+	}
+
+	static int frame_counter = 0;
+	frame_counter++;
+	if(frame_counter % config.emoji_frame_delay == 0) {
+		sheet.emoji_frame = (sheet.emoji_frame + 1) % 4;
+	}
 }
 
 void
 cmd_cycle_emoji(void)
 {
-    static int emoji_set = 0;
-    int i;
-    
-    emoji_set = (emoji_set + 1) % 7;
-    
-    switch(emoji_set) {
-    case 0:
-        for(i = 0; i < 4; i++)
-            sheet.emoji_frames[i] = rcc_style[i];
-        break;
-    case 1:
-        for(i = 0; i < 4; i++)
-            sheet.emoji_frames[i] = kirby_dance[i];
-        break;
-    case 2:
-        for(i = 0; i < 4; i++)
-            sheet.emoji_frames[i] = lambda_dance[i];
-        break;
-    case 3:
-        for(i = 0; i < 4; i++)
-            sheet.emoji_frames[i] = dancing_guy[i];
-        break;
-    case 4:
-        for(i = 0; i < 4; i++)
-            sheet.emoji_frames[i] = happy_faces[i];
-        break;
-    case 5:
-        for(i = 0; i < 4; i++)
-            sheet.emoji_frames[i] = cat_faces[i];
-        break;
-    case 6:
-        for(i = 0; i < 4; i++)
-            sheet.emoji_frames[i] = shrug_guys[i];
-        break;
-    }
-    sheet.needredraw = 1;
+	static int emoji_set = 0;
+	int i;
+
+	emoji_set = (emoji_set + 1) % 7;
+
+	switch(emoji_set) {
+	case 0:
+		for(i = 0; i < 4; i++)
+			sheet.emoji_frames[i] = rcc_style[i];
+		break;
+	case 1:
+		for(i = 0; i < 4; i++)
+			sheet.emoji_frames[i] = kirby_dance[i];
+		break;
+	case 2:
+		for(i = 0; i < 4; i++)
+			sheet.emoji_frames[i] = lambda_dance[i];
+		break;
+	case 3:
+		for(i = 0; i < 4; i++)
+			sheet.emoji_frames[i] = dancing_guy[i];
+		break;
+	case 4:
+		for(i = 0; i < 4; i++)
+			sheet.emoji_frames[i] = happy_faces[i];
+		break;
+	case 5:
+		for(i = 0; i < 4; i++)
+			sheet.emoji_frames[i] = cat_faces[i];
+		break;
+	case 6:
+		for(i = 0; i < 4; i++)
+			sheet.emoji_frames[i] = shrug_guys[i];
+		break;
+	}
+	sheet.needredraw = 1;
 }
 
 void
 cmd_toggle_emoji(void)
 {
-    sheet.emoji_enabled = !sheet.emoji_enabled;
-    sheet.needredraw = 1;
+	sheet.emoji_enabled = !sheet.emoji_enabled;
+	config.emoji_enabled = sheet.emoji_enabled;
+	sheet.needredraw = 1;
 }
 
 int
@@ -1570,7 +1924,7 @@
 	USED(maxlen);
 	buf[0] = '\0';
 	*pos = 0;
-	return -1;  /* Signal cancellation */
+	return -1;
 }
 
 int
@@ -1588,8 +1942,7 @@
 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 */
+	USED(buf); USED(pos); USED(maxlen);
 	return 0;
 }
 
@@ -1596,22 +1949,22 @@
 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);
+	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.current_mode = 3;
 	sheet.entering_filename = 1;
 	sheet.save_mode = 1;
-	strcpy(sheet.filenamebuf, "/tmp/sheet.spr");
+	strcpy(sheet.filenamebuf, config.default_save_path);
 	sheet.filenamepos = strlen(sheet.filenamebuf);
 	sheet.needredraw = 1;
 }
@@ -1633,7 +1986,7 @@
 	sheet.current_mode = 3;
 	sheet.entering_filename = 1;
 	sheet.save_mode = 2;
-	strcpy(sheet.filenamebuf, "/tmp/sheet.spr");
+	strcpy(sheet.filenamebuf, config.default_save_path);
 	sheet.filenamepos = strlen(sheet.filenamebuf);
 	sheet.needredraw = 1;
 }
@@ -1653,7 +2006,7 @@
 cmd_start_label(void)
 {
 	if(sheet.selected >= 0) {
-		sheet.current_mode = 2;  /* Label edit mode */
+		sheet.current_mode = 2;
 		sheet.editing_label = sheet.selected;
 		strncpy(sheet.labelbuf, sheet.boxes[sheet.selected].label, 31);
 		sheet.labelbuf[31] = '\0';
@@ -1666,6 +2019,7 @@
 cmd_toggle_grid(void)
 {
 	sheet.gridsnap = !sheet.gridsnap;
+	config.gridsnap = sheet.gridsnap;
 	sheet.needredraw = 1;
 }
 
@@ -1683,8 +2037,7 @@
 handle_normal_mode(int key)
 {
 	Command *cmd;
-	
-	/* Look up command in table */
+
 	for(cmd = commands; cmd->action; cmd++) {
 		if(cmd->key == key) {
 			cmd->action();
@@ -1691,8 +2044,6 @@
 			return;
 		}
 	}
-	
-	/* No match - could show help or ignore */
 }
 
 void
@@ -1699,12 +2050,10 @@
 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 {
@@ -1712,8 +2061,7 @@
 			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);
@@ -1720,18 +2068,15 @@
 			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';
@@ -1738,7 +2083,6 @@
 			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;
@@ -1749,9 +2093,8 @@
 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;
@@ -1758,12 +2101,10 @@
 		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';
@@ -1770,7 +2111,6 @@
 			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;
@@ -1784,15 +2124,15 @@
 	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);
@@ -1807,10 +2147,10 @@
 			int j;
 			for(j = 0; j < box->nrefs; j++)
 				Bprint(b, " %d", box->refs[j]);
-				Bprint(b, "\n");
+			Bprint(b, "\n");
 		}
 	}
-	
+
 	Bterm(b);
 	close(fd);
 }
@@ -1822,31 +2162,30 @@
 	char *line;
 	char *fields[10];
 	int nf;
-	
+
 	b = Bopen(file, OREAD);
 	if(b == nil){
 		fprint(2, "cannot open %s: %r\n", file);
 		return;
 	}
-	
+
 	memset(&sheet, 0, sizeof(sheet));
 	sheet.selected = -1;
 	sheet.editing = -1;
-	sheet.editing_label = -1; 
+	sheet.editing_label = -1;
 	sheet.entering_filename = 0;
 	sheet.current_mode = 0;
-	sheet.gridsize = 32;
-	sheet.gridsnap = 1;
+	sheet.gridsize = config.gridsize;
+	sheet.gridsnap = config.gridsnap;
 	init_emoji();
 
 	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){
@@ -1859,7 +2198,7 @@
 			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->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]);
@@ -1866,7 +2205,6 @@
 			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){
@@ -1877,9 +2215,9 @@
 			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];
@@ -1897,11 +2235,10 @@
 handle_filename_input(int key)
 {
 	if(key == '\n') {
-		/* Execute save/load */
 		if(sheet.filenamebuf[0] == '\0') {
-			strcpy(sheet.filenamebuf, "/tmp/sheet.spr");
+			strcpy(sheet.filenamebuf, config.default_save_path);
 		}
-		
+
 		if(sheet.save_mode == 1) {
 			save(sheet.filenamebuf);
 		} else {
@@ -1908,17 +2245,15 @@
 			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, "/tmp/")) {
 			strcat(sheet.filenamebuf, "/tmp/");
 			sheet.filenamepos = strlen(sheet.filenamebuf);
@@ -1925,7 +2260,6 @@
 			sheet.needredraw = 1;
 		}
 	} else if(key == Kbs) {
-		/* Backspace */
 		if(sheet.filenamepos > 0) {
 			sheet.filenamepos--;
 			sheet.filenamebuf[sheet.filenamepos] = '\0';
@@ -1932,7 +2266,6 @@
 			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;
@@ -1945,13 +2278,10 @@
 	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){
@@ -1964,7 +2294,6 @@
 				m = emouse();
 			}
 		} else {
-			/* Create new box */
 			i = addbox(m.xy);
 			if(i >= 0){
 				sheet.selected = i;
@@ -1976,9 +2305,8 @@
 		}
 		sheet.needredraw = 1;
 	}
-	
+
 	if(m.buttons & 2){
-		/* Middle click - edit box */
 		i = boxat(m.xy);
 		if(i >= 0){
 			sheet.selected = i;
@@ -1990,16 +2318,15 @@
 		}
 	}
 }
+
 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 {
@@ -2007,8 +2334,7 @@
 			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);
@@ -2015,14 +2341,12 @@
 			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;
@@ -2034,7 +2358,6 @@
 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;
@@ -2046,36 +2369,35 @@
 void
 handle_filename_mouse(Mouse m)
 {
-	/* Clicks do nothing in this mode */
 	USED(m);
 }
 
-void 
+void
 ctl_addbox(char **args, int nargs)
 {
 	Point p = Pt(atoi(args[0]), atoi(args[1]));
 	int idx = addbox(p);
-	
+
 	if(idx >= 0 && nargs > 2) {
 		strncpy(sheet.boxes[idx].formula, args[2], MAXFORMULA-1);
 		sheet.boxes[idx].formula[MAXFORMULA-1] = '\0';
-		
+
 		BoxType *bt = &boxtypes[sheet.boxes[idx].type];
 		if(bt->parse) bt->parse(&sheet.boxes[idx]);
 		if(bt->eval) bt->eval(&sheet.boxes[idx]);
-		
+
 		recalc_all();
 	}
-	
+
 	if(idx >= 0 && nargs > 3) {
 		strncpy(sheet.boxes[idx].label, args[3], 31);
 		sheet.boxes[idx].label[31] = '\0';
 	}
-	
+
 	sheet.needredraw = 1;
 }
 
-void 
+void
 ctl_load(char **args, int nargs)
 {
 	if(nargs >= 1) {
@@ -2085,7 +2407,7 @@
 	}
 }
 
-void 
+void
 ctl_save(char **args, int nargs)
 {
 	if(nargs >= 1) {
@@ -2093,7 +2415,7 @@
 	}
 }
 
-void 
+void
 ctl_quit(char **args, int nargs)
 {
 	USED(args); USED(nargs);
@@ -2100,33 +2422,32 @@
 	cmd_quit();
 }
 
-void 
+void
 check_ctl_file(void)
 {
 	int fd, nt;
 
-	fd = open(CTLFILE, OREAD);
+	fd = open(config.ctl_file, OREAD);
 	if(fd < 0) {
-		fprint(2, "cannot open control file\n");
 		return;
 	}
-	
+
 	Biobuf bin;
 	Binit(&bin, fd, OREAD);
 	char *line;
-	
+
 	while((line = Brdline(&bin, '\n'))) {
 		line[Blinelen(&bin)-1] = '\0';
-		
+
 		char *tokens[10];
 		nt = tokenize(line, tokens, nelem(tokens));
 		if(nt == 0) continue;
-		
+
 		CommandHandler *h;
 		for(h = cmd_handlers; h->name; h++) {
 			if(strcmp(tokens[0], h->name) == 0) {
 				if(nt-1 < h->minargs) {
-					fprint(2, "%s: needs %d arguments\n", 
+					fprint(2, "%s: needs %d arguments\n",
 						h->name, h->minargs);
 				} else {
 					h->execute(&tokens[1], nt-1);
@@ -2135,20 +2456,19 @@
 			}
 		}
 	}
-	
+
 	Bterm(&bin);
 	close(fd);
-	
-	fd = create(CTLFILE, OWRITE, 0644);
+
+	fd = create(config.ctl_file, OWRITE, 0644);
 	if(fd >= 0) close(fd);
 }
 
-void 
-eresized(int new) 
+void
+eresized(int new)
 {
 	if(new && getwindow(display, Refnone) < 0)
 		sysfatal("can't reattach to window");
-		
 }
 
 void
@@ -2155,57 +2475,65 @@
 main(int argc, char *argv[])
 {
 	Event e;
-	
-	USED(argc);
-	USED(argv);
-	
+	char *configfile = CONFIG_FILE;
+
+	if(argc > 1)
+		configfile = argv[1];
+
+	load_config(configfile);
+	validate_config();
+
+	if(access(configfile, AEXIST) < 0) {
+		save_config(configfile);
+		fprint(2, "Created default configuration at %s\n", configfile);
+	}
+
 	if(initdraw(nil, nil, "freebox") < 0)
 		sysfatal("initdraw: %r");
-	
+
 	initcolors();
-	
-    int ticks = 0;
 
+	int ticks = 0;
+
 	einit(Emouse | Ekeyboard);
 	eresized(0);
 
-	int ctlfd = create(CTLFILE, OWRITE, 0644);
+	int ctlfd = create(config.ctl_file, OWRITE, 0644);
 	if(ctlfd >= 0) close(ctlfd);
 
 	memset(&sheet, 0, sizeof(sheet));
 	sheet.selected = -1;
 	sheet.editing = -1;
-	sheet.editing_label = -1; 
+	sheet.editing_label = -1;
 	sheet.entering_filename = 0;
 	sheet.current_mode = 0;
-	sheet.gridsize = 32;
-	sheet.gridsnap = 1;
+	sheet.gridsize = config.gridsize;
+	sheet.gridsnap = config.gridsnap;
 	init_emoji();
 	redraw();
 
-
 	for(;;){
 		switch(event(&e)){
 		case Emouse:
 			handlemouse(e.mouse);
 			break;
-			
+
 		case Ekeyboard:
 			handlekey(e.kbdc);
 			break;
 		}
 
-        ticks++;
-        if(ticks % 200 == 0) {
-            check_ctl_file();
-        }
-        if(ticks % 30 == 0) {  /* Adjust for emoji speed */
-            sheet.needredraw = 1;
-        }
-        
-        if(sheet.needredraw){
-            redraw();
-            sheet.needredraw = 0;
-        }
+		ticks++;
+		if(ticks % config.ctl_check_interval == 0) {
+			check_ctl_file();
+		}
+		if(ticks % config.redraw_interval == 0) {
+			sheet.needredraw = 1;
+		}
+
+		if(sheet.needredraw){
+			redraw();
+			sheet.needredraw = 0;
+		}
 	}
-}
+}
\ No newline at end of file
--