juegos

Juegos de terminal de Pancho: Catan (TUI, GUI, web, servidor, PicoCalc), ajedrez, calculadora y minijuegos
git clone https://git.lu3dhn.xyz/juegos.git
Log | Files | Refs

calc.c (21689B)


      1 /* calc.c - estado y teclas de la calculadora (modo Calcular, menus, ajustes, catalogo). */
      2 #include <string.h>
      3 #include "calc.h"
      4 #include "dmath.h"
      5 #include "str.h"
      6 
      7 static const char *const MODE_NAMES[NMODES] = { "Calcular", "Ecuaciones", "Tabla", "Gráfico", "Programador", "Estadística", "Complejos", "Matrices" };
      8 const char *calc_mode_name(int m) { return m >= 0 && m < NMODES ? MODE_NAMES[m] : "?"; }
      9 const char *calc_ang_name(int a) { return a == ANG_DEG ? "D" : a == ANG_RAD ? "R" : "G"; }
     10 
     11 const char *calc_err_text(int err)
     12 {
     13     switch (err) {
     14     case CE_SYNTAX: return "Error de sintaxis";
     15     case CE_MATH: return "Error matemático";
     16     case CE_STACK: return "Demasiado largo";
     17     case CE_ARG: return "Error de argumento";
     18     case CE_NOVAR: return "Falta un valor";
     19     }
     20     return "";
     21 }
     22 
     23 /* ------------------------------------------------------------ catalogo */
     24 
     25 static const CatItem C_TRIG[] = {
     26     { "sin(", "sin(" }, { "cos(", "cos(" }, { "tan(", "tan(" },
     27     { "sin⁻¹(", "asin(" }, { "cos⁻¹(", "acos(" }, { "tan⁻¹(", "atan(" },
     28 };
     29 static const CatItem C_HYP[] = {
     30     { "sinh(", "sinh(" }, { "cosh(", "cosh(" }, { "tanh(", "tanh(" },
     31     { "sinh⁻¹(", "asinh(" }, { "cosh⁻¹(", "acosh(" }, { "tanh⁻¹(", "atanh(" },
     32 };
     33 static const CatItem C_EXP[] = {
     34     { "ln(", "ln(" }, { "log(", "log(" }, { "log□(□)", "\\l{" }, { "e^□", "\\e\\x{" },
     35     { "10^□", "10\\x{" }, { "√□", "\\r{" }, { "∛□", "\\n{3}{" }, { "□√□", "\\n{" },
     36     { "x²", "\\x{2}" }, { "x³", "\\x{3}" }, { "x⁻¹", "\\inv" }, { "x^□", "\\x{" },
     37 };
     38 static const CatItem C_PROB[] = {
     39     { "x!", "!" }, { "nPr", "\\P" }, { "nCr", "\\C" }, { "Ran#", "\\ran" }, { "%", "%" },
     40 };
     41 static const CatItem C_NUM[] = {
     42     { "Abs  |□|", "\\a{" }, { "Int(", "int(" }, { "Intg(", "intg(" }, { "Rnd(", "rnd(" },
     43     { "GCD(", "gcd(" }, { "LCM(", "lcm(" }, { "□/□", "\\f{" }, { "□ □/□", "\\m{" },
     44     { "×10^", "\\E" }, { "Pol(", "pol(" }, { "Rec(", "rec(" },
     45 };
     46 static const CatItem C_CONST[] = {
     47     { "π", "\\pi" }, { "e", "\\e" },
     48     { "c  velocidad de la luz", "299792458" },
     49     { "g  gravedad estándar", "9.80665" },
     50     { "G  gravitación", "6.6743\\E-11" },
     51     { "h  Planck", "6.62607015\\E-34" },
     52     { "q  carga elemental", "1.602176634\\E-19" },
     53     { "me masa del electrón", "9.1093837015\\E-31" },
     54     { "mp masa del protón", "1.67262192369\\E-27" },
     55     { "NA Avogadro", "6.02214076\\E23" },
     56     { "k  Boltzmann", "1.380649\\E-23" },
     57     { "R  gases", "8.314462618" },
     58     { "atm", "101325" },
     59 };
     60 static const CatItem C_VAR[] = {
     61     { "A", "\\vA" }, { "B", "\\vB" }, { "C", "\\vC" }, { "D", "\\vD" }, { "E", "\\vE" },
     62     { "F", "\\vF" }, { "M", "\\vM" }, { "X", "\\vX" }, { "Y", "\\vY" }, { "Ans", "\\ans" },
     63     { "PreAns", "\\pans" },
     64 };
     65 
     66 static const CatItem C_DIST[] = {
     67     { "NormPD(x, σ, μ)  densidad", "normpd(" }, { "NormCD(a, b, σ, μ)  P(a≤X≤b)", "normcd(" },
     68     { "InvN(área, σ, μ)  normal inversa", "invnorm(" }, { "BinomPD(x, n, p)  P(X=x)", "binompd(" },
     69     { "BinomCD(x, n, p)  P(X≤x)", "binomcd(" }, { "PoissPD(x, λ)  P(X=x)", "poisspd(" },
     70     { "PoissCD(x, λ)  P(X≤x)", "poisscd(" },
     71 };
     72 static const CatItem C_MAT[] = {
     73     { "MatA", "\\MA" }, { "MatB", "\\MB" }, { "MatC", "\\MC" }, { "MatD", "\\MD" }, { "MatAns", "\\Mans" },
     74     { "VctA", "\\VA" }, { "VctB", "\\VB" }, { "VctC", "\\VC" }, { "VctD", "\\VD" }, { "VctAns", "\\Vans" },
     75     { "Det(  determinante", "det(" }, { "Trn(  traspuesta", "trn(" }, { "Identity(n)", "identity(" },
     76     { "x⁻¹  inversa", "\\inv" }, { "DotP(  producto escalar", "dot(" }, { "CrossP(  producto vectorial", "cross(" },
     77     { "Angle(  ángulo entre vectores", "angle(" }, { "UnitV(  vector unitario", "unitv(" }, { "|□|  norma", "\\a{" },
     78 };
     79 static const CatItem C_CPLX[] = {
     80     { "i  unidad imaginaria", "i" }, { "∠  forma polar r∠θ", "\\ang" }, { "arg(", "arg(" },
     81     { "Conjg(", "conjg(" }, { "ReP(  parte real", "real(" }, { "ImP(  parte imaginaria", "imag(" },
     82 };
     83 static const CatItem C_CALC[] = {
     84     { "∫ integral definida", "\\i{" }, { "d/dx derivada en un punto", "\\d{" },
     85     { "Σ sumatoria", "\\s{" }, { "Π productoria", "\\p{" },
     86 };
     87 
     88 #define G(name, arr) { name, arr, (int)(sizeof arr / sizeof arr[0]) }
     89 const CatGroup CATALOG[] = {
     90     G("Trigonométricas", C_TRIG), G("Hiperbólicas", C_HYP), G("Exponenciales y raíces", C_EXP),
     91     G("Análisis (∫ d/dx Σ Π)", C_CALC), G("Probabilidad", C_PROB), G("Distribuciones", C_DIST),
     92     G("Complejos", C_CPLX), G("Matrices y vectores", C_MAT), G("Numéricas", C_NUM), G("Constantes", C_CONST), G("Variables", C_VAR),
     93 };
     94 const int CATALOG_N = (int)(sizeof CATALOG / sizeof CATALOG[0]);
     95 
     96 /* ------------------------------------------------------------ estado */
     97 
     98 void calc_init(Calc *c)
     99 {
    100     memset(c, 0, sizeof *c);
    101     c->ang = ANG_DEG;
    102     fmt_default(&c->fmt);
    103     for (int i = 0; i < NVARS; i++) c->vars[i] = c->vars_im[i] = num_int(0);
    104     c->ans = c->preans = c->ans_im = c->res_im = num_int(0);
    105     c->hsel = -1;
    106     c->cat = -1;
    107     c->pg.base = 10;
    108     c->pg.bits = 32;
    109     c->tb.start = num_int(1);
    110     c->tb.end = num_int(5);
    111     c->tb.step = num_int(1);
    112     c->gr.xmin = dec_int(-10); c->gr.xmax = dec_int(10);
    113     c->gr.ymin = dec_int(-6); c->gr.ymax = dec_int(6);
    114     c->gr.autoy = true;
    115 }
    116 
    117 EvalCtx calc_ctx(const Calc *c)
    118 {
    119     EvalCtx cx;
    120     memset(&cx, 0, sizeof cx);
    121     cx.ang = c->ang;
    122     cx.vars = (Num *)c->vars;
    123     cx.ans = c->ans;
    124     cx.preans = c->preans;
    125     cx.rand = c->rand;
    126     cx.prog = c->mode == MODE_PROG;
    127     cx.base = c->pg.base;
    128     cx.bits = c->pg.bits;
    129     cx.rvalid = c->st.rvalid;
    130     cx.rtype = c->st.rtype;
    131     cx.ra = c->st.ra; cx.rb = c->st.rb; cx.rc = c->st.rc;
    132     cx.cplx = c->mode == MODE_CMPLX;
    133     cx.ans_im = c->ans_im;
    134     cx.vars_im = (Num *)c->vars_im;
    135     cx.mats = c->mt.m;
    136     cx.vcts = c->mt.v;
    137     cx.ans_mat = c->mt.ans_mat;
    138     return cx;
    139 }
    140 
    141 /* re + im·i (o r∠θ) como expresion */
    142 static void cplx_expr(const Calc *c, Num re, Num im, bool dec, bool polar, Expr *out)
    143 {
    144     static Expr a;
    145     bool d = dec || c->dec_default;
    146     if (polar) {
    147         Num r = num_sqrt(num_add(num_mul(re, re), num_mul(im, im)));
    148         fmt_result(&r, &c->fmt, d, out);
    149         if (!re.v.m && !im.v.m) return;
    150         Num th = cplx_arg(re, im, c->ang);
    151         fmt_result(&th, &c->fmt, d, &a);
    152         out->cur = out->n;
    153         ex_insert_raw(out, T_ANGLE);
    154         ex_insert_expr(out, &a);
    155         return;
    156     }
    157     fmt_result(&re, &c->fmt, d, out);
    158     if (!im.v.m) return;
    159     bool neg = im.v.neg;
    160     if (neg) im = num_neg(im);
    161     fmt_result(&im, &c->fmt, d, &a);
    162     out->cur = out->n;
    163     if (!re.v.m) ex_clear(out);
    164     if (re.v.m || neg) ex_insert_raw(out, neg ? '-' : '+');
    165     bool one = im.exact && ex_is_int(&im.x) && im.x.a == 1;
    166     /* fraccion o suma en la parte imaginaria: i al costado queda claro igual */
    167     if (!one) ex_insert_expr(out, &a);
    168     ex_insert_raw(out, 'i');
    169 }
    170 
    171 void calc_res_expr(const Calc *c, Expr *out)
    172 {
    173     if (c->mode == MODE_CMPLX && !c->dms) { cplx_expr(c, c->res, c->res_im, c->want_dec, c->polar, out); return; }
    174     calc_result_expr(c, &c->res, c->want_dec, out);
    175 }
    176 
    177 void calc_hist_expr(const Calc *c, const Hist *h, Expr *out)
    178 {
    179     if (h->mkind) {
    180         ex_clear(out);
    181         ex_insert_raw(out, h->mkind == MV_MAT ? T_MATANS : T_VCTANS);
    182         return;
    183     }
    184     if (h->res_im.v.m || h->polar) { cplx_expr(c, h->res, h->res_im, h->dec, h->polar, out); return; }
    185     fmt_result(&h->res, &c->fmt, h->dec || c->dec_default, out);
    186 }
    187 
    188 bool calc_res_has_exact(const Calc *c)
    189 {
    190     return fmt_has_exact(&c->res, &c->fmt) || (c->mode == MODE_CMPLX && fmt_has_exact(&c->res_im, &c->fmt));
    191 }
    192 
    193 bool calc_result_expr(const Calc *c, const Num *v, bool dec, Expr *out)
    194 {
    195     if (v == &c->res && c->dms) { fmt_dms(v->v, out); return false; }
    196     return fmt_result(v, &c->fmt, dec || c->dec_default, out);
    197 }
    198 
    199 static void changed(Calc *c) { c->version++; }
    200 
    201 static void say(Calc *c, const char *m) { str_put(c->msg, sizeof c->msg, 0, m); }
    202 
    203 /* ------------------------------------------------------------ evaluar */
    204 
    205 static void push_hist(Calc *c)
    206 {
    207     if (c->nh == HIST_MAX) { memmove(&c->hist[0], &c->hist[1], sizeof c->hist[0] * (HIST_MAX - 1)); c->nh--; }
    208     Hist *h = &c->hist[c->nh++];
    209     h->in = c->in;
    210     h->res = c->res;
    211     h->err = (uint8_t)c->err;
    212     h->dec = c->want_dec;
    213     h->res_im = c->mode == MODE_CMPLX ? c->res_im : num_int(0);
    214     h->polar = c->polar && c->mode == MODE_CMPLX;
    215     h->mkind = c->mode == MODE_MATRIX && !c->err ? c->mt.res.kind : 0;
    216 }
    217 
    218 static void exe(Calc *c, bool force_dec)
    219 {
    220     if (ex_empty(&c->in)) return;
    221     c->in.cur = c->in.n;
    222     ex_tidy(&c->in);
    223     EvalCtx cx = calc_ctx(c);
    224     Num r, ri = num_int(0);
    225     int pos = 0;
    226     if (c->mode == MODE_CMPLX) c->err = calc_eval_c(&c->in, &cx, &r, &ri, &pos);
    227     else if (c->mode == MODE_MATRIX) {
    228         c->err = calc_eval_m(&c->in, &cx, &c->mt.res, &pos);
    229         const MVal *mv = &c->mt.res;
    230         if (!c->err) {
    231             if (mv->kind == MV_SCALAR) { r = mv->a[0]; c->mt.ans_mat = false; }
    232             else {
    233                 r = num_int(0);
    234                 c->mt.m[4].r = c->mt.v[4].r = 0;
    235                 if (mv->kind == MV_MAT) c->mt.m[4] = *mv; else c->mt.v[4] = *mv;
    236                 c->mt.ans_mat = true;
    237             }
    238         }
    239     }
    240     else c->err = calc_eval(&c->in, &cx, &r, &pos);
    241     c->err_pos = pos;
    242     c->shown = true;
    243     c->want_dec = force_dec;
    244     c->dms = false;
    245     if (!c->err) {
    246         /* lo que se guarda en Ans va redondeado a 15 cifras, como la Casio */
    247         if (!r.exact) r.v = dec_round_sig(r.v, 15);
    248         c->res = r;
    249         c->res_im = ri;
    250         c->preans = c->ans;
    251         c->ans = r;
    252         c->ans_im = ri;
    253     }
    254     if (c->hsel >= 0 && c->hsel < c->nh) {
    255         /* se re-ejecuto una del historial: va al final como nueva */
    256         c->hsel = -1;
    257     }
    258     push_hist(c);
    259     c->hsel = -1;
    260     c->dirty_save = true;
    261 }
    262 
    263 /* guarda el valor actual (o el resultado) en una variable */
    264 static void store(Calc *c, int var)
    265 {
    266     if (!c->shown && !ex_empty(&c->in)) exe(c, false);
    267     if (c->err) return;
    268     c->vars[var] = c->shown ? c->res : c->ans;
    269     c->vars_im[var] = c->mode == MODE_CMPLX ? (c->shown ? c->res_im : c->ans_im) : num_int(0);
    270     char m[32] = "Guardado en ";
    271     int p = (int)strlen(m);
    272     m[p] = VAR_NAMES[var]; m[p + 1] = 0;
    273     say(c, m);
    274     c->dirty_save = true;
    275 }
    276 
    277 static int var_of_key(int key)
    278 {
    279     if (key >= 'a' && key <= 'z') key -= 32;
    280     const char *v = key ? strchr(VAR_NAMES, key) : 0;
    281     return v ? (int)(v - VAR_NAMES) : -1;
    282 }
    283 
    284 /* una tecla con el resultado en pantalla: empezar una cuenta nueva */
    285 static void new_entry(Calc *c, int key)
    286 {
    287     bool op = key == '+' || key == '-' || key == '*' || key == ':' || key == '/' || key == '^' ||
    288               key == '!' || key == '%';
    289     ex_clear(&c->in);
    290     c->shown = false;
    291     c->err = 0;
    292     c->want_dec = false;
    293     c->dms = false;
    294     if (op && !c->err) ex_insert_raw(&c->in, T_ANS);
    295 }
    296 
    297 static void recall(Calc *c, int idx)
    298 {
    299     c->hsel = idx;
    300     if (idx < 0) {
    301         ex_clear(&c->in);
    302         c->shown = false;
    303         c->err = 0;
    304         return;
    305     }
    306     Hist *h = &c->hist[idx];
    307     c->in = h->in;
    308     c->in.cur = c->in.n;
    309     c->res = h->res;
    310     c->res_im = h->res_im;
    311     c->polar = h->polar;
    312     c->err = h->err;
    313     c->want_dec = h->dec;
    314     c->shown = true;
    315 }
    316 
    317 /* tecla de edicion sobre una expresion cualquiera (se usa tambien en los modos) */
    318 bool edit_key(Calc *c, Expr *e, int key)
    319 {
    320     switch (key) {
    321     case K_LEFT: ex_left(e); return true;
    322     case K_RIGHT: ex_right(e); return true;
    323     case K_HOME: ex_home(e); return true;
    324     case K_END: ex_end(e); return true;
    325     case K_BKSP: ex_backspace(e); return true;
    326     case K_DEL: ex_delete(e); return true;
    327     case '*': return ex_insert(e, T_MUL);
    328     case ':': return ex_insert(e, T_DIV);
    329     case '#': return c->shift ? ex_root_n(e, 3) : ex_template(e, T_SQRT);
    330     case '$': return ex_template(e, T_ROOT);
    331     case '|': {
    332         /* la segunda | cierra el valor absoluto, como al escribirlo a mano */
    333         int fld, ct = ex_container(e, e->cur, &fld);
    334         if (ct >= 0 && e->t[ct] == T_ABS && e->cur < e->n && e->t[e->cur] == T_END && e->t[e->cur - 1] != T_ABS) {
    335             e->cur++;
    336             return true;
    337         }
    338         return ex_template(e, T_ABS);
    339     }
    340     case ';': return ex_template(e, T_MIXED);
    341     case '[': return ex_insert(e, T_E10);
    342     case '^': return c->shift ? ex_insert(e, T_INV) : ex_insert(e, '^');
    343     }
    344     if (c->mode == MODE_PROG && key >= 'A' && key <= 'F') return ex_insert_raw(e, key);   /* digitos hex */
    345     if (c->mode == MODE_MATRIX && key >= 'A' && key <= 'D')       /* MatA-D; con SHIFT, VctA-D */
    346         return ex_insert_raw(e, (c->shift ? T_VCT : T_MAT) + key - 'A');
    347     if (key >= 32 && key < 127) return ex_insert(e, key);
    348     return false;
    349 }
    350 
    351 /* ------------------------------------------------------------ menus */
    352 
    353 static void setup_form(Calc *c)
    354 {
    355     static const char *const ANG[] = { "Grados", "Radianes", "Gradianes" };
    356     static const char *const RES[] = { "Exacto (fracción, raíz, pi)", "Decimal" };
    357     static const char *const FMTS[] = { "Norm 1", "Norm 2", "Fix", "Sci", "Eng" };
    358     static const char *const NS[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
    359     static const char *const FR[] = { "a/b", "mixta  a b/c" };
    360     static const char *const DG[] = { "10", "12", "15" };
    361     Form *f = &c->form;
    362     form_begin(f, "Ajustes");
    363     form_choice(f, 1, "Ángulo", ANG, 3, c->ang);
    364     form_choice(f, 2, "Resultado", RES, 2, c->dec_default);
    365     form_choice(f, 3, "Formato", FMTS, 5, c->fmt.mode);
    366     form_choice(f, 4, "Fix/Sci: cifras", NS, 10, c->fmt.n);
    367     form_choice(f, 5, "Fracciones", FR, 2, c->fmt.mixed);
    368     form_choice(f, 6, "Cifras en Norm", DG, 3, c->fmt.digits == 15 ? 2 : c->fmt.digits == 12 ? 1 : 0);
    369     form_button(f, 10, "Borrar el historial");
    370     form_button(f, 11, "Borrar las variables");
    371     form_button(f, 12, "Probar el teclado");
    372     form_button(f, 13, "Volver");
    373     c->form_kind = FORM_SETUP;
    374     c->scr = SCR_FORM;
    375 }
    376 
    377 static void setup_apply(Calc *c)
    378 {
    379     Form *f = &c->form;
    380     c->ang = form_get(f, 1)->value;
    381     c->dec_default = form_get(f, 2)->value;
    382     c->fmt.mode = (uint8_t)form_get(f, 3)->value;
    383     c->fmt.n = (uint8_t)form_get(f, 4)->value;
    384     c->fmt.mixed = form_get(f, 5)->value;
    385     int d = form_get(f, 6)->value;
    386     c->fmt.digits = (uint8_t)(d == 2 ? 15 : d == 1 ? 12 : 10);
    387     c->dirty_save = true;
    388 }
    389 
    390 /* formularios de los modos (ver modes.c) */
    391 bool mode_form_done(Calc *c, int id);
    392 
    393 static bool form_key_calc(Calc *c, int key)
    394 {
    395     int r = form_key(&c->form, key);
    396     if (c->form_kind == FORM_SETUP) {
    397         setup_apply(c);
    398         if (r == 10) { c->nh = 0; c->hsel = -1; say(c, "Historial borrado"); c->scr = SCR_MAIN; }
    399         else if (r == 11) {
    400             for (int i = 0; i < NVARS; i++) c->vars[i] = c->vars_im[i] = num_int(0);
    401             c->ans = c->preans = c->ans_im = num_int(0);
    402             say(c, "Variables en cero");
    403             c->scr = SCR_MAIN;
    404         } else if (r == 12) { c->scr = SCR_KEYTEST; c->keytest = -1; }
    405         else if (r == 13 || r == FORM_CANCEL) c->scr = SCR_MAIN;
    406         return true;
    407     }
    408     if (r == FORM_NONE) return true;
    409     if (r == FORM_CANCEL) { c->scr = SCR_MAIN; return true; }
    410     return mode_form_done(c, r);
    411 }
    412 
    413 static bool menu_key(Calc *c, int key)
    414 {
    415     int n = NMODES + 1;                           /* + Ajustes */
    416     if (key == K_UP) c->menu_sel = (c->menu_sel + n - 1) % n;
    417     else if (key == K_DOWN || key == K_TAB) c->menu_sel = (c->menu_sel + 1) % n;
    418     else if (key == K_LEFT) c->menu_sel = (c->menu_sel + n - 3) % n;
    419     else if (key == K_RIGHT) c->menu_sel = (c->menu_sel + 3) % n;
    420     else if (key == K_BACK || key == '@' || key == K_F1 + 1) c->scr = SCR_MAIN;
    421     else if (key >= '1' && key <= '0' + n) { c->menu_sel = key - '1'; key = K_OK; }
    422     if (key == K_OK) {
    423         if (c->menu_sel == NMODES) setup_form(c);
    424         else { c->scr = SCR_MAIN; mode_enter(c, c->menu_sel); }
    425     }
    426     return true;
    427 }
    428 
    429 static void catalog_insert(Calc *c, const char *text)
    430 {
    431     Expr *e = &c->in;
    432     extern Expr *mode_target(Calc *c);
    433     Expr *t = mode_target(c);
    434     if (t) e = t;
    435     if (e == &c->in && c->shown) new_entry(c, 0);
    436     ex_type_text(e, text);
    437 }
    438 
    439 static bool catalog_key(Calc *c, int key)
    440 {
    441     int n = c->cat < 0 ? CATALOG_N : CATALOG[c->cat].n;
    442     if (key == K_UP) c->cat_sel = (c->cat_sel + n - 1) % n;
    443     else if (key == K_DOWN || key == K_TAB) c->cat_sel = (c->cat_sel + 1) % n;
    444     else if (key == K_BACK || key == K_LEFT) {
    445         if (c->cat >= 0) { c->cat_sel = c->cat; c->cat = -1; }
    446         else c->scr = SCR_MAIN;
    447     } else if (key >= '1' && key <= '9' && key - '1' < n) { c->cat_sel = key - '1'; key = K_OK; }
    448     if (key == K_OK || key == K_RIGHT) {
    449         if (c->cat < 0) { c->cat = c->cat_sel; c->cat_sel = 0; }
    450         else if (key == K_OK) {
    451             catalog_insert(c, CATALOG[c->cat].items[c->cat_sel].text);
    452             c->scr = SCR_MAIN;
    453         }
    454     }
    455     return true;
    456 }
    457 
    458 /* ------------------------------------------------------------ teclas */
    459 
    460 static bool main_key(Calc *c, int key)
    461 {
    462     /* STO pendiente: la tecla que sigue es la variable */
    463     if (c->sto) {
    464         c->sto = false;
    465         int v = var_of_key(key);
    466         if (v >= 0) { store(c, v); return true; }
    467         say(c, "");
    468     }
    469     switch (key) {
    470     case K_OK:
    471         exe(c, c->shift);
    472         return true;
    473     case K_BACK:                                 /* AC */
    474         ex_clear(&c->in);
    475         c->shown = false;
    476         c->err = 0;
    477         c->hsel = -1;
    478         return true;
    479     case K_TAB: case K_F1 + 3:                   /* S⇔D (en Programador: la base) */
    480         if (c->mode == MODE_PROG) {
    481             static const int B[] = { 10, 16, 2, 8 };
    482             int i = 0;
    483             while (i < 4 && B[i] != c->pg.base) i++;
    484             c->pg.base = B[(i + 1) % 4];
    485             if (c->shown) { c->shown = false; ex_clear(&c->in); }
    486             c->dirty_save = true;
    487             return true;
    488         }
    489         if (c->shown && !c->err && calc_res_has_exact(c)) c->want_dec = !c->want_dec;
    490         else if (!c->shown && !ex_empty(&c->in)) exe(c, true);
    491         return true;
    492     case '=':
    493         if (c->mode == MODE_MATRIX) { extern void mat_pick_form(Calc *c); mat_pick_form(c); return true; }
    494         break;
    495     case '\'':                                 /* °′″: en el resultado alterna; si no, escribe ° */
    496         if (c->shown && !c->err && c->mode == MODE_CALC) { c->dms = !c->dms; return true; }
    497         if (c->shown) new_entry(c, 0);
    498         c->hsel = -1;
    499         return ex_insert(&c->in, T_DEGS);
    500     case '<':                                   /* complejos: ∠, o alternar a+bi / r∠θ */
    501         if (c->mode != MODE_CMPLX) break;
    502         if (c->shown && !c->err) { c->polar = !c->polar; return true; }
    503         if (c->shown) new_entry(c, 0);
    504         c->hsel = -1;
    505         return ex_insert(&c->in, T_ANGLE);
    506     case '>':
    507         c->sto = true;
    508         say(c, "Guardar en: tocá A-F, M, X o Y");
    509         return true;
    510     case K_UP: case K_DOWN: {
    511         if (!c->shown) {
    512             /* moverse dentro de la expresion 2D lo resuelve el frontend con el layout;
    513              * si no hay fila, historial */
    514             if (key == K_UP && c->nh > 0) { recall(c, c->hsel < 0 ? c->nh - 1 : c->hsel > 0 ? c->hsel - 1 : 0); return true; }
    515             return false;
    516         }
    517         if (key == K_UP) { if (c->nh) recall(c, c->hsel < 0 ? (c->nh > 1 ? c->nh - 2 : 0) : c->hsel > 0 ? c->hsel - 1 : 0); }
    518         else {
    519             if (c->hsel < 0) return false;
    520             recall(c, c->hsel + 1 < c->nh ? c->hsel + 1 : -1);
    521         }
    522         return true;
    523     }
    524     case K_LEFT: case K_RIGHT:
    525         if (c->shown) {
    526             /* volver a editar la cuenta (con error: el cursor va al lugar del error) */
    527             c->shown = false;
    528             if (c->err) c->in.cur = c->err_pos < c->in.n ? c->err_pos : c->in.n;
    529             else c->in.cur = key == K_LEFT ? c->in.n : 0;
    530             c->err = 0;
    531             return true;
    532         }
    533         break;
    534     }
    535     if (c->shown) {
    536         if (key == K_BKSP || key == K_DEL) { c->shown = false; c->err = 0; return true; }
    537         if ((key >= 32 && key < 127) || key == K_F1) new_entry(c, key);
    538     }
    539     c->hsel = -1;
    540     c->err = 0;
    541     return edit_key(c, &c->in, key);
    542 }
    543 
    544 bool calc_key(Calc *c, int key)
    545 {
    546     if (key <= 0) return key == 0;
    547     bool shift = c->shift;
    548     if (key == K_F1 || key == '`') {
    549         c->shift = !c->shift;
    550         changed(c);
    551         return true;
    552     }
    553     c->msg[0] = 0;
    554     bool r = false;
    555     if (key == K_F1 + 1 || key == '@') {        /* MENU */
    556         if (c->scr == SCR_MENU) c->scr = SCR_MAIN;
    557         else { c->scr = SCR_MENU; c->menu_sel = c->mode; }
    558         r = true;
    559     } else if ((key == K_F1 + 4) && c->scr != SCR_FORM) { setup_form(c); r = true; }
    560     else if ((key == K_F1 + 2 || key == '?') && c->scr == SCR_MAIN) {
    561         c->scr = SCR_CATALOG; c->cat = -1; c->cat_sel = 0; r = true;
    562     } else switch (c->scr) {
    563         case SCR_MENU: r = menu_key(c, key); break;
    564         case SCR_FORM: r = form_key_calc(c, key); break;
    565         case SCR_CATALOG: r = catalog_key(c, key); break;
    566         case SCR_KEYTEST:
    567             if (key == K_BACK && c->keytest == K_BACK) c->scr = SCR_MAIN;   /* Esc dos veces sale */
    568             c->keytest = key;
    569             r = true;
    570             break;
    571         default:
    572             r = calc_main_screen(c) ? main_key(c, key) : mode_key(c, key);
    573         }
    574     if (shift && key != K_F1) c->shift = false;
    575     if (r) changed(c);
    576     return r;
    577 }