/*

	Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc.
	and the "Aleph One" developers.
 
	This program 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; either version 3 of the License, or
	(at your option) any later version.

	This program 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.

	This license is contained in the file "COPYING",
	which is included with this source code; it is available online at
	http://www.gnu.org/licenses/gpl.html

*/

/*
 *  sdl_widgets.cpp - Widgets for SDL dialogs
 *
 *  Written in 2000 by Christian Bauer
 *
 *  Sept-Nov. 2001 (Woody Zenfell):
 *      Significant extensions to support more dynamic behavior and more useful layouts
 *
 *  Mar 1, 2002 (Woody Zenfell):
 *      Moved w_levels here from shell_sdl; am using it in Setup Network Game box.
 *
 *  May 16, 2002 (Woody Zenfell):
 *      changes to w_key to support assignment of mouse buttons as well as keys;
 *      also fixed mouse-movement-while-binding behavior.
 *
 *  August 27, 2003 (Woody Zenfell):
 *	new w_enabling_toggle can enable/disable a bank of other widgets according to its state
 *	new w_file_chooser displays filename; allows selection of file (via FileSpecifier::ReadDialog())
 */

#include "cseries.h"
#include "sdl_dialogs.h"
#include "network_dialog_widgets_sdl.h"
#include "sdl_fonts.h"
#include "sdl_widgets.h"
#include "resource_manager.h"

#include "shape_descriptors.h"
#include "screen_drawing.h"
#include "images.h"
#include "shell.h"
#include "world.h"
#include "SoundManager.h"
#include "interface.h"
#include "player.h"

#include "screen.h"

// ZZZ: for stringset business for modified w_select
#include	"TextStrings.h"

#include    "mouse.h"   // (ZZZ) NUM_SDL_MOUSE_BUTTONS, SDLK_BASE_MOUSE_BUTTON
#include "joystick.h"

#include <sstream>

/*
 *  Widget base class
 */

// ZZZ: initialize my additional storage elements
// (thought: I guess "widget" could be simplified, and have a subclass "useful_widget" to handle most things
// other than spacers.  Spacers are common and I guess we're starting to eat a fair amount of storage for a
// widget that does nothing and draws nothing... oh well, at least RAM is cheap.  ;) )
widget::widget() : active(false), dirty(false), enabled(true), font(NULL), identifier(NONE), owning_dialog(NULL), saved_min_width(0), saved_min_height(0), associated_label(0)
{
    rect.x = 0;
    rect.y = 0;
    rect.w = 0;
    rect.h = 0;
}

widget::widget(int theme_widget) : active(false), dirty(false), enabled(true), font(get_theme_font(theme_widget, style)), identifier(NONE), owning_dialog(NULL), saved_min_width(0), saved_min_height(0), associated_label(0)
{
    rect.x = 0;
    rect.y = 0;
    rect.w = 0;
    rect.h = 0;
}

void widget::associate_label(w_label *label)
{
	associated_label = label;
}

w_label *widget::label(const char *text)
{
	if (!associated_label)
	{
		associated_label = new w_label(text);
		associated_label->associate_widget(this);
	}

	return associated_label;
}

// ZZZ: enable/disable
void widget::set_enabled(bool inEnabled)
{
	if(enabled != inEnabled) {
		enabled = inEnabled;
		
		// If we had the focus when we were disabled, we should not have the focus afterward.
		if(active && !enabled)
			owning_dialog->activate_next_widget();
		
		// Assume we need a redraw to reflect new state
		dirty = true;
		
		if (associated_label)
			associated_label->set_enabled(inEnabled);
	}
}

void widget::place(const SDL_Rect &r, placement_flags flags)
{
	rect.h = r.h;
	rect.y = r.y;

	if (flags & placeable::kFill)
	{
		rect.x = r.x;
		rect.w = r.w;
	}
	else
	{
		rect.w = saved_min_width;
		if (flags & placeable::kAlignLeft)
		{
			rect.x = r.x;
		}
		else if (flags & placeable::kAlignRight)
		{
			rect.x = r.x + r.w - saved_min_width;
		}
		else
		{
			rect.x = r.x + (r.w - saved_min_width) / 2;
		}
	}
}


/*
 *  Static text
 */

// ZZZ change: copy the given string instead of just pointing to it.  Much easier for messages that change.
w_static_text::w_static_text(const char *t, int _theme_type) : widget(_theme_type), theme_type(_theme_type)
{
        text = strdup(t);
	rect.w = text_width(text, font, style);
	rect.h = font->get_line_height();
	saved_min_height = rect.h;
	saved_min_width = rect.w;
}

void w_static_text::draw(SDL_Surface *s) const
{
	uint32 pixel;
	pixel = get_theme_color(theme_type, DEFAULT_STATE, 0);

	draw_text(s, text, rect.x, rect.y + font->get_ascent(), pixel, font, style);
}

void w_label::click(int x, int y)
{
	if (associated_widget)
		associated_widget->click(0, 0);

}

void w_label::draw(SDL_Surface *s) const
{
	int state = enabled ? (active ? ACTIVE_STATE : DEFAULT_STATE) : DISABLED_STATE;
	uint16 style = 0;
	draw_text(s, text, rect.x, rect.y + font->get_ascent() + (rect.h - font->get_line_height()) / 2, get_theme_color(LABEL_WIDGET, state, FOREGROUND_COLOR), font, style);
}

// ZZZ addition: change text.
void
w_static_text::set_text(const char* t) {
    free(text);
    text = strdup(t);
    dirty = true;
}

// ZZZ addition: free text buffer.
w_static_text::~w_static_text() {
    free(text);
}

w_styled_text::w_styled_text(const char *t, int _theme_type) : w_static_text(t, _theme_type), text_string(t) {
	rect.w = font->styled_text_width(text_string, text_string.size(), style);
	saved_min_width = rect.w;
}

void w_styled_text::set_text(const char* t)
{
	w_static_text::set_text(t);
	text_string = t;
	// maybe reset rect.width here, but parent w_static_text doesn't, so we won't either
}

void w_styled_text::draw(SDL_Surface *s) const
{
	uint32 pixel;
	pixel = get_theme_color(theme_type, DEFAULT_STATE, 0);

	font->draw_styled_text(s, text_string, text_string.size(), rect.x, rect.y + font->get_ascent(), pixel, style);
}

/*
 *  Picture (PICT resource)
 */

w_pict::w_pict(int id)
{
	LoadedResource rsrc;
	get_resource(FOUR_CHARS_TO_INT('P', 'I', 'C', 'T'), id, rsrc);
	picture = picture_to_surface(rsrc);
	if (picture) {
		rect.w = static_cast<uint16>(picture->w);
		rect.h = static_cast<uint16>(picture->h);
		SDL_SetColorKey(picture, SDL_SRCCOLORKEY, SDL_MapRGB(picture->format, 0xff, 0xff, 0xff));
	} else
		rect.w = rect.h = 0;
}

w_pict::~w_pict()
{
	if (picture)
		SDL_FreeSurface(picture);
}

void w_pict::draw(SDL_Surface *s) const
{
	if (picture)
		SDL_BlitSurface(picture, NULL, s, const_cast<SDL_Rect *>(&rect));
}


/*
 *  Button
 */

w_button_base::w_button_base(const char *t, action_proc p, void *a, int _type) : widget(_type), text(t), proc(p), arg(a), down(false), pressed(false), type(_type)
{
	rect.w = text_width(text.c_str(), font, style) + get_theme_space(_type, BUTTON_L_SPACE) + get_theme_space(_type, BUTTON_R_SPACE);
	button_c_default = get_theme_image(_type, DEFAULT_STATE, BUTTON_C_IMAGE, rect.w - get_theme_image(_type, DEFAULT_STATE, BUTTON_L_IMAGE)->w - get_theme_image(_type, DEFAULT_STATE, BUTTON_R_IMAGE)->w);
	button_c_active = get_theme_image(_type, ACTIVE_STATE, BUTTON_C_IMAGE, rect.w - get_theme_image(_type, ACTIVE_STATE, BUTTON_L_IMAGE)->w - get_theme_image(_type, ACTIVE_STATE, BUTTON_R_IMAGE)->w);
	button_c_disabled = get_theme_image(_type, DISABLED_STATE, BUTTON_C_IMAGE, rect.w - get_theme_image(_type, DISABLED_STATE, BUTTON_L_IMAGE)->w - get_theme_image(_type, DISABLED_STATE, BUTTON_R_IMAGE)->w);
	button_c_pressed = get_theme_image(_type, PRESSED_STATE, BUTTON_C_IMAGE, rect.w - get_theme_image(_type, PRESSED_STATE, BUTTON_L_IMAGE)->w - get_theme_image(_type, PRESSED_STATE, BUTTON_R_IMAGE)->w);

	rect.h = static_cast<uint16>(get_theme_space(_type, BUTTON_HEIGHT));

	saved_min_width = rect.w;
	saved_min_height = rect.h;
}

w_button_base::~w_button_base()
{
	if (button_c_default) SDL_FreeSurface(button_c_default);
	if (button_c_active) SDL_FreeSurface(button_c_active);
	if (button_c_disabled) SDL_FreeSurface(button_c_disabled);
	if (button_c_pressed) SDL_FreeSurface(button_c_pressed);
}

void w_button_base::set_callback(action_proc p, void *a)
{
	proc = p;
	arg = a;
}

void w_button_base::draw(SDL_Surface *s) const
{
	// Label (ZZZ: different color for disabled)
	int state = DEFAULT_STATE;
	if (pressed)
		state = PRESSED_STATE;
	else if (!enabled)
		state = DISABLED_STATE;
	else if (active)
		state = ACTIVE_STATE;
		
	if (use_theme_images(type))
	{
		SDL_Surface *button_l = get_theme_image(type, state, BUTTON_L_IMAGE);
		SDL_Surface *button_r = get_theme_image(type, state, BUTTON_R_IMAGE);
		SDL_Surface *button_c = button_c_default;
		if (pressed)
			button_c = button_c_pressed;
		else if (!enabled)
			button_c = button_c_disabled;
		else if (active)
			button_c = button_c_active;

		// Button image
		SDL_Rect r = {rect.x, rect.y, 
			      static_cast<Uint16>(button_l->w), 
			      static_cast<Uint16>(button_l->h)};
		SDL_BlitSurface(button_l, NULL, s, &r);
		r.x = r.x + static_cast<Sint16>(button_l->w); // MDA: MSVC throws warnings if we use +=
		r.w = static_cast<Uint16>(button_c->w);
		r.h = static_cast<Uint16>(button_c->h);
		SDL_BlitSurface(button_c, NULL, s, &r);
		r.x = r.x + static_cast<Sint16>(button_c->w);
		r.w = static_cast<Uint16>(button_r->w);
		r.h = static_cast<Uint16>(button_r->h);
		SDL_BlitSurface(button_r, NULL, s, &r);
	}
	else
	{
		uint32 pixel;
		if (use_theme_color(type, BACKGROUND_COLOR))
		{
			uint32 pixel = get_theme_color(type, state, BACKGROUND_COLOR);
			SDL_Rect r = {rect.x + 1, rect.y + 1, rect.w - 2, rect.h - 2};
			SDL_FillRect(s, &r, pixel);
		}

		pixel = get_theme_color(type, state, FRAME_COLOR);
		draw_rectangle(s, &rect, pixel);
	}

	draw_text(s, text.c_str(), rect.x + get_theme_space(type, BUTTON_L_SPACE),
		  rect.y + get_theme_space(type, BUTTON_T_SPACE) + font->get_ascent(),
		  get_theme_color(type, state), font, style);
}

void w_button_base::mouse_move(int x, int y)
{
	if (down)
	{
		if (x >= 0 && x <= rect.w && y >= 0 && y <= rect.h)
		{
			if (!pressed)
				dirty = true;
			pressed = true;
		}
		else
		{
			if (pressed)
				dirty = true;
			pressed = false;
		}
		get_owning_dialog()->draw_dirty_widgets();
	}
}

void w_button_base::mouse_down(int, int)
{
	if (!enabled) return;
	down = true;
	pressed = true;
	dirty = true;
	get_owning_dialog()->draw_dirty_widgets();
}

void w_button_base::mouse_up(int x, int y)
{
	if (!enabled) return;

	down = false;
	pressed = false;
	dirty = true;
	get_owning_dialog()->draw_dirty_widgets();
	
	if (proc && x >= 0 && x <= rect.w && y >= 0 && y <= rect.h)
		proc(arg);
}

void w_button_base::click(int /*x*/, int /*y*/)
{
	// simulate a mouse press
	mouse_down(0, 0);
	SDL_Delay(1000 / 12);
	mouse_up(0, 0);
}

/*
 * Clickable link
 */

void w_hyperlink::prochandler(void *arg)
{
	toggle_fullscreen(false);
	launch_url_in_browser(static_cast<const char *>(arg));
	get_owning_dialog()->draw();
}

w_hyperlink::w_hyperlink(const char *url, const char *txt) : w_button_base((txt ? txt : url), boost::bind(&w_hyperlink::prochandler, this, _1), const_cast<char *>(url), HYPERLINK_WIDGET)
{
	rect.w = text_width(text.c_str(), font, style);
	rect.h = font->get_line_height();
	saved_min_height = rect.h;
	saved_min_width = rect.w;
}

void w_hyperlink::draw(SDL_Surface *s) const
{
	int state = DEFAULT_STATE;
	if (pressed)
		state = PRESSED_STATE;
	else if (!enabled)
		state = DISABLED_STATE;
	else if (active)
		state = ACTIVE_STATE;
	
	uint32 pixel = get_theme_color(HYPERLINK_WIDGET, state, 0);
	
	draw_text(s, text.c_str(), rect.x, rect.y + font->get_ascent(), pixel, font, style);
	
	// draw_text doesn't support underline, so draw one manually
	if (style & styleUnderline)
	{
		SDL_Rect r = {rect.x, rect.y + rect.h - 1, rect.w, 1};
		SDL_FillRect(s, &r, pixel);
	}
}


/* 
 * Tabs
 */

w_tab::w_tab(const vector<string>& _labels, tab_placer *_placer) : widget(TAB_WIDGET), labels(_labels), placer(_placer), active_tab(1), pressed_tab(0)
{
	saved_min_height = get_theme_space(TAB_WIDGET, BUTTON_HEIGHT);
	for (vector<string>::iterator it = labels.begin(); it != labels.end(); ++it)
	{
		int l_space = (it == labels.begin()) ? get_theme_space(TAB_WIDGET, BUTTON_L_SPACE) : get_theme_space(TAB_WIDGET, TAB_LC_SPACE);
		int r_space = (it == labels.end() - 1) ? get_theme_space(TAB_WIDGET, BUTTON_R_SPACE) : get_theme_space(TAB_WIDGET, TAB_RC_SPACE);
		int width = l_space + r_space + font->text_width(it->c_str(), style);
		widths.push_back(width);
		saved_min_width += width;

		// load center images
		const int states[] = { DEFAULT_STATE, PRESSED_STATE, ACTIVE_STATE, DISABLED_STATE };
		images.resize(PRESSED_STATE + 1);
		for (int i = 0; i < 4; ++i)
		{
			int li_space = (it == labels.begin()) ? get_theme_image(TAB_WIDGET, states[i], TAB_L_IMAGE)->w : get_theme_image(TAB_WIDGET, states[i], TAB_LC_IMAGE)->w;
			int ri_space = (it == labels.end() - 1) ? get_theme_image(TAB_WIDGET, states[i], TAB_R_IMAGE)->w : get_theme_image(TAB_WIDGET, states[i], TAB_RC_IMAGE)->w;
			int c_space = width - li_space - ri_space;
			images[states[i]].push_back(get_theme_image(TAB_WIDGET, states[i], TAB_C_IMAGE, c_space));
		}
	}

}

w_tab::~w_tab()
{
	for (std::vector<std::vector<SDL_Surface *> >::iterator it = images.begin(); it != images.end(); ++it)
	{
		for (std::vector<SDL_Surface *>::iterator it2 = it->begin(); it2 != it->end(); ++it2)
		{
			SDL_FreeSurface(*it2);
		}
	}
}

void w_tab::draw(SDL_Surface *s) const 
{
	int x = rect.x;
	for (int i = 0; i < labels.size(); ++i)
	{
		int state;
		if (!enabled)
			state = DISABLED_STATE;
		else if (i == pressed_tab)
			state = PRESSED_STATE;
		else if (active && i == active_tab)
			state = ACTIVE_STATE;
		else
			state = DEFAULT_STATE;

		int l_space;
		int l_offset = 0;

		SDL_Surface *l_image;
		if (i == 0)
		{
			l_space = get_theme_space(TAB_WIDGET, BUTTON_L_SPACE);
			l_offset = 1;
			l_image = get_theme_image(TAB_WIDGET, state, TAB_L_IMAGE);
		}
		else
		{
			l_space = get_theme_space(TAB_WIDGET, TAB_LC_SPACE);
			l_image = get_theme_image(TAB_WIDGET, state, TAB_LC_IMAGE);
		}

		int r_space;
		int r_offset = 0;
		SDL_Surface *r_image;
		if (i == labels.size() - 1)
		{
			r_space = get_theme_space(TAB_WIDGET, BUTTON_R_SPACE);
			r_offset = 1;
			r_image = get_theme_image(TAB_WIDGET, state, TAB_R_IMAGE);
		}
		else
		{
			r_space = get_theme_space(TAB_WIDGET, TAB_RC_SPACE);
			r_image = get_theme_image(TAB_WIDGET, state, TAB_RC_IMAGE);
		}

		int c_space;
		SDL_Surface *c_image = images[state][i];
		c_space = font->text_width(labels[i].c_str(), style);

		if (use_theme_images(TAB_WIDGET))
		{
			SDL_Rect r = { x, rect.y, static_cast<Uint16>(l_image->w), static_cast<Uint16>(l_image->h) };
			SDL_BlitSurface(l_image, NULL, s, &r);
			r.x = r.x + static_cast<Sint16>(l_image->w);
			r.w = static_cast<Uint16>(c_image->w);
			r.h = static_cast<Uint16>(c_image->h);
			SDL_BlitSurface(c_image, NULL, s, &r);
			r.x = r.x + static_cast<Sint16>(c_image->w);
			r.w = static_cast<Uint16>(r_image->w);
			r.h = static_cast<Uint16>(r_image->h);
			SDL_BlitSurface(r_image, NULL, s, &r);
		}
		else
		{
			if (use_theme_color(TAB_WIDGET, BACKGROUND_COLOR))
			{
				SDL_Rect r = { x + l_offset, rect.y + 1, l_space + c_space + r_space - l_offset - r_offset, get_theme_space(TAB_WIDGET, BUTTON_HEIGHT) - 2 };
				uint32 pixel = get_theme_color(TAB_WIDGET, state, BACKGROUND_COLOR);
				SDL_FillRect(s, &r, pixel);
			}
		}

		font->draw_text(s, labels[i].c_str(), labels[i].size(), x + l_space, rect.y + get_theme_space(TAB_WIDGET, BUTTON_T_SPACE) + font->get_ascent(), get_theme_color(TAB_WIDGET, state, FOREGROUND_COLOR), style);

		x += l_space + c_space + r_space;
	}

	if (!use_theme_images(TAB_WIDGET))
	{
		uint32 pixel = get_theme_color(TAB_WIDGET, DEFAULT_STATE, FRAME_COLOR);
		// draw the frame
		SDL_Rect frame = { rect.x, rect.y, x - rect.x, get_theme_space(TAB_WIDGET, BUTTON_HEIGHT) };
		draw_rectangle(s, &frame, pixel);
	}
}

void w_tab::choose_tab(int i)
{
	pressed_tab = i;
	active_tab = (i + 1) % labels.size();
	placer->choose_tab(i);
	get_owning_dialog()->draw();
}

void w_tab::click(int x, int y)
{
	if (enabled)
	{
		if (!x && !y)
		{
			choose_tab(active_tab);
		}
		else
		{
			int width = 0;
			for (int i = 0; i < labels.size(); ++i)
			{
				if (x > width && x < width + widths[i])
				{
					choose_tab(i);
					return;
				}

				width += widths[i];
			}
		}
	}
}

void w_tab::event(SDL_Event& e)
{
	if (e.type == SDL_KEYDOWN)
	{
		switch (e.key.keysym.sym) {
		case SDLK_LEFT:
			if (active_tab > 0)
			{
				if (active_tab - 1== pressed_tab)
				{
					if (pressed_tab > 0)
						active_tab -= 2;
				}
				else
					active_tab--;

			}
			dirty = true;
			e.type = SDL_NOEVENT;
			break;

		case SDLK_RIGHT:
			if (active_tab < labels.size() - 1)
			{
				if (active_tab + 1 == pressed_tab)
				{
					if (pressed_tab < labels.size() - 1)
						active_tab += 2;
				}
				else
					active_tab++;
			}
			dirty = true;
			e.type = SDL_NOEVENT;
			break;
		
		default:
			break;

		}
	}
}

/*
 *  Selection button
 */

const uint16 MAX_TEXT_WIDTH = 200;

// ZZZ: how come we have to do this?  because of that "&" in the typedef for action_proc?
// Anyway, this fixes the "crash when clicking in the Environment menu" bug we've seen
// in the Windows version all this time.
w_select_button::w_select_button(const char *s, action_proc p, void *a, bool u)
	: widget(LABEL_WIDGET), selection(s), proc(p), arg(a), utf8(u), p_flags(placeable::kDefault)
{
	uint16 max_selection_width = MAX_TEXT_WIDTH;

	saved_min_width = max_selection_width;
	saved_min_height = font->get_line_height();
}



void w_select_button::draw(SDL_Surface *s) const
{
	int y = rect.y + font->get_ascent();
	
	// Selection (ZZZ: different color for disabled)
	set_drawing_clip_rectangle(0, rect.x + selection_x, static_cast<uint16>(s->h), rect.x + rect.w);
	
	int state = enabled ? (active ? ACTIVE_STATE : DEFAULT_STATE) : DISABLED_STATE;
	
	draw_text(s, selection, rect.x + selection_x, y, get_theme_color(ITEM_WIDGET, state), font, style, utf8);
	set_drawing_clip_rectangle(SHRT_MIN, SHRT_MIN, SHRT_MAX, SHRT_MAX);
	
	// Cursor
	if (active) {
		//!!
	}
}

void w_select_button::click(int /*x*/, int /*y*/)
{
    if(enabled)
	    proc(arg);
}

void w_select_button::set_selection(const char *s)
{
	selection = s;
	if (p_flags & placeable::kAlignRight)
	{
		selection_x = rect.w - text_width(selection, font, style);
	}
	dirty = true;
}

void w_select_button::place(const SDL_Rect &r, placement_flags flags)
{
	rect.h = r.h;
	rect.y = r.y;

	rect.x = r.x;
	rect.w = r.w;
	p_flags = flags;

	if (flags & placeable::kAlignRight)
	{
		selection_x = rect.w - text_width(selection, font, style);
	}
	else
	{
		selection_x = 0;
	}

}

/*
 *  Selection widget (base class)
 */

// ZZZ: change of behavior/semantics:
// if passed an invalid selection, reset to a VALID one (i.e. 0)
// if no valid labels, returns -1 when asked for selection
// draw(), get_selection() check num_labels directly instead of trying to keep selection set at -1

static const char* sNoValidOptionsString = "(no valid options)"; // XXX should be moved outside compiled code e.g. to MML

w_select::w_select(size_t s, const char **l) : widget(LABEL_WIDGET), labels(l), we_own_labels(false), selection(s), selection_changed_callback(NULL), utf8(false)
{
	num_labels = 0;
        if(labels) {
            while (labels[num_labels])
                    num_labels++;
            if (selection >= num_labels || selection < 0)
                    selection = 0;
        }

	saved_min_height = font->get_line_height();
}


w_select::~w_select() {
    if(we_own_labels && labels)
        free(labels);
}

void w_select::place(const SDL_Rect& r, placement_flags flags)
{
	rect.h = r.h;
	rect.y = r.y;
	rect.x = r.x;
	rect.w = r.w;

}

int w_select::min_width()
{
	return get_largest_label_width();
}

void w_select::draw(SDL_Surface *s) const
{
	int y = rect.y + font->get_ascent() + (rect.h - font->get_line_height()) / 2;

	// Selection (ZZZ: different color for disabled)
	const char *str = (num_labels > 0 ? labels[selection] : sNoValidOptionsString);

    int state = enabled ? (active ? ACTIVE_STATE : DEFAULT_STATE) : DISABLED_STATE;

    draw_text(s, str, rect.x, y, get_theme_color(ITEM_WIDGET, state), font, style, utf8);

	// Cursor
	if (active) {
		//!!
	}
}

void w_select::click(int /*x*/, int /*y*/)
{
    if(enabled) {
	    selection++;
	    if (selection >= num_labels)
		    selection = 0;

	    selection_changed();
    }
}

void w_select::event(SDL_Event &e)
{
	if (e.type == SDL_KEYDOWN) {
		if (e.key.keysym.sym == SDLK_LEFT) {
			if (num_labels == 0)
				selection = 0;
			else if (selection == 0)
				selection = num_labels - 1;
			else
				selection--;
			selection_changed();
			e.type = SDL_NOEVENT;	// Swallow event
		} else if (e.key.keysym.sym == SDLK_RIGHT) {
			if (selection >= num_labels - 1)
				selection = 0;
			else
				selection++;
			selection_changed();
			e.type = SDL_NOEVENT;	// Swallow event
		}
	}
}

void w_select::set_selection(size_t s, bool simulate_user_input /* default: false */)
{
	//if (s >= num_labels || s < 0)
	//	s = 0;
	assert(s == PIN(s, 0, num_labels - 1));

    if(selection != s) {
        selection = s;

        if(simulate_user_input)
            selection_changed();
    }

	dirty = true;
}

// ZZZ: change labels after creation
void w_select::set_labels(const char** inLabels) {
    if(we_own_labels && labels != NULL)
        free(labels);

    labels = inLabels;
    num_labels = 0;
    if(labels) {
        while(inLabels[num_labels])
            num_labels++;
    }
    we_own_labels = false;
	if (selection >= num_labels)
		set_selection(0);
	else
		set_selection(selection);

    // Hope new labels have same max width as old, or that user called set_full_width() on us.
}


// ZZZ: set labels according to stringset
void w_select::set_labels_stringset(short inStringSetID) {
    // free old label pointers, if appropriate
    if(we_own_labels && labels != NULL)
        free(labels);

    // see if we need space for label pointers.  if so, allocate and fill them in.
    if(TS_IsPresent(inStringSetID) && TS_CountStrings(inStringSetID) > 0) {
        num_labels = TS_CountStrings(inStringSetID);
        
        // Allocate one extra pointer slot to fill with NULL
        // Note: this works right if the string count is 0... but we check anyway, can save a malloc/free.
        labels = (const char**)malloc(sizeof(const char*) * (num_labels + 1));
        labels[num_labels] = NULL;
        
		for(size_t i = 0; i < num_labels; i++) {
            // shared references should be OK, stringsets ought to be pretty stable.  No need to copy...
            labels[i] = TS_GetCString(inStringSetID, i);
        }
        
        // we allocated; we free.
        we_own_labels = true;
    }
    else {	// no stringset or no strings in stringset
        labels = NULL;
        num_labels = 0;
        we_own_labels = false;
    }
    
	if (selection >= num_labels)
		set_selection(0);
	else
		set_selection(selection);
    
    // Hope new labels have same max width as old, or that user called set_full_width() on us.
}

void w_select::selection_changed(void)
{
	play_dialog_sound(DIALOG_CLICK_SOUND);
	dirty = true;

        // ZZZ: call user-specified callback
        if(selection_changed_callback != NULL)
            selection_changed_callback(this);
}


// ZZZ addition
uint16 w_select::get_largest_label_width() {
    uint16 max_label_width = 0;
    for (size_t i=0; i<num_labels; i++) {
            uint16 width = text_width(labels[i], font, style, utf8);
            if (width > max_label_width)
                    max_label_width = width;
    }

    // ZZZ: account for "no valid options" string
    if(num_labels <= 0)
	    max_label_width = text_width(sNoValidOptionsString, font, style, utf8);
    
    return max_label_width;
}


/*
 *  On-off toggle
 */

#ifdef HAVE_SDL_TTF
const char *w_toggle::onoff_labels[] = {"\342\230\220", "\342\230\221", NULL };
#else
const char *w_toggle::onoff_labels[] = {"Off", "On", NULL};
#endif

w_toggle::w_toggle(bool selection, const char **labels) : w_select(selection, labels) {
	if (labels == onoff_labels && use_theme_images(CHECKBOX))
	{
		saved_min_height = get_theme_space(CHECKBOX, BUTTON_HEIGHT);
	}
#ifdef HAVE_SDL_TTF
	else if (labels == onoff_labels)
	{
		labels_are_utf8(true);
		font = get_theme_font(CHECKBOX, style);
		saved_min_height = get_theme_space(CHECKBOX, BUTTON_HEIGHT);
	}
#endif
}

void w_toggle::draw(SDL_Surface *s) const
{
	// Selection (ZZZ: different color for disabled)
	const char *str = (num_labels > 0 ? labels[selection] : sNoValidOptionsString);

	int state = enabled ? (active ? ACTIVE_STATE : DEFAULT_STATE) : DISABLED_STATE;

	if (labels == onoff_labels && use_theme_images(CHECKBOX))
	{
		SDL_Surface *image = get_theme_image(CHECKBOX, state, selection);
		SDL_Rect r = { rect.x, rect.y + (rect.h - saved_min_height) / 2 + get_theme_space(CHECKBOX, BUTTON_T_SPACE), image->w, image->h };
		SDL_BlitSurface(image, 0, s, &r); 
	}
#ifdef HAVE_SDL_TTF
	else if (labels == onoff_labels)
	{
		draw_text(s, str, rect.x, rect.y + (rect.h - saved_min_height) / 2 + get_theme_space(CHECKBOX, BUTTON_T_SPACE), get_theme_color(LABEL_WIDGET, state, FOREGROUND_COLOR), font, style, utf8);
	}
#endif
	else
	{
		draw_text(s, str, rect.x, rect.y + font->get_ascent(), get_theme_color(ITEM_WIDGET, state), font, style, utf8);
	}
	
	// Cursor
	if (active) {
		//!!
	}
}

int w_toggle::min_width()
{
	if (labels == onoff_labels && use_theme_images(CHECKBOX))
	{
		return get_theme_image(CHECKBOX, DEFAULT_STATE, 0)->w;
	}
	else
	{
		return w_select::min_width();
	}
}


/*
 *	Enabling toggle (ZZZ)
 *
 *	Can enable/disable a bank of other widgets according to its state
 */


w_enabling_toggle::w_enabling_toggle(bool inSelection, bool inEnablesWhenOn, const char** inLabels)
	: w_toggle(inSelection, inLabels), enables_when_on(inEnablesWhenOn)
{
}


void
w_enabling_toggle::selection_changed()
{
	w_toggle::selection_changed();

	for(DependentCollection::iterator i = dependents.begin(); i != dependents.end(); i++)
		update_widget_enabled(*i);
}

	
/*
 *  Player color selection
 */

w_player_color::w_player_color(int selection) : w_select(selection, NULL)
{
	set_labels_stringset(kTeamColorsStringSetID);
}

void w_player_color::draw(SDL_Surface *s) const
{
	int y = rect.y + font->get_ascent();

	// Selection
	if (selection >= 0) {
		uint32 pixel = get_dialog_player_color(selection);
		SDL_Rect r = {rect.x, rect.y + 1, 48, rect.h - 2};
		SDL_FillRect(s, &r, pixel);
	} else {
		int state = enabled ? (active ? ACTIVE_STATE : DEFAULT_STATE) : DISABLED_STATE;

		draw_text(s, "<unknown>", rect.x, y, get_theme_color(ITEM_WIDGET, state), font, style);
	}

	// Cursor
	if (active)	{
		//!!
	}
}

class w_color_block : public widget {
public:
	w_color_block(const rgb_color *color) : m_color(color) { 
		saved_min_height = 64;
		saved_min_width = 64;
	}

	void draw(SDL_Surface *s) const {
		uint32 pixel = SDL_MapRGB(s->format, m_color->red >> 8, m_color->green >> 8, m_color->blue >> 8);
		SDL_Rect r = { rect.x, rect.y, 64, 64 };
		SDL_FillRect(s, &r, pixel);
	}

	bool is_dirty() { return true; }
private:
	const rgb_color *m_color;
};


void w_color_picker::click(int, int)
{
	if (!enabled) return;
	dialog d;
	
	vertical_placer *placer = new vertical_placer;
	placer->dual_add(new w_title("CHOOSE A COLOR"), d);
	placer->add(new w_spacer(), true);

	w_color_block *color_block = new w_color_block(&m_color);
	placer->dual_add(color_block, d);

	table_placer *table = new table_placer(2, get_theme_space(ITEM_WIDGET));
	table->col_flags(0, placeable::kAlignRight);

	w_slider *red_w = new w_slider(16, m_color.red >> 12);
	table->dual_add(red_w->label("Red"), d);
	table->dual_add(red_w, d);

	w_slider *green_w = new w_slider(16, m_color.green >> 12);
	table->dual_add(green_w->label("Green"), d);
	table->dual_add(green_w, d);

	w_slider *blue_w = new w_slider(16, m_color.blue >> 12);
	table->dual_add(blue_w->label("Blue"), d);
	table->dual_add(blue_w, d);

	placer->add(table, true);
	placer->add(new w_spacer(), true);
	
	horizontal_placer *button_placer = new horizontal_placer;
	button_placer->dual_add(new w_button("CANCEL", dialog_cancel, &d), d);
	button_placer->dual_add(new w_button("OK", dialog_ok, &d), d);
	
	placer->add(button_placer, true);

	rgb_color old_color = m_color;

	d.set_widget_placer(placer);
	d.set_processing_function(w_color_picker::update_color(red_w, green_w, blue_w, &m_color.red, &m_color.green, &m_color.blue));

	if (d.run() == 0)
	{
		m_color.red = red_w->get_selection() << 12;
		m_color.green = green_w->get_selection() << 12;
		m_color.blue = blue_w->get_selection() << 12;

		dirty = true;
		get_owning_dialog()->draw_dirty_widgets();
	}
	else
	{
		m_color = old_color;
	}
}

void w_color_picker::draw(SDL_Surface *s) const
{
	uint32 pixel = SDL_MapRGB(s->format, m_color.red >> 8, m_color.green >> 8, m_color.blue >> 8);
	SDL_Rect r = {rect.x, rect.y + 1, 48, rect.h - 2 };
	SDL_FillRect(s, &r, pixel);
}

/*
 *  Text entry widget
 */

w_text_entry::w_text_entry(size_t max_c, const char *initial_text)
	: widget(TEXT_ENTRY_WIDGET), enter_pressed_callback(NULL), value_changed_callback(NULL), max_chars(max_c), enable_mac_roman(false)
{
	// Initialize buffer
	buf = new char[max_chars + 1];
	set_text(initial_text);

	saved_min_width = MAX_TEXT_WIDTH;

	saved_min_height =  (int16) font->get_ascent() + font->get_descent() + font->get_leading();
}

w_text_entry::~w_text_entry()
{
	delete[] buf;
}

void w_text_entry::place(const SDL_Rect& r, placement_flags flags)
{
	rect.h = (int16) font->get_ascent() + (int16) font->get_descent() + font->get_leading();

	rect.y = r.y + (r.h - rect.h) / 2;

	text_x = 0;
	rect.x = r.x;
	rect.w = r.w;
	max_text_width = rect.w;
}

void w_text_entry::draw(SDL_Surface *s) const
{
  int y = rect.y + font->get_ascent();
  
  int16 theRectX = rect.x;
  uint16 theRectW = rect.w;
  int16 theTextX = text_x;
  
  // Text
  int16 x = theRectX + theTextX;
  uint16 width = text_width(buf, font, style);
  if (width > max_text_width)
    x -= width - max_text_width;
  set_drawing_clip_rectangle(0, theRectX + theTextX, static_cast<uint16>(s->h), theRectX + theRectW);
  
  int state = enabled ? (active ? ACTIVE_STATE : DEFAULT_STATE) : DISABLED_STATE;
  
  draw_text(s, buf, x, y, get_theme_color(TEXT_ENTRY_WIDGET, state), font, style);
  set_drawing_clip_rectangle(SHRT_MIN, SHRT_MIN, SHRT_MAX, SHRT_MAX);
  
  // Cursor
  if (active) {
	  width = text_width(buf, cursor_position, font, style);
	  SDL_Rect r = {x + width - (width ? 1 : 0), rect.y, 1, rect.h};
	  SDL_FillRect(s, &r, get_theme_color(TEXT_ENTRY_WIDGET, CURSOR_STATE));
  }
}

void w_text_entry::set_active(bool new_active) {
	if (new_active && !active)
		cursor_position = num_chars;
	widget::set_active(new_active);
}

void w_text_entry::event(SDL_Event &e)
{
	if (e.type == SDL_KEYDOWN) {
		switch (e.key.keysym.sym) {
			case SDLK_LEFT:
				if (cursor_position > 0) {
					cursor_position--;
					dirty = true;
				}
				e.type = SDL_NOEVENT;
				break;
			case SDLK_RIGHT:
				if (cursor_position < num_chars) {
					cursor_position++;
					dirty = true;
				}
				e.type = SDL_NOEVENT;	// Swallow event
				break;
                                
                        case SDLK_RETURN:
                        case SDLK_KP_ENTER:
                                if(enter_pressed_callback)
                                    enter_pressed_callback(this);
                        
                                e.type = SDL_NOEVENT;	// Swallow event (shouldn't typing do this also??)
                                break;

			case SDLK_BACKSPACE:	// Backspace deletes character at cursor
backspace:		if (num_chars && cursor_position) {
					memmove(&buf[cursor_position - 1], &buf[cursor_position], num_chars - cursor_position);
					buf[--num_chars] = 0;
					--cursor_position;
					modified_text();
					play_dialog_sound(DIALOG_DELETE_SOUND);
				}
				break;
		case SDLK_DELETE:
del:			if (cursor_position < num_chars) {
				memmove(&buf[cursor_position], &buf[cursor_position + 1], num_chars - cursor_position - 1);
				buf[--num_chars] = 0;
				modified_text();
				play_dialog_sound(DIALOG_DELETE_SOUND);
			}
			break;
		case SDLK_UP:
		case SDLK_DOWN:
			break;

			default: {				// Printable characters are entered into the buffer
				uint16 uc = e.key.keysym.unicode;
				if (uc >= ' ' && (uc < 0x80 || enable_mac_roman) && (num_chars + 1) < max_chars)
				{
					char c;
					if (uc < 0x80)
					{
						c = static_cast<char>(uc);
					}
					else if (enable_mac_roman)
					{
						c = unicode_to_mac_roman(uc);
					}
					memmove(&buf[cursor_position + 1], &buf[cursor_position], num_chars - cursor_position);
					buf[cursor_position++] = c;
					buf[++num_chars] = 0;
					modified_text();
					play_dialog_sound(DIALOG_TYPE_SOUND);
				} else if (uc == 21) {			// Ctrl-U: erase text
					buf[0] = 0;
					num_chars = 0;
					cursor_position = 0;
					modified_text();
					play_dialog_sound(DIALOG_ERASE_SOUND);
				} else if (uc == 4)	// Ctrl-D: delete
					goto del;
				else if (uc == 8) // Ctrl-H: backspace
					goto backspace;
				break;
			}
		}
	}
}

void w_text_entry::click(int x, int y)
{
	bool was_active = active;
	get_owning_dialog()->activate_widget(this, true);
	
	// Don't reposition cursor if:
	// - we were inactive before this click
	// - our text field is empty
	// - the click was simulated (0, 0) or out of bounds
	if (!was_active || !num_chars ||
		(x == 0 && y == 0) ||
	    x < 0 || y < 0 || x >= rect.w || y >= rect.h) {
		return;
	}
	
	// Find closest character boundary to click
	int width_remaining = x - text_x;
	size_t pos = 0;
	while (pos < num_chars && width_remaining > 0) {
		int cw = char_width(buf[pos], font, style);
		if (width_remaining > cw/2) {
			pos++; // right side is closer to target than left
		}
		width_remaining -= cw;
	}
	cursor_position = pos;
	dirty = true;
}

void w_text_entry::set_text(const char *text)
{
	memset(buf, 0, max_chars + 1);
	if (text)
		strncpy(buf, text, max_chars);
	num_chars = strlen(buf);
	cursor_position = num_chars;
	modified_text();
}

void w_text_entry::modified_text(void)
{
	dirty = true;
                                        
        // ZZZ: callback if desired
        if(value_changed_callback)
            value_changed_callback(this);
}

/*
 *  Number entry widget
 */

w_number_entry::w_number_entry(int initial_number) : w_text_entry(/*16*/4, NULL)
{
	set_number(initial_number);
	saved_min_width = MAX_TEXT_WIDTH / 2;
}

void w_number_entry::event(SDL_Event &e)
{
	if (e.type == SDL_KEYDOWN) {
            // ZZZ fix: under Mac OS X, Christian's code was filtering out backspace in numeric entry fields
            // Maybe it would be better to have w_text_entry call a virtual method "typed_printable" or something
            // which w_number_entry could override to filter out non-numeric characters.
            // Anyway, here I just ignore the keysym.sym's that show up in w_text_entry::event(), to avoid
            // filtering them out incorrectly.
            switch(e.key.keysym.sym) {
                case SDLK_LEFT:
                case SDLK_RIGHT:
                case SDLK_RETURN:
                case SDLK_KP_ENTER:
                case SDLK_BACKSPACE:
                break;
                
                default:
                {
                    uint16 uc = e.key.keysym.unicode;
                    if (uc >= ' ' && uc < 0x80) {
                            if (uc < '0' || uc > '9') {
                                    // Swallow all non-numbers
                                    e.type = SDL_NOEVENT;
                                    return;
                            }
                    }
                } // default
                break;
            } // switch
	} // if key down
        
	w_text_entry::event(e);
}

void w_number_entry::set_number(int number)
{
	char str[16];
	sprintf(str, "%d", number);
	set_text(str);
}

w_password_entry::w_password_entry(size_t max_chars, const char *initial_text) : w_text_entry(max_chars, initial_text)
{

}

void w_password_entry::draw(SDL_Surface *s) const
{
	string real_text = buf;
	memset(buf, '*', strlen(buf));
	w_text_entry::draw(s);
	strcpy(buf, real_text.c_str());
}

/*
 *  Key name widget
 */

static const char *WAITING_TEXT = "waiting for new key";

w_key::w_key(SDLKey key) : widget(LABEL_WIDGET), binding(false)
{
	set_key(key);

	saved_min_width = text_width(WAITING_TEXT, font, style);
	saved_min_height = font->get_line_height();
}

void w_key::place(const SDL_Rect& r, placement_flags flags)
{
	rect.h = r.h;
	rect.y = r.y;

	key_x = 0;
	rect.x = r.x;
	rect.w = r.w;
}
		
// ZZZ: we provide phony key names for the phony keys used for mouse buttons.
static const char* sMouseButtonKeyName[NUM_SDL_MOUSE_BUTTONS] = {
        "mouse 1",   // things like "Middle Mouse Button" are too long to draw properly
        "mouse 3",
        "mouse 2",
        "mouse 4",
        "mouse 5",
        "mouse 6",
        "mouse 7",
        "mouse 8"
};

static const char* sJoystickButtonKeyName[NUM_SDL_JOYSTICK_BUTTONS] = {
	"joystick 1",
	"joystick 2",
	"joystick 3",
	"joystick 4",
	"joystick 5",
	"joystick 6",
	"joystick 7",
	"joystick 8",
	"joystick 9",
	"joystick 10",
	"joystick 11",
	"joystick 12",
	"joystick 13",
	"joystick 14",
	"joystick 15",
	"joystick 16",
	"joystick 17",
	"joystick 18"
};

// ZZZ: this injects our phony key names but passes along the rest.
static const char*
GetSDLKeyName(SDLKey inKey) {
    if(inKey >= SDLK_BASE_MOUSE_BUTTON && inKey < SDLK_BASE_MOUSE_BUTTON + NUM_SDL_MOUSE_BUTTONS)
        return sMouseButtonKeyName[inKey - SDLK_BASE_MOUSE_BUTTON];
    else if (inKey >= SDLK_BASE_JOYSTICK_BUTTON && inKey < SDLK_BASE_JOYSTICK_BUTTON + NUM_SDL_JOYSTICK_BUTTONS)
	    return sJoystickButtonKeyName[inKey - SDLK_BASE_JOYSTICK_BUTTON];
    else
        return SDL_GetKeyName(inKey);
}

void w_key::draw(SDL_Surface *s) const
{
	int y = rect.y + font->get_ascent();

	// Key
	int16 x = rect.x + key_x;
	if (binding) {
		draw_text(s, WAITING_TEXT, x, y, get_theme_color(ITEM_WIDGET, ACTIVE_STATE), font, style);
	} else {
        int state = enabled ? (active ? ACTIVE_STATE : DEFAULT_STATE) : DISABLED_STATE;

        // ZZZ: potential to use the phony (i.e. mouse-button) key names
	draw_text(s, GetSDLKeyName(key), x, y, get_theme_color(ITEM_WIDGET, state), font, style);
	}
}

void w_key::click(int /*x*/, int /*y*/)
{
	get_owning_dialog()->activate_widget(this);
    if(enabled) {
	    if (!binding) {
		    binding = true;
		    dirty = true;
	    }
    }
}

void w_key::event(SDL_Event &e)
{
    if(binding) {
        // ZZZ: let mouse buttons assign like (unused) keys
        if(e.type == SDL_MOUSEBUTTONDOWN) {
            e.type = SDL_KEYDOWN;
            e.key.keysym.sym = (SDLKey)(SDLK_BASE_MOUSE_BUTTON + e.button.button - 1);
        } else if (e.type == SDL_JOYBUTTONDOWN && e.button.button < NUM_SDL_JOYSTICK_BUTTONS) {
		e.type = SDL_KEYDOWN;
		e.key.keysym.sym = (SDLKey) (SDLK_BASE_JOYSTICK_BUTTON + e.button.button);
	}

    	if (e.type == SDL_KEYDOWN) {
			if (e.key.keysym.sym != SDLK_ESCAPE)
				set_key(e.key.keysym.sym);
			dirty = true;
			binding = false;
			e.key.keysym.sym = SDLK_DOWN;	// Activate next widget
	    }

        // ZZZ: suppress mouse motion while assigning
        // (it's annoying otherwise, trust me)
        if(e.type == SDL_MOUSEMOTION)
            e.type = SDL_NOEVENT;
    }
}

void w_key::set_key(SDLKey k)
{
	key = k;
}

/*
 * Progress
 */
void w_progress_bar::draw(SDL_Surface* s) const
{
	int filled_width = (rect.w - 2) * value / max_value;
	SDL_Rect dst_rect = rect;
	dst_rect.h -= 2;
	dst_rect.y += 2;
	SDL_FillRect(s, &dst_rect, get_theme_color(MESSAGE_WIDGET, DEFAULT_STATE, FOREGROUND_COLOR));
	dst_rect.x += filled_width + 1;
	dst_rect.y++;
	dst_rect.h -= 2;
	dst_rect.w = dst_rect.w - filled_width - 2;
	if (use_theme_color(DIALOG_FRAME, BACKGROUND_COLOR))
		SDL_FillRect(s, &dst_rect, get_theme_color(DIALOG_FRAME, DEFAULT_STATE, BACKGROUND_COLOR));
}

void w_progress_bar::set_progress(int inValue, int inMaxValue)
{
	value = inValue;
	max_value = inMaxValue;
	dirty = true;
}
 


/*
 *  Slider
 */

const int SLIDER_WIDTH = 160;
const int SLIDER_THUMB_HEIGHT = 14;
const int SLIDER_THUMB_WIDTH = 8;
const int SLIDER_TROUGH_HEIGHT = 8;

w_slider::w_slider(int num, int s) : widget(LABEL_WIDGET), selection(s), num_items(num), thumb_dragging(false)
{
	slider_l = get_theme_image(SLIDER_WIDGET, DEFAULT_STATE, SLIDER_L_IMAGE);
	slider_r = get_theme_image(SLIDER_WIDGET, DEFAULT_STATE, SLIDER_R_IMAGE);
	slider_c = get_theme_image(SLIDER_WIDGET, DEFAULT_STATE, SLIDER_C_IMAGE, SLIDER_WIDTH - slider_l->w - slider_r->w);
	thumb = get_theme_image(SLIDER_THUMB, DEFAULT_STATE, 0);
	
	trough_width = SLIDER_WIDTH - get_theme_space(SLIDER_WIDGET, SLIDER_L_SPACE) - get_theme_space(SLIDER_WIDGET, SLIDER_R_SPACE);

	saved_min_width = SLIDER_WIDTH;
	if (use_theme_images(SLIDER_WIDGET))
		saved_min_height = std::max(static_cast<uint16>(slider_c->h), static_cast<uint16>(thumb->h));
	else
		saved_min_height = SLIDER_THUMB_HEIGHT + 2;
}

w_slider::~w_slider()
{
	if (slider_c) SDL_FreeSurface(slider_c);
}

void w_slider::place(const SDL_Rect& r, placement_flags flags)
{
	rect.h = r.h;
	rect.y = r.y + (r.h - saved_min_height) / 2;
	rect.x = r.x;
	slider_x = 0;
	rect.w = r.w;

	set_selection(selection);
}

void w_slider::draw(SDL_Surface *s) const
{
	if (use_theme_images(SLIDER_WIDGET))
	{
		// Slider trough
		SDL_Rect r = {rect.x + slider_x, rect.y + (saved_min_height - slider_l->h) / 2,
			      static_cast<Uint16>(slider_l->w),
			      static_cast<Uint16>(slider_l->h)};
		SDL_BlitSurface(slider_l, NULL, s, &r);
		r.x = r.x + static_cast<Sint16>(slider_l->w);
		r.w = static_cast<Uint16>(slider_c->w);
		r.h = static_cast<Uint16>(slider_c->h);
		SDL_BlitSurface(slider_c, NULL, s, &r);
		r.x = r.x + static_cast<Sint16>(slider_c->w);
		r.w = static_cast<Uint16>(slider_r->w);
		r.h = static_cast<Uint16>(slider_r->h);
		SDL_BlitSurface(slider_r, NULL, s, &r);

		// Slider thumb
		r.x = rect.x + static_cast<Sint16>(thumb_x);
		r.y = rect.y + (saved_min_height - thumb->h) / 2 + get_theme_space(SLIDER_WIDGET, SLIDER_T_SPACE);
		r.w = static_cast<Uint16>(thumb->w);
		r.h = static_cast<Uint16>(thumb->h);
		SDL_BlitSurface(thumb, NULL, s, &r);
	} 
	else
	{
		SDL_Rect r = {rect.x, rect.y + (saved_min_height - SLIDER_TROUGH_HEIGHT) / 2 + get_theme_space(SLIDER_WIDGET, SLIDER_T_SPACE), SLIDER_WIDTH, SLIDER_TROUGH_HEIGHT};
		uint32 pixel = get_theme_color(SLIDER_WIDGET, DEFAULT_STATE, FRAME_COLOR);
		draw_rectangle(s, &r, pixel);

		pixel = get_theme_color(SLIDER_WIDGET, DEFAULT_STATE, FOREGROUND_COLOR);
		r.x = r.x + 1;
		r.y = r.y + 1;
		r.w = r.w - 2;
		r.h = r.h - 2;
		SDL_FillRect(s, &r, pixel);

		pixel = get_theme_color(SLIDER_THUMB, DEFAULT_STATE, FRAME_COLOR);
		r.x = rect.x + static_cast<Sint16>(thumb_x);
		r.y = rect.y + (saved_min_height - SLIDER_THUMB_HEIGHT) / 2;
		r.w = thumb_width();
		r.h = SLIDER_THUMB_HEIGHT;
		draw_rectangle(s, &r, pixel);

		pixel = get_theme_color(SLIDER_THUMB, DEFAULT_STATE, FOREGROUND_COLOR);
		r.x = r.x + 1;
		r.y = r.y + 1;
		r.w = r.w - 2;
		r.h = r.h - 2;
		SDL_FillRect(s, &r, pixel);
		
	}
}

void w_slider::mouse_move(int x, int /*y*/)
{
	if (thumb_dragging) {
		int delta_x = (x - slider_x - get_theme_space(SLIDER_WIDGET, SLIDER_L_SPACE)) - thumb_drag_x;
		set_selection(delta_x * num_items / (trough_width - thumb_width()));
	}
}

void w_slider::click(int x, int /*y*/)
{
    if(enabled) {
	    if (x >= thumb_x && x < thumb_x + thumb_width()) {
		    thumb_dragging = dirty = true;
		    thumb_drag_x = x - thumb_x;
	    }
    }
}

void w_slider::event(SDL_Event &e)
{
	if (e.type == SDL_KEYDOWN) {
		if (e.key.keysym.sym == SDLK_LEFT) {
			set_selection(selection - 1);
			item_selected();
			e.type = SDL_NOEVENT;	// Swallow event
		} else if (e.key.keysym.sym == SDLK_RIGHT) {
			set_selection(selection + 1);
			item_selected();
			e.type = SDL_NOEVENT;	// Swallow event
		}
	} else if (e.type == SDL_MOUSEBUTTONUP) {
		if (thumb_dragging) {
			thumb_dragging = false;
			dirty = true;
			item_selected();
		}
	}
}

void w_slider::set_selection(int s)
{
	if (s >= num_items)
		s = num_items - 1;
	else if (s < 0)
		s = 0;
	selection = s;
	thumb_x = int(float(selection * (trough_width - thumb_width())) / (num_items - 1) + 0.5);
	thumb_x += get_theme_space(SLIDER_WIDGET, SLIDER_L_SPACE) + slider_x;
	dirty = true;
}

int w_slider::thumb_width() const
{
	if (use_theme_images(SLIDER_WIDGET))
		return thumb->w;
	else
		return SLIDER_THUMB_WIDTH;
}


/*
 *  List selection
 */

w_list_base::w_list_base(uint16 width, size_t lines, size_t /*sel*/) : widget(ITEM_WIDGET), num_items(0), shown_items(lines), thumb_dragging(false), top_item(0)
{
	rect.w = width;
	rect.h = item_height() * static_cast<uint16>(shown_items) + get_theme_space(LIST_WIDGET, T_SPACE) + get_theme_space(LIST_WIDGET, B_SPACE);

	frame_tl = get_theme_image(LIST_WIDGET, DEFAULT_STATE, TL_IMAGE);
	frame_tr = get_theme_image(LIST_WIDGET, DEFAULT_STATE, TR_IMAGE);
	frame_bl = get_theme_image(LIST_WIDGET, DEFAULT_STATE, BL_IMAGE);
	frame_br = get_theme_image(LIST_WIDGET, DEFAULT_STATE, BR_IMAGE);
	frame_t = get_theme_image(LIST_WIDGET, DEFAULT_STATE, T_IMAGE, rect.w - frame_tl->w - frame_tr->w, 0);
	frame_l = get_theme_image(LIST_WIDGET, DEFAULT_STATE, L_IMAGE, 0, rect.h - frame_tl->h - frame_bl->h);
	frame_r = get_theme_image(LIST_WIDGET, DEFAULT_STATE, R_IMAGE, 0, rect.h - frame_tr->h - frame_br->h);
	frame_b = get_theme_image(LIST_WIDGET, DEFAULT_STATE, B_IMAGE, rect.w - frame_bl->w - frame_br->w, 0);

	thumb_t = get_theme_image(LIST_THUMB, DEFAULT_STATE, THUMB_T_IMAGE);
	thumb_tc = NULL;
	SDL_Surface *thumb_tc_unscaled = get_theme_image(LIST_THUMB, DEFAULT_STATE, THUMB_TC_IMAGE);
	thumb_c = get_theme_image(LIST_THUMB, DEFAULT_STATE, THUMB_C_IMAGE);
	SDL_Surface *thumb_bc_unscaled = get_theme_image(LIST_THUMB, DEFAULT_STATE, THUMB_BC_IMAGE);
	thumb_bc = NULL;
	thumb_b = get_theme_image(LIST_THUMB, DEFAULT_STATE, THUMB_B_IMAGE);

	min_thumb_height = static_cast<uint16>(thumb_t->h + thumb_tc_unscaled->h + thumb_c->h + thumb_bc_unscaled->h + thumb_b->h);

	trough_rect.x = rect.w - get_theme_space(LIST_WIDGET, TROUGH_R_SPACE);
	trough_rect.y = get_theme_space(LIST_WIDGET, TROUGH_T_SPACE);
	trough_rect.w = get_theme_space(LIST_WIDGET, TROUGH_WIDTH);
	trough_rect.h = rect.h - get_theme_space(LIST_WIDGET, TROUGH_T_SPACE) - get_theme_space(LIST_WIDGET, TROUGH_B_SPACE);

	saved_min_width = rect.w;
	saved_min_height = rect.h;
}

w_list_base::~w_list_base()
{
	if (frame_t) SDL_FreeSurface(frame_t);
	if (frame_l) SDL_FreeSurface(frame_l);
	if (frame_r) SDL_FreeSurface(frame_r);
	if (frame_b) SDL_FreeSurface(frame_b);
	if (thumb_tc) SDL_FreeSurface(thumb_tc);
	if (thumb_bc) SDL_FreeSurface(thumb_bc);
}

void w_list_base::draw_image(SDL_Surface *dst, SDL_Surface *s, int16 x, int16 y) const
{
	SDL_Rect r = {x, y, static_cast<Uint16>(s->w), static_cast<Uint16>(s->h)};
	SDL_BlitSurface(s, NULL, dst, &r);
}

void w_list_base::draw(SDL_Surface *s) const
{
	if (use_theme_images(LIST_WIDGET))
	{
		// Draw frame
		int16 x = rect.x;
		int16 y = rect.y;
		draw_image(s, frame_tl, x, y);
		draw_image(s, frame_t, x + static_cast<int16>(frame_tl->w), y);
		draw_image(s, frame_tr, x + static_cast<int16>(frame_tl->w) + static_cast<int16>(frame_t->w), y);
		draw_image(s, frame_l, x, y + static_cast<int16>(frame_tl->h));
		draw_image(s, frame_r, x + rect.w - static_cast<int16>(frame_r->w), y + static_cast<int16>(frame_tr->h));
		draw_image(s, frame_bl, x, y + static_cast<int16>(frame_tl->h) + static_cast<int16>(frame_l->h));
		draw_image(s, frame_b, x + static_cast<int16>(frame_bl->w), y + rect.h - static_cast<int16>(frame_b->h));
		draw_image(s, frame_br, x + static_cast<int16>(frame_bl->w) + static_cast<int16>(frame_b->w), y + static_cast<int16>(frame_tr->h) + static_cast<int16>(frame_r->h));

		// Draw thumb
		x = rect.x + trough_rect.x;
		y = rect.y + thumb_y;
		draw_image(s, thumb_t, x, y);
		draw_image(s, thumb_tc, x, y = y + static_cast<int16>(thumb_t->h));
		draw_image(s, thumb_c, x, y = y + static_cast<int16>(thumb_tc->h));
		draw_image(s, thumb_bc, x, y = y + static_cast<int16>(thumb_c->h));
		draw_image(s, thumb_b, x, y = y + static_cast<int16>(thumb_bc->h));
		
	}
	else
	{
		uint32 pixel = get_theme_color(LIST_WIDGET, DEFAULT_STATE, FRAME_COLOR);
		draw_rectangle(s, &rect, pixel);

		SDL_Rect real_trough = { rect.x + trough_rect.x, rect.y + trough_rect.y, trough_rect.w, trough_rect.h };
		draw_rectangle(s, &real_trough, pixel);
		real_trough.x = real_trough.x + 1;
		real_trough.y = real_trough.y + 1;
		real_trough.w = real_trough.w - 2;
		real_trough.h = real_trough.h - 2;
		if (use_theme_color(LIST_THUMB, BACKGROUND_COLOR))
		{
			pixel = get_theme_color(LIST_THUMB, DEFAULT_STATE, BACKGROUND_COLOR);
			SDL_FillRect(s, &real_trough, pixel);
		}

		pixel = get_theme_color(LIST_THUMB, DEFAULT_STATE, FRAME_COLOR);
		SDL_Rect thumb_rect = { rect.x + trough_rect.x, rect.y + thumb_y, trough_rect.w, thumb_t->h + thumb_tc->h + thumb_c->h + thumb_bc->h + thumb_b->h};
		draw_rectangle(s, &thumb_rect, pixel);
		
		pixel = get_theme_color(LIST_THUMB, DEFAULT_STATE, FOREGROUND_COLOR);
		thumb_rect.x = thumb_rect.x + 1;
		thumb_rect.y = thumb_rect.y + 1;
		thumb_rect.w = thumb_rect.w - 2;
		thumb_rect.h = thumb_rect.h - 2;
		SDL_FillRect(s, &thumb_rect, pixel);
		
	}
		
	// Draw items
	draw_items(s);
}

void w_list_base::mouse_move(int x, int y)
{
	if (thumb_dragging) {
		int delta_y = y - thumb_drag_y;
		if (delta_y > 0 && num_items > shown_items && trough_rect.h > thumb_height) {
			set_top_item(delta_y * (num_items - shown_items) / (trough_rect.h - thumb_height));
		} else {
		  set_top_item(0);
		}
	} else {
		if (x < get_theme_space(LIST_WIDGET, L_SPACE) || x >= rect.w - get_theme_space(LIST_WIDGET, R_SPACE)
		    || y < get_theme_space(LIST_WIDGET, T_SPACE) || y >= rect.h - get_theme_space(LIST_WIDGET, B_SPACE))
			return;

		if ((y - get_theme_space(LIST_WIDGET, T_SPACE)) / item_height() + top_item < min(num_items, top_item + shown_items))
		{	set_selection((y - get_theme_space(LIST_WIDGET, T_SPACE)) / item_height() + top_item); }
//		else
//		{	set_selection(num_items - 1); }
	}
}

void w_list_base::place(const SDL_Rect& r, placement_flags flags)
{
	widget::place(r, flags);
	
	trough_rect.x = rect.w - get_theme_space(LIST_WIDGET, TROUGH_R_SPACE);
	trough_rect.y = get_theme_space(LIST_WIDGET, TROUGH_T_SPACE);
	trough_rect.w = get_theme_space(LIST_WIDGET, TROUGH_WIDTH);
	trough_rect.h = rect.h - get_theme_space(LIST_WIDGET, TROUGH_T_SPACE) - get_theme_space(LIST_WIDGET, TROUGH_B_SPACE);

	frame_t = get_theme_image(LIST_WIDGET, DEFAULT_STATE, T_IMAGE, rect.w - frame_tl->w - frame_tr->w, 0);
	frame_l = get_theme_image(LIST_WIDGET, DEFAULT_STATE, L_IMAGE, 0, rect.h - frame_tl->h - frame_bl->h);
	frame_r = get_theme_image(LIST_WIDGET, DEFAULT_STATE, R_IMAGE, 0, rect.h - frame_tr->h - frame_br->h);
	frame_b = get_theme_image(LIST_WIDGET, DEFAULT_STATE, B_IMAGE, rect.w - frame_bl->w - frame_br->w, 0);
}

void w_list_base::click(int x, int y)
{
	if (x >= trough_rect.x && x < trough_rect.x + trough_rect.w
//	 && y >= trough_rect.y && y < trough_rect.y + trough_rect.h) {
	    && y >= thumb_y && y <= thumb_y + thumb_height) {
		thumb_dragging = dirty = true;
		thumb_drag_y = y - thumb_y;
	} else {
		if (num_items > 0 && is_item_selectable(selection))
			item_selected();
	}
}

void w_list_base::event(SDL_Event &e)
{
	if (e.type == SDL_KEYDOWN) {
		switch (e.key.keysym.sym) {
			case SDLK_UP:
				if (selection != 0)
				{	set_selection(selection - 1); }
				e.type = SDL_NOEVENT;	// Prevent selection of previous widget
				break;
			case SDLK_DOWN:
				if (selection < num_items - 1)
				{	set_selection(selection + 1); }
				e.type = SDL_NOEVENT;	// Prevent selection of next widget
				break;
			case SDLK_PAGEUP:
				if (selection > shown_items)
				{	set_selection(selection - shown_items); }
				else
				{	set_selection(0); }
				break;
			case SDLK_PAGEDOWN:
				if (selection + shown_items < num_items - 1)
				{	set_selection(selection + shown_items); }
				else
				{	set_selection(num_items - 1); }
				break;
			case SDLK_HOME:
				set_selection(0);
				break;
			case SDLK_END:
				set_selection(num_items - 1);
				break;
			default:
				break;
		}
	} else if (e.type == SDL_MOUSEBUTTONUP) {
		if (thumb_dragging) {
			thumb_dragging = false;
			dirty = true;
		}
	} else if (e.type == SDL_MOUSEBUTTONDOWN) {
		switch (e.button.button) {
		case SDL_BUTTON_WHEELUP:
			if (top_item > 3)
				set_top_item(top_item - 3); 
			else 
				set_top_item(0); 
			break;	
		case SDL_BUTTON_WHEELDOWN:
			if (top_item < num_items - shown_items - 3)
				set_top_item(top_item + 3);
			else 
				set_top_item(num_items - shown_items);
			break;
		default:
			break;
		}
	}
}

void w_list_base::set_selection(size_t s)
{
	// Set selection, check for bounds
	assert(s == PIN(s, 0, num_items - 1));
	if (s != selection)
		dirty = true;
	selection = s;

	// Make selection visible
	if (s < top_item)
		set_top_item(s);
	else if (s >= top_item + shown_items)
		set_top_item(s - shown_items + 1);
}

void w_list_base::new_items(void)
{
	// Reset top item and selection
	
	// ghs: actually, remember top item and selection
	size_t saved_top_item = top_item;
	size_t saved_selection = get_selection();
	top_item = selection = 0;
	dirty = true;

	// Calculate thumb height
	if (num_items <= shown_items)
		thumb_height = trough_rect.h;
	else if (num_items == 0)
		thumb_height = static_cast<uint16>(shown_items) * trough_rect.h;
	else
		thumb_height = uint16(float(shown_items * trough_rect.h) / num_items + 0.5);
	if (thumb_height < min_thumb_height)
		thumb_height = min_thumb_height;
	else if (thumb_height > trough_rect.h)
		thumb_height = trough_rect.h;

	// Create dynamic thumb images
	if (thumb_tc)
		SDL_FreeSurface(thumb_tc);
	if (thumb_bc)
		SDL_FreeSurface(thumb_bc);
	int rem_height = thumb_height - thumb_t->h - thumb_c->h - thumb_b->h;
	int dyn_height = rem_height / 2;
	thumb_tc = get_theme_image(LIST_THUMB, DEFAULT_STATE, THUMB_TC_IMAGE, 0, dyn_height);
	thumb_bc = get_theme_image(LIST_THUMB, DEFAULT_STATE, THUMB_BC_IMAGE, 0, (rem_height & 1) ? dyn_height + 1 : dyn_height);

	thumb_y = 0;
	if (thumb_y > trough_rect.h - thumb_height)
		thumb_y = trough_rect.h - thumb_height;
	thumb_y = thumb_y + trough_rect.y;

	if (saved_selection < num_items) 
		set_selection(saved_selection);
	if (saved_top_item)
		set_top_item(saved_top_item);
}

void w_list_base::center_item(size_t i)
{
  set_top_item((i > shown_items / 2) ? i - shown_items / 2 : 0);
}

void w_list_base::set_top_item(size_t i)
{
	// Set top item (check for bounds)
	if (num_items > shown_items)
		i = PIN(i, 0, num_items - shown_items);
	else
		i = 0;
	if (i != top_item)
		dirty = true;
	top_item = i;

	// Calculate thumb y position
	if (num_items <= shown_items)
		thumb_y = 0;
	else
		thumb_y = int16(float(top_item * (trough_rect.h - thumb_height)) / (num_items - shown_items) + 0.5);
	if (thumb_y > trough_rect.h - thumb_height)
		thumb_y = trough_rect.h - thumb_height;
	thumb_y = thumb_y + trough_rect.y;
}



// ZZZ: maybe this belongs in a new file or something - it's definitely A1-related
// whereas most (but not all) of the other widgets here are sort of 'general-purpose'.
// Anyway, moved here from shell_sdl.h and enhanced ever so slightly, will now be
// using it in the setup network game dialog as well.

/*
 *  Level number dialog
 */

w_levels::w_levels(const vector<entry_point> &items, dialog *d)
	  : w_list<entry_point>(items, 400, 8, 0), parent(d), show_level_numbers(true) {}

// ZZZ: new constructor gives more control over widget's appearance.
w_levels::w_levels(const vector<entry_point>& items, dialog* d, uint16 inWidth,
        size_t inNumLines, size_t inSelectedItem, bool in_show_level_numbers)
	  : w_list<entry_point>(items, inWidth, inNumLines, inSelectedItem), parent(d), show_level_numbers(in_show_level_numbers) {}

void
w_levels::item_selected(void)
{
	parent->quit(0);
}

void
w_levels::draw_item(vector<entry_point>::const_iterator i, SDL_Surface *s, int16 x, int16 y, uint16 width, bool selected) const
{
	y = y + font->get_ascent();
	char str[256];

    if(show_level_numbers)
    	sprintf(str, "%d - %s", i->level_number + 1, i->level_name);
    else
        sprintf(str, "%s", i->level_name);

	set_drawing_clip_rectangle(0, x, static_cast<short>(s->h), x + width);
	draw_text(s, str, x, y, get_theme_color(ITEM_WIDGET, selected ? ACTIVE_STATE : DEFAULT_STATE), font, style);
	set_drawing_clip_rectangle(SHRT_MIN, SHRT_MIN, SHRT_MAX, SHRT_MAX);
}


/*
 *  String List
 */

w_string_list::w_string_list(const vector<string> &items, dialog *d, int sel)
	: w_list<string>(items, 400, 8, sel), parent(d) {}

void w_string_list::item_selected(void)
{
	parent->quit(0);
}

void w_string_list::draw_item(vector<string>::const_iterator i, SDL_Surface *s, int16 x, int16 y, uint16 width, bool selected) const
{
	y = y + font->get_ascent();
	char str[256];

	sprintf(str, "%s", i->c_str ());

	set_drawing_clip_rectangle(0, x, static_cast<short>(s->h), x + width);
	draw_text(s, str, x, y, get_theme_color(ITEM_WIDGET, selected ? ACTIVE_STATE : DEFAULT_STATE), font, style);
	set_drawing_clip_rectangle(SHRT_MIN, SHRT_MIN, SHRT_MAX, SHRT_MAX);
}


/*
 *  Selection Popup
 */

w_select_popup::w_select_popup (action_proc p, void *a) : w_select_button ("", gotSelectedCallback, NULL)
{
	set_arg(this);
	selection = -1;
	
	action = p;
	arg = a;
}

void w_select_popup::set_labels(const vector<string>& inLabels)
{
	labels = inLabels;

	// recalculate min width
	saved_min_width = 0;
	for (vector<string>::iterator it = labels.begin(); it != labels.end(); ++it)
	{
		uint16 width = text_width(it->c_str(), font, style);
		if (width > saved_min_width)
			saved_min_width = width;
	}
}

void w_select_popup::set_selection (int value)
{
	if (value < labels.size () && value >= 0)
		selection = value;
	else
		selection = -1;
	
	if (selection == -1)
		w_select_button::set_selection ("");
	else
		w_select_button::set_selection (labels[selection].c_str ());
}

void w_select_popup::gotSelected ()
{
	if (labels.size () > 1) {
		dialog theDialog;
		vertical_placer *placer = new vertical_placer;
		
		w_string_list* string_list_w = new w_string_list (labels, &theDialog, selection >= 0 ? selection : 0);
		placer->dual_add (string_list_w, theDialog);
		theDialog.activate_widget(string_list_w);
		
		theDialog.set_widget_placer(placer);
		if (theDialog.run () == 0)
			set_selection (string_list_w->get_selection ());
	}
	
	if (action)
		action (arg);
}


static const char* const sFileChooserInvalidFileString = "(no valid selection)";

w_file_chooser::w_file_chooser(const char* inDialogPrompt, Typecode inTypecode)
	: w_select_button("", NULL, NULL, true), typecode(inTypecode)
{
	strncpy(dialog_prompt, inDialogPrompt, sizeof(dialog_prompt));
	set_selection(sFileChooserInvalidFileString);
}



void
w_file_chooser::set_file(const FileSpecifier& inFile)
{
	file = inFile;
	update_filename();
}



void
w_file_chooser::click(int, int)
{
	if(enabled)
	{
		if(file.ReadDialog(typecode, dialog_prompt))
		{
			update_filename();
			if (m_callback)
				m_callback ();
		}
	}	
}



void
w_file_chooser::update_filename()
{
	if(file.Exists())
	{
		file.GetName(filename);
		std::string filename_copy = filename;
		strcpy(filename, FileSpecifier::HideExtension(filename_copy).c_str());
		set_selection(filename);
	}
	else
		set_selection(sFileChooserInvalidFileString);
}


const string w_items_in_room_get_name_of_item (GameListMessage::GameListEntry item)
{
	return item.name ();
}

const string w_items_in_room_get_name_of_item (prospective_joiner_info item)
{
	return pstring_to_string (item.name);
}

const string w_items_in_room_get_name_of_item (MetaserverPlayerInfo item)
{
	return item.name ();
}

void w_games_in_room::draw_item(const GameListMessage::GameListEntry& item, SDL_Surface* s, int16 x, int16 y, uint16 width, bool selected) const
{
	int game_style = style;

	int state;
	if (!item.compatible())
	{
		state = item.target() ? SELECTED_INCOMPATIBLE_GAME : INCOMPATIBLE_GAME;
	}
	else if (item.running())
	{
		state = item.target() ? SELECTED_RUNNING_GAME : RUNNING_GAME;
	}
	else 
	{
		state = item.target() ? SELECTED_GAME : GAME;
	}

	uint32 fg;
	if (selected)
	{
		fg = get_theme_color(ITEM_WIDGET, ACTIVE_STATE);
	}
	else 
	{
		fg = get_theme_color(METASERVER_GAMES, state, FOREGROUND_COLOR);
	}

	uint32 bg = get_theme_color(METASERVER_GAMES, state, BACKGROUND_COLOR);
	SDL_Rect r = { x, y, width, 3 * font->get_line_height() + 2};
	SDL_FillRect(s, &r, bg);

	if (use_theme_color(METASERVER_GAMES, FRAME_COLOR))
	{
		uint32 frame = get_theme_color(METASERVER_GAMES, state, FRAME_COLOR);
		draw_rectangle(s, &r, frame);
	}
	

	x += 1;
	width -= 2;
	y += font->get_ascent() + 1;

	ostringstream time_or_ping;
	int right_text_width = 0;

	// first line, game name, ping or time remaining
	if (item.running())
	{
		if (item.m_description.m_timeLimit && !(item.m_description.m_timeLimit == INT32_MAX || item.m_description.m_timeLimit == -1))
		{
			if (item.minutes_remaining() == 1)
			{
				time_or_ping << "~1 Minute";
			}
			else
			{
				time_or_ping << item.minutes_remaining() << " Minutes";
			}
		}
		else
		{
			time_or_ping << "Untimed";
		}
	}
	else
	{
		// draw ping
	}

	right_text_width = text_width(time_or_ping.str().c_str(), font, game_style);

	// draw game name
	set_drawing_clip_rectangle(0, x, static_cast<short>(s->h), x + width - right_text_width);
	font->draw_styled_text(s, item.name(), item.name().size(), x, y, fg, game_style);
	
	// draw remaining or ping
	set_drawing_clip_rectangle(0, x, static_cast<short>(s->h), x + width);
	draw_text(s, time_or_ping.str().c_str(), x + width - right_text_width, y, fg, font, game_style);

	y += font->get_line_height();

	ostringstream game_and_map;

	if (!item.compatible())
	{
		game_and_map << "|i" << item.m_description.m_scenarioName;
		if (item.m_description.m_scenarioVersion != "")
		{
			game_and_map << ", Version " << item.m_description.m_scenarioVersion;
		}
	} 
	else
	{
		game_and_map << item.game_string()
			     << " on |i"
			     << item.m_description.m_mapName;
	}
	
	font->draw_styled_text(s, game_and_map.str().c_str(), game_and_map.str().size(), x, y, fg, game_style);

	y += font->get_line_height();

	right_text_width = font->styled_text_width(item.m_hostPlayerName, item.m_hostPlayerName.size(), game_style);
	set_drawing_clip_rectangle(0, x, static_cast<short>(s->h), x + width - right_text_width);

	ostringstream game_settings;
	if (item.running())
	{
		if (item.m_description.m_numPlayers == 1)
		{
			game_settings << "1 Player";
		}
		else
		{
			game_settings << static_cast<uint16>(item.m_description.m_numPlayers) << " Players";
		}
	}
	else
	{
		game_settings << static_cast<uint16>(item.m_description.m_numPlayers)
			      << "/"
			      << item.m_description.m_maxPlayers
			      << " Players";
	}

	if (item.m_description.m_timeLimit && !(item.m_description.m_timeLimit == INT32_MAX || item.m_description.m_timeLimit == -1))
	{
		game_settings << ", " 
			      << item.m_description.m_timeLimit / 60 / TICKS_PER_SECOND 
			      << " Minutes";
	}

	if (item.m_description.m_teamsAllowed)
	{
		game_settings << ", Teams";
	}

	draw_text(s, game_settings.str().c_str(), x, y, fg, font, game_style);

	set_drawing_clip_rectangle(0, x, static_cast<short>(s->h), x + width);
	font->draw_styled_text(s, item.m_hostPlayerName, item.m_hostPlayerName.size(), x + width - right_text_width, y, fg, game_style);

	set_drawing_clip_rectangle(SHRT_MIN, SHRT_MIN, SHRT_MAX, SHRT_MAX);
}


static inline uint8 darken(uint8 component, uint8 amount)
{
	return PIN((uint16) component * (255 - amount) / 255, 0, 255);
}

static inline uint8 lighten(uint8 component, uint8 amount)
{
	return PIN((uint16) component + (255 - component) * amount / 255, 0, 255);
}

void w_players_in_room::draw_item(const MetaserverPlayerInfo& item, SDL_Surface* s,
					int16 x, int16 y, uint16 width, bool selected) const
{
	set_drawing_clip_rectangle(0, x, static_cast<short>(s->h), x + width);

	SDL_Rect r = {x, y, width, font->get_line_height() + 4};

	if (item.target())
	{
		SDL_FillRect(s, &r, SDL_MapRGB(s->format, 0xff, 0xff, 0xff));
	}

	// background is player color
	uint32 pixel;
	if (item.target())
	{
		int amount = 0x7f;
		pixel = SDL_MapRGB(s->format, 
				   lighten(item.color()[0] >> 8, amount),
				   lighten(item.color()[1] >> 8, amount),
				   lighten(item.color()[2] >> 8, amount));
	}
	else
	{
		int amount;
		if (item.away()) 
			amount = 0xbf;
		else
			amount = 0;

		pixel = SDL_MapRGB(s->format,
				   darken(item.color()[0] >> 8, amount),
				   darken(item.color()[1] >> 8, amount),
				   darken(item.color()[2] >> 8, amount));
	}

	r.x = x + kPlayerColorSwatchWidth + kSwatchGutter + 1;
	r.y = y + 1;
	r.w = width - kPlayerColorSwatchWidth - kSwatchGutter - 2;
	r.h = font->get_line_height() + 2;
	SDL_FillRect(s, &r, pixel);

	// team swatch
	r.x = x + 1;
	r.y = y + 1;
	r.w = kPlayerColorSwatchWidth;
	r.h = font->get_line_height() + 2;

	if (item.target())
	{
		int amount = 0x7f;
		pixel = SDL_MapRGB(s->format, 
				   lighten(item.team_color()[0] >> 8, amount),
				   lighten(item.team_color()[1] >> 8, amount),
				   lighten(item.team_color()[2] >> 8, amount));
	}
	else
	{
		int amount;
		if (item.away()) 
			amount = 0x7f;
		else
			amount = 0;

		pixel = SDL_MapRGB(s->format,
				   darken(item.team_color()[0] >> 8, amount),
				   darken(item.team_color()[1] >> 8, amount),
				   darken(item.team_color()[2] >> 8, amount));
	}

	SDL_FillRect(s, &r, pixel);

	y += font->get_ascent();
	uint32 color;
	if (selected)
	{
		color = get_theme_color(ITEM_WIDGET, ACTIVE_STATE);
	}
	else if (item.away())
	{
		color = SDL_MapRGB(s->format, 0x7f, 0x7f, 0x7f);
	}
	else
	{
		color = SDL_MapRGB(s->format, 0xff, 0xff, 0xff);
	}

	font->draw_styled_text(s, item.name(), item.name().size(), x + kPlayerColorSwatchWidth + kSwatchGutter + 2, y + 1, color, item.away() ? style : style | styleShadow);

	set_drawing_clip_rectangle(SHRT_MIN, SHRT_MIN, SHRT_MAX, SHRT_MAX);
}


void w_colorful_chat::append_entry(const ColoredChatEntry& e)
{
	if (e.message.empty())
	{
		get_owning_dialog()->draw_dirty_widgets();
		return;
	}

	string name;
	if (font->styled_text_width(e.sender, e.sender.size(), style | styleShadow) > kNameWidth)
		name = string(e.sender, 0, font->trunc_styled_text(e.sender, kNameWidth, style | styleShadow));
	else
		name = e.sender;
	
	int message_style = style;
	int available_width = rect.w - get_theme_space(LIST_WIDGET, L_SPACE) - get_theme_space(LIST_WIDGET, R_SPACE);
	if (e.type == ColoredChatEntry::ChatMessage)
	{
		available_width -= kNameWidth + taper_width() + 2;
	}
	else if (e.type == ColoredChatEntry::PrivateMessage)
	{
		message_style |= styleShadow;
		available_width -= kNameWidth + taper_width() + 4;
	}
	else
	{
		message_style |= styleShadow;
		available_width -= 2;
	}

	size_t usable_characters = font->trunc_styled_text(e.message, available_width, message_style);
	string::const_iterator middle;
	string::const_iterator rest;
	if (usable_characters != e.message.size()) {
		size_t last_space = e.message.find_last_of(' ', usable_characters);
		if (last_space != 0 && last_space <= usable_characters)
		{
			middle = e.message.begin() + last_space;
			rest = middle + 1;
		}
		else
		{
			middle = e.message.begin() + usable_characters;
			rest = middle;
		}
	} else {
		middle = e.message.begin() + usable_characters;
		rest = middle;
	}

	ColoredChatEntry e_begin = e;
	e_begin.message = string(e.message.begin(), middle);
	e_begin.sender = name;
	
	ColoredChatEntry e_rest = e;
	if (rest != e.message.end())
	{
		e_rest.message = font->style_at(e.message, middle, message_style) + string(rest, e.message.end());
	}
	else
		e_rest.message = string(rest, e.message.end());
	e_rest.sender = name;
	
	bool save_top_item = top_item < num_items - shown_items;
	size_t saved_top_item = top_item;
	entries.push_back(e_begin);
	
	num_items = entries.size();
	new_items();
	if (save_top_item) {
		set_top_item(saved_top_item);
	} else if (num_items > shown_items) {
		set_top_item(num_items - shown_items);
	}
	
	append_entry(e_rest);
}

void w_colorful_chat::draw_item(vector<ColoredChatEntry>::const_iterator it, SDL_Surface *s, int16 x, int16 y, uint16 width, bool selected) const
{
	int computed_y = y + font->get_ascent();

	uint16 message_x = x;
	uint16 message_width = width;

	if ((*it).type == ColoredChatEntry::ChatMessage || (*it).type == ColoredChatEntry::PrivateMessage)
	{
		// draw the name
		SDL_Rect r = { x, y, kNameWidth, font->get_line_height() + 1};
		uint32 pixel = SDL_MapRGB(s->format,
					  (*it).color.red >> 8,
					  (*it).color.green >> 8,
					  (*it).color.blue >> 8);
		SDL_FillRect(s, &r, pixel);

		// draw taper
		r.x += kNameWidth ;
		if (it->type == ColoredChatEntry::PrivateMessage)
		{
			// red bar under taper
			r.w = taper_width() + 2;
			SDL_FillRect(s, &r, SDL_MapRGB(s->format, 0x7f, 0x0, 0x0));
		}
			
		r.w = 1;
		for (int i = 0; i < taper_width(); ++i)
		{
			r.y++;
			r.h -= 2;
			SDL_FillRect(s, &r, pixel);
			r.x++;
		}
		
		set_drawing_clip_rectangle(0, x, static_cast<uint16>(s->h), x + kNameWidth);
		font->draw_styled_text(s, it->sender, it->sender.size(), x + 1, computed_y, SDL_MapRGB(s->format, 0xff, 0xff, 0xff), style | styleShadow);

		message_x += kNameWidth + taper_width() + 2;
		message_width -= kNameWidth + taper_width() + 2;
	}

	uint32 message_color = SDL_MapRGB(s->format, 0xff, 0xff, 0xff);
	if (it->type == ColoredChatEntry::ChatMessage)
	{
		message_color = get_theme_color(CHAT_ENTRY, DEFAULT_STATE, FOREGROUND_COLOR);
	}

	int message_style = style;
	if ((*it).type != ColoredChatEntry::ChatMessage) message_style |= styleShadow;

	if ((*it).type == ColoredChatEntry::ServerMessage)
	{
		// draw the blue bar
		uint32 pixel = SDL_MapRGB(s->format, 0x0, 0x0, 0x7f);
		SDL_Rect r = { message_x, y, message_width, font->get_line_height() + 1 };
		SDL_FillRect(s, &r, pixel);
		message_x += 1;
		message_width -= 2;
	}
	else if ((*it).type == ColoredChatEntry::PrivateMessage)
	{
		// draw a red bar
		uint32 pixel = SDL_MapRGB(s->format, 0x7f, 0x0, 0x0);
		SDL_Rect r = { message_x, y, message_width, font->get_line_height() + 1 };
		SDL_FillRect(s, &r, pixel);
		message_x += 1;
		message_width -= 2;
	}
	else if ((*it).type == ColoredChatEntry::LocalMessage)
	{
		// draw a gray bar
		uint32 pixel = SDL_MapRGB(s->format, 0x3f, 0x3f, 0x3f);
		SDL_Rect r = { message_x, y, message_width, font->get_line_height() + 1 };
		SDL_FillRect(s, &r, pixel);
		message_x += 1;
		message_width -= 2;
	}
	
	set_drawing_clip_rectangle(0, message_x, static_cast<uint16>(s->h), message_x + message_width);
	font->draw_styled_text(s, it->message, it->message.size(), message_x, computed_y, message_color, message_style);

	set_drawing_clip_rectangle(SHRT_MIN, SHRT_MIN, SHRT_MAX, SHRT_MAX);
}

void SDLWidgetWidget::hide ()
{
	hidden = true;
	m_widget->set_enabled (false);
}

void SDLWidgetWidget::show ()
{
	hidden = false;
	m_widget->set_enabled (!inactive);
}

void SDLWidgetWidget::deactivate ()
{
	inactive = true;
	m_widget->set_enabled (false);
}

void SDLWidgetWidget::activate ()
{
	inactive = false;
	m_widget->set_enabled (!hidden);
}


PlayersInGameWidget::PlayersInGameWidget (w_players_in_game2* pig)
	: SDLWidgetWidget (pig)
	, m_pig (pig)
	{}
	
void PlayersInGameWidget::redraw ()
{
	m_pig->start_displaying_actual_information ();
	m_pig->update_display ();
	m_pig->get_owning_dialog ()->draw_dirty_widgets ();
}



