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

Contents of /lbbs/src/menu.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.33 - (show annotations)
Thu May 8 15:24:59 2025 UTC (10 months, 1 week ago) by sysadm
Branch: MAIN
Changes since 1.32: +4 -2 lines
Content type: text/x-csrc
Fix bug

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

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