2 * Copyright (c) 2023 Jiri Svoboda
3 * Copyright (c) 2016 Martin Decky
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 /** @addtogroup calculator
35 * Inspired by the code released at https://github.com/osgroup/HelenOSProject
39 #include <clipboard.h>
41 #include <io/kbd_event.h>
49 #include <ui/menubar.h>
50 #include <ui/menudd.h>
51 #include <ui/menuentry.h>
52 #include <ui/pbutton.h>
54 #include <ui/window.h>
56 #define NAME "calculator"
58 #define NULL_DISPLAY "0"
60 #define SYNTAX_ERROR_DISPLAY "Syntax error"
61 #define NUMERIC_ERROR_DISPLAY "Numerical error"
62 #define UNKNOWN_ERROR_DISPLAY "Unknown error"
64 #define EXPR_MAX_LEN 22
95 stack_item_type_t type
;
102 /** Dimensions. Most of this should not be needed with auto layout */
104 gfx_rect_t menubar_rect
;
105 gfx_rect_t entry_rect
;
106 gfx_coord2_t btn_orig
;
107 gfx_coord2_t btn_stride
;
108 gfx_coord2_t btn_dim
;
113 ui_resource_t
*ui_res
;
114 ui_pbutton_t
*btn_eval
;
115 ui_pbutton_t
*btn_clear
;
116 ui_pbutton_t
*btn_add
;
117 ui_pbutton_t
*btn_sub
;
118 ui_pbutton_t
*btn_mul
;
119 ui_pbutton_t
*btn_div
;
130 ui_menu_bar_t
*menubar
;
134 static void display_update(void);
136 static void calc_file_exit(ui_menu_entry_t
*, void *);
137 static void calc_edit_copy(ui_menu_entry_t
*, void *);
138 static void calc_edit_paste(ui_menu_entry_t
*, void *);
140 static void calc_pb_clicked(ui_pbutton_t
*, void *);
141 static void calc_eval_clicked(ui_pbutton_t
*, void *);
142 static void calc_clear_clicked(ui_pbutton_t
*, void *);
144 static ui_pbutton_cb_t calc_pbutton_cb
= {
145 .clicked
= calc_pb_clicked
148 static ui_pbutton_cb_t calc_clear_cb
= {
149 .clicked
= calc_clear_clicked
152 static ui_pbutton_cb_t calc_eval_cb
= {
153 .clicked
= calc_eval_clicked
156 static void wnd_close(ui_window_t
*, void *);
157 static void wnd_kbd_event(ui_window_t
*, void *, kbd_event_t
*);
159 static ui_window_cb_t window_cb
= {
164 static char *expr
= NULL
;
165 static ui_entry_t
*display
;
167 /** Window close request
169 * @param window Window
170 * @param arg Argument (calc_t *)
172 static void wnd_close(ui_window_t
*window
, void *arg
)
174 calc_t
*calc
= (calc_t
*) arg
;
179 /** Window keyboard event
181 * @param window Window
182 * @param arg Argument (calc_t *)
183 * @param event Keyboard event
185 static void wnd_kbd_event(ui_window_t
*window
, void *arg
, kbd_event_t
*event
)
187 calc_t
*calc
= (calc_t
*) arg
;
189 if (ui_window_def_kbd(window
, event
) == ui_claimed
)
192 if (event
->type
== KEY_PRESS
&& (event
->mods
& KM_CTRL
) != 0) {
193 switch (event
->key
) {
195 calc_edit_copy(NULL
, calc
);
198 calc_edit_paste(NULL
, calc
);
205 switch (event
->key
) {
207 if (event
->type
== KEY_PRESS
)
208 ui_pbutton_press(calc
->btn_eval
);
210 ui_pbutton_release(calc
->btn_eval
);
213 if (event
->type
== KEY_PRESS
)
214 ui_pbutton_press(calc
->btn_clear
);
216 ui_pbutton_release(calc
->btn_clear
);
219 if (event
->type
== KEY_PRESS
)
220 ui_pbutton_press(calc
->btn_sub
);
222 ui_pbutton_release(calc
->btn_sub
);
225 if (event
->type
== KEY_PRESS
)
226 ui_pbutton_press(calc
->btn_add
);
228 ui_pbutton_release(calc
->btn_add
);
231 if (event
->type
== KEY_PRESS
)
232 ui_pbutton_press(calc
->btn_div
);
234 ui_pbutton_release(calc
->btn_div
);
237 if (event
->type
== KEY_PRESS
)
238 ui_pbutton_press(calc
->btn_0
);
240 ui_pbutton_release(calc
->btn_0
);
243 if (event
->type
== KEY_PRESS
)
244 ui_pbutton_press(calc
->btn_1
);
246 ui_pbutton_release(calc
->btn_1
);
249 if (event
->type
== KEY_PRESS
)
250 ui_pbutton_press(calc
->btn_2
);
252 ui_pbutton_release(calc
->btn_2
);
255 if (event
->type
== KEY_PRESS
)
256 ui_pbutton_press(calc
->btn_3
);
258 ui_pbutton_release(calc
->btn_3
);
261 if (event
->type
== KEY_PRESS
)
262 ui_pbutton_press(calc
->btn_4
);
264 ui_pbutton_release(calc
->btn_4
);
267 if (event
->type
== KEY_PRESS
)
268 ui_pbutton_press(calc
->btn_5
);
270 ui_pbutton_release(calc
->btn_5
);
273 if (event
->type
== KEY_PRESS
)
274 ui_pbutton_press(calc
->btn_6
);
276 ui_pbutton_release(calc
->btn_6
);
279 if (event
->type
== KEY_PRESS
)
280 ui_pbutton_press(calc
->btn_7
);
282 ui_pbutton_release(calc
->btn_7
);
285 if ((event
->mods
& KM_SHIFT
) != 0) {
286 if (event
->type
== KEY_PRESS
)
287 ui_pbutton_press(calc
->btn_mul
);
289 ui_pbutton_release(calc
->btn_mul
);
291 if (event
->type
== KEY_PRESS
)
292 ui_pbutton_press(calc
->btn_8
);
294 ui_pbutton_release(calc
->btn_8
);
298 if (event
->type
== KEY_PRESS
)
299 ui_pbutton_press(calc
->btn_9
);
301 ui_pbutton_release(calc
->btn_9
);
308 /** File / Exit menu entry selected.
310 * @param mentry Menu entry
311 * @param arg Argument (calc_t *)
313 static void calc_file_exit(ui_menu_entry_t
*mentry
, void *arg
)
315 calc_t
*calc
= (calc_t
*) arg
;
320 /** Edit / Copy menu entry selected.
322 * @param mentry Menu entry
323 * @param arg Argument (calc_t *)
325 static void calc_edit_copy(ui_menu_entry_t
*mentry
, void *arg
)
330 str
= (expr
!= NULL
) ? expr
: NULL_DISPLAY
;
332 (void) clipboard_put_str(str
);
335 /** Edit / Paste menu entry selected.
337 * @param mentry Menu entry
338 * @param arg Argument (calc_t *)
340 static void calc_edit_paste(ui_menu_entry_t
*mentry
, void *arg
)
349 rc
= clipboard_get_str(&str
);
353 /* Make sure string only contains allowed characters */
355 while (*cp
!= '\0') {
356 if (!isdigit(*cp
) && *cp
!= '+' && *cp
!= '-' &&
357 *cp
!= '*' && *cp
!= '/')
362 /* Update expression */
370 static bool is_digit(char c
)
372 return ((c
>= '0') && (c
<= '9'));
375 static int get_digit(char c
)
382 static bool is_plus(char c
)
387 static bool is_minus(char c
)
392 static bool is_finish(char c
)
397 static operator_t
get_operator(char c
)
409 return OPERATOR_NONE
;
413 static bool is_operator(char c
)
415 return (get_operator(c
) != OPERATOR_NONE
);
418 static bool stack_push_value(list_t
*stack
, int64_t value
, bool value_neg
)
420 stack_item_t
*item
= malloc(sizeof(stack_item_t
));
424 link_initialize(&item
->link
);
425 item
->type
= ITEM_VALUE
;
428 item
->data
.value
= -value
;
430 item
->data
.value
= value
;
432 list_prepend(&item
->link
, stack
);
437 static bool stack_push_operator(list_t
*stack
, operator_t
operator)
439 stack_item_t
*item
= malloc(sizeof(stack_item_t
));
443 link_initialize(&item
->link
);
444 item
->type
= ITEM_OPERATOR
;
445 item
->data
.operator = operator;
446 list_prepend(&item
->link
, stack
);
451 static bool stack_pop_value(list_t
*stack
, int64_t *value
)
453 link_t
*link
= list_first(stack
);
457 stack_item_t
*item
= list_get_instance(link
, stack_item_t
, link
);
458 if (item
->type
!= ITEM_VALUE
)
461 *value
= item
->data
.value
;
469 static bool stack_pop_operator(list_t
*stack
, operator_t
*operator)
471 link_t
*link
= list_first(stack
);
475 stack_item_t
*item
= list_get_instance(link
, stack_item_t
, link
);
476 if (item
->type
!= ITEM_OPERATOR
)
479 *operator = item
->data
.operator;
487 static void stack_cleanup(list_t
*stack
)
489 while (!list_empty(stack
)) {
490 link_t
*link
= list_first(stack
);
492 stack_item_t
*item
= list_get_instance(link
, stack_item_t
,
501 static bool compute(int64_t a
, operator_t
operator, int64_t b
, int64_t *value
)
526 static unsigned int get_priority(operator_t
operator)
542 static void evaluate(list_t
*stack
, int64_t *value
, parser_state_t
*state
,
543 error_type_t
*error_type
)
545 while (!list_empty(stack
)) {
546 if (!stack_pop_value(stack
, value
)) {
547 *state
= STATE_ERROR
;
548 *error_type
= ERROR_SYNTAX
;
552 if (!list_empty(stack
)) {
554 if (!stack_pop_operator(stack
, &operator)) {
555 *state
= STATE_ERROR
;
556 *error_type
= ERROR_SYNTAX
;
561 if (!stack_pop_value(stack
, &value_a
)) {
562 *state
= STATE_ERROR
;
563 *error_type
= ERROR_SYNTAX
;
567 if (!compute(value_a
, operator, *value
, value
)) {
568 *state
= STATE_ERROR
;
569 *error_type
= ERROR_NUMERIC
;
573 if (!stack_push_value(stack
, *value
, false)) {
574 *state
= STATE_ERROR
;
575 *error_type
= ERROR_SYNTAX
;
582 static void display_update(void)
585 (void) ui_entry_set_text(display
, (void *) expr
);
587 (void) ui_entry_set_text(display
, (void *) NULL_DISPLAY
);
589 ui_entry_paint(display
);
592 static void display_error(error_type_t error_type
)
599 switch (error_type
) {
601 (void) ui_entry_set_text(display
,
602 (void *) SYNTAX_ERROR_DISPLAY
);
605 (void) ui_entry_set_text(display
,
606 (void *) NUMERIC_ERROR_DISPLAY
);
609 (void) ui_entry_set_text(display
,
610 (void *) UNKNOWN_ERROR_DISPLAY
);
614 ui_entry_paint(display
);
617 static void calc_pb_clicked(ui_pbutton_t
*pbutton
, void *arg
)
619 const char *subexpr
= (const char *) arg
;
624 if (str_length(expr
) < EXPR_MAX_LEN
) {
625 asprintf(&new_expr
, "%s%s", expr
, subexpr
);
630 expr
= str_dup(subexpr
);
636 static void calc_clear_clicked(ui_pbutton_t
*pbutton
, void *arg
)
646 static void calc_eval_clicked(ui_pbutton_t
*pbutton
, void *arg
)
652 list_initialize(&stack
);
654 error_type_t error_type
= ERROR_SYNTAX
;
656 parser_state_t state
= STATE_INITIAL
;
658 bool value_neg
= false;
659 operator_t last_operator
= OPERATOR_NONE
;
661 while ((state
!= STATE_FINISH
) && (state
!= STATE_ERROR
)) {
664 if (is_digit(expr
[i
])) {
665 value
= get_digit(expr
[i
]);
668 } else if (is_plus(expr
[i
])) {
672 } else if (is_minus(expr
[i
])) {
681 if (is_digit(expr
[i
])) {
682 value
= get_digit(expr
[i
]);
690 if (is_digit(expr
[i
])) {
692 value
+= get_digit(expr
[i
]);
694 } else if (is_operator(expr
[i
])) {
695 if (!stack_push_value(&stack
, value
, value_neg
)) {
703 operator_t
operator = get_operator(expr
[i
]);
705 if (get_priority(operator) <= get_priority(last_operator
)) {
706 evaluate(&stack
, &value
, &state
, &error_type
);
707 if (state
== STATE_ERROR
)
710 if (!stack_push_value(&stack
, value
, value_neg
)) {
716 if (!stack_push_operator(&stack
, operator)) {
721 last_operator
= operator;
724 } else if (is_finish(expr
[i
])) {
725 if (!stack_push_value(&stack
, value
, value_neg
)) {
730 state
= STATE_FINISH
;
740 evaluate(&stack
, &value
, &state
, &error_type
);
741 stack_cleanup(&stack
);
743 if (state
== STATE_ERROR
) {
744 display_error(error_type
);
749 asprintf(&expr
, "%" PRId64
, value
);
753 static errno_t
calc_button_create(calc_t
*calc
, ui_fixed_t
*fixed
,
754 int x
, int y
, const char *text
, ui_pbutton_cb_t
*cb
, void *arg
,
755 ui_pbutton_t
**rbutton
)
761 rc
= ui_pbutton_create(calc
->ui_res
, text
, &pb
);
763 printf("Error creating button.\n");
767 ui_pbutton_set_cb(pb
, cb
, arg
);
769 rect
.p0
.x
= calc
->geom
.btn_orig
.x
+ calc
->geom
.btn_stride
.x
* x
;
770 rect
.p0
.y
= calc
->geom
.btn_orig
.y
+ calc
->geom
.btn_stride
.y
* y
;
771 rect
.p1
.x
= rect
.p0
.x
+ calc
->geom
.btn_dim
.x
;
772 rect
.p1
.y
= rect
.p0
.y
+ calc
->geom
.btn_dim
.y
;
773 ui_pbutton_set_rect(pb
, &rect
);
775 rc
= ui_fixed_add(fixed
, ui_pbutton_ctl(pb
));
777 printf("Error adding control to layout.\n");
786 static void print_syntax(void)
788 printf("Syntax: %s [-d <display-spec>]\n", NAME
);
791 int main(int argc
, char *argv
[])
793 const char *display_spec
= UI_ANY_DEFAULT
;
795 ui_resource_t
*ui_res
;
797 ui_wnd_params_t params
;
800 ui_menu_entry_t
*mexit
;
802 ui_menu_entry_t
*mcopy
;
803 ui_menu_entry_t
*mpaste
;
810 if (str_cmp(argv
[i
], "-d") == 0) {
813 printf("Argument missing.\n");
818 display_spec
= argv
[i
++];
820 printf("Invalid option '%s'.\n", argv
[i
]);
826 rc
= ui_create(display_spec
, &ui
);
828 printf("Error creating UI on display %s.\n", display_spec
);
832 ui_wnd_params_init(¶ms
);
833 params
.caption
= "Calculator";
834 params
.rect
.p0
.x
= 0;
835 params
.rect
.p0
.y
= 0;
837 if (ui_is_textmode(ui
)) {
838 params
.rect
.p1
.x
= 38;
839 params
.rect
.p1
.y
= 18;
841 calc
.geom
.menubar_rect
.p0
.x
= 1;
842 calc
.geom
.menubar_rect
.p0
.y
= 1;
843 calc
.geom
.menubar_rect
.p1
.x
= params
.rect
.p1
.x
- 1;
844 calc
.geom
.menubar_rect
.p1
.y
= 2;
845 calc
.geom
.entry_rect
.p0
.x
= 4;
846 calc
.geom
.entry_rect
.p0
.y
= 3;
847 calc
.geom
.entry_rect
.p1
.x
= 34;
848 calc
.geom
.entry_rect
.p1
.y
= 4;
849 calc
.geom
.btn_orig
.x
= 4;
850 calc
.geom
.btn_orig
.y
= 5;
851 calc
.geom
.btn_dim
.x
= 6;
852 calc
.geom
.btn_dim
.y
= 2;
853 calc
.geom
.btn_stride
.x
= 8;
854 calc
.geom
.btn_stride
.y
= 3;
856 params
.rect
.p1
.x
= 250;
857 params
.rect
.p1
.y
= 270;
859 calc
.geom
.menubar_rect
.p0
.x
= 4;
860 calc
.geom
.menubar_rect
.p0
.y
= 30;
861 calc
.geom
.menubar_rect
.p1
.x
= params
.rect
.p1
.x
- 4;
862 calc
.geom
.menubar_rect
.p1
.y
= 52;
863 calc
.geom
.entry_rect
.p0
.x
= 10;
864 calc
.geom
.entry_rect
.p0
.y
= 51;
865 calc
.geom
.entry_rect
.p1
.x
= 240;
866 calc
.geom
.entry_rect
.p1
.y
= 76;
867 calc
.geom
.btn_orig
.x
= 10;
868 calc
.geom
.btn_orig
.y
= 90;
869 calc
.geom
.btn_dim
.x
= 50;
870 calc
.geom
.btn_dim
.y
= 35;
871 calc
.geom
.btn_stride
.x
= 60;
872 calc
.geom
.btn_stride
.y
= 45;
875 rc
= ui_window_create(ui
, ¶ms
, &window
);
877 printf("Error creating window.\n");
881 ui_window_set_cb(window
, &window_cb
, (void *) &calc
);
884 ui_res
= ui_window_get_res(window
);
885 calc
.ui_res
= ui_res
;
887 rc
= ui_fixed_create(&fixed
);
889 printf("Error creating fixed layout.\n");
893 rc
= ui_menu_bar_create(ui
, window
, &calc
.menubar
);
895 printf("Error creating menu bar.\n");
899 rc
= ui_menu_dd_create(calc
.menubar
, "~F~ile", NULL
, &mfile
);
901 printf("Error creating menu.\n");
905 rc
= ui_menu_entry_create(mfile
, "E~x~it", "Alt-F4", &mexit
);
907 printf("Error creating menu.\n");
911 ui_menu_entry_set_cb(mexit
, calc_file_exit
, (void *) &calc
);
913 rc
= ui_menu_dd_create(calc
.menubar
, "~E~dit", NULL
, &medit
);
915 printf("Error creating menu.\n");
919 rc
= ui_menu_entry_create(medit
, "~C~opy", "Ctrl-C", &mcopy
);
921 printf("Error creating menu.\n");
925 ui_menu_entry_set_cb(mcopy
, calc_edit_copy
, (void *) &calc
);
927 rc
= ui_menu_entry_create(medit
, "~P~aste", "Ctrl-V", &mpaste
);
929 printf("Error creating menu.\n");
933 ui_menu_entry_set_cb(mpaste
, calc_edit_paste
, (void *) &calc
);
935 ui_menu_bar_set_rect(calc
.menubar
, &calc
.geom
.menubar_rect
);
937 rc
= ui_fixed_add(fixed
, ui_menu_bar_ctl(calc
.menubar
));
939 printf("Error adding control to layout.\n");
943 rc
= ui_entry_create(window
, NULL_DISPLAY
, &display
);
945 printf("Error creating text lentry.\n");
949 ui_entry_set_rect(display
, &calc
.geom
.entry_rect
);
950 ui_entry_set_halign(display
, gfx_halign_right
);
951 ui_entry_set_read_only(display
, true);
953 rc
= ui_fixed_add(fixed
, ui_entry_ctl(display
));
955 printf("Error adding control to layout.\n");
959 rc
= calc_button_create(&calc
, fixed
, 0, 0, "7", &calc_pbutton_cb
,
960 (void *) "7", &calc
.btn_7
);
964 rc
= calc_button_create(&calc
, fixed
, 1, 0, "8", &calc_pbutton_cb
,
965 (void *) "8", &calc
.btn_8
);
969 rc
= calc_button_create(&calc
, fixed
, 2, 0, "9", &calc_pbutton_cb
,
970 (void *) "9", &calc
.btn_9
);
974 rc
= calc_button_create(&calc
, fixed
, 3, 0, "/", &calc_pbutton_cb
,
975 (void *) "/", &calc
.btn_div
);
979 rc
= calc_button_create(&calc
, fixed
, 0, 1, "4", &calc_pbutton_cb
,
980 (void *) "4", &calc
.btn_4
);
984 rc
= calc_button_create(&calc
, fixed
, 1, 1, "5", &calc_pbutton_cb
,
985 (void *) "5", &calc
.btn_5
);
989 rc
= calc_button_create(&calc
, fixed
, 2, 1, "6", &calc_pbutton_cb
,
990 (void *) "6", &calc
.btn_6
);
994 rc
= calc_button_create(&calc
, fixed
, 3, 1, "*", &calc_pbutton_cb
,
995 (void *) "*", &calc
.btn_mul
);
999 rc
= calc_button_create(&calc
, fixed
, 0, 2, "1", &calc_pbutton_cb
,
1000 (void *) "1", &calc
.btn_1
);
1004 rc
= calc_button_create(&calc
, fixed
, 1, 2, "2", &calc_pbutton_cb
,
1005 (void *) "2", &calc
.btn_2
);
1009 rc
= calc_button_create(&calc
, fixed
, 2, 2, "3", &calc_pbutton_cb
,
1010 (void *) "3", &calc
.btn_3
);
1014 rc
= calc_button_create(&calc
, fixed
, 3, 2, "-", &calc_pbutton_cb
,
1015 (void *) "-", &calc
.btn_sub
);
1019 rc
= calc_button_create(&calc
, fixed
, 0, 3, "0", &calc_pbutton_cb
,
1020 (void *) "0", &calc
.btn_0
);
1024 rc
= calc_button_create(&calc
, fixed
, 1, 3, "C", &calc_clear_cb
,
1025 (void *) "C", &calc
.btn_clear
);
1029 rc
= calc_button_create(&calc
, fixed
, 2, 3, "=", &calc_eval_cb
,
1030 (void *) "=", &calc
.btn_eval
);
1034 rc
= calc_button_create(&calc
, fixed
, 3, 3, "+", &calc_pbutton_cb
,
1035 (void *) "+", &calc
.btn_add
);
1039 ui_pbutton_set_default(calc
.btn_eval
, true);
1041 ui_window_add(window
, ui_fixed_ctl(fixed
));
1043 rc
= ui_window_paint(window
);
1045 printf("Error painting window.\n");
1050 ui_window_destroy(window
);