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
--
⑨