Files
UnixCalc/control.c
2026-01-19 00:20:27 +09:00

383 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <X11/keysym.h>
#include <math.h>
#include <ctype.h>
#include "control.h"
#define NUM_ROWS 6
#define NUM_COLS 4
#define DESIGN_WIDTH 382
#define DESIGN_HEIGHT 534
#define DESIGN_BUTTON_AREA_Y 224
#define DESIGN_BUTTON_WIDTH 93
#define DESIGN_BUTTON_HEIGHT 60
#define DESIGN_PADDING 2
char curinput[64] = {0};
int input_pos = 0;
static const char *btn_labels[][10] = {
{ NULL }, // 数字表示
{ "C", "±", "%", "÷" },
{ "7", "8", "9", "×" },
{ "4", "5", "6", "-" },
{ "1", "2", "3", "+" },
{ "0", ".", "=", "<" },
};
static SuwaButton *find_button_at(SuwaWindow *window, int mx, int my) {
static SuwaButton found = {0};
float scale = get_scale(window);
if (scale <= 0) scale = 1.0f;
int btn_w = (int)(DESIGN_BUTTON_WIDTH * scale + .5f);
int btn_h = (int)(DESIGN_BUTTON_HEIGHT * scale + .5f);
int padding = (int)(DESIGN_PADDING * scale + .5f);;
int start_y = (int)(DESIGN_BUTTON_AREA_Y * scale + .5f);;
int row = -1;
int col = -1;
for (int r = 1; r < NUM_ROWS; ++r) {
int y_start = start_y + (r - 1) * btn_h + padding;
if (my >= y_start && my < y_start + btn_h) {
row = r;
break;
}
}
if (row < 0) return NULL;
for (int c = 0; c < NUM_COLS; ++c) {
int x_start = padding + c * (btn_w + padding);
if (mx >= x_start && mx < x_start + btn_w) {
col = c;
break;
}
}
if (col < 0) return NULL;
found.x = padding + col * (btn_w + padding);
found.y = start_y + (row - 1) * (btn_h + padding);
found.width = btn_w;
found.height = btn_h;
found.text = btn_labels[row][col];
found.pressed = 1;
return &found;
}
void append_to_input(CtrlLabels *labels, char c) {
if ((unsigned long)input_pos >= sizeof(curinput) - 2) return;
strcpy(labels->problem->text, "");
curinput[input_pos++] = c;
curinput[input_pos] = '\0';
strncpy(labels->res->text, curinput, sizeof(labels->res->text) - 1);
labels->res->text[sizeof(labels->res->text) - 1] = '\0';
}
void clear_calculator(CtrlLabels *labels) {
strcpy(labels->problem->text, "");
curinput[0] = '\0';
input_pos = 0;
strcpy(labels->res->text, "0");
}
double evaluate_simple(CtrlLabels *labels, const char *expr) {
double res = 0.0;
double cur = 0.0;
char op = '+';
int i = 0;
snprintf(labels->problem->text, sizeof(labels->problem->text), "%s=", expr);
while (expr[i]) {
if (expr[i] == ' ') { i++; continue; }
if (isdigit(expr[i]) || expr[i] == '.') {
char numbuf[32];
int j = 0;
while (isdigit(expr[i]) || expr[i] == '.') {
if (j < 31) numbuf[j++] = expr[i];
++i;
}
numbuf[j] = '\0';
cur = atof(numbuf);
} else {
if (op == '+') res += cur;
else if (op == '-') res -= cur;
else if (op == '*') res *= cur;
else if (op == '/') {
if (cur == 0) return NAN;
res /= cur;
}
op = expr[i];
++i;
cur = 0.0;
}
}
if (op == '+') res += cur;
else if (op == '-') res -= cur;
else if (op == '*') res *= cur;
else if (op == '/') {
if (cur == 0) return NAN;
res /= cur;
}
return res;
}
void control_expose(SuwaWindow *window, CtrlLabels *labels) {
if (window->event.type != Expose && window->event.type != ConfigureNotify) return;
XWindowAttributes attr;
XGetWindowAttributes(window->display, window->xwindow, &attr);
int w = attr.width;
int h = attr.height;
if (window->backbuf != None) XFreePixmap(window->display, window->backbuf);
window->backbuf = XCreatePixmap(window->display, window->xwindow, w, h,
DefaultDepth(window->display, DefaultScreen(window->display)));
if (window->backbuf == None) window->backbuf = window->xwindow;
window->target = window->backbuf;
XftDraw *backdraw = XftDrawCreate(window->display, window->backbuf,
DefaultVisual(window->display, DefaultScreen(window->display)),
DefaultColormap(window->display, DefaultScreen(window->display)));
if (!backdraw) {
fprintf(stderr, "Pixmap向けXftDrawの作成に失敗。\n");
XFreePixmap(window->display, window->backbuf);
window->backbuf = None;
return;
}
XSetForeground(window->display, window->gc, BGCOL);
XFillRectangle(window->display, window->backbuf, window->gc, 0, 0, w, h);
float scale = get_scale(window);
if (scale <= 0) scale = 1.f;
int label_padding = (int)(20 * scale + .5f);
// 出力
{
XGlyphInfo extents;
XftTextExtentsUtf8(window->display, labels->res->font,
(const FcChar8 *)labels->res->text,
strlen(labels->res->text), &extents);
int tx = w - label_padding - extents.xOff;
int ty = (int)(180 * scale + .5f);
XftDrawStringUtf8(
backdraw, &labels->res->fg_color, labels->res->font,
tx, ty, (const FcChar8 *)labels->res->text, 64);
}
{
XGlyphInfo extents;
XftTextExtentsUtf8(window->display, labels->problem->font,
(const FcChar8 *)labels->problem->text,
strlen(labels->problem->text), &extents);
int tx = w - label_padding - extents.xOff;
int ty = (int)(80 * scale + .5f);
XftDrawStringUtf8(
backdraw, &labels->problem->fg_color, labels->problem->font,
tx, ty, (const FcChar8 *)labels->problem->text, 64);
}
int bw = (int)(DESIGN_BUTTON_WIDTH * scale + .5f);
int bh = (int)(DESIGN_BUTTON_HEIGHT * scale + .5f);
int bp = (int)(DESIGN_PADDING * scale + .5f);
int by = (int)(DESIGN_BUTTON_AREA_Y * scale + .5f);
XftColor buttonColor;
#if defined(__OpenBSD__)
buttonColor = suwaui_set_button_fgcolor(window, "#232320");
#elif defined(__FreeBSD__)
buttonColor = suwaui_set_button_fgcolor(window, "#232020");
#endif
for (int row = 1; row < NUM_ROWS; ++row) {
for (int col = 0; col < NUM_COLS; ++col) {
SuwaButton btn = suwaui_add_button(bp + col * (bw + bp),
by + (row - 1) * (bh + bp),
bw, bh, btn_labels[row][col], window->font, BTCOL, buttonColor);
suwaui_draw_button(window, &btn, backdraw);
}
}
XCopyArea(window->display, window->backbuf, window->xwindow, window->gc,
0, 0, w, h, 0, 0);
XftDrawDestroy(backdraw);
XFlush(window->display);
}
void handle_button_press(SuwaWindow *window, int mx, int my) {
SuwaButton *btn = find_button_at(window, mx, my);
if (!btn) return;
btn->pressed = 1;
XftDraw *backdraw = XftDrawCreate(window->display, window->backbuf,
DefaultVisual(window->display, DefaultScreen(window->display)),
DefaultColormap(window->display, DefaultScreen(window->display)));
if (!backdraw) {
fprintf(stderr, "Pixmap向けXftDrawの作成に失敗。\n");
XFreePixmap(window->display, window->backbuf);
window->backbuf = None;
return;
}
suwaui_draw_button(window, btn, backdraw);
XFlush(window->display);
}
void handle_button_release(SuwaWindow *window, CtrlLabels *labels, int mx, int my) {
SuwaButton *btn = find_button_at(window, mx, my);
if (!btn) return;
btn->pressed = 0;
XftDraw *backdraw = XftDrawCreate(window->display, window->backbuf,
DefaultVisual(window->display, DefaultScreen(window->display)),
DefaultColormap(window->display, DefaultScreen(window->display)));
if (!backdraw) {
fprintf(stderr, "Pixmap向けXftDrawの作成に失敗。\n");
XFreePixmap(window->display, window->backbuf);
window->backbuf = None;
return;
}
window->target = window->backbuf;
suwaui_draw_button(window, btn, backdraw);
XftDrawDestroy(backdraw);
XFlush(window->display);
const char *label = btn->text;
if (strcmp(label, "C") == 0) {
clear_calculator(labels);
} else if (strcmp(label, "×") == 0) {
append_to_input(labels, '*');
} else if (strcmp(label, "÷") == 0) {
append_to_input(labels, '/');
} else if (strcmp(label, "<") == 0) {
curinput[--input_pos] = '\0';
strcpy(labels->res->text, curinput[0] ? curinput : "0");
} else if (strcmp(label, "=") == 0) {
double res = evaluate_simple(labels, curinput);
if (isnan(res)) strncpy(labels->res->text, "Error", 5);
else snprintf(labels->res->text, sizeof(labels->res->text), "%.8g", res);
} else if (strlen(label) == 1) {
append_to_input(labels, label[0]);
}
window->event = (XEvent){.type = Expose};
control_expose(window, labels);
}
void handle_key_press(SuwaWindow *window, CtrlLabels *labels) {
KeySym keysym;
char buf[32];
int len;
(void)len;
keysym = XLookupKeysym(&window->event.xkey, 0);
len = XLookupString(&window->event.xkey, buf, sizeof(buf), &keysym, NULL);
if (keysym == XK_Q) {
window->isrunning = 0;
return;
}
if (keysym == XK_B) {
puts("標準電卓画面");
}
if (keysym == XK_P) {
puts("プログラマー電卓画面");
}
if (keysym == XK_H) {
puts("履歴画面");
}
if (keysym == XK_h) {
puts("ヘルプ画面");
puts("-----");
puts("Q = 終了 Exit");
puts("X = 1文字の消し Remove 1 character");
puts("C = クリ了 Clear");
puts("h = ヘルプ画面 Help screen");
puts("H = 履歴画面 History screen");
puts("B = 標準電卓画面 Basic calculator screen");
puts("P = プログラマー電卓画面 Programmer calculator screen");
}
if (keysym == XK_C) {
clear_calculator(labels);
goto redraw;
}
if (keysym == XK_Return || keysym == XK_KP_Enter) {
double res = evaluate_simple(labels, curinput);
if (isnan(res)) strncpy(labels->res->text, "Error", 5);
else snprintf(labels->res->text, sizeof(labels->res->text), "%.8g", res);
goto redraw;
}
if ((keysym == XK_BackSpace && input_pos > 0)
|| (keysym == XK_Delete && input_pos > 0)
|| (keysym == XK_X && input_pos > 0)) {
curinput[--input_pos] = '\0';
strcpy(labels->res->text, curinput[0] ? curinput : "0");
goto redraw;
}
char c = 0;
if (keysym >= XK_0 && keysym <= XK_9) c = '0' + (keysym - XK_0);
else if (keysym >= XK_KP_0 && keysym <= XK_KP_9) c = '0' + (keysym - XK_0);
else if (keysym == XK_KP_Add || keysym == XK_plus) c = '+';
else if (keysym == XK_KP_Subtract || keysym == XK_minus) c = '-';
else if (keysym == XK_KP_Multiply || keysym == XK_asterisk) c = '*';
else if (keysym == XK_KP_Divide || keysym == XK_slash) c = '/';
else if (keysym == XK_KP_Decimal || keysym == XK_period) c = '.';
if (c) {
append_to_input(labels, c);
goto redraw;
}
return;
redraw:
window->event = (XEvent){.type = Expose};
control_expose(window, labels);
}
// void handle_mouse_hover(SuwaWindow *window) {
// int x = window->event,xmotion.x;
// int y = window->event,xmotion.y;
// SuwaButton *hover = find_button_at(x, y);
// if (hover != hovered_btn) {
// if (hovered_btn) {
// hovered_btn->pressed = 0;
// redraw_single_button(&window, hovered_btn);
// }
// hovered_btn = hover;
// if (hovered_btn) {
// redraw_single_button(&window, hovered_btn);
// }
// }
// }