/*
 * Copyright 2004, 2005 Richard Wilson <info@tinct.net>
 * Copyright 2008 John Tytgat <joty@netsurf-browser.org>
 *
 * 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
 * General RISC OS WIMP/OS library functions (implementation).
 */

#include <assert.h>
#include <locale.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "oslib/colourtrans.h"
#include "oslib/os.h"
#include "oslib/osfile.h"
#include "oslib/wimp.h"
#include "oslib/wimpextend.h"
#include "oslib/wimpspriteop.h"

#include "utils/log.h"
#include "utils/utf8.h"

#include "riscos/gui.h"
#include "riscos/oslib_pre7.h"
#include "riscos/wimp.h"
#include "riscos/ucstables.h"


static void ro_gui_wimp_cache_furniture_sizes(wimp_w w);
static size_t ro_gui_strlen(const char *str);
static int ro_gui_strncmp(const char *s1, const char *s2, size_t len);

static wimpextend_furniture_sizes furniture_sizes;
static wimp_w furniture_window = NULL;

/**
 * Gets the horizontal scrollbar height
 *
 * \param  w  the window to read (or NULL to read a cached value)
 */
int ro_get_hscroll_height(wimp_w w)
{
	ro_gui_wimp_cache_furniture_sizes(w);
	return furniture_sizes.border_widths.y0;
}


/**
 * Gets the vertical scrollbar width
 *
 * \param  w  the window to read (or NULL to read a cached value)
 */
int ro_get_vscroll_width(wimp_w w)
{
	ro_gui_wimp_cache_furniture_sizes(w);
	return furniture_sizes.border_widths.x1;
}


/**
 * Gets the title bar height
 *
 * \param  w  the window to read (or NULL to read a cached value)
 */
int ro_get_title_height(wimp_w w)
{
	ro_gui_wimp_cache_furniture_sizes(w);
	return furniture_sizes.border_widths.y1;
}

/**
 * Caches window furniture information
 *
 * \param  w  the window to cache information from
 * \return true on success, false on error (default values cached)
 */
void ro_gui_wimp_cache_furniture_sizes(wimp_w w)
{
	os_error *error;

	if (furniture_window == w)
		return;
	furniture_window = w;
	furniture_sizes.w = w;
	furniture_sizes.border_widths.y0 = 40;
	furniture_sizes.border_widths.x1 = 40;
	error = xwimpextend_get_furniture_sizes(&furniture_sizes);
	if (error) {
		LOG("xwimpextend_get_furniture_sizes: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
	}
}


/**
 * Reads a modes EIG factors.
 *
 * \param[in] mode  mode to read EIG factors for, or -1 for current
 * \param[out] xeig  The x eig value
 * \param[out] yeig  The y eig value
 * \return true on success else false. 
 */
bool ro_gui_wimp_read_eig_factors(os_mode mode, int *xeig, int *yeig)
{
	os_error *error;

	error = xos_read_mode_variable(mode, os_MODEVAR_XEIG_FACTOR, xeig, 0);
	if (error) {
		LOG("xos_read_mode_variable: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("MiscError", error->errmess);
		return false;
	}
	error = xos_read_mode_variable(mode, os_MODEVAR_YEIG_FACTOR, yeig, 0);
	if (error) {
		LOG("xos_read_mode_variable: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("MiscError", error->errmess);
		return false;
	}
	return true;
}


/**
 * Converts the supplied os_coord from OS units to pixels.
 *
 * \param  os_units  values to convert
 * \param  mode	     mode to use EIG factors for, or -1 for current
 */
void ro_convert_os_units_to_pixels(os_coord *os_units, os_mode mode)
{
	int xeig = 1, yeig = 1;

	ro_gui_wimp_read_eig_factors(mode, &xeig, &yeig);
	os_units->x = ((os_units->x + (1 << xeig) - 1) >> xeig);
	os_units->y = ((os_units->y + (1 << yeig) - 1) >> yeig);
}


/**
 * Converts the supplied os_coord from pixels to OS units.
 *
 * \param  pixels  values to convert
 * \param  mode	   mode to use EIG factors for, or -1 for current
 */
void ro_convert_pixels_to_os_units(os_coord *pixels, os_mode mode)
{
	int xeig = 1, yeig = 1;

	ro_gui_wimp_read_eig_factors(mode, &xeig, &yeig);
	pixels->x = (pixels->x << xeig);
	pixels->y = (pixels->y << yeig);
}


/**
 * Redraws an icon
 *
 * \param  w  window handle
 * \param  i  icon handle
 */

#define ro_gui_redraw_icon(w, i) xwimp_set_icon_state(w, i, 0, 0)


/**
 * Forces an icon to be redrawn entirely (ie not just updated).
 *
 * \param  w  window handle
 * \param  i  icon handle
 */
void ro_gui_force_redraw_icon(wimp_w w, wimp_i i)
{
	wimp_icon_state ic;
	os_error *error;

	/*	Get the icon data
	*/
	ic.w = w;
	ic.i = i;
	error = xwimp_get_icon_state(&ic);
	if (error) {
		LOG("xwimp_get_icon_state: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		return;
	}
	error = xwimp_force_redraw(w, ic.icon.extent.x0, ic.icon.extent.y0,
			ic.icon.extent.x1, ic.icon.extent.y1);
	if (error) {
		LOG("xwimp_force_redraw: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
	}
}


/**
 * Read the contents of a text or sprite icon.
 *
 * \param  w  window handle
 * \param  i  icon handle
 * \return NUL terminated string in icon
 *
 * If the icon contains direct text then the returned data will
 * be invalidated by the next call to this function. Therefore,
 * all client calls to this function must either copy the string or
 * ensure that this function is not called again until they are
 * finished with the string data returned.
 *
 * \todo this doesn't do local encoding -> UTF-8 to match what is done in
 * ro_gui_set_icon_string.
 */
const char *ro_gui_get_icon_string(wimp_w w, wimp_i i)
{
	static wimp_icon_state ic;
	os_error *error;
	char *itext;

	ic.w = w;
	ic.i = i;
	error = xwimp_get_icon_state(&ic);
	if (error) {
		LOG("xwimp_get_icon_state: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		return NULL;
	}
	itext = (ic.icon.flags & wimp_ICON_INDIRECTED)
			? ic.icon.data.indirected_text.text : ic.icon.data.text;
	/* Guarantee NUL termination.  */
	itext[ro_gui_strlen(itext)] = '\0';

	return itext;
}


/**
 * Set the contents of a text or sprite icon to a string.
 *
 * \param  w	 window handle
 * \param  i	 icon handle
 * \param  text  NUL terminated string (copied)
 * \param  is_utf8 When true, the given string is UTF-8 encoded and will be
 * converted to local encoding currently used by the Wimp. When false, the
 * given string is assumed to be in local encoding in use by the Wimp.
 */
void ro_gui_set_icon_string(wimp_w w, wimp_i i, const char *text, bool is_utf8)
{
	wimp_caret caret;
	wimp_icon_state ic;
	os_error *error;
	size_t old_len, new_len;
	char *local_text = NULL;
	const char *text_for_icon;
	char *dst_text;
	size_t dst_max_len;
	unsigned int button_type;

	if (is_utf8) {
		nserror err;
		/* convert text to local encoding */
		err = utf8_to_local_encoding(text, 0, &local_text);
		if (err != NSERROR_OK) {
			/* A bad encoding should never happen, so assert this */
			assert(err != NSERROR_BAD_ENCODING);
			LOG("utf8_to_enc failed");
			/* Paranoia */
			local_text = NULL;
		}
		text_for_icon = local_text ? local_text : text;
	}
	else
		text_for_icon = text;
	new_len = strlen(text_for_icon);

	/* get the icon data */
	ic.w = w;
	ic.i = i;
	error = xwimp_get_icon_state(&ic);
	if (error) {
		LOG("xwimp_get_icon_state: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		goto exit;
	}

	if (ic.icon.flags & wimp_ICON_INDIRECTED) {
		dst_text = ic.icon.data.indirected_text.text;
		dst_max_len = ic.icon.data.indirected_text.size;
	}
	else {
		dst_text = ic.icon.data.text;
		dst_max_len = sizeof(ic.icon.data.text);
	}
	old_len = ro_gui_strlen(dst_text);
	assert(old_len < dst_max_len);

	/* check that the existing text is not the same as the updated text
	 * to stop flicker */
	if (dst_max_len) {
		if (!ro_gui_strncmp(dst_text, text_for_icon, dst_max_len))
			goto exit;

		/* copy the text across */
		strncpy(dst_text, text_for_icon, dst_max_len - 1);
		dst_text[dst_max_len - 1] = '\0';

		/* handle the caret being in the icon */
		button_type = (ic.icon.flags & wimp_ICON_BUTTON_TYPE)
				>> wimp_ICON_BUTTON_TYPE_SHIFT;
		if ((button_type == wimp_BUTTON_WRITABLE) ||
				(button_type == wimp_BUTTON_WRITE_CLICK_DRAG)) {
			error = xwimp_get_caret_position(&caret);
			if (error) {
				LOG("xwimp_get_caret_position: 0x%x: %s", error->errnum, error->errmess);
				ro_warn_user("WimpError", error->errmess);
				goto exit;
			}
			if ((caret.w == w) && (caret.i == i)) {
				if ((size_t)caret.index > new_len
						|| (size_t)caret.index == old_len)
					caret.index = new_len;
				error = xwimp_set_caret_position(w, i, caret.pos.x,
						caret.pos.y, -1, caret.index);
				if (error) {
					LOG("xwimp_set_caret_position: 0x%x: %s", error->errnum, error->errmess);
					ro_warn_user("WimpError", error->errmess);
				}
			}
		}
		ro_gui_redraw_icon(w, i);
	}

exit:
	free(local_text);
}


/**
 * Set the contents of an icon to a number.
 *
 * \param  w	  window handle
 * \param  i	  icon handle
 * \param  value  value
 */
void ro_gui_set_icon_integer(wimp_w w, wimp_i i, int value)
{
	char buffer[20]; // Big enough for 64-bit int

	sprintf(buffer, "%d", value);

	ro_gui_set_icon_string(w, i, buffer, true);
}


/**
 * Set the contents of an icon to a number.
 *
 * \param w  window handle
 * \param i  icon handle
 * \param value  value to use in icon.
 * \param decimal_places The number of decimal places to use.
 */
void ro_gui_set_icon_decimal(wimp_w w, wimp_i i, int value, int decimal_places)
{
	char buffer[20]; // Big enough for 64-bit int

	switch (decimal_places) {
		case 0:
			sprintf(buffer, "%d", value);
			break;
		case 1:
			sprintf(buffer, "%.1f", (float)value / 10);
			break;
		case 2:
			sprintf(buffer, "%.2f", (float)value / 100);
			break;
		default:
			assert(!"Unsupported decimal format");
			break;
	}

	ro_gui_set_icon_string(w, i, buffer, true);
}


/**
 * Get the contents of an icon as a number.
 *
 * \param w  window handle
 * \param i  icon handle
 * \param decimal_places  number of places to show.
 * \return value used.
 */
int ro_gui_get_icon_decimal(wimp_w w, wimp_i i, int decimal_places)
{
	double value;
	int multiple = 1;

	for (; decimal_places > 0; decimal_places--)
		multiple *= 10;

	value = atof(ro_gui_get_icon_string(w, i)) * multiple;

	return (int)value;
}


/**
 * Set the selected state of an icon.
 *
 * \param  w	 window handle
 * \param  i	 icon handle
 * \param  state selected state
 */
void ro_gui_set_icon_selected_state(wimp_w w, wimp_i i, bool state)
{
	os_error *error;
	if (ro_gui_get_icon_selected_state(w, i) == state) return;
	error = xwimp_set_icon_state(w, i,
			(state ? wimp_ICON_SELECTED : 0), wimp_ICON_SELECTED);
	if (error) {
		LOG("xwimp_set_icon_state: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
	}
}

/**
 * Gets the selected state of an icon.
 *
 * \param  w	 window handle
 * \param  i	 icon handle
 */
bool ro_gui_get_icon_selected_state(wimp_w w, wimp_i i)
{
	os_error *error;
	wimp_icon_state ic;
	ic.w = w;
	ic.i = i;
	error = xwimp_get_icon_state(&ic);
	if (error) {
		LOG("xwimp_get_icon_state: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		return false;
	}
	return ((ic.icon.flags & wimp_ICON_SELECTED) != 0);
}


/**
 * Set the shaded state of an icon.
 *
 * \param  w	 window handle
 * \param  i	 icon handle
 * \param  state shaded state
 */
void ro_gui_set_icon_shaded_state(wimp_w w, wimp_i i, bool state)
{
	wimp_caret caret;
	os_error *error;

	/* update the state */
	if (ro_gui_get_icon_shaded_state(w, i) == state)
		return;
	error = xwimp_set_icon_state(w, i,
			(state ? wimp_ICON_SHADED : 0), wimp_ICON_SHADED);
	if (error) {
		LOG("xwimp_get_icon_state: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
	}
	if (!state)
		return;

	/* ensure the caret is not in a shaded icon */
	error = xwimp_get_caret_position(&caret);
	if (error) {
		LOG("xwimp_get_caret_position: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		return;
	}
	if ((caret.w != w) || (caret.i != i))
		return;
	/* move the caret to the first avaiable writable */
	if (ro_gui_set_caret_first(w))
		return;
	/* lose the caret */
	error = xwimp_set_caret_position((wimp_w)-1, (wimp_i)-1, -1, -1, -1, -1);
	if (error) {
		LOG("xwimp_set_caret_position: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		return;
	}
}


/**
 * Gets the shaded state of an icon.
 *
 * \param  w	 window handle
 * \param  i	 icon handle
 */
bool ro_gui_get_icon_shaded_state(wimp_w w, wimp_i i)
{
	wimp_icon_state ic;
	ic.w = w;
	ic.i = i;
	xwimp_get_icon_state(&ic);
	return (ic.icon.flags & wimp_ICON_SHADED) != 0;
}


/**
 * Set the deleted state of an icon.
 *
 * \param  w	 window handle
 * \param  i	 icon handle
 * \param  state shaded state
 */
void ro_gui_set_icon_deleted_state(wimp_w w, wimp_i i, bool state)
{
	wimp_caret caret;
	os_error *error;

	/* update the state */
	if (ro_gui_get_icon_deleted_state(w, i) == state)
		return;
	error = xwimp_set_icon_state(w, i,
			(state ? wimp_ICON_DELETED : 0), wimp_ICON_DELETED);
	if (error) {
		LOG("xwimp_get_icon_state: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
	}
	if (!state)
		return;

	/* ensure the caret is not in a shaded icon */
	error = xwimp_get_caret_position(&caret);
	if (error) {
		LOG("xwimp_get_caret_position: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		return;
	}
	if ((caret.w != w) || (caret.i != i))
		return;
	/* move the caret to the first avaiable writable */
	if (ro_gui_set_caret_first(w))
		return;
	/* lose the caret */
	error = xwimp_set_caret_position((wimp_w)-1, (wimp_i)-1, -1, -1, -1, -1);
	if (error) {
		LOG("xwimp_set_caret_position: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		return;
	}
}


/**
 * Gets the deleted state of an icon.
 *
 * \param  w	 window handle
 * \param  i	 icon handle
 */
bool ro_gui_get_icon_deleted_state(wimp_w w, wimp_i i)
{
	wimp_icon_state ic;
	ic.w = w;
	ic.i = i;
	xwimp_get_icon_state(&ic);
	return (ic.icon.flags & wimp_ICON_DELETED) != 0;
}


/**
 * Set the button type of an icon.
 *
 * \param  w	window handle
 * \param  i	icon handle
 * \param  type button type
 */
void ro_gui_set_icon_button_type(wimp_w w, wimp_i i, int type)
{
	os_error *error;
	error = xwimp_set_icon_state(w, i, wimp_ICON_BUTTON_TYPE,
			(type << wimp_ICON_BUTTON_TYPE_SHIFT));
	if (error) {
		LOG("xwimp_set_icon_state: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
	}
}


/**
 * Set an icon's sprite
 *
 * \param  w	window handle
 * \param  i	icon handle
 * \param  area sprite area containing sprite
 * \param  name name of sprite in area (in local encoding)
 */
void ro_gui_set_icon_sprite(wimp_w w, wimp_i i, osspriteop_area *area,
		const char *name)
{
	wimp_icon_state ic;
	os_error *error;

	/* get the icon data */
	ic.w = w;
	ic.i = i;
	error = xwimp_get_icon_state(&ic);
	if (error) {
		LOG("xwimp_get_icon_state: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		return;
	}

	/* copy the name across */
	if (ic.icon.data.indirected_text.size) {
		strncpy(ic.icon.data.indirected_text.text, name,
			(unsigned int)ic.icon.data.indirected_text.size - 1);
		ic.icon.data.indirected_text.text[
				ic.icon.data.indirected_text.size - 1] = '\0';
	}

	ic.icon.data.indirected_sprite.area = area;

	ro_gui_redraw_icon(w, i);
}


/**
 * Set a window title
 *
 * \param  w	 window handle
 * \param  text  new title (copied)
 */
void ro_gui_set_window_title(wimp_w w, const char *text)
{
	wimp_window_info_base window;
	os_error *error;
	char *title_local_enc;
	nserror err;

	/*	Get the window details
	*/
	window.w = w;
	error = xwimp_get_window_info_header_only((wimp_window_info *)&window);
	if (error) {
		LOG("xwimp_get_window_info: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		return;
	}

	/* convert text to local encoding */
	err = utf8_to_local_encoding(text, 0, &title_local_enc);
	if (err != NSERROR_OK) {
		/* A bad encoding should never happen,
		 * so assert this */
		assert(err != NSERROR_BAD_ENCODING);
		LOG("utf8_to_enc failed");
		return;
	}

	/*	Set the title string
	*/
	strncpy(window.title_data.indirected_text.text, title_local_enc,
			(unsigned int)window.title_data.indirected_text.size
					- 1);
	window.title_data.indirected_text.text[
			window.title_data.indirected_text.size - 1] = '\0';

	/*	Redraw accordingly
	*/
	error = xwimp_force_redraw_title(w);
	if (error) {
		LOG("xwimp_force_redraw_title: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		return;
	}

	free(title_local_enc);
}


/**
 * Places the caret in the first available icon
 *
 * \param w the window to place the caret in
 * \return true if the caret was placed, false otherwise
 */
bool ro_gui_set_caret_first(wimp_w w)
{
	int icon, b;
	wimp_window_state win_state;
	wimp_window_info_base window;
	wimp_icon_state state;
	os_error *error;

	/* check the window is open */
	win_state.w = w;
	error = xwimp_get_window_state(&win_state);
	if (error) {
		LOG("xwimp_get_window_state: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		return false;
	}
	if (!(win_state.flags & wimp_WINDOW_OPEN))
		return false;

	/* get the window details for the icon count */
	window.w = w;
	error = xwimp_get_window_info_header_only((wimp_window_info *)&window);
	if (error) {
		LOG("xwimp_get_window_info: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		return false;
	}

	/* work through all the icons */
	state.w = w;
	for (icon = 0; icon < window.icon_count; icon++) {
		state.i = icon;
		error = xwimp_get_icon_state(&state);
		if (error) {
			LOG("xwimp_get_icon_state: 0x%x: %s", error->errnum, error->errmess);
			ro_warn_user("WimpError", error->errmess);
			return false;
		}

		/* ignore if it's shaded or not writable */
		if (state.icon.flags & wimp_ICON_SHADED)
			continue;
		b = (state.icon.flags >> wimp_ICON_BUTTON_TYPE_SHIFT) & 0xf;
		if ((b != wimp_BUTTON_WRITE_CLICK_DRAG) &&
				(b != wimp_BUTTON_WRITABLE))
			continue;

		/* move the caret */
		error = xwimp_set_caret_position(w, icon, 0, 0, -1,
				strlen(state.icon.data.indirected_text.text));
		if (error) {
			LOG("xwimp_set_caret_position: 0x%x: %s", error->errnum, error->errmess);
			ro_warn_user("WimpError", error->errmess);
		}
		return true;
	}
	return false;
}


/**
 * Load a sprite file into memory.
 *
 * \param  pathname  file to load
 * \return  sprite area, or 0 on memory exhaustion or error and error reported
 */

osspriteop_area *ro_gui_load_sprite_file(const char *pathname)
{
	int len;
	fileswitch_object_type obj_type;
	osspriteop_area *area;
	os_error *error;

	error = xosfile_read_stamped_no_path(pathname,
			&obj_type, 0, 0, &len, 0, 0);
	if (error) {
		LOG("xosfile_read_stamped_no_path: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("MiscError", error->errmess);
		return 0;
	}
	if (obj_type != fileswitch_IS_FILE) {
		ro_warn_user("FileError", pathname);
		return 0;
	}

	area = malloc(len + 4);
	if (!area) {
		ro_warn_user("NoMemory", 0);
		return 0;
	}

	area->size = len + 4;
	area->sprite_count = 0;
	area->first = 16;
	area->used = 16;

	error = xosspriteop_load_sprite_file(osspriteop_USER_AREA,
			area, pathname);
	if (error) {
		LOG("xosspriteop_load_sprite_file: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("MiscError", error->errmess);
		free(area);
		return 0;
	}

	return area;
}


/**
 * Check if a sprite is present in the Wimp sprite pool.
 *
 * \param  sprite  name of sprite
 * \return  true if the sprite is present
 */

bool ro_gui_wimp_sprite_exists(const char *sprite)
{
	static char last_sprite_found[16];
	os_error *error;

	/* make repeated calls fast */
	if (!strncmp(sprite, last_sprite_found, sizeof(last_sprite_found)))
		return true;

	/* fallback if not known to exist */
	error = xwimpspriteop_select_sprite(sprite, 0);
	if (error) {
		if (error->errnum != error_SPRITE_OP_DOESNT_EXIST) {
			LOG("xwimpspriteop_select_sprite: 0x%x: %s", error->errnum, error->errmess);
			ro_warn_user("MiscError", error->errmess);
		}
		return false;
	}
  	snprintf(last_sprite_found, sizeof(last_sprite_found), sprite);
	return true;
}


/**
 * Locate a sprite in the Wimp sprite pool, returning a pointer to it.
 *
 * \param  name	   sprite name
 * \param  sprite  receives pointer to sprite if found
 * \return error ptr iff not found
 */

os_error *ro_gui_wimp_get_sprite(const char *name, osspriteop_header **sprite)
{
	osspriteop_area *rom_base, *ram_base;
	os_error *error;

	error = xwimp_base_of_sprites(&rom_base, &ram_base);
	if (error) return error;

	error = xosspriteop_select_sprite(osspriteop_USER_AREA,
			ram_base, (osspriteop_id)name, sprite);

	if (error && error->errnum == error_SPRITE_OP_DOESNT_EXIST)
		error = xosspriteop_select_sprite(osspriteop_USER_AREA,
				rom_base, (osspriteop_id)name, sprite);

	return error;
}


/**
 * Get the dimensions of a sprite
 *
 * \param *area			The sprite area to use.
 * \param *sprite		Pointer to the sprite name.
 * \param *width		Return the sprite width.
 * \param *height		Return the sprite height.
 * \return			true if successful; else false.
 */

bool ro_gui_wimp_get_sprite_dimensions(osspriteop_area *area, char *sprite,
		int *width, int *height)
{
	os_error			*error = NULL;
	os_mode				mode;
	os_coord			dimensions;

	dimensions.x = 0;
	dimensions.y = 0;

	if (area != (osspriteop_area *) -1)
		error = xosspriteop_read_sprite_info(osspriteop_USER_AREA,
				area, (osspriteop_id) sprite,
				&dimensions.x, &dimensions.y, 0, &mode);

	if (error != NULL || area == (osspriteop_area *) -1)
		error = xwimpspriteop_read_sprite_info(sprite,
				&dimensions.x, &dimensions.y, 0, &mode);

	if (error == NULL) {
		ro_convert_pixels_to_os_units(&dimensions, mode);
		if (width != NULL)
			*width = dimensions.x;
		if (height != NULL)
			*height = dimensions.y;
	} else if (error->errnum != error_SPRITE_OP_DOESNT_EXIST) {
		LOG("xosspriteop_read_sprite_info: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("MiscError", error->errmess);
		return false;
	}

	return true;
}


/**
 * Performs simple user redraw for a window.
 *
 * \param redraw  wimp draw 
 * \param user_fill  whether to fill the redraw area
 * \param user_colour  the colour to use when filling
 */

void ro_gui_user_redraw(wimp_draw *redraw, bool user_fill,
		os_colour user_colour)
{
	os_error *error;
	osbool more;

	error = xwimp_redraw_window(redraw, &more);
	if (error) {
		LOG("xwimp_redraw_window: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		return;
	}
	while (more) {
		if (user_fill) {
			error = xcolourtrans_set_gcol(user_colour,
					colourtrans_SET_BG_GCOL,
					os_ACTION_OVERWRITE, 0, 0);
			if (error) {
				LOG("xcolourtrans_set_gcol: 0x%x: %s", error->errnum, error->errmess);
				ro_warn_user("MiscError", error->errmess);
			}
			os_clg();
		}
		error = xwimp_get_rectangle(redraw, &more);
		if (error) {
			LOG("xwimp_get_rectangle: 0x%x: %s", error->errnum, error->errmess);
			ro_warn_user("WimpError", error->errmess);
			return;
		}
	}
}


/**
 * Sets whether a piece of window furniture is present for a window.
 *
 * \param  w	     the window to modify
 * \param  bic_mask  the furniture flags to clear
 * \param  xor_mask  the furniture flags to toggle
 */
void ro_gui_wimp_update_window_furniture(wimp_w w, wimp_window_flags bic_mask,
		wimp_window_flags xor_mask)
{
	wimp_window_state state;
	wimp_w parent;
	bits linkage;
	os_error *error;
	bool open;

	state.w = w;
	error = xwimp_get_window_state_and_nesting(&state, &parent, &linkage);
	if (error) {
		LOG("xwimp_get_window_state: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		return;
	}

	open = state.flags & wimp_WINDOW_OPEN;
	state.flags &= ~(63 << 16); /* clear bits 16-21 */
	state.flags &= ~bic_mask;
	state.flags ^= xor_mask;
	if (!open)
		state.next = wimp_HIDDEN;
	error = xwimp_open_window_nested_with_flags(&state, parent, linkage);
	if (error) {
		LOG("xwimp_open_window: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		return;
	}

	if (!open) {
		error = xwimp_close_window(w);
		if (error) {
			LOG("xwimp_close_window: 0x%x: %s", error->errnum, error->errmess);
			ro_warn_user("WimpError", error->errmess);
			return;
		}
	}
}


/**
 * Checks whether a piece of window furniture is present for a window.
 *
 * \param  w	     the window to modify
 * \param  mask	     the furniture flags to check
 */
bool ro_gui_wimp_check_window_furniture(wimp_w w, wimp_window_flags mask)
{
	wimp_window_state state;
	os_error *error;

	state.w = w;
	error = xwimp_get_window_state(&state);
	if (error) {
		LOG("xwimp_get_window_state: 0x%x: %s", error->errnum, error->errmess);
		ro_warn_user("WimpError", error->errmess);
		return false;
	}
	return state.flags & mask;
}

/**
 * RO GUI-specific strlen, for control character terminated strings
 *
 * \param str  The string to measure the length of
 * \return The length of the string
 */
size_t ro_gui_strlen(const char *str)
{
	const char *str_begin;

	if (str == NULL)
		return 0;

	for (str_begin = str; *str++ >= ' '; /* */)
		/* */;

	return str - str_begin - 1;
}

/**
 * RO GUI-specific strncmp, for control character terminated strings
 *
 * \param s1 The first string for comparison
 * \param s2 The second string for comparison
 * \param len Maximum number of bytes to be checked
 * \return 0 for equal strings up to len bytes; pos for s1 being bigger than
 * s2; neg for s1 being smaller than s2.
 */
int ro_gui_strncmp(const char *s1, const char *s2, size_t len)
{
	while (len--) {
		char c1 = *s1++;
		char c2 = *s2++;
		if (c1 < ' ' || c2 < ' ')
			return (c1 < ' ' ? 0 : c1) - (c2 < ' ' ? 0 : c2);
		int diff = c1 - c2;
		if (diff)
			return diff;
	}
	return 0;
}


/**
 * Generic window scroll event handler.
 *
 * \param  *scroll		Pointer to Scroll Event block.
 */

void ro_gui_scroll(wimp_scroll *scroll)
{
	os_error	*error;
	int		x = scroll->visible.x1 - scroll->visible.x0 - 32;
	int		y = scroll->visible.y1 - scroll->visible.y0 - 32;

	switch (scroll->xmin) {
	case wimp_SCROLL_PAGE_LEFT:
		scroll->xscroll -= x;
		break;
	case wimp_SCROLL_COLUMN_LEFT:
		scroll->xscroll -= 100;
		break;
	case wimp_SCROLL_COLUMN_RIGHT:
		scroll->xscroll += 100;
		break;
	case wimp_SCROLL_PAGE_RIGHT:
		scroll->xscroll += x;
		break;
	default:
		scroll->xscroll += (x * (scroll->xmin>>2)) >> 2;
		break;
	}

	switch (scroll->ymin) {
	case wimp_SCROLL_PAGE_UP:
		scroll->yscroll += y;
		break;
	case wimp_SCROLL_LINE_UP:
		scroll->yscroll += 100;
		break;
	case wimp_SCROLL_LINE_DOWN:
		scroll->yscroll -= 100;
		break;
	case wimp_SCROLL_PAGE_DOWN:
		scroll->yscroll -= y;
		break;
	default:
		scroll->yscroll += (y * (scroll->ymin>>2)) >> 2;
		break;
	}

	error = xwimp_open_window((wimp_open *) scroll);
	if (error) {
		LOG("xwimp_open_window: 0x%x: %s", error->errnum, error->errmess);
	}
}

