1 /*
2  * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net>
3  * Copyright 2005 James Bursa <bursa@users.sourceforge.net>
4  * Copyright 2003 John M Bell <jmb202@ecs.soton.ac.uk>
5  * Copyright 2005 Richard Wilson <info@tinct.net>
6  *
7  * This file is part of NetSurf, http://www.netsurf-browser.org/
8  *
9  * NetSurf is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; version 2 of the License.
12  *
13  * NetSurf is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 /**
23  * \file
24  * Menu creation and handling implementation.
25  */
26 
27 #include <ctype.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <stddef.h>
31 #include "oslib/os.h"
32 #include "oslib/osbyte.h"
33 #include "oslib/osgbpb.h"
34 #include "oslib/territory.h"
35 #include "oslib/wimp.h"
36 
37 #include "utils/log.h"
38 #include "utils/messages.h"
39 #include "utils/utf8.h"
40 #include "desktop/cookie_manager.h"
41 
42 #include "riscos/dialog.h"
43 #include "riscos/configure.h"
44 #include "riscos/cookies.h"
45 #include "riscos/gui.h"
46 #include "riscos/global_history.h"
47 #include "riscos/help.h"
48 #include "riscos/hotlist.h"
49 #include "riscos/menus.h"
50 #include "utils/nsoption.h"
51 #include "riscos/save.h"
52 #include "riscos/tinct.h"
53 #include "riscos/toolbar.h"
54 #include "riscos/url_suggest.h"
55 #include "riscos/wimp.h"
56 #include "riscos/wimp_event.h"
57 #include "riscos/ucstables.h"
58 
59 struct menu_definition_entry {
60 	menu_action action;			/**< menu action */
61 	wimp_menu_entry *menu_entry;		/**< corresponding menu entry */
62 	const char *entry_key;			/**< Messages key for entry text */
63 	struct menu_definition_entry *next;	/**< next menu entry */
64 };
65 
66 struct menu_definition {
67 	wimp_menu *menu;			/**< corresponding menu */
68 	const char *title_key;			/**< Messages key for title text */
69 	int current_encoding;			/**< Identifier for current text encoding of menu text (as per OS_Byte,71,127) */
70 	struct menu_definition_entry *entries;	/**< menu entries */
71 	struct menu_definition *next;		/**< next menu */
72 };
73 
74 static void ro_gui_menu_closed(void);
75 static void ro_gui_menu_define_menu_add(struct menu_definition *definition,
76 		const struct ns_menu *menu, int depth,
77 		wimp_menu_entry *parent_entry,
78 		int first, int last, const char *prefix, int prefix_length);
79 static struct menu_definition *ro_gui_menu_find_menu(wimp_menu *menu);
80 static struct menu_definition_entry *ro_gui_menu_find_entry(wimp_menu *menu,
81 		menu_action action);
82 static menu_action ro_gui_menu_find_action(wimp_menu *menu,
83 		wimp_menu_entry *menu_entry);
84 static int ro_gui_menu_get_checksum(void);
85 static bool ro_gui_menu_translate(struct menu_definition *menu);
86 
87 
88 /* default menu item flags */
89 #define DEFAULT_FLAGS (wimp_ICON_TEXT | wimp_ICON_FILLED | \
90 		(wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT) | \
91 		(wimp_COLOUR_WHITE << wimp_ICON_BG_COLOUR_SHIFT))
92 
93 /** The currently defined menus to perform actions for */
94 static struct menu_definition *ro_gui_menu_definitions;
95 /** The current menu being worked with (may not be open) */
96 wimp_menu *current_menu;
97 /** Whether a menu is currently open */
98 bool current_menu_open = false;
99 /** Window that owns the current menu */
100 wimp_w current_menu_window;
101 /** Icon that owns the current menu (only valid for popup menus) */
102 static wimp_i current_menu_icon;
103 /** The available menus */
104 wimp_menu *image_quality_menu, *proxy_type_menu, *languages_menu;
105 
106 /* the values given in PRM 3-157 for how to check menus/windows are
107  * incorrect so we use a hack of checking if the sub-menu has bit 0
108  * set which is undocumented but true of window handles on
109  * all target OS versions */
110 #define IS_MENU(menu) !((int)(menu) & 1)
111 
112 /**
113  * Create menu structures.
114  */
115 
ro_gui_menu_init(void)116 void ro_gui_menu_init(void)
117 {
118 	/* image quality menu */
119 	static const struct ns_menu images_definition = {
120 		"Display", {
121 			{ "ImgStyle0", NO_ACTION, 0 },
122 			{ "ImgStyle1", NO_ACTION, 0 },
123 			{ "ImgStyle2", NO_ACTION, 0 },
124 			{ "ImgStyle3", NO_ACTION, 0 },
125 			{NULL, 0, 0}
126 		}
127 	};
128 	image_quality_menu = ro_gui_menu_define_menu(&images_definition);
129 
130 	/* proxy menu */
131 	static const struct ns_menu proxy_type_definition = {
132 		"ProxyType", {
133 			{ "ProxyNone", NO_ACTION, 0 },
134 			{ "ProxyNoAuth", NO_ACTION, 0 },
135 			{ "ProxyBasic", NO_ACTION, 0 },
136 			{ "ProxyNTLM", NO_ACTION, 0 },
137 			{NULL, 0, 0}
138 		}
139 	};
140 	proxy_type_menu = ro_gui_menu_define_menu(&proxy_type_definition);
141 
142 	/* special case menus */
143 	ro_gui_url_suggest_init();
144 
145 	/* Note: This table *must* be kept in sync with the LangNames file */
146 	static const struct ns_menu lang_definition = {
147 		"Languages", {
148 			{ "lang_af", NO_ACTION, 0 },
149 			{ "lang_bm", NO_ACTION, 0 },
150 			{ "lang_ca", NO_ACTION, 0 },
151 			{ "lang_cs", NO_ACTION, 0 },
152 			{ "lang_cy", NO_ACTION, 0 },
153 			{ "lang_da", NO_ACTION, 0 },
154 			{ "lang_de", NO_ACTION, 0 },
155 			{ "lang_en", NO_ACTION, 0 },
156 			{ "lang_es", NO_ACTION, 0 },
157 			{ "lang_et", NO_ACTION, 0 },
158 			{ "lang_eu", NO_ACTION, 0 },
159 			{ "lang_ff", NO_ACTION, 0 },
160 			{ "lang_fi", NO_ACTION, 0 },
161 			{ "lang_fr", NO_ACTION, 0 },
162 			{ "lang_ga", NO_ACTION, 0 },
163 			{ "lang_gl", NO_ACTION, 0 },
164 			{ "lang_ha", NO_ACTION, 0 },
165 			{ "lang_hr", NO_ACTION, 0 },
166 			{ "lang_hu", NO_ACTION, 0 },
167 			{ "lang_id", NO_ACTION, 0 },
168 			{ "lang_is", NO_ACTION, 0 },
169 			{ "lang_it", NO_ACTION, 0 },
170 			{ "lang_lt", NO_ACTION, 0 },
171 			{ "lang_lv", NO_ACTION, 0 },
172 			{ "lang_ms", NO_ACTION, 0 },
173 			{ "lang_mt", NO_ACTION, 0 },
174 			{ "lang_nl", NO_ACTION, 0 },
175 			{ "lang_no", NO_ACTION, 0 },
176 			{ "lang_pl", NO_ACTION, 0 },
177 			{ "lang_pt", NO_ACTION, 0 },
178 			{ "lang_rn", NO_ACTION, 0 },
179 			{ "lang_ro", NO_ACTION, 0 },
180 			{ "lang_rw", NO_ACTION, 0 },
181 			{ "lang_sk", NO_ACTION, 0 },
182 			{ "lang_sl", NO_ACTION, 0 },
183 			{ "lang_so", NO_ACTION, 0 },
184 			{ "lang_sq", NO_ACTION, 0 },
185 			{ "lang_sr", NO_ACTION, 0 },
186 			{ "lang_sv", NO_ACTION, 0 },
187 			{ "lang_sw", NO_ACTION, 0 },
188 			{ "lang_tr", NO_ACTION, 0 },
189 			{ "lang_uz", NO_ACTION, 0 },
190 			{ "lang_vi", NO_ACTION, 0 },
191 			{ "lang_wo", NO_ACTION, 0 },
192 			{ "lang_xs", NO_ACTION, 0 },
193 			{ "lang_yo", NO_ACTION, 0 },
194 			{ "lang_zu", NO_ACTION, 0 },
195 			{ NULL, 0, 0 }
196 		}
197 	};
198 	languages_menu = ro_gui_menu_define_menu(&lang_definition);
199 }
200 
201 
202 /**
203  * Display a menu.
204  *
205  * \param *menu			Pointer to the menu to be displayed.
206  * \param x			The x position.
207  * \param y			The y position.
208  * \param w			The window that the menu belongs to.
209  */
210 
ro_gui_menu_create(wimp_menu * menu,int x,int y,wimp_w w)211 void ro_gui_menu_create(wimp_menu *menu, int x, int y, wimp_w w)
212 {
213 	os_error *error;
214 	struct menu_definition *definition;
215 
216 	/* translate menu, if necessary (this returns quickly
217 	 * if there's nothing to be done) */
218 	definition = ro_gui_menu_find_menu(menu);
219 	if (definition) {
220 		if (!ro_gui_menu_translate(definition)) {
221 			ro_warn_user("NoMemory", 0);
222 			return;
223 		}
224 	}
225 
226 	/* store the menu characteristics */
227 	current_menu = menu;
228 	current_menu_window = w;
229 	current_menu_icon = wimp_ICON_WINDOW;
230 
231 	/* create the menu */
232 	current_menu_open = true;
233 	error = xwimp_create_menu(menu, x - 64, y);
234 	if (error) {
235 		NSLOG(netsurf, INFO, "xwimp_create_menu: 0x%x: %s",
236 		      error->errnum, error->errmess);
237 		ro_warn_user("MenuError", error->errmess);
238 		ro_gui_menu_closed();
239 	}
240 }
241 
242 
243 /**
244  * Display a pop-up menu next to the specified icon.
245  *
246  * \param  menu  menu to open
247  * \param  w	 window handle
248  * \param  i	 icon handle
249  */
250 
ro_gui_popup_menu(wimp_menu * menu,wimp_w w,wimp_i i)251 void ro_gui_popup_menu(wimp_menu *menu, wimp_w w, wimp_i i)
252 {
253 	wimp_window_state state;
254 	wimp_icon_state icon_state;
255 	os_error *error;
256 
257 	state.w = w;
258 	icon_state.w = w;
259 	icon_state.i = i;
260 	error = xwimp_get_window_state(&state);
261 	if (error) {
262 		NSLOG(netsurf, INFO, "xwimp_get_window_state: 0x%x: %s",
263 		      error->errnum, error->errmess);
264 		ro_warn_user("MenuError", error->errmess);
265 		return;
266 	}
267 
268 	error = xwimp_get_icon_state(&icon_state);
269 	if (error) {
270 		NSLOG(netsurf, INFO, "xwimp_get_icon_state: 0x%x: %s",
271 		      error->errnum, error->errmess);
272 		ro_warn_user("MenuError", error->errmess);
273 		return;
274 	}
275 
276 	ro_gui_menu_create(menu,
277 			state.visible.x0 + icon_state.icon.extent.x1 + 64,
278 			state.visible.y1 + icon_state.icon.extent.y1 -
279 			state.yscroll, w);
280 	current_menu_icon = i;
281 }
282 
283 
284 /**
285  * Forcibly close any menu or transient dialogue box that is currently open.
286  */
287 
ro_gui_menu_destroy(void)288 void ro_gui_menu_destroy(void)
289 {
290 	os_error *error;
291 
292 	if (current_menu == NULL)
293 		return;
294 
295 	error = xwimp_create_menu(wimp_CLOSE_MENU, 0, 0);
296 	if (error) {
297 		NSLOG(netsurf, INFO, "xwimp_create_menu: 0x%x: %s",
298 		      error->errnum, error->errmess);
299 		ro_warn_user("MenuError", error->errmess);
300 	}
301 
302 	ro_gui_menu_closed();
303 }
304 
305 
306 /**
307  * Allow the current menu window to change, if the window is deleted and
308  * recreated while a menu is active on an Adjust-click.
309  *
310  * \param from			The original window handle.
311  * \param to			The new replacement window handle.
312  */
313 
ro_gui_menu_window_changed(wimp_w from,wimp_w to)314 void ro_gui_menu_window_changed(wimp_w from, wimp_w to)
315 {
316 
317 	if (from == current_menu_window)
318 		current_menu_window = to;
319 }
320 
321 
322 /**
323  * Handle menu selection.
324  */
325 
ro_gui_menu_selection(wimp_selection * selection)326 void ro_gui_menu_selection(wimp_selection *selection)
327 {
328 	int			i; //, j;
329 	wimp_menu_entry		*menu_entry;
330 	menu_action		action;
331 	wimp_pointer		pointer;
332 	os_error		*error;
333 	int			previous_menu_icon = current_menu_icon;
334 
335 	/* if we are using gui_multitask then menu selection events
336 	 * may be delivered after the menu has been closed. As such,
337 	 * we simply ignore these events. */
338 	if (!current_menu)
339 		return;
340 
341 	assert(current_menu_window);
342 
343 	/* get the menu entry and associated action and definition */
344 	menu_entry = &current_menu->entries[selection->items[0]];
345 	for (i = 1; selection->items[i] != -1; i++)
346 		menu_entry = &menu_entry->sub_menu->
347 				entries[selection->items[i]];
348 	action = ro_gui_menu_find_action(current_menu, menu_entry);
349 
350 	/* Deal with the menu action.  If this manages to re-prepare the
351 	 * menu for re-opening, we test for and act on Adjust clicks.
352 	 */
353 
354 	if (!ro_gui_wimp_event_menu_selection(current_menu_window,
355 			current_menu_icon, current_menu, selection, action))
356 		return;
357 
358 	/* re-open the menu for Adjust clicks */
359 	error = xwimp_get_pointer_info(&pointer);
360 	if (error) {
361 		NSLOG(netsurf, INFO, "xwimp_get_pointer_info: 0x%x: %s",
362 		      error->errnum, error->errmess);
363 		ro_warn_user("WimpError", error->errmess);
364 		ro_gui_menu_closed();
365 		return;
366 	}
367 
368 	if (pointer.buttons != wimp_CLICK_ADJUST) {
369 		ro_gui_menu_closed();
370 		return;
371 	}
372 
373 	ro_gui_menu_create(current_menu, 0, 0, current_menu_window);
374 	current_menu_icon = previous_menu_icon;
375 }
376 
377 
378 /**
379  * Handle Message_MenuWarning.
380  */
ro_gui_menu_warning(wimp_message_menu_warning * warning)381 void ro_gui_menu_warning(wimp_message_menu_warning *warning)
382 {
383 	int i;
384 	menu_action action;
385 	wimp_menu_entry *menu_entry;
386 	os_error *error;
387 
388 	assert(current_menu);
389 	assert(current_menu_window);
390 
391 	/* get the sub-menu of the warning */
392 	if (warning->selection.items[0] == -1)
393 		return;
394 	menu_entry = &current_menu->entries[warning->selection.items[0]];
395 	for (i = 1; warning->selection.items[i] != -1; i++)
396 		menu_entry = &menu_entry->sub_menu->
397 				entries[warning->selection.items[i]];
398 	action = ro_gui_menu_find_action(current_menu, menu_entry);
399 
400 	/* Process the warning via Wimp_Event, then register the resulting
401 	 * submenu with the module.
402 	 */
403 
404 	ro_gui_wimp_event_submenu_warning(current_menu_window,
405 			current_menu_icon, current_menu, &(warning->selection),
406 			action);
407 
408 	if (IS_MENU(menu_entry->sub_menu)) {
409 		ro_gui_wimp_event_register_submenu((wimp_w) 0);
410 	} else {
411 		ro_gui_wimp_event_register_submenu((wimp_w)
412 				menu_entry->sub_menu);
413 
414 		/* If this is a dialogue box, remove the close and back icons.
415 		 */
416 
417 		ro_gui_wimp_update_window_furniture((wimp_w)
418 				menu_entry->sub_menu,
419 				wimp_WINDOW_CLOSE_ICON | wimp_WINDOW_BACK_ICON,
420 				0);
421 	}
422 
423 	/* open the sub-menu */
424 
425 	error = xwimp_create_sub_menu(menu_entry->sub_menu,
426 			warning->pos.x, warning->pos.y);
427 	if (error) {
428 		NSLOG(netsurf, INFO, "xwimp_create_sub_menu: 0x%x: %s",
429 		      error->errnum, error->errmess);
430 		ro_warn_user("MenuError", error->errmess);
431 	}
432 }
433 
434 
435 /**
436  * Handle Message_MenusDeleted, removing our current record of an open menu
437  * if it matches the deleted menu handle.
438  *
439  * \param *deleted		The message block.
440  */
441 
ro_gui_menu_message_deleted(wimp_message_menus_deleted * deleted)442 void ro_gui_menu_message_deleted(wimp_message_menus_deleted *deleted)
443 {
444 	if (deleted != NULL && deleted->menu == current_menu)
445 		ro_gui_menu_closed();
446 }
447 
448 
449 /**
450  * Clean up after a menu has been closed, or forcibly close an open menu.
451   */
452 
ro_gui_menu_closed(void)453 static void ro_gui_menu_closed(void)
454 {
455 	if (current_menu != NULL)
456 		ro_gui_wimp_event_menus_closed(current_menu_window,
457 				current_menu_icon, current_menu);
458 
459 	current_menu = NULL;
460 	current_menu_window = NULL;
461 	current_menu_icon = 0;
462 	current_menu_open = false;
463 }
464 
465 
466 /**
467  * Update the current menu by sending it a Menu Prepare event through wimp_event
468  * and then reopening it if the contents has changed.
469  *
470  * \param *menu		The menu to refresh: if 0, the current menu will be
471  *			refreshed regardless, otherwise it will be refreshed
472  *			only if it matches the supplied handle.
473  */
474 
ro_gui_menu_refresh(wimp_menu * menu)475 void ro_gui_menu_refresh(wimp_menu *menu)
476 {
477 	int checksum = 0;
478 
479 	if (!current_menu_open)
480 		return;
481 
482 	checksum = ro_gui_menu_get_checksum();
483 
484 	if (!ro_gui_wimp_event_prepare_menu(current_menu_window,
485 			current_menu_icon, current_menu))
486 		return;
487 
488 	/* \TODO -- Call the menu's event handler here. */
489 
490 	if (checksum != ro_gui_menu_get_checksum()) {
491 		os_error *error;
492 		error = xwimp_create_menu(current_menu, 0, 0);
493 		if (error) {
494 			NSLOG(netsurf, INFO, "xwimp_create_menu: 0x%x: %s",
495 			      error->errnum, error->errmess);
496 			ro_warn_user("MenuError", error->errmess);
497 		}
498 	}
499 }
500 
501 
502 
503 
504 /**
505  * Creates a wimp_menu and adds it to the list to handle actions for.
506  *
507  * \param  menu The data to create the menu with
508  * \return The menu created, or NULL on failure
509  */
ro_gui_menu_define_menu(const struct ns_menu * menu)510 wimp_menu *ro_gui_menu_define_menu(const struct ns_menu *menu)
511 {
512 	struct menu_definition *definition;
513 	int entry;
514 
515 	definition = calloc(sizeof(struct menu_definition), 1);
516 	if (!definition) {
517 		die("No memory to create menu definition.");
518 		return NULL; /* For the benefit of scan-build */
519 	}
520 
521 	/* link in the menu to our list */
522 	definition->next = ro_gui_menu_definitions;
523 	ro_gui_menu_definitions = definition;
524 
525 	/* count number of menu entries */
526 	for (entry = 0; menu->entries[entry].text; entry++)
527 		/* do nothing */;
528 
529 	/* create our definitions */
530 	ro_gui_menu_define_menu_add(definition, menu, 0, NULL,
531 			0, entry, NULL, 0);
532 
533 	/* and translate menu into current encoding */
534 	if (!ro_gui_menu_translate(definition))
535 		die("No memory to translate menu.");
536 
537 	return definition->menu;
538 }
539 
540 /**
541  * Create a wimp menu tree from ns_menu data.
542  * This function does *not* deal with the menu textual content - it simply
543  * creates and populates the appropriate structures. Textual content is
544  * generated by ro_gui_menu_translate_menu()
545  *
546  * \param definition  Top level menu definition
547  * \param menu  Menu declaration data
548  * \param depth  Depth of menu we're currently building
549  * \param parent_entry  Entry in parent menu, or NULL if root menu
550  * \param first  First index in declaration data that is used by this menu
551  * \param last Last index in declaration data that is used by this menu
552  * \param prefix  Prefix pf menu declaration string already seen
553  * \param prefix_length  Length of prefix
554  */
ro_gui_menu_define_menu_add(struct menu_definition * definition,const struct ns_menu * menu,int depth,wimp_menu_entry * parent_entry,int first,int last,const char * prefix,int prefix_length)555 void ro_gui_menu_define_menu_add(struct menu_definition *definition,
556 		const struct ns_menu *menu, int depth,
557 		wimp_menu_entry *parent_entry, int first, int last,
558 		const char *prefix, int prefix_length)
559 {
560 	int entry;
561 	int entries = 0;
562 	int matches[last - first + 1];
563 	const char *text, *menu_text;
564 	wimp_menu *new_menu;
565 	struct menu_definition_entry *definition_entry;
566 
567 	/* step 1: store the matches for depth and subset string */
568 	for (entry = first; entry < last; entry++) {
569 		const char *match;
570 		int cur_depth = 0;
571 		match = menu->entries[entry].text;
572 
573 		/* skip specials at start of string */
574 		while (!isalnum(*match))
575 			match++;
576 
577 		/* attempt prefix match */
578 		if ((prefix) && (strncmp(match, prefix, prefix_length)))
579 			continue;
580 
581 		/* Find depth of this entry */
582 		while (*match)
583 			if (*match++ == '.')
584 				cur_depth++;
585 
586 		if (depth == cur_depth)
587 			matches[entries++] = entry;
588 	}
589 	matches[entries] = last;
590 
591 	/* no entries, so exit */
592 	if (entries == 0)
593 		return;
594 
595 	/* step 2: build and link the menu. we must use realloc to stop
596 	 * our memory fragmenting so we can test for sub-menus easily */
597 	new_menu = (wimp_menu *)malloc(wimp_SIZEOF_MENU(entries));
598 	if (!new_menu)
599 		die("No memory to create menu.");
600 
601 	if (parent_entry) {
602 		/* Fix up sub menu pointer */
603 		parent_entry->sub_menu = new_menu;
604 	} else {
605 		/* Root menu => fill in definition struct */
606 		definition->title_key = menu->title;
607 		definition->current_encoding = 0;
608 		definition->menu = new_menu;
609 	}
610 
611 	/* this is fixed up in ro_gui_menu_translate() */
612 	new_menu->title_data.indirected_text.text = NULL;
613 
614 	/* fill in menu flags */
615 	ro_gui_menu_init_structure(new_menu, entries);
616 
617 	/* and then create the entries */
618 	for (entry = 0; entry < entries; entry++) {
619 		/* add the entry */
620 		int id = matches[entry];
621 
622 		text = menu->entries[id].text;
623 
624 		/* fill in menu flags from specials at start of string */
625 		new_menu->entries[entry].menu_flags = 0;
626 		while (!isalnum(*text)) {
627 			if (*text == '_')
628 				new_menu->entries[entry].menu_flags |=
629 							wimp_MENU_SEPARATE;
630 			text++;
631 		}
632 
633 		/* get messages key for menu entry */
634 		menu_text = strrchr(text, '.');
635 		if (!menu_text)
636 			/* no '.' => top-level entry */
637 			menu_text = text;
638 		else
639 			menu_text++; /* and move past the '.' */
640 
641 		/* fill in submenu data */
642 		if (menu->entries[id].sub_window)
643 			new_menu->entries[entry].sub_menu =
644 				(wimp_menu *) (*menu->entries[id].sub_window);
645 
646 		/* this is fixed up in ro_gui_menu_translate() */
647 		new_menu->entries[entry].data.indirected_text.text = NULL;
648 
649 		/* create definition entry */
650 		definition_entry =
651 			malloc(sizeof(struct menu_definition_entry));
652 		if (!definition_entry)
653 			die("Unable to create menu definition entry");
654 		definition_entry->action = menu->entries[id].action;
655 		definition_entry->menu_entry = &new_menu->entries[entry];
656 		definition_entry->entry_key = menu_text;
657 		definition_entry->next = definition->entries;
658 		definition->entries = definition_entry;
659 
660 		/* recurse */
661 		if (new_menu->entries[entry].sub_menu == wimp_NO_SUB_MENU) {
662 			ro_gui_menu_define_menu_add(definition, menu,
663 					depth + 1, &new_menu->entries[entry],
664 					matches[entry], matches[entry + 1],
665 					text, strlen(text));
666 		}
667 
668 		/* give menu warnings */
669 		if (new_menu->entries[entry].sub_menu != wimp_NO_SUB_MENU)
670 			new_menu->entries[entry].menu_flags |=
671 						wimp_MENU_GIVE_WARNING;
672 	}
673 	new_menu->entries[0].menu_flags |= wimp_MENU_TITLE_INDIRECTED;
674 	new_menu->entries[entries - 1].menu_flags |= wimp_MENU_LAST;
675 }
676 
677 
678 /**
679  * Initialise the basic state of a menu structure so all entries are
680  * indirected text with no flags, no submenu.
681  */
ro_gui_menu_init_structure(wimp_menu * menu,int entries)682 void ro_gui_menu_init_structure(wimp_menu *menu, int entries)
683 {
684   	int i;
685 
686 	menu->title_fg = wimp_COLOUR_BLACK;
687 	menu->title_bg = wimp_COLOUR_LIGHT_GREY;
688 	menu->work_fg = wimp_COLOUR_BLACK;
689 	menu->work_bg = wimp_COLOUR_WHITE;
690 	menu->width = 200;
691 	menu->height = wimp_MENU_ITEM_HEIGHT;
692 	menu->gap = wimp_MENU_ITEM_GAP;
693 
694 	for (i = 0; i < entries; i++) {
695 		menu->entries[i].menu_flags = 0;
696 		menu->entries[i].sub_menu = wimp_NO_SUB_MENU;
697 		menu->entries[i].icon_flags =
698 				DEFAULT_FLAGS | wimp_ICON_INDIRECTED;
699 		menu->entries[i].data.indirected_text.validation =
700 				(char *)-1;
701 	}
702 	menu->entries[0].menu_flags |= wimp_MENU_TITLE_INDIRECTED;
703 	menu->entries[i - 1].menu_flags |= wimp_MENU_LAST;
704 }
705 
706 
707 /**
708  * Finds the menu_definition corresponding to a wimp_menu.
709  *
710  * \param menu  the menu to find the definition for
711  * \return the associated definition, or NULL if one could not be found
712  */
ro_gui_menu_find_menu(wimp_menu * menu)713 struct menu_definition *ro_gui_menu_find_menu(wimp_menu *menu)
714 {
715 	struct menu_definition *definition;
716 
717 	if (!menu)
718 		return NULL;
719 
720 	for (definition = ro_gui_menu_definitions; definition;
721 			definition = definition->next)
722 		if (definition->menu == menu)
723 			return definition;
724 	return NULL;
725 }
726 
727 
728 /**
729  * Finds the key associated with a menu entry translation.
730  *
731  * \param menu  the menu to search
732  * \param translated  the translated text
733  * \return the original message key, or NULL if one could not be found
734  */
ro_gui_menu_find_menu_entry_key(wimp_menu * menu,const char * translated)735 const char *ro_gui_menu_find_menu_entry_key(wimp_menu *menu,
736 		const char *translated)
737 {
738 	struct menu_definition_entry *entry;
739 	struct menu_definition *definition = ro_gui_menu_find_menu(menu);
740 
741 	if (!definition)
742 		return NULL;
743 
744 	for (entry = definition->entries; entry; entry = entry->next)
745 		if (!strcmp(entry->menu_entry->data.indirected_text.text, translated))
746 			return entry->entry_key;
747 	return NULL;
748 }
749 
750 
751 /**
752  * Finds the menu_definition_entry corresponding to an action for a wimp_menu.
753  *
754  * \param menu	  the menu to search for an action within
755  * \param action  the action to find
756  * \return the associated menu entry, or NULL if one could not be found
757  */
ro_gui_menu_find_entry(wimp_menu * menu,menu_action action)758 struct menu_definition_entry *ro_gui_menu_find_entry(wimp_menu *menu,
759 		menu_action action)
760 {
761 	struct menu_definition_entry *entry;
762 	struct menu_definition *definition = ro_gui_menu_find_menu(menu);
763 
764 	if (!definition)
765 		return NULL;
766 
767 	for (entry = definition->entries; entry; entry = entry->next)
768 		if (entry->action == action)
769 			return entry;
770 	return NULL;
771 }
772 
773 
774 /**
775  * Finds the action corresponding to a wimp_menu_entry for a wimp_menu.
776  *
777  * \param menu	      the menu to search for an action within
778  * \param menu_entry  the menu_entry to find
779  * \return the associated action, or 0 if one could not be found
780  */
ro_gui_menu_find_action(wimp_menu * menu,wimp_menu_entry * menu_entry)781 menu_action ro_gui_menu_find_action(wimp_menu *menu, wimp_menu_entry *menu_entry)
782 {
783 	struct menu_definition_entry *entry;
784 	struct menu_definition *definition = ro_gui_menu_find_menu(menu);
785 
786 	if (!definition)
787 		return NO_ACTION;
788 
789 	for (entry = definition->entries; entry; entry = entry->next) {
790 		if (entry->menu_entry == menu_entry)
791 			return entry->action;
792 	}
793 	return NO_ACTION;
794 }
795 
796 
797 /**
798  * Sets an action within a menu as having a specific ticked status.
799  *
800  * \param menu	  the menu containing the action
801  * \param action  the action to tick/untick
802  * \param shaded  whether to set the item as shaded
803  */
ro_gui_menu_set_entry_shaded(wimp_menu * menu,menu_action action,bool shaded)804 void ro_gui_menu_set_entry_shaded(wimp_menu *menu, menu_action action,
805 		bool shaded)
806 {
807 	struct menu_definition_entry *entry;
808 	struct menu_definition *definition = ro_gui_menu_find_menu(menu);
809 
810 	if (!definition)
811 		return;
812 
813 	/* we can't use find_entry as multiple actions may appear in one menu */
814 	for (entry = definition->entries; entry; entry = entry->next)
815 		if (entry->action == action) {
816 			if (shaded)
817 				entry->menu_entry->icon_flags |= wimp_ICON_SHADED;
818 			else
819 				entry->menu_entry->icon_flags &= ~wimp_ICON_SHADED;
820 		}
821 }
822 
823 
824 /**
825  * Sets an action within a menu as having a specific ticked status.
826  *
827  * \param menu	  the menu containing the action
828  * \param action  the action to tick/untick
829  * \param ticked  whether to set the item as ticked
830  */
ro_gui_menu_set_entry_ticked(wimp_menu * menu,menu_action action,bool ticked)831 void ro_gui_menu_set_entry_ticked(wimp_menu *menu, menu_action action,
832 		bool ticked)
833 {
834 	struct menu_definition_entry *entry =
835 			ro_gui_menu_find_entry(menu, action);
836 	if (entry) {
837 		if (ticked)
838 			entry->menu_entry->menu_flags |= wimp_MENU_TICKED;
839 		else
840 			entry->menu_entry->menu_flags &= ~wimp_MENU_TICKED;
841 	}
842 }
843 
844 
845 /**
846  * Calculates a simple checksum for the current menu state
847  */
ro_gui_menu_get_checksum(void)848 int ro_gui_menu_get_checksum(void)
849 {
850 	wimp_selection menu_tree;
851 	int i = 0, j, checksum = 0;
852 	os_error *error;
853 	wimp_menu *menu;
854 
855 	if (!current_menu_open)
856 		return 0;
857 
858 	error = xwimp_get_menu_state((wimp_menu_state_flags)0,
859 			&menu_tree, 0, 0);
860 	if (error) {
861 		NSLOG(netsurf, INFO, "xwimp_get_menu_state: 0x%x: %s",
862 		      error->errnum, error->errmess);
863 		ro_warn_user("MenuError", error->errmess);
864 		return 0;
865 	}
866 
867 	menu = current_menu;
868 	do {
869 		j = 0;
870 		do {
871 			if (menu->entries[j].icon_flags & wimp_ICON_SHADED)
872 				checksum ^= (1 << (i + j * 2));
873 			if (menu->entries[j].menu_flags & wimp_MENU_TICKED)
874 				checksum ^= (2 << (i + j * 2));
875 		} while (!(menu->entries[j++].menu_flags & wimp_MENU_LAST));
876 
877 		j = menu_tree.items[i++];
878 		if (j != -1) {
879 			menu = menu->entries[j].sub_menu;
880 			if ((!menu) || (menu == wimp_NO_SUB_MENU) || (!IS_MENU(menu)))
881 				break;
882 		}
883 	} while (j != -1);
884 
885 	return checksum;
886 }
887 
888 /**
889  * Translate a menu's textual content into the system local encoding
890  *
891  * \param menu  The menu to translate
892  * \return false if out of memory, true otherwise
893  */
ro_gui_menu_translate(struct menu_definition * menu)894 bool ro_gui_menu_translate(struct menu_definition *menu)
895 {
896 	os_error *error;
897 	int alphabet;
898 	struct menu_definition_entry *entry;
899 	char *translated;
900 	nserror err;
901 
902 	/* read current alphabet */
903 	error = xosbyte1(osbyte_ALPHABET_NUMBER, 127, 0, &alphabet);
904 	if (error) {
905 		NSLOG(netsurf, INFO, "failed reading alphabet: 0x%x: %s",
906 		      error->errnum, error->errmess);
907 		/* assume Latin1 */
908 		alphabet = territory_ALPHABET_LATIN1;
909 	}
910 
911 	if (menu->current_encoding == alphabet)
912 		/* menu text is already in the correct encoding */
913 		return true;
914 
915 	/* translate root menu title text */
916 	free(menu->menu->title_data.indirected_text.text);
917 	err = utf8_to_local_encoding(messages_get(menu->title_key),
918 			0, &translated);
919 	if (err != NSERROR_OK) {
920 		assert(err != NSERROR_BAD_ENCODING);
921 		NSLOG(netsurf, INFO, "utf8_to_enc failed");
922 		return false;
923 	}
924 
925 	/* and fill in WIMP menu field */
926 	menu->menu->title_data.indirected_text.text = translated;
927 
928 	/* now the menu entries */
929 	for (entry = menu->entries; entry; entry = entry->next) {
930 		wimp_menu *submenu = entry->menu_entry->sub_menu;
931 
932 		/* tranlate menu entry text */
933 		free(entry->menu_entry->data.indirected_text.text);
934 		err = utf8_to_local_encoding(messages_get(entry->entry_key),
935 				0, &translated);
936 		if (err != NSERROR_OK) {
937 			assert(err != NSERROR_BAD_ENCODING);
938 			NSLOG(netsurf, INFO, "utf8_to_enc failed");
939 			return false;
940 		}
941 
942 		/* fill in WIMP menu fields */
943 		entry->menu_entry->data.indirected_text.text = translated;
944 		entry->menu_entry->data.indirected_text.validation =
945 				(char *) -1;
946 		entry->menu_entry->data.indirected_text.size =
947 				strlen(translated);
948 
949 		/* child menu title - this is the same as the text of
950 		 * the parent menu entry, so just copy the pointer */
951 		if (submenu != wimp_NO_SUB_MENU && IS_MENU(submenu)) {
952 			submenu->title_data.indirected_text.text =
953 					translated;
954 		}
955 	}
956 
957 	/* finally, set the current encoding of the menu */
958 	menu->current_encoding = alphabet;
959 
960 	return true;
961 }
962 
963