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

Contents of /lbbs/src/menu.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.35 - (show annotations)
Sat May 10 02:52:17 2025 UTC (10 months, 1 week ago) by sysadm
Branch: MAIN
Changes since 1.34: +8 -3 lines
Content type: text/x-csrc
Refine menu item select on KEY_LEFT

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 strncpy(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->item_cur_pos > 0 &&
592 checkpriv(&BBS_priv, 0, p_menu->items[p_menu->item_cur_pos]->priv) != 0 &&
593 checklevel(&BBS_priv, p_menu->items[p_menu->item_cur_pos]->level) != 0)
594 {
595 menu_selectable = 1;
596 }
597
598 if (p_menu->title.show)
599 {
600 show_top(p_menu->title.text);
601 }
602
603 if (p_menu->screen.show)
604 {
605 moveto(p_menu->screen.row, p_menu->screen.col);
606 if (display_file(p_menu->screen.filename) != 0)
607 {
608 log_error("Display menu screen <%s> failed!\n",
609 p_menu->screen.filename);
610 }
611 }
612
613 for (int i = 0; i < p_menu->item_count; i++)
614 {
615 if (p_menu->items[i]->row != 0)
616 {
617 row = p_menu->items[i]->row;
618 }
619 if (p_menu->items[i]->col != 0)
620 {
621 col = p_menu->items[i]->col;
622 }
623
624 if (checkpriv(&BBS_priv, 0, p_menu->items[i]->priv) == 0 || checklevel(&BBS_priv, p_menu->items[i]->level) == 0)
625 {
626 p_menu->items[i]->display = 0;
627 p_menu->items[i]->r_row = 0;
628 p_menu->items[i]->r_col = 0;
629 }
630 else
631 {
632 p_menu->items[i]->display = 1;
633
634 if (!menu_selectable)
635 {
636 p_menu->item_cur_pos = i;
637 menu_selectable = 1;
638 }
639
640 p_menu->items[i]->r_row = row;
641 p_menu->items[i]->r_col = col;
642
643 moveto(row, col);
644 prints("%s", p_menu->items[i]->text);
645
646 row++;
647 }
648 }
649
650 if (!menu_selectable)
651 {
652 return -1;
653 }
654
655 display_menu_cursor(p_menu, 1);
656
657 return 0;
658 }
659
660 int display_current_menu(MENU_SET *p_menu_set)
661 {
662 MENU *p_menu;
663
664 p_menu = p_menu_set->p_menu_select[p_menu_set->menu_select_depth];
665
666 return display_menu(p_menu);
667 }
668
669 int menu_control(MENU_SET *p_menu_set, int key)
670 {
671 int i;
672 MENU *p_menu;
673
674 if (p_menu_set->menu_count == 0)
675 {
676 return 0;
677 }
678
679 p_menu = p_menu_set->p_menu_select[p_menu_set->menu_select_depth];
680
681 switch (key)
682 {
683 case CR:
684 igetch(1); // Cleanup remaining '\n' in the buffer
685 case KEY_RIGHT:
686 if (p_menu->items[p_menu->item_cur_pos]->submenu)
687 {
688 if (strcmp(p_menu->items[p_menu->item_cur_pos]->action, "..") == 0)
689 {
690 return menu_control(p_menu_set, KEY_LEFT);
691 }
692 p_menu_set->menu_select_depth++;
693 p_menu = get_menu(p_menu_set, p_menu->items[p_menu->item_cur_pos]->action);
694 p_menu_set->p_menu_select[p_menu_set->menu_select_depth] = p_menu;
695
696 if (display_menu(p_menu) != 0)
697 {
698 return menu_control(p_menu_set, KEY_LEFT);
699 }
700 break;
701 }
702 else
703 {
704 return (exec_cmd(p_menu->items[p_menu->item_cur_pos]->action,
705 p_menu->items[p_menu->item_cur_pos]->name));
706 }
707 case KEY_LEFT:
708 if (p_menu_set->menu_select_depth > 0)
709 {
710 p_menu_set->menu_select_depth--;
711 if (display_current_menu(p_menu_set) != 0)
712 {
713 return menu_control(p_menu_set, KEY_LEFT);
714 }
715 break;
716 }
717 else
718 {
719 display_menu_cursor(p_menu, 0);
720 p_menu->item_cur_pos = p_menu->item_count - 1;
721 while (p_menu->item_cur_pos >= 0 && !p_menu->items[p_menu->item_cur_pos]->display)
722 {
723 p_menu->item_cur_pos--;
724 }
725 display_menu_cursor(p_menu, 1);
726 break;
727 }
728 case KEY_UP:
729 display_menu_cursor(p_menu, 0);
730 do
731 {
732 p_menu->item_cur_pos--;
733 if (p_menu->item_cur_pos < 0)
734 {
735 p_menu->item_cur_pos = p_menu->item_count - 1;
736 }
737 } while (!p_menu->items[p_menu->item_cur_pos]->display);
738 display_menu_cursor(p_menu, 1);
739 break;
740 case KEY_DOWN:
741 display_menu_cursor(p_menu, 0);
742 do
743 {
744 p_menu->item_cur_pos++;
745 if (p_menu->item_cur_pos >= p_menu->item_count)
746 {
747 p_menu->item_cur_pos = 0;
748 }
749 } while (!p_menu->items[p_menu->item_cur_pos]->display);
750 display_menu_cursor(p_menu, 1);
751 break;
752 default:
753 if (isalnum(key))
754 {
755 for (i = 0; i < p_menu->item_count; i++)
756 {
757 if (toupper(key) == toupper(p_menu->items[i]->name[0]) &&
758 p_menu->items[i]->display)
759 {
760 display_menu_cursor(p_menu, 0);
761 p_menu->item_cur_pos = i;
762 display_menu_cursor(p_menu, 1);
763 return 0;
764 }
765 }
766 }
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