/*
 * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net>
 * Copyright 2005 James Bursa <bursa@users.sourceforge.net>
 * Copyright 2003 John M Bell <jmb202@ecs.soton.ac.uk>
 * Copyright 2005 Richard Wilson <info@tinct.net>
 *
 * This file is part of NetSurf, http://www.netsurf-browser.org/
 *
 * NetSurf is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * NetSurf is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * \file
 * Menu creation and handling implementation.
 */

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include "oslib/os.h"
#include "oslib/osbyte.h"
#include "oslib/osgbpb.h"
#include "oslib/territory.h"
#include "oslib/wimp.h"

#include "utils/log.h"
#include "utils/messages.h"
#include "utils/utf8.h"
#include "desktop/cookie_manager.h"

#include "riscos/dialog.h"
#include "riscos/configure.h"
#include "riscos/cookies.h"
#include "riscos/gui.h"
#include "riscos/global_history.h"
#include "riscos/help.h"
#include "riscos/hotlist.h"
#include "riscos/menus.h"
#include "utils/nsoption.h"
#include "riscos/save.h"
#include "riscos/tinct.h"
#include "riscos/toolbar.h"
#include "riscos/treeview.h"
#include "riscos/url_suggest.h"
#include "riscos/wimp.h"
#include "riscos/wimp_event.h"
#include "riscos/ucstables.h"

struct menu_definition_entry {
	menu_action action;			/**< menu action */
	wimp_menu_entry *menu_entry;		/**< corresponding menu entry */
	const char *entry_key;			/**< Messages key for entry text */
	struct menu_definition_entry *next;	/**< next menu entry */
};

struct menu_definition {
	wimp_menu *menu;			/**< corresponding menu */
	const char *title_key;			/**< Messages key for title text */
	int current_encoding;			/**< Identifier for current text encoding of menu text (as per OS_Byte,71,127) */
	struct menu_definition_entry *entries;	/**< menu entries */
	struct menu_definition *next;		/**< next menu */
};

static void ro_gui_menu_closed(void);
static void 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);
static struct menu_definition *ro_gui_menu_find_menu(wimp_menu *menu);
static struct menu_definition_entry *ro_gui_menu_find_entry(wimp_menu *menu,
		menu_action action);
static menu_action ro_gui_menu_find_action(wimp_menu *menu,
		wimp_menu_entry *menu_entry);
static int ro_gui_menu_get_checksum(void);
static bool ro_gui_menu_translate(struct menu_definition *menu);


/* default menu item flags */
#define DEFAULT_FLAGS (wimp_ICON_TEXT | wimp_ICON_FILLED | \
		(wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT) | \
		(wimp_COLOUR_WHITE << wimp_ICON_BG_COLOUR_SHIFT))

/** The currently defined menus to perform actions for */
static struct menu_definition *ro_gui_menu_definitions;
/** The current menu being worked with (may not be open) */
wimp_menu *current_menu;
/** Whether a menu is currently open */
bool current_menu_open = false;
/** Window that owns the current menu */
wimp_w current_menu_window;
/** Icon that owns the current menu (only valid for popup menus) */
static wimp_i current_menu_icon;
/** The available menus */
wimp_menu *image_quality_menu, *proxy_type_menu, *languages_menu;

/* the values given in PRM 3-157 for how to check menus/windows are
 * incorrect so we use a hack of checking if the sub-menu has bit 0
 * set which is undocumented but true of window handles on
 * all target OS versions */
#define IS_MENU(menu) !((int)(menu) & 1)

/**
 * Create menu structures.
 */

void ro_gui_menu_init(void)
{
	/* image quality menu */
	static const struct ns_menu images_definition = {
		"Display", {
			{ "ImgStyle0", NO_ACTION, 0 },
			{ "ImgStyle1", NO_ACTION, 0 },
			{ "ImgStyle2", NO_ACTION, 0 },
			{ "ImgStyle3", NO_ACTION, 0 },
			{NULL, 0, 0}
		}
	};
	image_quality_menu = ro_gui_menu_define_menu(&images_definition);

	/* proxy menu */
	static const struct ns_menu proxy_type_definition = {
		"ProxyType", {
			{ "ProxyNone", NO_ACTION, 0 },
			{ "ProxyNoAuth", NO_ACTION, 0 },
			{ "ProxyBasic", NO_ACTION, 0 },
			{ "ProxyNTLM", NO_ACTION, 0 },
			{NULL, 0, 0}
		}
	};
	proxy_type_menu = ro_gui_menu_define_menu(&proxy_type_definition);

	/* special case menus */
	ro_gui_url_suggest_init();

	/* Note: This table *must* be kept in sync with the LangNames file */
	static const struct ns_menu lang_definition = {
		"Languages", {
			{ "lang_af", NO_ACTION, 0 },
			{ "lang_bm", NO_ACTION, 0 },
			{ "lang_ca", NO_ACTION, 0 },
			{ "lang_cs", NO_ACTION, 0 },
			{ "lang_cy", NO_ACTION, 0 },
			{ "lang_da", NO_ACTION, 0 },
			{ "lang_de", NO_ACTION, 0 },
			{ "lang_en", NO_ACTION, 0 },
			{ "lang_es", NO_ACTION, 0 },
			{ "lang_et", NO_ACTION, 0 },
			{ "lang_eu", NO_ACTION, 0 },
			{ "lang_ff", NO_ACTION, 0 },
			{ "lang_fi", NO_ACTION, 0 },
			{ "lang_fr", NO_ACTION, 0 },
			{ "lang_ga", NO_ACTION, 0 },
			{ "lang_gl", NO_ACTION, 0 },
			{ "lang_ha", NO_ACTION, 0 },
			{ "lang_hr", NO_ACTION, 0 },
			{ "lang_hu", NO_ACTION, 0 },
			{ "lang_id", NO_ACTION, 0 },
			{ "lang_is", NO_ACTION, 0 },
			{ "lang_it", NO_ACTION, 0 },
			{ "lang_lt", NO_ACTION, 0 },
			{ "lang_lv", NO_ACTION, 0 },
			{ "lang_ms", NO_ACTION, 0 },
			{ "lang_mt", NO_ACTION, 0 },
			{ "lang_nl", NO_ACTION, 0 },
			{ "lang_no", NO_ACTION, 0 },
			{ "lang_pl", NO_ACTION, 0 },
			{ "lang_pt", NO_ACTION, 0 },
			{ "lang_rn", NO_ACTION, 0 },
			{ "lang_ro", NO_ACTION, 0 },
			{ "lang_rw", NO_ACTION, 0 },
			{ "lang_sk", NO_ACTION, 0 },
			{ "lang_sl", NO_ACTION, 0 },
			{ "lang_so", NO_ACTION, 0 },
			{ "lang_sq", NO_ACTION, 0 },
			{ "lang_sr", NO_ACTION, 0 },
			{ "lang_sv", NO_ACTION, 0 },
			{ "lang_sw", NO_ACTION, 0 },
			{ "lang_tr", NO_ACTION, 0 },
			{ "lang_uz", NO_ACTION, 0 },
			{ "lang_vi", NO_ACTION, 0 },
			{ "lang_wo", NO_ACTION, 0 },
			{ "lang_xs", NO_ACTION, 0 },
			{ "lang_yo", NO_ACTION, 0 },
			{ "lang_zu", NO_ACTION, 0 },
			{ NULL, 0, 0 }
		}
	};
	languages_menu = ro_gui_menu_define_menu(&lang_definition);
}


/**
 * Display a menu.
 *
 * \param *menu			Pointer to the menu to be displayed.
 * \param x			The x position.
 * \param y			The y position.
 * \param w			The window that the menu belongs to.
 */

void ro_gui_menu_create(wimp_menu *menu, int x, int y, wimp_w w)
{
	os_error *error;
	struct menu_definition *definition;

	/* translate menu, if necessary (this returns quickly
	 * if there's nothing to be done) */
	definition = ro_gui_menu_find_menu(menu);
	if (definition) {
		if (!ro_gui_menu_translate(definition)) {
			ro_warn_user("NoMemory", 0);
			return;
		}
	}

	/* store the menu characteristics */
	current_menu = menu;
	current_menu_window = w;
	current_menu_icon = wimp_ICON_WINDOW;

	/* create the menu */
	current_menu_open = true;
	error = xwimp_create_menu(menu, x - 64, y);
	if (error) {
		LOG("xwimp_create_menu: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("MenuError", error->errmess);
		ro_gui_menu_closed();
	}
}


/**
 * Display a pop-up menu next to the specified icon.
 *
 * \param  menu  menu to open
 * \param  w	 window handle
 * \param  i	 icon handle
 */

void ro_gui_popup_menu(wimp_menu *menu, wimp_w w, wimp_i i)
{
	wimp_window_state state;
	wimp_icon_state icon_state;
	os_error *error;

	state.w = w;
	icon_state.w = w;
	icon_state.i = i;
	error = xwimp_get_window_state(&state);
	if (error) {
		LOG("xwimp_get_window_state: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("MenuError", error->errmess);
		return;
	}

	error = xwimp_get_icon_state(&icon_state);
	if (error) {
		LOG("xwimp_get_icon_state: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("MenuError", error->errmess);
		return;
	}

	ro_gui_menu_create(menu,
			state.visible.x0 + icon_state.icon.extent.x1 + 64,
			state.visible.y1 + icon_state.icon.extent.y1 -
			state.yscroll, w);
	current_menu_icon = i;
}


/**
 * Forcibly close any menu or transient dialogue box that is currently open.
 */

void ro_gui_menu_destroy(void)
{
	os_error *error;

	if (current_menu == NULL)
		return;

	error = xwimp_create_menu(wimp_CLOSE_MENU, 0, 0);
	if (error) {
		LOG("xwimp_create_menu: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("MenuError", error->errmess);
	}

	ro_gui_menu_closed();
}


/**
 * Allow the current menu window to change, if the window is deleted and
 * recreated while a menu is active on an Adjust-click.
 *
 * \param from			The original window handle.
 * \param to			The new replacement window handle.
 */

void ro_gui_menu_window_changed(wimp_w from, wimp_w to)
{

	if (from == current_menu_window)
		current_menu_window = to;
}


/**
 * Handle menu selection.
 */

void ro_gui_menu_selection(wimp_selection *selection)
{
	int			i; //, j;
	wimp_menu_entry		*menu_entry;
	menu_action		action;
	wimp_pointer		pointer;
	os_error		*error;
	int			previous_menu_icon = current_menu_icon;

	/* if we are using gui_multitask then menu selection events
	 * may be delivered after the menu has been closed. As such,
	 * we simply ignore these events. */
	if (!current_menu)
		return;

	assert(current_menu_window);

	/* get the menu entry and associated action and definition */
	menu_entry = &current_menu->entries[selection->items[0]];
	for (i = 1; selection->items[i] != -1; i++)
		menu_entry = &menu_entry->sub_menu->
				entries[selection->items[i]];
	action = ro_gui_menu_find_action(current_menu, menu_entry);

	/* Deal with the menu action.  If this manages to re-prepare the
	 * menu for re-opening, we test for and act on Adjust clicks.
	 */

	if (!ro_gui_wimp_event_menu_selection(current_menu_window,
			current_menu_icon, current_menu, selection, action))
		return;

	/* re-open the menu for Adjust clicks */
	error = xwimp_get_pointer_info(&pointer);
	if (error) {
		LOG("xwimp_get_pointer_info: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		ro_gui_menu_closed();
		return;
	}

	if (pointer.buttons != wimp_CLICK_ADJUST) {
		ro_gui_menu_closed();
		return;
	}

	ro_gui_menu_create(current_menu, 0, 0, current_menu_window);
	current_menu_icon = previous_menu_icon;
}


/**
 * Handle Message_MenuWarning.
 */
void ro_gui_menu_warning(wimp_message_menu_warning *warning)
{
	int i;
	menu_action action;
	wimp_menu_entry *menu_entry;
	os_error *error;

	assert(current_menu);
	assert(current_menu_window);

	/* get the sub-menu of the warning */
	if (warning->selection.items[0] == -1)
		return;
	menu_entry = &current_menu->entries[warning->selection.items[0]];
	for (i = 1; warning->selection.items[i] != -1; i++)
		menu_entry = &menu_entry->sub_menu->
				entries[warning->selection.items[i]];
	action = ro_gui_menu_find_action(current_menu, menu_entry);

	/* Process the warning via Wimp_Event, then register the resulting
	 * submenu with the module.
	 */

	ro_gui_wimp_event_submenu_warning(current_menu_window,
			current_menu_icon, current_menu, &(warning->selection),
			action);

	if (IS_MENU(menu_entry->sub_menu)) {
		ro_gui_wimp_event_register_submenu((wimp_w) 0);
	} else {
		ro_gui_wimp_event_register_submenu((wimp_w)
				menu_entry->sub_menu);

		/* If this is a dialogue box, remove the close and back icons.
		 */

		ro_gui_wimp_update_window_furniture((wimp_w)
				menu_entry->sub_menu,
				wimp_WINDOW_CLOSE_ICON | wimp_WINDOW_BACK_ICON,
				0);
	}

	/* open the sub-menu */

	error = xwimp_create_sub_menu(menu_entry->sub_menu,
			warning->pos.x, warning->pos.y);
	if (error) {
		LOG("xwimp_create_sub_menu: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("MenuError", error->errmess);
	}
}


/**
 * Handle Message_MenusDeleted, removing our current record of an open menu
 * if it matches the deleted menu handle.
 *
 * \param *deleted		The message block.
 */

void ro_gui_menu_message_deleted(wimp_message_menus_deleted *deleted)
{
	if (deleted != NULL && deleted->menu == current_menu)
		ro_gui_menu_closed();
}


/**
 * Clean up after a menu has been closed, or forcibly close an open menu.
  */

static void ro_gui_menu_closed(void)
{
	if (current_menu != NULL)
		ro_gui_wimp_event_menus_closed(current_menu_window,
				current_menu_icon, current_menu);

	current_menu = NULL;
	current_menu_window = NULL;
	current_menu_icon = 0;
	current_menu_open = false;
}


/**
 * Update the current menu by sending it a Menu Prepare event through wimp_event
 * and then reopening it if the contents has changed.
 *
 * \param *menu		The menu to refresh: if 0, the current menu will be
 *			refreshed regardless, otherwise it will be refreshed
 *			only if it matches the supplied handle.
 */

void ro_gui_menu_refresh(wimp_menu *menu)
{
	int checksum = 0;

	if (!current_menu_open)
		return;

	checksum = ro_gui_menu_get_checksum();

	if (!ro_gui_wimp_event_prepare_menu(current_menu_window,
			current_menu_icon, current_menu))
		return;

	/* \TODO -- Call the menu's event handler here. */

	if (checksum != ro_gui_menu_get_checksum()) {
		os_error *error;
		error = xwimp_create_menu(current_menu, 0, 0);
		if (error) {
			LOG("xwimp_create_menu: 0x%x: %s", error->errnum, error->errmess);
			ro_warn_user("MenuError", error->errmess);
		}
	}
}




/**
 * Creates a wimp_menu and adds it to the list to handle actions for.
 *
 * \param  menu The data to create the menu with
 * \return The menu created, or NULL on failure
 */
wimp_menu *ro_gui_menu_define_menu(const struct ns_menu *menu)
{
	struct menu_definition *definition;
	int entry;

	definition = calloc(sizeof(struct menu_definition), 1);
	if (!definition) {
		die("No memory to create menu definition.");
		return NULL; /* For the benefit of scan-build */
	}

	/* link in the menu to our list */
	definition->next = ro_gui_menu_definitions;
	ro_gui_menu_definitions = definition;

	/* count number of menu entries */
	for (entry = 0; menu->entries[entry].text; entry++)
		/* do nothing */;

	/* create our definitions */
	ro_gui_menu_define_menu_add(definition, menu, 0, NULL,
			0, entry, NULL, 0);

	/* and translate menu into current encoding */
	if (!ro_gui_menu_translate(definition))
		die("No memory to translate menu.");

	return definition->menu;
}

/**
 * Create a wimp menu tree from ns_menu data.
 * This function does *not* deal with the menu textual content - it simply
 * creates and populates the appropriate structures. Textual content is
 * generated by ro_gui_menu_translate_menu()
 *
 * \param definition  Top level menu definition
 * \param menu  Menu declaration data
 * \param depth  Depth of menu we're currently building
 * \param parent_entry  Entry in parent menu, or NULL if root menu
 * \param first  First index in declaration data that is used by this menu
 * \param last Last index in declaration data that is used by this menu
 * \param prefix  Prefix pf menu declaration string already seen
 * \param prefix_length  Length of prefix
 */
void 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)
{
	int entry;
	int entries = 0;
	int matches[last - first + 1];
	const char *text, *menu_text;
	wimp_menu *new_menu;
	struct menu_definition_entry *definition_entry;

	/* step 1: store the matches for depth and subset string */
	for (entry = first; entry < last; entry++) {
		const char *match;
		int cur_depth = 0;
		match = menu->entries[entry].text;

		/* skip specials at start of string */
		while (!isalnum(*match))
			match++;

		/* attempt prefix match */
		if ((prefix) && (strncmp(match, prefix, prefix_length)))
			continue;

		/* Find depth of this entry */
		while (*match)
			if (*match++ == '.')
				cur_depth++;

		if (depth == cur_depth)
			matches[entries++] = entry;
	}
	matches[entries] = last;

	/* no entries, so exit */
	if (entries == 0)
		return;

	/* step 2: build and link the menu. we must use realloc to stop
	 * our memory fragmenting so we can test for sub-menus easily */
	new_menu = (wimp_menu *)malloc(wimp_SIZEOF_MENU(entries));
	if (!new_menu)
		die("No memory to create menu.");

	if (parent_entry) {
		/* Fix up sub menu pointer */
		parent_entry->sub_menu = new_menu;
	} else {
		/* Root menu => fill in definition struct */
		definition->title_key = menu->title;
		definition->current_encoding = 0;
		definition->menu = new_menu;
	}

	/* this is fixed up in ro_gui_menu_translate() */
	new_menu->title_data.indirected_text.text = NULL;

	/* fill in menu flags */
	ro_gui_menu_init_structure(new_menu, entries);

	/* and then create the entries */
	for (entry = 0; entry < entries; entry++) {
		/* add the entry */
		int id = matches[entry];

		text = menu->entries[id].text;

		/* fill in menu flags from specials at start of string */
		new_menu->entries[entry].menu_flags = 0;
		while (!isalnum(*text)) {
			if (*text == '_')
				new_menu->entries[entry].menu_flags |=
							wimp_MENU_SEPARATE;
			text++;
		}

		/* get messages key for menu entry */
		menu_text = strrchr(text, '.');
		if (!menu_text)
			/* no '.' => top-level entry */
			menu_text = text;
		else
			menu_text++; /* and move past the '.' */

		/* fill in submenu data */
		if (menu->entries[id].sub_window)
			new_menu->entries[entry].sub_menu =
				(wimp_menu *) (*menu->entries[id].sub_window);

		/* this is fixed up in ro_gui_menu_translate() */
		new_menu->entries[entry].data.indirected_text.text = NULL;

		/* create definition entry */
		definition_entry =
			malloc(sizeof(struct menu_definition_entry));
		if (!definition_entry)
			die("Unable to create menu definition entry");
		definition_entry->action = menu->entries[id].action;
		definition_entry->menu_entry = &new_menu->entries[entry];
		definition_entry->entry_key = menu_text;
		definition_entry->next = definition->entries;
		definition->entries = definition_entry;

		/* recurse */
		if (new_menu->entries[entry].sub_menu == wimp_NO_SUB_MENU) {
			ro_gui_menu_define_menu_add(definition, menu,
					depth + 1, &new_menu->entries[entry],
					matches[entry], matches[entry + 1],
					text, strlen(text));
		}

		/* give menu warnings */
		if (new_menu->entries[entry].sub_menu != wimp_NO_SUB_MENU)
			new_menu->entries[entry].menu_flags |=
						wimp_MENU_GIVE_WARNING;
	}
	new_menu->entries[0].menu_flags |= wimp_MENU_TITLE_INDIRECTED;
	new_menu->entries[entries - 1].menu_flags |= wimp_MENU_LAST;
}


/**
 * Initialise the basic state of a menu structure so all entries are
 * indirected text with no flags, no submenu.
 */
void ro_gui_menu_init_structure(wimp_menu *menu, int entries)
{
  	int i;

	menu->title_fg = wimp_COLOUR_BLACK;
	menu->title_bg = wimp_COLOUR_LIGHT_GREY;
	menu->work_fg = wimp_COLOUR_BLACK;
	menu->work_bg = wimp_COLOUR_WHITE;
	menu->width = 200;
	menu->height = wimp_MENU_ITEM_HEIGHT;
	menu->gap = wimp_MENU_ITEM_GAP;

	for (i = 0; i < entries; i++) {
		menu->entries[i].menu_flags = 0;
		menu->entries[i].sub_menu = wimp_NO_SUB_MENU;
		menu->entries[i].icon_flags =
				DEFAULT_FLAGS | wimp_ICON_INDIRECTED;
		menu->entries[i].data.indirected_text.validation =
				(char *)-1;
	}
	menu->entries[0].menu_flags |= wimp_MENU_TITLE_INDIRECTED;
	menu->entries[i - 1].menu_flags |= wimp_MENU_LAST;
}


/**
 * Finds the menu_definition corresponding to a wimp_menu.
 *
 * \param menu  the menu to find the definition for
 * \return the associated definition, or NULL if one could not be found
 */
struct menu_definition *ro_gui_menu_find_menu(wimp_menu *menu)
{
	struct menu_definition *definition;

	if (!menu)
		return NULL;

	for (definition = ro_gui_menu_definitions; definition;
			definition = definition->next)
		if (definition->menu == menu)
			return definition;
	return NULL;
}


/**
 * Finds the key associated with a menu entry translation.
 *
 * \param menu  the menu to search
 * \param translated  the translated text
 * \return the original message key, or NULL if one could not be found
 */
const char *ro_gui_menu_find_menu_entry_key(wimp_menu *menu,
		const char *translated)
{
	struct menu_definition_entry *entry;
	struct menu_definition *definition = ro_gui_menu_find_menu(menu);

	if (!definition)
		return NULL;

	for (entry = definition->entries; entry; entry = entry->next)
		if (!strcmp(entry->menu_entry->data.indirected_text.text, translated))
			return entry->entry_key;
	return NULL;
}


/**
 * Finds the menu_definition_entry corresponding to an action for a wimp_menu.
 *
 * \param menu	  the menu to search for an action within
 * \param action  the action to find
 * \return the associated menu entry, or NULL if one could not be found
 */
struct menu_definition_entry *ro_gui_menu_find_entry(wimp_menu *menu,
		menu_action action)
{
	struct menu_definition_entry *entry;
	struct menu_definition *definition = ro_gui_menu_find_menu(menu);

	if (!definition)
		return NULL;

	for (entry = definition->entries; entry; entry = entry->next)
		if (entry->action == action)
			return entry;
	return NULL;
}


/**
 * Finds the action corresponding to a wimp_menu_entry for a wimp_menu.
 *
 * \param menu	      the menu to search for an action within
 * \param menu_entry  the menu_entry to find
 * \return the associated action, or 0 if one could not be found
 */
menu_action ro_gui_menu_find_action(wimp_menu *menu, wimp_menu_entry *menu_entry)
{
	struct menu_definition_entry *entry;
	struct menu_definition *definition = ro_gui_menu_find_menu(menu);

	if (!definition)
		return NO_ACTION;

	for (entry = definition->entries; entry; entry = entry->next) {
		if (entry->menu_entry == menu_entry)
			return entry->action;
	}
	return NO_ACTION;
}


/**
 * Sets an action within a menu as having a specific ticked status.
 *
 * \param menu	  the menu containing the action
 * \param action  the action to tick/untick
 * \param shaded  whether to set the item as shaded
 */
void ro_gui_menu_set_entry_shaded(wimp_menu *menu, menu_action action,
		bool shaded)
{
	struct menu_definition_entry *entry;
	struct menu_definition *definition = ro_gui_menu_find_menu(menu);

	if (!definition)
		return;

	/* we can't use find_entry as multiple actions may appear in one menu */
	for (entry = definition->entries; entry; entry = entry->next)
		if (entry->action == action) {
			if (shaded)
				entry->menu_entry->icon_flags |= wimp_ICON_SHADED;
			else
				entry->menu_entry->icon_flags &= ~wimp_ICON_SHADED;
		}
}


/**
 * Sets an action within a menu as having a specific ticked status.
 *
 * \param menu	  the menu containing the action
 * \param action  the action to tick/untick
 * \param ticked  whether to set the item as ticked
 */
void ro_gui_menu_set_entry_ticked(wimp_menu *menu, menu_action action,
		bool ticked)
{
	struct menu_definition_entry *entry =
			ro_gui_menu_find_entry(menu, action);
	if (entry) {
		if (ticked)
			entry->menu_entry->menu_flags |= wimp_MENU_TICKED;
		else
			entry->menu_entry->menu_flags &= ~wimp_MENU_TICKED;
	}
}


/**
 * Calculates a simple checksum for the current menu state
 */
int ro_gui_menu_get_checksum(void)
{
	wimp_selection menu_tree;
	int i = 0, j, checksum = 0;
	os_error *error;
	wimp_menu *menu;

	if (!current_menu_open)
		return 0;

	error = xwimp_get_menu_state((wimp_menu_state_flags)0,
			&menu_tree, 0, 0);
	if (error) {
		LOG("xwimp_get_menu_state: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("MenuError", error->errmess);
		return 0;
	}

	menu = current_menu;
	do {
		j = 0;
		do {
			if (menu->entries[j].icon_flags & wimp_ICON_SHADED)
				checksum ^= (1 << (i + j * 2));
			if (menu->entries[j].menu_flags & wimp_MENU_TICKED)
				checksum ^= (2 << (i + j * 2));
		} while (!(menu->entries[j++].menu_flags & wimp_MENU_LAST));

		j = menu_tree.items[i++];
		if (j != -1) {
			menu = menu->entries[j].sub_menu;
			if ((!menu) || (menu == wimp_NO_SUB_MENU) || (!IS_MENU(menu)))
				break;
		}
	} while (j != -1);

	return checksum;
}

/**
 * Translate a menu's textual content into the system local encoding
 *
 * \param menu  The menu to translate
 * \return false if out of memory, true otherwise
 */
bool ro_gui_menu_translate(struct menu_definition *menu)
{
	os_error *error;
	int alphabet;
	struct menu_definition_entry *entry;
	char *translated;
	nserror err;

	/* read current alphabet */
	error = xosbyte1(osbyte_ALPHABET_NUMBER, 127, 0, &alphabet);
	if (error) {
		LOG("failed reading alphabet: 0x%x: %s", error->errnum, error->errmess);
		/* assume Latin1 */
		alphabet = territory_ALPHABET_LATIN1;
	}

	if (menu->current_encoding == alphabet)
		/* menu text is already in the correct encoding */
		return true;

	/* translate root menu title text */
	free(menu->menu->title_data.indirected_text.text);
	err = utf8_to_local_encoding(messages_get(menu->title_key),
			0, &translated);
	if (err != NSERROR_OK) {
		assert(err != NSERROR_BAD_ENCODING);
		LOG("utf8_to_enc failed");
		return false;
	}

	/* and fill in WIMP menu field */
	menu->menu->title_data.indirected_text.text = translated;

	/* now the menu entries */
	for (entry = menu->entries; entry; entry = entry->next) {
		wimp_menu *submenu = entry->menu_entry->sub_menu;

		/* tranlate menu entry text */
		free(entry->menu_entry->data.indirected_text.text);
		err = utf8_to_local_encoding(messages_get(entry->entry_key),
				0, &translated);
		if (err != NSERROR_OK) {
			assert(err != NSERROR_BAD_ENCODING);
			LOG("utf8_to_enc failed");
			return false;
		}

		/* fill in WIMP menu fields */
		entry->menu_entry->data.indirected_text.text = translated;
		entry->menu_entry->data.indirected_text.validation =
				(char *) -1;
		entry->menu_entry->data.indirected_text.size =
				strlen(translated);

		/* child menu title - this is the same as the text of
		 * the parent menu entry, so just copy the pointer */
		if (submenu != wimp_NO_SUB_MENU && IS_MENU(submenu)) {
			submenu->title_data.indirected_text.text =
					translated;
		}
	}

	/* finally, set the current encoding of the menu */
	menu->current_encoding = alphabet;

	return true;
}

