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