/[LeafOK_CVS]/lbbs/src/menu.c
ViewVC logotype

Contents of /lbbs/src/menu.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.31 - (show annotations)
Thu May 8 08:05:58 2025 UTC (10 months, 1 week ago) by sysadm
Branch: MAIN
Changes since 1.30: +466 -195 lines
Content type: text/x-csrc
Refact bbs_menu

1 /***************************************************************************
2 menu.c - description
3 -------------------
4 Copyright : (C) 2004-2025 by Leaflet
5 Email : leaflet@leafok.com
6 ***************************************************************************/
7
8 /***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 3 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
17 #include "bbs.h"
18 #include "bbs_cmd.h"
19 #include "user_priv.h"
20 #include "reg_ex.h"
21 #include "bbs_cmd.h"
22 #include "menu.h"
23 #include "log.h"
24 #include "io.h"
25 #include "screen.h"
26 #include "common.h"
27 #include <string.h>
28 #include <stdio.h>
29 #include <ctype.h>
30 #include <stdlib.h>
31
32 #define MENU_SCREEN_PATH_PREFIX "var/MENU_SCR_"
33 #define MENU_CONF_DELIM_WITH_SPACE " ,\t\r\n"
34 #define MENU_CONF_DELIM_WITHOUT_SPACE "\r\n"
35
36 MENU_SET bbs_menu;
37
38 int load_menu(MENU_SET *p_menu_set, const char *conf_file)
39 {
40 FILE *fin, *fout;
41 int fin_line = 0;
42 int i = 0;
43 int j = 0;
44 char buffer[LINE_BUFFER_LEN];
45 char screen_filename[FILE_PATH_LEN];
46 char *p = NULL;
47 char *q = NULL;
48 char *saveptr = NULL;
49 MENU *p_menu = NULL;
50 MENU_ITEM *p_item = NULL;
51
52 p_menu_set->menu_count = 0;
53
54 if ((fin = fopen(conf_file, "r")) == NULL)
55 {
56 log_error("Open %s failed", conf_file);
57 return -2;
58 }
59
60 strncpy(p_menu_set->conf_file, conf_file, sizeof(p_menu_set->conf_file) - 1);
61 p_menu_set->conf_file[sizeof(p_menu_set->conf_file) - 1] = '\0';
62
63 while (fgets(buffer, sizeof(buffer), fin))
64 {
65 fin_line++;
66
67 p = strtok_r(buffer, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
68 if (p == NULL) // Blank line
69 {
70 continue;
71 }
72
73 if (*p == '#' || *p == '\r' || *p == '\n') // Comment or blank line
74 {
75 continue;
76 }
77
78 if (*p == '%')
79 {
80 p++;
81
82 if (strcmp(p, "menu") == 0) // BEGIN of sub-menu
83 {
84 if (p_menu != NULL)
85 {
86 log_error("Begin new menu without end the prior one, in menu config line %d\n", fin_line);
87 return -1;
88 }
89 p_menu = (MENU *)malloc(sizeof(MENU));
90 if (p_menu == NULL)
91 {
92 log_error("Unable to allocate memory for menu\n");
93 return -3;
94 }
95 p_menu_set->p_menu[i] = p_menu;
96 i++;
97 p_menu_set->menu_count = i;
98
99 j = 0; // Menu item counter
100 p_menu->item_count = 0;
101 p_menu->item_cur_pos = 0;
102 p_menu->title.show = 0;
103 p_menu->screen.show = 0;
104
105 q = strtok_r(NULL, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
106 if (q == NULL)
107 {
108 log_error("Error menu name in menu config line %d\n", fin_line);
109 return -1;
110 }
111 p = q;
112 while (isalnum(*q) || *q == '_')
113 {
114 q++;
115 }
116 if (*q != '\0')
117 {
118 log_error("Error menu name in menu config line %d\n", fin_line);
119 return -1;
120 }
121
122 if (q - p > sizeof(p_menu->name) - 1)
123 {
124 log_error("Too longer menu name in menu config line %d\n", fin_line);
125 return -1;
126 }
127 strncpy(p_menu->name, p, sizeof(p_menu->name) - 1);
128 p_menu->name[sizeof(p_menu->name) - 1] = '\0';
129
130 // Check syntax
131 q = strtok_r(NULL, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
132 if (q != NULL)
133 {
134 log_error("Unknown extra content in menu config line %d\n", fin_line);
135 return -1;
136 }
137
138 while (fgets(buffer, sizeof(buffer), fin))
139 {
140 fin_line++;
141
142 p = strtok_r(buffer, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
143 if (p == NULL) // Blank line
144 {
145 continue;
146 }
147
148 if (*p == '#' || *p == '\r' || *p == '\n') // Comment or blank line
149 {
150 continue;
151 }
152
153 if (*p == '%') // END of sub-menu
154 {
155 p_menu = NULL;
156 break;
157 }
158 else if (*p == '!' || *p == '@')
159 {
160 // BEGIN of menu item
161 p_item = (MENU_ITEM *)malloc(sizeof(MENU_ITEM));
162 if (p_item == NULL)
163 {
164 log_error("Unable to allocate memory for menu item\n");
165 return -3;
166 }
167 p_menu->items[j] = p_item;
168 j++;
169 p_menu->item_count = j;
170
171 p_item->submenu = (*p == '!' ? 1 : 0);
172
173 // Menu item action
174 p++;
175 if (strcmp(p, "..") == 0) // Return to parent menu
176 {
177 q = p + 2; // strlen("..")
178 }
179 else
180 {
181 q = p;
182 while (isalnum(*q) || *q == '_')
183 {
184 q++;
185 }
186 if (*q != '\0')
187 {
188 log_error("Error menu item action in menu config line %d\n", fin_line);
189 return -1;
190 }
191 }
192
193 if (q - p > sizeof(p_item->action) - 1)
194 {
195 log_error("Too longer menu action in menu config line %d\n", fin_line);
196 return -1;
197 }
198 strncpy(p_item->action, p, sizeof(p_item->action) - 1);
199 p_item->action[sizeof(p_item->action) - 1] = '\0';
200
201 // Menu item row
202 q = strtok_r(NULL, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
203 if (q == NULL)
204 {
205 log_error("Error menu item row in menu config line %d\n", fin_line);
206 return -1;
207 }
208 p = q;
209 while (isdigit(*q))
210 {
211 q++;
212 }
213 if (*q != '\0')
214 {
215 log_error("Error menu item row in menu config line %d\n", fin_line);
216 return -1;
217 }
218 p_item->row = atoi(p);
219
220 // Menu item col
221 q = strtok_r(NULL, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
222 if (q == NULL)
223 {
224 log_error("Error menu item col in menu config line %d\n", fin_line);
225 return -1;
226 }
227 p = q;
228 while (isdigit(*q))
229 {
230 q++;
231 }
232 if (*q != '\0')
233 {
234 log_error("Error menu item col in menu config line %d\n", fin_line);
235 return -1;
236 }
237 p_item->col = atoi(p);
238
239 // Menu item priv
240 q = strtok_r(NULL, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
241 if (q == NULL)
242 {
243 log_error("Error menu item priv in menu config line %d\n", fin_line);
244 return -1;
245 }
246 p = q;
247 while (isdigit(*q))
248 {
249 q++;
250 }
251 if (*q != '\0')
252 {
253 log_error("Error menu item priv in menu config line %d\n", fin_line);
254 return -1;
255 }
256 p_item->priv = atoi(p);
257
258 // Menu item level
259 q = strtok_r(NULL, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
260 if (q == NULL)
261 {
262 log_error("Error menu item level in menu config line %d\n", fin_line);
263 return -1;
264 }
265 p = q;
266 while (isdigit(*q))
267 {
268 q++;
269 }
270 if (*q != '\0')
271 {
272 log_error("Error menu item level in menu config line %d\n", fin_line);
273 return -1;
274 }
275 p_item->level = atoi(p);
276
277 // Menu item name
278 q = strtok_r(NULL, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
279 if (q == NULL || *q != '\"')
280 {
281 log_error("Error menu item name in menu config line %d\n", fin_line);
282 return -1;
283 }
284 q++;
285 p = q;
286 while (*q != '\0' && *q != '\"')
287 {
288 q++;
289 }
290 if (*q != '\"' || *(q + 1) != '\0')
291 {
292 log_error("Error menu item name in menu config line %d\n", fin_line);
293 return -1;
294 }
295 *q = '\0';
296
297 if (q - p > sizeof(p_item->name) - 1)
298 {
299 log_error("Too longer menu name in menu config line %d\n", fin_line);
300 return -1;
301 }
302 strncpy(p_item->name, p, sizeof(p_item->name) - 1);
303 p_item->name[sizeof(p_item->name) - 1] = '\0';
304
305 // Menu item text
306 q = strtok_r(NULL, MENU_CONF_DELIM_WITHOUT_SPACE, &saveptr);
307 if (q == NULL || (q = strchr(q, '\"')) == NULL)
308 {
309 log_error("Error #1 menu item text in menu config line %d\n", fin_line);
310 return -1;
311 }
312 q++;
313 p = q;
314 while (*q != '\0' && *q != '\"')
315 {
316 q++;
317 }
318 if (*q != '\"')
319 {
320 log_error("Error menu item text in menu config line %d\n", fin_line);
321 return -1;
322 }
323 *q = '\0';
324
325 if (q - p > sizeof(p_item->text) - 1)
326 {
327 log_error("Too longer menu item text in menu config line %d\n", fin_line);
328 return -1;
329 }
330 strncpy(p_item->text, p, sizeof(p_item->text) - 1);
331 p_item->text[sizeof(p_item->text) - 1] = '\0';
332
333 // Check syntax
334 q = strtok_r(q + 1, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
335 if (q != NULL)
336 {
337 log_error("Unknown extra content in menu config line %d\n", fin_line);
338 return -1;
339 }
340 }
341 else if (strcmp(p, "title") == 0)
342 {
343 p_menu->title.show = 1;
344
345 // Menu title row
346 q = strtok_r(NULL, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
347 if (q == NULL)
348 {
349 log_error("Error menu title row in menu config line %d\n", fin_line);
350 return -1;
351 }
352 p = q;
353 while (isdigit(*q))
354 {
355 q++;
356 }
357 if (*q != '\0')
358 {
359 log_error("Error menu title row in menu config line %d\n", fin_line);
360 return -1;
361 }
362 p_menu->title.row = atoi(p);
363
364 // Menu title col
365 q = strtok_r(NULL, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
366 if (q == NULL)
367 {
368 log_error("Error menu title col in menu config line %d\n", fin_line);
369 return -1;
370 }
371 p = q;
372 while (isdigit(*q))
373 {
374 q++;
375 }
376 if (*q != '\0')
377 {
378 log_error("Error menu title col in menu config line %d\n", fin_line);
379 return -1;
380 }
381 p_menu->title.col = atoi(p);
382
383 // Menu title text
384 q = strtok_r(NULL, MENU_CONF_DELIM_WITHOUT_SPACE, &saveptr);
385 if (q == NULL || (q = strchr(q, '\"')) == NULL)
386 {
387 log_error("Error menu title text in menu config line %d\n", fin_line);
388 return -1;
389 }
390 q++;
391 p = q;
392 while (*q != '\0' && *q != '\"')
393 {
394 q++;
395 }
396 if (*q != '\"')
397 {
398 log_error("Error menu title text in menu config line %d\n", fin_line);
399 return -1;
400 }
401 *q = '\0';
402
403 if (q - p > sizeof(p_item->text) - 1)
404 {
405 log_error("Too longer menu title text in menu config line %d\n", fin_line);
406 return -1;
407 }
408 strncpy(p_menu->title.text, p, sizeof(p_menu->title.text) - 1);
409 p_menu->title.text[sizeof(p_menu->title.text) - 1] = '\0';
410
411 // Check syntax
412 q = strtok_r(q + 1, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
413 if (q != NULL)
414 {
415 log_error("Unknown extra content in menu config line %d\n", fin_line);
416 return -1;
417 }
418 }
419 else if (strcmp(p, "screen") == 0)
420 {
421 p_menu->screen.show = 1;
422
423 // Menu screen row
424 q = strtok_r(NULL, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
425 if (q == NULL)
426 {
427 log_error("Error menu screen row in menu config line %d\n", fin_line);
428 return -1;
429 }
430 p = q;
431 while (isdigit(*q))
432 {
433 q++;
434 }
435 if (*q != '\0')
436 {
437 log_error("Error menu screen row in menu config line %d\n", fin_line);
438 return -1;
439 }
440 p_menu->screen.row = atoi(p);
441
442 // Menu screen col
443 q = strtok_r(NULL, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
444 if (q == NULL)
445 {
446 log_error("Error menu screen col in menu config line %d\n", fin_line);
447 return -1;
448 }
449 p = q;
450 while (isdigit(*q))
451 {
452 q++;
453 }
454 if (*q != '\0')
455 {
456 log_error("Error menu screen col in menu config line %d\n", fin_line);
457 return -1;
458 }
459 p_menu->screen.col = atoi(p);
460
461 // Menu screen name
462 q = strtok_r(NULL, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
463 if (q == NULL)
464 {
465 log_error("Error menu screen name in menu config line %d\n", fin_line);
466 return -1;
467 }
468 p = q;
469 while (isalnum(*q) || *q == '_')
470 {
471 q++;
472 }
473 if (*q != '\0')
474 {
475 log_error("Error menu screen name in menu config line %d\n", fin_line);
476 return -1;
477 }
478
479 snprintf(p_menu->screen.filename, sizeof(p_menu->screen.filename), "%s%s", MENU_SCREEN_PATH_PREFIX, p);
480
481 // Check syntax
482 q = strtok_r(NULL, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
483 if (q != NULL)
484 {
485 log_error("Unknown extra content in menu config line %d\n", fin_line);
486 return -1;
487 }
488 }
489 }
490 }
491 else // BEGIN of menu screen
492 {
493 q = p;
494 while (isalnum(*q) || *q == '_')
495 {
496 q++;
497 }
498 if (*q != '\0')
499 {
500 log_error("Error menu screen name in menu config line %d\n", fin_line);
501 return -1;
502 }
503
504 snprintf(screen_filename, sizeof(screen_filename), "%s%s", MENU_SCREEN_PATH_PREFIX, p);
505
506 // Check syntax
507 q = strtok_r(NULL, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
508 if (q != NULL)
509 {
510 log_error("Unknown extra content in menu config line %d\n", fin_line);
511 return -1;
512 }
513
514 if ((fout = fopen(screen_filename, "w")) == NULL)
515 {
516 log_error("Open %s failed", screen_filename);
517 return -2;
518 }
519
520 while (fgets(buffer, sizeof(buffer), fin))
521 {
522 fin_line++;
523
524 p = strtok_r(buffer, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
525 if (p != NULL && *p == '%') // END of menu screen
526 {
527 break;
528 }
529
530 if (fputs(buffer, fout) < 0)
531 {
532 log_error("Write %s failed", screen_filename);
533 return -2;
534 }
535 }
536
537 fclose(fout);
538 }
539 }
540 else // Invalid prefix
541 {
542 log_error("Error in menu config line %d\n", fin_line);
543 return -1;
544 }
545 }
546 fclose(fin);
547
548 p_menu_set->menu_count = i;
549 p_menu_set->menu_select_depth = 0;
550 p_menu_set->p_menu_select[p_menu_set->menu_select_depth] = (i == 0 ? NULL : p_menu_set->p_menu[0]);
551
552 return 0;
553 }
554
555 MENU *get_menu(MENU_SET *p_menu_set, const char *menu_name)
556 {
557 int i;
558
559 for (i = 0; i < p_menu_set->menu_count; i++)
560 {
561 if (strcmp(p_menu_set->p_menu[i]->name, menu_name) == 0)
562 {
563 return p_menu_set->p_menu[i];
564 }
565 }
566
567 return NULL;
568 }
569
570 static void display_menu_cursor(MENU *p_menu, int show)
571 {
572 moveto((p_menu->items[p_menu->item_cur_pos])->r_row,
573 (p_menu->items[p_menu->item_cur_pos])->r_col - 2);
574 outc(show ? '>' : ' ');
575 iflush();
576 }
577
578 int display_menu(MENU *p_menu)
579 {
580 int row = 0;
581 int col = 0;
582 int menu_selectable = 0;
583
584 if (p_menu == NULL)
585 {
586 return -1;
587 }
588
589 if (p_menu->title.show)
590 {
591 show_top(p_menu->title.text);
592 }
593
594 if (p_menu->screen.show)
595 {
596 moveto(p_menu->screen.row, p_menu->screen.col);
597 if (display_file(p_menu->screen.filename) != 0)
598 {
599 log_error("Display menu screen <%s> failed!\n",
600 p_menu->screen.filename);
601 }
602 }
603
604 for (int i = 0; i < p_menu->item_count; i++)
605 {
606 if (p_menu->items[i]->row != 0)
607 {
608 row = p_menu->items[i]->row;
609 }
610 if (p_menu->items[i]->col != 0)
611 {
612 col = p_menu->items[i]->col;
613 }
614
615 if (checkpriv(&BBS_priv, 0, p_menu->items[i]->priv) == 0 || checklevel(&BBS_priv, p_menu->items[i]->level) == 0)
616 {
617 p_menu->items[i]->display = 0;
618 p_menu->items[i]->r_row = 0;
619 p_menu->items[i]->r_col = 0;
620 }
621 else
622 {
623 p_menu->items[i]->display = 1;
624
625 if (!menu_selectable)
626 {
627 p_menu->item_cur_pos = i;
628 menu_selectable = 1;
629 }
630
631 p_menu->items[i]->r_row = row;
632 p_menu->items[i]->r_col = col;
633
634 moveto(row, col);
635 prints("%s", p_menu->items[i]->text);
636
637 row++;
638 }
639 }
640
641 if (!menu_selectable)
642 {
643 return -1;
644 }
645
646 display_menu_cursor(p_menu, 1);
647
648 return 0;
649 }
650
651 int display_current_menu(MENU_SET *p_menu_set)
652 {
653 MENU *p_menu;
654
655 p_menu = p_menu_set->p_menu_select[p_menu_set->menu_select_depth];
656
657 return display_menu(p_menu);
658 }
659
660 int menu_control(MENU_SET *p_menu_set, int key)
661 {
662 int i;
663 MENU *p_menu;
664
665 if (p_menu_set->menu_count == 0)
666 {
667 return 0;
668 }
669
670 p_menu = p_menu_set->p_menu_select[p_menu_set->menu_select_depth];
671
672 switch (key)
673 {
674 case CR:
675 igetch(1); // Cleanup remaining '\n' in the buffer
676 case KEY_RIGHT:
677 if (p_menu->items[p_menu->item_cur_pos]->submenu)
678 {
679 if (strcmp(p_menu->items[p_menu->item_cur_pos]->action, "..") == 0)
680 {
681 return menu_control(p_menu_set, KEY_LEFT);
682 }
683 p_menu_set->menu_select_depth++;
684 p_menu = get_menu(p_menu_set, p_menu->items[p_menu->item_cur_pos]->action);
685 p_menu_set->p_menu_select[p_menu_set->menu_select_depth] = p_menu;
686
687 if (display_menu(p_menu) != 0)
688 {
689 return menu_control(p_menu_set, KEY_LEFT);
690 }
691 break;
692 }
693 else
694 {
695 return (exec_cmd(p_menu->items[p_menu->item_cur_pos]->action,
696 p_menu->items[p_menu->item_cur_pos]->name));
697 }
698 case KEY_LEFT:
699 if (p_menu_set->menu_select_depth > 0)
700 {
701 p_menu_set->menu_select_depth--;
702 if (display_current_menu(p_menu_set) != 0)
703 {
704 return menu_control(p_menu_set, KEY_LEFT);
705 }
706 break;
707 }
708 else
709 {
710 display_menu_cursor(p_menu, 0);
711 p_menu->item_cur_pos = p_menu->item_count - 1;
712 while (!p_menu->items[p_menu->item_cur_pos]->display ||
713 p_menu->items[p_menu->item_cur_pos]->priv != 0 ||
714 p_menu->items[p_menu->item_cur_pos]->level != 0)
715 {
716 p_menu->item_cur_pos--;
717 }
718 display_menu_cursor(p_menu, 1);
719 break;
720 }
721 case KEY_UP:
722 display_menu_cursor(p_menu, 0);
723 do
724 {
725 p_menu->item_cur_pos--;
726 if (p_menu->item_cur_pos < 0)
727 {
728 p_menu->item_cur_pos = p_menu->item_count - 1;
729 }
730 } while (!p_menu->items[p_menu->item_cur_pos]->display);
731 display_menu_cursor(p_menu, 1);
732 break;
733 case KEY_DOWN:
734 display_menu_cursor(p_menu, 0);
735 do
736 {
737 p_menu->item_cur_pos++;
738 if (p_menu->item_cur_pos >= p_menu->item_count)
739 {
740 p_menu->item_cur_pos = 0;
741 }
742 } while (!p_menu->items[p_menu->item_cur_pos]->display);
743 display_menu_cursor(p_menu, 1);
744 break;
745 default:
746 if (isalnum(key))
747 {
748 for (i = 0; i < p_menu->item_count; i++)
749 {
750 if (toupper(key) == toupper(p_menu->items[i]->name[0]) &&
751 p_menu->items[i]->display)
752 {
753 display_menu_cursor(p_menu, 0);
754 p_menu->item_cur_pos = i;
755 display_menu_cursor(p_menu, 1);
756 return 0;
757 }
758 }
759 }
760 }
761
762 return 0;
763 }
764
765 void unload_menu(MENU_SET *p_menu_set)
766 {
767 MENU *p_menu;
768 int i, j;
769
770 for (i = 0; i < p_menu_set->menu_count; i++)
771 {
772 p_menu = p_menu_set->p_menu[i];
773 for (j = 0; j < p_menu->item_count; j++)
774 {
775 free(p_menu->items[j]);
776 }
777 free(p_menu);
778 }
779
780 p_menu_set->menu_count = 0;
781 p_menu_set->menu_select_depth = 0;
782 }
783
784 int reload_menu(MENU_SET *p_menu_set)
785 {
786 int result;
787 char conf_file[FILE_PATH_LEN];
788
789 strncpy(conf_file, p_menu_set->conf_file, sizeof(conf_file) - 1);
790 conf_file[sizeof(conf_file) - 1] = '\0';
791
792 unload_menu(p_menu_set);
793 result = load_menu(p_menu_set, conf_file);
794
795 return result;
796 }

webmaster@leafok.com
ViewVC Help
Powered by ViewVC 1.3.0-beta1