[cmds] Add simple calculator by HelleBenjamin · Pull Request #2393 · ghaerr/elks (original) (raw)

Here is one auto-generated:

/* nxcalc - Simple graphical calculator for Microwindows / Nano-X
 *
 * Inspired by nxclock example (Greg Haerr).
 *
 * Build:
 *   gcc -O2 nxcalc.c -o nxcalc -lNano-X -lpthread -lm
 *
 * Run inside an environment where the Nano-X server is available.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include "nano-X.h"

#define WIN_W  320
#define WIN_H  400
#define DISP_H 70

static GR_WINDOW_ID win;
static GR_GC_ID gc_text, gc_button, gc_button_press, gc_border;

/* calculator state */
static char display[128];
static double acc = 0.0;
static char pending_op = 0;
static int entering = 0;
static int dot_entered = 0;

/* button layout */
static const char *btn_labels[] = {
    "C", "<-", "+/-", "/",
    "7", "8", "9", "*",
    "4", "5", "6", "-",
    "1", "2", "3", "+",
    "0", ".", "=", NULL
};

#define COLS 4
#define ROWS 5

static GR_RECT btn_rects[ROWS][COLS];

/* function prototypes */
static void redraw(void);
static void press_button(const char *label);
static void compute_equal(void);
static void apply_pending(double val);

static double display_to_double(void) {
    if (strlen(display) == 0) return 0.0;
    return strtod(display, NULL);
}

static void set_display_from_double(double v) {
    char buf[128];
    double iv;

    if (fabs(v) < 1e-12) v = 0.0;
    if (modf(v, &iv) == 0.0) {
        snprintf(buf, sizeof(buf), "%.0f", v);
    } else {
        snprintf(buf, sizeof(buf), "%.10g", v);
    }

    strncpy(display, buf, sizeof(display) - 1);
    display[sizeof(display) - 1] = '\0';
}

/* ========================= MAIN ========================= */

int main(int argc, char **argv) {
    GR_EVENT event;
    int r, c, idx;

    if (GrOpen() < 0) {
        fprintf(stderr, "nxcalc: cannot open Nano-X\n");
        return 1;
    }

    win = GrNewWindowEx(GR_WM_PROPS_APPWINDOW, "nxcalc", GR_ROOT_WINDOW_ID,
                        10, 10, WIN_W, WIN_H, GrGetSysColor(GR_COLOR_WINDOW));

    GrSelectEvents(win, GR_EVENT_MASK_EXPOSURE | GR_EVENT_MASK_BUTTON_DOWN |
                   GR_EVENT_MASK_BUTTON_UP | GR_EVENT_MASK_CLOSE_REQ);

    gc_text = GrNewGC();
    gc_button = GrNewGC();
    gc_button_press = GrNewGC();
    gc_border = GrNewGC();

    GrSetGCForeground(gc_text, GrGetSysColor(GR_COLOR_WINDOWTEXT));
    GrSetGCBackground(gc_text, GrGetSysColor(GR_COLOR_WINDOW));

    GrSetGCForeground(gc_button, GrGetSysColor(GR_COLOR_BTNFACE));
    GrSetGCBackground(gc_button, GrGetSysColor(GR_COLOR_WINDOW));

    GrSetGCForeground(gc_button_press, GrGetSysColor(GR_COLOR_BTNSHADOW));
    GrSetGCBackground(gc_button_press, GrGetSysColor(GR_COLOR_WINDOW));

    GrSetGCForeground(gc_border, GrGetSysColor(GR_COLOR_BTNTEXT));
    GrSetGCBackground(gc_border, GrGetSysColor(GR_COLOR_WINDOW));

    /* init state */
    display[0] = '\0';
    acc = 0.0;
    pending_op = 0;
    entering = 0;
    dot_entered = 0;

    /* layout buttons */
    {
        int grid_x = 10, grid_y = DISP_H + 10;
        int grid_w = WIN_W - 20;
        int grid_h = WIN_H - DISP_H - 20;
        int cell_w = grid_w / COLS;
        int cell_h = grid_h / ROWS;

        idx = 0;
        for (r = 0; r < ROWS; r++) {
            for (c = 0; c < COLS; c++) {
                int x = grid_x + c * cell_w;
                int y = grid_y + r * cell_h;
                btn_rects[r][c].x = x;
                btn_rects[r][c].y = y;
                btn_rects[r][c].w = cell_w - 6;
                btn_rects[r][c].h = cell_h - 6;
                idx++;
            }
        }
    }

    GrMapWindow(win);
    set_display_from_double(0.0);
    redraw();

    while (1) {
        GrGetNextEvent(&event);
        switch (event.type) {
            case GR_EVENT_TYPE_EXPOSURE:
                redraw();
                break;

            case GR_EVENT_TYPE_BUTTON_DOWN: {
                int mx = event.button.x;
                int my = event.button.y;
                int found = 0;
                for (r = 0; r < ROWS && !found; r++) {
                    for (c = 0; c < COLS && !found; c++) {
                        GR_RECT *rc = &btn_rects[r][c];
                        if (mx >= rc->x && mx <= rc->x + rc->w &&
                            my >= rc->y && my <= rc->y + rc->h) {
                            GrFillRect(win, gc_button_press, rc->x, rc->y, rc->w, rc->h);
                            found = 1;
                        }
                    }
                }
                break;
            }

            case GR_EVENT_TYPE_BUTTON_UP: {
                int mx = event.button.x;
                int my = event.button.y;
                int found = 0;
                for (r = 0; r < ROWS && !found; r++) {
                    for (c = 0; c < COLS && !found; c++) {
                        GR_RECT *rc = &btn_rects[r][c];
                        if (mx >= rc->x && mx <= rc->x + rc->w &&
                            my >= rc->y && my <= rc->y + rc->h) {
                            idx = r * COLS + c;
                            const char *lbl =
                                (idx < (int)(sizeof(btn_labels)/sizeof(btn_labels[0]))
                                 ? btn_labels[idx] : NULL);
                            if (lbl) {
                                press_button(lbl);
                                redraw();
                            }
                            found = 1;
                        }
                    }
                }
                break;
            }

            case GR_EVENT_TYPE_CLOSE_REQ:
                GrClose();
                return 0;
        }
    }

    return 0;
}

/* ========================= UI DRAWING ========================= */

static void redraw(void) {
    int r, c, idx, tw, th, tb;

    /* clear */
    GrFillRect(win, gc_button, 0, 0, WIN_W, WIN_H);

    /* display area */
    GrSetGCForeground(gc_button, GrGetSysColor(GR_COLOR_WINDOW));
    GrFillRect(win, gc_button, 5, 5, WIN_W - 10, DISP_H - 10);
    GrSetGCForeground(gc_border, GrGetSysColor(GR_COLOR_BTNSHADOW));
    GrRect(win, gc_border, 5, 5, WIN_W - 6, DISP_H - 6);

    /* display text (right aligned) */
    GrGetGCTextSize(gc_text, display, strlen(display), GR_TFTOP, &tw, &th, &tb);
    {
        int x = WIN_W - 12 - tw;
        if (x < 10) x = 10;
        GrText(win, gc_text, x, 5 + (DISP_H / 2) - 8, display, strlen(display));
    }

    /* draw buttons */
    idx = 0;
    GrSetGCForeground(gc_button, GrGetSysColor(GR_COLOR_BTNFACE));
    for (r = 0; r < ROWS; r++) {
        for (c = 0; c < COLS; c++) {
            GR_RECT *rc = &btn_rects[r][c];
            const char *lbl =
                (idx < (int)(sizeof(btn_labels)/sizeof(btn_labels[0]))
                 ? btn_labels[idx] : NULL);

            GrFillRect(win, gc_button, rc->x, rc->y, rc->w, rc->h);
            GrSetGCForeground(gc_border, GrGetSysColor(GR_COLOR_BTNSHADOW));
            GrRect(win, gc_border, rc->x, rc->y, rc->x + rc->w - 1, rc->y + rc->h - 1);

            if (lbl) {
                GrGetGCTextSize(gc_text, lbl, strlen(lbl), GR_TFTOP, &tw, &th, &tb);
                GrText(win, gc_text, rc->x + rc->w/2 - tw/2, rc->y + rc->h/2 - 8, lbl, strlen(lbl));
            }
            idx++;
        }
    }

    GrFlush();
}

/* ========================= LOGIC ========================= */

static void press_button(const char *label) {
    char tmp[128];
    double val;

    if (strcmp(label, "C") == 0) {
        display[0] = '\0';
        acc = 0.0;
        pending_op = 0;
        entering = 0;
        dot_entered = 0;
        set_display_from_double(0.0);
        return;
    }

    if (strcmp(label, "<-") == 0) {
        int l = strlen(display);
        if (l > 0) {
            if (display[l-1] == '.') dot_entered = 0;
            display[l-1] = '\0';
            if (strlen(display) == 0) set_display_from_double(0.0);
        }
        entering = (strlen(display) > 0);
        return;
    }

    if (strcmp(label, "+/-") == 0) {
        if (display[0] == '-') {
            memmove(display, display+1, strlen(display));
        } else {
            if (strlen(display) == 0 || strcmp(display, "0") == 0) return;
            snprintf(tmp, sizeof(tmp), "-%s", display);
            strncpy(display, tmp, sizeof(display)-1);
        }
        return;
    }

    if (strcmp(label, "+") == 0 || strcmp(label, "-") == 0 ||
        strcmp(label, "*") == 0 || strcmp(label, "/") == 0) {

        val = display_to_double();
        if (pending_op == 0)
            acc = val;
        else
            apply_pending(val);

        pending_op = label[0];
        entering = 0;
        dot_entered = 0;
        set_display_from_double(acc);
        return;
    }

    if (strcmp(label, "=") == 0) {
        compute_equal();
        return;
    }

    if (strcmp(label, ".") == 0) {
        if (!dot_entered) {
            if (!entering) {
                strcpy(display, "0.");
                entering = 1;
            } else {
                strncat(display, ".", sizeof(display)-strlen(display)-1);
            }
            dot_entered = 1;
        }
        return;
    }

    if (strlen(label) == 1 && isdigit((unsigned char)label[0])) {
        if (!entering) {
            display[0] = label[0];
            display[1] = '\0';
            entering = 1;
        } else {
            if (strcmp(display, "0") == 0 && label[0] != '0' && !dot_entered) {
                display[0] = label[0]; display[1] = '\0';
            } else if (strlen(display) < sizeof(display) - 2) {
                strncat(display, label, 1);
            }
        }
        return;
    }
}

static void apply_pending(double val) {
    if (pending_op == '+') acc = acc + val;
    else if (pending_op == '-') acc = acc - val;
    else if (pending_op == '*') acc = acc * val;
    else if (pending_op == '/') {
        if (val == 0.0) {
            strncpy(display, "Error", sizeof(display)-1);
            display[sizeof(display)-1] = '\0';
            pending_op = 0;
            entering = 0;
            dot_entered = 0;
            redraw();
            return;
        } else {
            acc = acc / val;
        }
    }
}

static void compute_equal(void) {
    double val = display_to_double();
    if (pending_op != 0) {
        apply_pending(val);
        pending_op = 0;
        set_display_from_double(acc);
        entering = 0;
        dot_entered = 0;
    } else {
        set_display_from_double(val);
    }
}