1 /*
2 Copyright (c) 1999-2002 Perry Rapp
3 "The MIT license"
4 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
7 */
8 /*=============================================================
9 * menuset.c -- dynamic menus
10 * This module holds the code to construct a menu from strings
11 * and to search for a keystroke & return the corresponding command
12 * Created: 1999/03 by Perry Rapp
13 * Brought into repository: 2001/01/28 by Perry Rapp
14 *==============================================================*/
15
16 #include "llstdlib.h"
17 #include "feedback.h"
18 #include "gedcom.h"
19 #include "menuitem.h"
20
21
22 /*********************************************
23 * external/imported variables
24 *********************************************/
25
26 extern STRING qSttlfambrw, qSttl2perbrw, qSttl2fambrw;
27 extern STRING qSttlauxbrw, qSttllstbrw;
28
29 /*********************************************
30 * local types
31 *********************************************/
32
33 struct tag_cmditem {
34 uchar c;
35 BOOLEAN direct; /* (T: command value, F: pointer) */
36 UNION value; /* command value, or pointer to CommandArray */
37 };
38 typedef struct tag_cmditem * CMDITEM;
39
40 struct tag_cmdarray {
41 INT alloc; /* size allocated */
42 INT used; /* size in use */
43 CMDITEM array;
44 };
45
46 /*********************************************
47 * local function prototypes
48 *********************************************/
49
50 /* alphabetical */
51 static void add_menu_item(CMDARRAY cmds, MenuItem * mitem);
52 static CMDARRAY create_cmd_array(INT alloc);
53 static void copy_cmditem(CMDITEM dest, CMDITEM src);
54 static BOOLEAN find_cmd(CMDARRAY cmds, uchar c, INT * pos);
55 static void free_cmds(CMDARRAY cmds);
56 static void get_menu_choice(STRING display, STRING choice, INT max);
57 static void grow_cmd_array(CMDARRAY cmds);
58 static void insert_cmd(CMDARRAY cmds, STRING str, INT cmdnum
59 , STRING display);
60 static INT menuitem_find_cmd(CMDARRAY cmds, STRING cmd);
61
62 /*********************************************
63 * local variables
64 *********************************************/
65
66 static STRING f_current_title=0;
67
68 /*********************************************
69 * local function definitions
70 * body of module
71 *********************************************/
72
73 /*============================
74 * menuset_init - Load menu items into cmd array
75 * Clears menuset and reloads it, so can be called with empty or full menuset
76 * Created: 2001/01/28, Perry Rapp
77 *==========================*/
78 void
menuset_init(MENUSET menuset,STRING title,MenuItem ** MenuItems,MenuItem ** extraItems)79 menuset_init (MENUSET menuset, STRING title, MenuItem ** MenuItems, MenuItem ** extraItems)
80 {
81 INT i;
82 CMDARRAY cmds = create_cmd_array(32);
83 f_current_title = title; /* for use in error messages */
84 menuset_clear(menuset);
85 menuset->Commands = cmds;
86 menuset->items = MenuItems;
87 for (i=0; MenuItems[i]; ++i)
88 add_menu_item(cmds, MenuItems[i]);
89 for (i=0; extraItems[i]; ++i)
90 add_menu_item(cmds, extraItems[i]);
91 f_current_title = 0;
92 }
93 /*============================
94 * menuset_clear - Free memory in menuset
95 * reentrant
96 * Created: 2002/10/24, Perry Rapp
97 *==========================*/
98 void
menuset_clear(MENUSET menuset)99 menuset_clear (MENUSET menuset)
100 {
101 if (menuset->Commands) {
102 free_cmds(menuset->Commands);
103 menuset->Commands = 0;
104 }
105 }
106 /*============================
107 * add_menu_item - add cmd for menu to cmdarray
108 * Title: [IN] title of menu (only used for log msgs)
109 * cmds: [I/O] cmdarray (tree used for command recognition)
110 * mitem: [IN] new menu item to add to cmds
111 * Created: 2002/01/24
112 *==========================*/
113 static void
add_menu_item(CMDARRAY cmds,MenuItem * mitem)114 add_menu_item (CMDARRAY cmds, MenuItem * mitem)
115 {
116 INT i;
117 char display[32];
118
119 /* localize string into current target language */
120 llstrncpy(display, _(mitem->Display), ARRSIZE(display), uu8);
121 if (mitem->LocalizedDisplay)
122 strfree(&mitem->LocalizedDisplay);
123 mitem->LocalizedDisplay = strsave(display);
124 if (mitem->Command == CMD_CHILD_DIRECT0) {
125 /* CMD_CHILD_DIRECT0 is always hooked up to digits */
126 for (i=1; i<=9; i++) {
127 char choice[2];
128 sprintf(choice, "%ld", i);
129 insert_cmd(cmds, choice, CMD_CHILD_DIRECT0+i, display);
130 }
131 } else {
132 char choice[9];
133 if (mitem->Choices)
134 strcpy(choice, mitem->Choices);
135 else
136 get_menu_choice(display, choice, sizeof(choice));
137 /* add to nested menu arrays (stored by choice keys */
138 insert_cmd(cmds, choice, mitem->Command, display);
139 }
140 }
141 /*============================
142 * get_menu_choice -- extract menu key sequence
143 * This must be first characters of display, ending with space
144 * Created: 2001/12/23, Perry Rapp
145 *==========================*/
146 /* This will work now, but it will break if we add arrows, PageUp, ... */
147 static void
get_menu_choice(STRING display,STRING choice,INT max)148 get_menu_choice (STRING display, STRING choice, INT max)
149 {
150 INT i;
151 for (i=0; i<max && display[i] && display[i]!=' ' ; ++i) {
152 choice[i] = display[i];
153 }
154 if (i == max) {
155 msg_error(_("Menu (%s) choice sequence too long: %s"), f_current_title, display);
156 FATAL();
157 }
158 if (display[i] != ' ') {
159 msg_error(_("Menu (%s) item lacked choice sequence: %s"), f_current_title, display);
160 FATAL();
161 }
162 choice[i]=0;
163 }
164 /*============================
165 * create_cmd_array -- create an empty array of commands
166 * Created: 2001/02/01, Perry Rapp
167 *==========================*/
168 static CMDARRAY
create_cmd_array(INT alloc)169 create_cmd_array (INT alloc)
170 {
171 CMDARRAY cmds = (CMDARRAY)stdalloc(sizeof(*cmds));
172 cmds->alloc = alloc;
173 cmds->used = 0;
174 cmds->array = (CMDITEM)stdalloc(alloc * sizeof(cmds->array[0]));
175 return cmds;
176 }
177 /*============================
178 * grow_cmd_array -- grow an array of commands
179 * Created: 2001/02/01, Perry Rapp
180 *==========================*/
181 static void
grow_cmd_array(CMDARRAY cmds)182 grow_cmd_array (CMDARRAY cmds)
183 {
184 INT alloc = cmds->alloc + cmds->alloc/2;
185 CMDITEM old = cmds->array;
186 INT i;
187 cmds->alloc = alloc;
188 cmds->array = (CMDITEM)stdalloc(alloc * sizeof(cmds->array[0]));
189 for (i=0; i<cmds->used; i++)
190 copy_cmditem(&cmds->array[i], &old[i]);
191 stdfree(old);
192 }
193 /*============================
194 * copy_cmditem -- copy a CmdItem_s struct
195 * Created: 2001/02/01, Perry Rapp
196 *==========================*/
197 static void
copy_cmditem(CMDITEM dest,CMDITEM src)198 copy_cmditem (CMDITEM dest, CMDITEM src)
199 {
200 dest->c = src->c;
201 dest->direct = src->direct;
202 dest->value = src->value;
203 }
204 /*============================
205 * find_cmd -- search commands for command by character
206 * Created: 2001/02/01, Perry Rapp
207 *==========================*/
208 static BOOLEAN
find_cmd(CMDARRAY cmds,uchar c,INT * pos)209 find_cmd (CMDARRAY cmds, uchar c, INT * pos)
210 {
211 INT lo=0, hi=cmds->used-1, i;
212 while (lo<=hi) {
213 i=(lo+hi)/2;
214 if (cmds->array[i].c < c)
215 lo=i+1;
216 else if (cmds->array[i].c > c)
217 hi=i-1;
218 else {
219 *pos = i;
220 return TRUE;
221 }
222 }
223 *pos = lo;
224 return FALSE;
225 }
226 /*============================
227 * insert_cmd -- add cmd to array (recursive)
228 * cmds: [I/O] cmd tree or subtree to which we add
229 * str: [IN] remaining part of cmd hotkey sequence
230 * cmdnum: [IN] cmd code to store (eg, CMD_QUIT)
231 * display: [IN] menu item text (for log msgs)
232 * Created: 2001/02/01, Perry Rapp
233 *==========================*/
234 static void
insert_cmd(CMDARRAY cmds,STRING str,INT cmdnum,STRING display)235 insert_cmd (CMDARRAY cmds, STRING str, INT cmdnum, STRING display)
236 {
237 INT len = strlen(str);
238 INT pos;
239 uchar c = str[0];
240 if (find_cmd(cmds, c, &pos)) {
241 if (len==1) {
242 crashlog(_("In menu: %s"), f_current_title);
243 if (cmds->array[pos].direct) {
244 crashlog(_("Duplicate hotkey for item: %s")
245 , display);
246 } else {
247 crashlog(_("Clash with longer hotkey in item: %s")
248 , display);
249
250 }
251 } else {
252 /* multicharacter new cmd */
253 if (cmds->array[pos].direct) {
254 crashlog(_("In menu: %s"), f_current_title);
255 crashlog(_("Clash with shorter hotkey in item: %s")
256 , display);
257 } else {
258 CMDARRAY subarr = (CMDARRAY)cmds->array[pos].value.w;
259 insert_cmd(subarr, &str[1], cmdnum, display);
260 }
261 }
262 } else {
263 INT i;
264 if (cmds->used == cmds->alloc)
265 grow_cmd_array(cmds);
266 /* not found */
267 for (i=cmds->used; i>pos; i--)
268 copy_cmditem(&cmds->array[i], &cmds->array[i-1]);
269 cmds->array[pos].c = c;
270 if (len==1) {
271 cmds->array[pos].direct = TRUE;
272 cmds->array[pos].value.i = cmdnum;
273 } else {
274 /* multicharacter new cmd */
275 CMDARRAY newcmds = create_cmd_array(8);
276 cmds->array[pos].direct = FALSE;
277 cmds->array[pos].value.w = newcmds;
278 insert_cmd(newcmds, &str[1], cmdnum, display);
279 }
280 cmds->used++;
281 }
282 }
283 /*============================
284 * free_cmds -- free menu arrays
285 * Created: 2001/02/01, Perry Rapp
286 *==========================*/
287 static void
free_cmds(CMDARRAY cmds)288 free_cmds (CMDARRAY cmds)
289 {
290 INT i;
291 for (i=0; i<cmds->used; i++) {
292 if (!cmds->array[i].direct) {
293 CMDARRAY subarr = (CMDARRAY)cmds->array[i].value.w;
294 free_cmds(subarr);
295 }
296 }
297 stdfree(cmds->array);
298 stdfree(cmds);
299 }
300 /*============================
301 * menuitem_check_cmd -- check input string & return cmd
302 * Created: 2001/02/01, Perry Rapp
303 *==========================*/
304 INT
menuset_check_cmd(MENUSET menuset,STRING str)305 menuset_check_cmd (MENUSET menuset, STRING str)
306 {
307 CMDARRAY cmds = menuset->Commands;
308 if (*str == '*') return CMD_MENU_TOGGLE;
309 return menuitem_find_cmd(cmds, str);
310 }
311 /*============================
312 * menuitem_find_cmd -- search cmd array for cmd
313 * recursive
314 * Created: 2001/02/01, Perry Rapp
315 *==========================*/
316 static INT
menuitem_find_cmd(CMDARRAY cmds,STRING str)317 menuitem_find_cmd (CMDARRAY cmds, STRING str)
318 {
319 INT pos;
320 if (!find_cmd(cmds, *str, &pos))
321 return CMD_NONE;
322 if (cmds->array[pos].direct) {
323 INT cmd = cmds->array[pos].value.i;
324 return cmd;
325 } else {
326 CMDARRAY subarr = (CMDARRAY)cmds->array[pos].value.w;
327 if (!str[1])
328 return CMD_PARTIAL;
329 return menuitem_find_cmd(subarr, &str[1]);
330 }
331 }
332 /*============================
333 * enuset_get_items -- return array of items
334 * Created: 2002/10/28, Perry Rapp
335 *==========================*/
336 MenuItem **
menuset_get_items(MENUSET menuset)337 menuset_get_items (MENUSET menuset)
338 {
339 return menuset->items;
340 }
341