Download
jcs
/subtext
/main_menu.c
(View History)
jcs *: Add dynamically configured main menu | Latest amendment: 281 on 2022-11-11 |
1 | /* |
2 | * Copyright (c) 2022 joshua stein <jcs@jcs.org> |
3 | * |
4 | * Permission to use, copy, modify, and distribute this software for any |
5 | * purpose with or without fee is hereby granted, provided that the above |
6 | * copyright notice and this permission notice appear in all copies. |
7 | * |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
15 | */ |
16 | |
17 | #include <stdio.h> |
18 | #include <string.h> |
19 | |
20 | #include "subtext.h" |
21 | #include "db.h" |
22 | #include "main_menu.h" |
23 | |
24 | struct main_menu_option *main_menu_options = NULL; |
25 | |
26 | void |
27 | main_menu_init(void) |
28 | { |
29 | struct main_menu_option *opts = NULL, *old_opts = NULL; |
30 | size_t size; |
31 | char *menu = NULL; |
32 | bool ok = false; |
33 | Handle default_menu; |
34 | |
35 | size = bile_read_alloc(db->bile, DB_TEXT_TYPE, DB_TEXT_MENU_OPTIONS_ID, |
36 | &menu); |
37 | if (size == 0 || menu == NULL) { |
38 | /* use default menu */ |
39 | if (menu != NULL) |
40 | xfree(&menu); |
41 | default_menu = GetResource('TEXT', MENU_DEFAULTS_ID); |
42 | if (default_menu == NULL) |
43 | panic("Failed to find TEXT resource %d", MENU_DEFAULTS_ID); |
44 | size = GetHandleSize(default_menu); |
45 | menu = xmalloc(size, "menu copy"); |
46 | HLock(default_menu); |
47 | memcpy(menu, *default_menu, size); |
48 | HUnlock(default_menu); |
49 | ReleaseResource(default_menu); |
50 | |
51 | bile_write(db->bile, DB_TEXT_TYPE, DB_TEXT_MENU_OPTIONS_ID, |
52 | menu, size); |
53 | } |
54 | |
55 | opts = main_menu_parse(menu, size); |
56 | free(&menu); |
57 | if (opts == NULL) { |
58 | main_menu_edit(); |
59 | return; |
60 | } |
61 | |
62 | /* swap this atomically */ |
63 | old_opts = main_menu_options; |
64 | main_menu_options = opts; |
65 | if (old_opts != NULL) |
66 | xfree(&old_opts); |
67 | } |
68 | |
69 | void |
70 | main_menu_edit(void) |
71 | { |
72 | view_editor_show(DB_TEXT_MENU_OPTIONS_ID, "Edit: Main Menu options"); |
73 | } |
74 | |
75 | struct main_menu_option * |
76 | main_menu_parse(char *opts, size_t len) |
77 | { |
78 | char *line, *action, *menu_key, *all_keys, *label; |
79 | size_t n, m, linelen, lastsep, linenum, ret_size; |
80 | short count, actionid, ret_count; |
81 | struct main_menu_option *ret = NULL; |
82 | |
83 | #define LINESIZE 1024 |
84 | line = xmalloc(LINESIZE, "parse_menu line"); |
85 | action = xmalloc(LINESIZE, "parse_menu action"); |
86 | menu_key = xmalloc(LINESIZE, "parse_menu menu_key"); |
87 | all_keys = xmalloc(LINESIZE, "parse_menu all_keys"); |
88 | label = xmalloc(LINESIZE, "parse_menu label"); |
89 | |
90 | ret_count = 0; |
91 | ret_size = 0; |
92 | |
93 | for (n = 0, lastsep = 0, linenum = 0; n <= len; n++) { |
94 | if (!(n == len || opts[n] == '\r')) |
95 | continue; |
96 | |
97 | linenum++; |
98 | linelen = MIN(n - lastsep, LINESIZE - 1); |
99 | if (n == len && linelen) |
100 | linelen--; |
101 | memcpy(line, opts + lastsep, linelen); |
102 | line[linelen] = '\0'; |
103 | lastsep = n + 1; |
104 | |
105 | if (line[0] == '\0' || line[0] == '#' || line[0] == '\r') |
106 | continue; |
107 | |
108 | /* BOARD_SHOW_FIRST:B:Bb:Message Board */ |
109 | |
110 | /* sscanf won't match %[^:] for an empty section ("A::Aa:aa") */ |
111 | action[0] = '\0'; |
112 | for (m = 0; m < linelen; m++) { |
113 | if (line[m] == ':') { |
114 | memcpy(action, line, m); |
115 | action[m] = '\0'; |
116 | linelen -= m + 1; |
117 | memmove(line, line + m + 1, linelen + 1); |
118 | break; |
119 | } |
120 | } |
121 | if (action[0] == '\0') { |
122 | warn("Error on line %ld: no action found", linenum); |
123 | ret_count = 0; |
124 | break; |
125 | } |
126 | |
127 | actionid = ACTION_NONE; |
128 | if (strcmp(action, "BOARD_SHOW_FIRST") == 0) |
129 | actionid = ACTION_BOARD_SHOW_FIRST; |
130 | else if (strcmp(action, "BOARD_SHOW_1") == 0) |
131 | actionid = ACTION_BOARD_SHOW_1; |
132 | else if (strcmp(action, "BOARD_SHOW_2") == 0) |
133 | actionid = ACTION_BOARD_SHOW_2; |
134 | else if (strcmp(action, "BOARD_SHOW_3") == 0) |
135 | actionid = ACTION_BOARD_SHOW_3; |
136 | else if (strcmp(action, "BOARD_SHOW_4") == 0) |
137 | actionid = ACTION_BOARD_SHOW_4; |
138 | else if (strcmp(action, "BOARD_SHOW_5") == 0) |
139 | actionid = ACTION_BOARD_SHOW_5; |
140 | else if (strcmp(action, "BOARD_SHOW_6") == 0) |
141 | actionid = ACTION_BOARD_SHOW_6; |
142 | else if (strcmp(action, "BOARD_SHOW_7") == 0) |
143 | actionid = ACTION_BOARD_SHOW_7; |
144 | else if (strcmp(action, "BOARD_SHOW_8") == 0) |
145 | actionid = ACTION_BOARD_SHOW_8; |
146 | else if (strcmp(action, "BOARD_SHOW_9") == 0) |
147 | actionid = ACTION_BOARD_SHOW_9; |
148 | else if (strcmp(action, "BOARD_SHOW_10") == 0) |
149 | actionid = ACTION_BOARD_SHOW_10; |
150 | else if (strcmp(action, "CHAT") == 0) |
151 | actionid = ACTION_CHAT; |
152 | else if (strcmp(action, "FILES_MENU") == 0) |
153 | actionid = ACTION_FILES_MENU; |
154 | else if (strcmp(action, "GOODBYE") == 0) |
155 | actionid = ACTION_GOODBYE; |
156 | else if (strcmp(action, "RECENT_LOGINS") == 0) |
157 | actionid = ACTION_RECENT_LOGINS; |
158 | else if (strcmp(action, "MAIL_COMPOSE") == 0) |
159 | actionid = ACTION_MAIL_COMPOSE; |
160 | else if (strcmp(action, "MAIL_MENU") == 0) |
161 | actionid = ACTION_MAIL_MENU; |
162 | else if (strcmp(action, "MOTD") == 0) |
163 | actionid = ACTION_MOTD; |
164 | else if (strcmp(action, "PAGE_SEND_OR_ANSWER") == 0) |
165 | actionid = ACTION_PAGE_SEND_OR_ANSWER; |
166 | else if (strcmp(action, "SETTINGS_OR_SIGNUP") == 0) |
167 | actionid = ACTION_SETTINGS_OR_SIGNUP; |
168 | else if (strcmp(action, "SHOW_MENU") == 0) |
169 | actionid = ACTION_SHOW_MENU; |
170 | else if (strcmp(action, "SYSOP_MENU") == 0) |
171 | actionid = ACTION_SYSOP_MENU; |
172 | else if (strcmp(action, "WHOS_ONLINE") == 0) |
173 | actionid = ACTION_WHOS_ONLINE; |
174 | else { |
175 | warn("Error on line %ld: invalid action \"%s\"", linenum, |
176 | action); |
177 | ret_count = 0; |
178 | break; |
179 | } |
180 | |
181 | menu_key[0] = '\0'; |
182 | for (m = 0; m < linelen; m++) { |
183 | if (line[m] == ':') { |
184 | memcpy(menu_key, line, m); |
185 | menu_key[m] = '\0'; |
186 | linelen -= m + 1; |
187 | memmove(line, line + m + 1, linelen + 1); |
188 | break; |
189 | } |
190 | } |
191 | /* menu_key can be 1 or 0 characters */ |
192 | if (strlen(menu_key) > 1) { |
193 | warn("Error on line %ld: Menu Key can only be 1 character", |
194 | linenum); |
195 | ret_count = 0; |
196 | break; |
197 | } |
198 | |
199 | all_keys[0] = '\0'; |
200 | for (m = 0; m < linelen; m++) { |
201 | if (line[m] == ':') { |
202 | memcpy(all_keys, line, m); |
203 | all_keys[m] = '\0'; |
204 | linelen -= m + 1; |
205 | memmove(line, line + m + 1, linelen + 1); |
206 | break; |
207 | } |
208 | } |
209 | if (all_keys[0] == '\0') { |
210 | warn("Error on line %ld: All Keys field cannot be empty", |
211 | linenum); |
212 | ret_count = 0; |
213 | break; |
214 | } |
215 | |
216 | if (line[0] == '\0') { |
217 | warn("Error on line %ld: Label cannot be empty", |
218 | linenum); |
219 | ret_count = 0; |
220 | break; |
221 | } |
222 | strlcpy(label, line, LINESIZE); |
223 | |
224 | EXPAND_TO_FIT(ret, ret_size, |
225 | sizeof(struct main_menu_option) * ret_count, |
226 | sizeof(struct main_menu_option), 4); |
227 | |
228 | ret[ret_count].action = actionid; |
229 | ret[ret_count].menu_key = menu_key[0]; |
230 | strlcpy(ret[ret_count].all_keys, all_keys, |
231 | sizeof(ret[ret_count].all_keys)); |
232 | strlcpy(ret[ret_count].label, label, sizeof(ret[ret_count].label)); |
233 | ret_count++; |
234 | } |
235 | |
236 | xfree(&line); |
237 | xfree(&action); |
238 | xfree(&menu_key); |
239 | xfree(&all_keys); |
240 | xfree(&label); |
241 | |
242 | if (ret_count == 0) { |
243 | xfree(&ret); |
244 | return NULL; |
245 | } |
246 | |
247 | EXPAND_TO_FIT(ret, ret_size, |
248 | sizeof(struct main_menu_option) * ret_count, |
249 | sizeof(struct main_menu_option), 1); |
250 | |
251 | ret[ret_count].action = ACTION_NONE; |
252 | ret[ret_count].menu_key = 0; |
253 | ret[ret_count].all_keys[0] = '\0'; |
254 | ret[ret_count].label[0] = '\0'; |
255 | |
256 | return ret; |
257 | #undef LINESIZE |
258 | } |