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

Contents of /lbbs/src/menu.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.38 - (show annotations)
Tue May 13 07:28:51 2025 UTC (10 months ago) by sysadm
Branch: MAIN
Changes since 1.37: +3 -4 lines
Content type: text/x-csrc
Refine igetch/igetch_t

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 "bbs_cmd.h"
21 #include "menu.h"
22 #include "log.h"
23 #include "io.h"
24 #include "screen.h"
25 #include "common.h"
26 #include <string.h>
27 #include <stdio.h>
28 #include <ctype.h>
29 #include <stdlib.h>
30
31 #define MENU_SCREEN_PATH_PREFIX "var/MENU_SCR_"
32 #define MENU_CONF_DELIM_WITH_SPACE " ,\t\r\n"
33 #define MENU_CONF_DELIM_WITHOUT_SPACE "\r\n"
34
35 MENU_SET bbs_menu;
36
37 int load_menu(MENU_SET *p_menu_set, const char *conf_file)
38 {
39 FILE *fin, *fout;
40 int fin_line = 0;
41 int i = 0;
42 int j = 0;
43 char buffer[LINE_BUFFER_LEN];
44 char temp[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("Incomplete menu definition 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 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 strncpy(temp, buffer, sizeof(temp)); // Duplicate line for strtok_r
525 p = strtok_r(temp, MENU_CONF_DELIM_WITH_SPACE, &saveptr);
526 if (p != NULL && *p == '%') // END of menu screen
527 {
528 break;
529 }
530
531 if (fputs(buffer, fout) < 0)
532 {
533 log_error("Write %s failed", screen_filename);
534 return -2;
535 }
536 }
537
538 fclose(fout);
539 }
540 }
541 else // Invalid prefix
542 {
543 log_error("Error in menu config line %d\n", fin_line);
544 return -1;
545 }
546 }
547 fclose(fin);
548
549 p_menu_set->menu_count = i;
550 p_menu_set->menu_select_depth = 0;
551 p_menu_set->p_menu_select[p_menu_set->menu_select_depth] = (i == 0 ? NULL : p_menu_set->p_menu[0]);
552
553 return 0;
554 }
555
556 MENU *get_menu(MENU_SET *p_menu_set, const char *menu_name)
557 {
558 int i;
559
560 for (i = 0; i < p_menu_set->menu_count; i++)
561 {
562 if (strcmp(p_menu_set->p_menu[i]->name, menu_name) == 0)
563 {
564 return p_menu_set->p_menu[i];
565 }
566 }
567
568 return NULL;
569 }
570
571 static void display_menu_cursor(MENU *p_menu, int show)
572 {
573 moveto((p_menu->items[p_menu->item_cur_pos])->r_row,
574 (p_menu->items[p_menu->item_cur_pos])->r_col - 2);
575 outc(show ? '>' : ' ');
576 iflush();
577 }
578
579 int display_menu(MENU *p_menu)
580 {
581 int row = 0;
582 int col = 0;
583 int menu_selectable = 0;
584
585 if (p_menu == NULL)
586 {
587 return -1;
588 }
589
590 if (p_menu->item_cur_pos > 0 &&
591 checkpriv(&BBS_priv, 0, p_menu->items[p_menu->item_cur_pos]->priv) != 0 &&
592 checklevel(&BBS_priv, p_menu->items[p_menu->item_cur_pos]->level) != 0)
593 {
594 menu_selectable = 1;
595 }
596
597 if (p_menu->title.show)
598 {
599 show_top(p_menu->title.text);
600 }
601
602 if (p_menu->screen.show)
603 {
604 moveto(p_menu->screen.row, p_menu->screen.col);
605 if (display_file(p_menu->screen.filename) != 0)
606 {
607 log_error("Display menu screen <%s> failed!\n",
608 p_menu->screen.filename);
609 }
610 }
611
612 for (int i = 0; i < p_menu->item_count; i++)
613 {
614 if (p_menu->items[i]->row != 0)
615 {
616 row = p_menu->items[i]->row;
617 }
618 if (p_menu->items[i]->col != 0)
619 {
620 col = p_menu->items[i]->col;
621 }
622
623 if (checkpriv(&BBS_priv, 0, p_menu->items[i]->priv) == 0 || checklevel(&BBS_priv, p_menu->items[i]->level) == 0)
624 {
625 p_menu->items[i]->display = 0;
626 p_menu->items[i]->r_row = 0;
627 p_menu->items[i]->r_col = 0;
628 }
629 else
630 {
631 p_menu->items[i]->display = 1;
632
633 if (!menu_selectable)
634 {
635 p_menu->item_cur_pos = i;
636 menu_selectable = 1;
637 }
638
639 p_menu->items[i]->r_row = row;
640 p_menu->items[i]->r_col = col;
641
642 moveto(row, col);
643 prints("%s", p_menu->items[i]->text);
644
645 row++;
646 }
647 }
648
649 if (!menu_selectable)
650 {
651 return -1;
652 }
653
654 display_menu_cursor(p_menu, 1);
655
656 return 0;
657 }
658
659 int display_current_menu(MENU_SET *p_menu_set)
660 {
661 MENU *p_menu;
662
663 p_menu = p_menu_set->p_menu_select[p_menu_set->menu_select_depth];
664
665 return display_menu(p_menu);
666 }
667
668 int menu_control(MENU_SET *p_menu_set, int key)
669 {
670 int i;
671 MENU *p_menu;
672
673 if (p_menu_set->menu_count == 0)
674 {
675 return 0;
676 }
677
678 p_menu = p_menu_set->p_menu_select[p_menu_set->menu_select_depth];
679
680 switch (key)
681 {
682 case CR:
683 case KEY_RIGHT:
684 if (p_menu->items[p_menu->item_cur_pos]->submenu)
685 {
686 if (strcmp(p_menu->items[p_menu->item_cur_pos]->action, "..") == 0)
687 {
688 return menu_control(p_menu_set, KEY_LEFT);
689 }
690 p_menu_set->menu_select_depth++;
691 p_menu = get_menu(p_menu_set, p_menu->items[p_menu->item_cur_pos]->action);
692 p_menu_set->p_menu_select[p_menu_set->menu_select_depth] = p_menu;
693
694 if (display_menu(p_menu) != 0)
695 {
696 return menu_control(p_menu_set, KEY_LEFT);
697 }
698 }
699 else
700 {
701 return (exec_cmd(p_menu->items[p_menu->item_cur_pos]->action,
702 p_menu->items[p_menu->item_cur_pos]->name));
703 }
704 break;
705 case KEY_LEFT:
706 if (p_menu_set->menu_select_depth > 0)
707 {
708 p_menu_set->menu_select_depth--;
709 if (display_current_menu(p_menu_set) != 0)
710 {
711 return menu_control(p_menu_set, KEY_LEFT);
712 }
713 }
714 else
715 {
716 display_menu_cursor(p_menu, 0);
717 p_menu->item_cur_pos = p_menu->item_count - 1;
718 while (p_menu->item_cur_pos >= 0 && (!p_menu->items[p_menu->item_cur_pos]->display ||
719 p_menu->items[p_menu->item_cur_pos]->priv != 0 ||
720 p_menu->items[p_menu->item_cur_pos]->level != 0))
721 {
722 p_menu->item_cur_pos--;
723 }
724 display_menu_cursor(p_menu, 1);
725 }
726 break;
727 case KEY_UP:
728 display_menu_cursor(p_menu, 0);
729 do
730 {
731 p_menu->item_cur_pos--;
732 if (p_menu->item_cur_pos < 0)
733 {
734 p_menu->item_cur_pos = p_menu->item_count - 1;
735 }
736 } while (!p_menu->items[p_menu->item_cur_pos]->display);
737 display_menu_cursor(p_menu, 1);
738 break;
739 case KEY_DOWN:
740 display_menu_cursor(p_menu, 0);
741 do
742 {
743 p_menu->item_cur_pos++;
744 if (p_menu->item_cur_pos >= p_menu->item_count)
745 {
746 p_menu->item_cur_pos = 0;
747 }
748 } while (!p_menu->items[p_menu->item_cur_pos]->display);
749 display_menu_cursor(p_menu, 1);
750 break;
751 default:
752 if (isalnum(key))
753 {
754 for (i = 0; i < p_menu->item_count; i++)
755 {
756 if (toupper(key) == toupper(p_menu->items[i]->name[0]) &&
757 p_menu->items[i]->display)
758 {
759 display_menu_cursor(p_menu, 0);
760 p_menu->item_cur_pos = i;
761 display_menu_cursor(p_menu, 1);
762 return 0;
763 }
764 }
765 }
766 break;
767 }
768
769 return 0;
770 }
771
772 void unload_menu(MENU_SET *p_menu_set)
773 {
774 MENU *p_menu;
775 int i, j;
776
777 for (i = 0; i < p_menu_set->menu_count; i++)
778 {
779 p_menu = p_menu_set->p_menu[i];
780 for (j = 0; j < p_menu->item_count; j++)
781 {
782 free(p_menu->items[j]);
783 }
784 free(p_menu);
785 }
786
787 p_menu_set->menu_count = 0;
788 p_menu_set->menu_select_depth = 0;
789 }
790
791 int reload_menu(MENU_SET *p_menu_set)
792 {
793 int result;
794 char conf_file[FILE_PATH_LEN];
795
796 strncpy(conf_file, p_menu_set->conf_file, sizeof(conf_file) - 1);
797 conf_file[sizeof(conf_file) - 1] = '\0';
798
799 unload_menu(p_menu_set);
800 result = load_menu(p_menu_set, conf_file);
801
802 return result;
803 }

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