/*

	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 2 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 <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(x, y);

}

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);
}

/*
 *  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);
}

/* 
 * 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();

	// 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[] = {"-", "X", 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
{
	int y = rect.y + font->get_ascent();

	// 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) {
	  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::event(SDL_Event &e)
{
	if (e.type == SDL_KEYDOWN) {
		switch (e.key.keysym.sym) {
			case SDLK_LEFT:			// Left/right does nothing (for now)
			case SDLK_RIGHT:
				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 last character
backspace:		if (num_chars) {
					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);
					}
					buf[num_chars++] = 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;
					modified_text();
					play_dialog_sound(DIALOG_ERASE_SOUND);
				} else if (uc == 4 || uc == 8)	// Ctrl-D/H: backspace
					goto backspace;
				break;
			}
		}
	}
}

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);
	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"
};

// 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
        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) {
		SDL_Rect r = {x, rect.y, text_width(WAITING_TEXT, font, style), rect.h};
//		SDL_FillRect(s, &r, get_dialog_color(KEY_BINDING_COLOR));
		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);
        }

    	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)
{
	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);
		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 ();
}



