/***************************************************************************
 widgets.c Copyright (C) 2000,01 Christoph Reichenbach


 This program may be modified and copied freely according to the terms of
 the GNU general public license (GPL), as long as the above copyright
 notice and the licensing information contained herein are preserved.

 Please refer to www.gnu.org for licensing details.

 This work is provided AS IS, without warranty of any kind, expressed or
 implied, including but not limited to the warranties of merchantibility,
 noninfringement, and fitness for a specific purpose. The author will not
 be held liable for any damage caused by this work or derivatives of it.

 By using this source code, you agree to the licensing terms as stated
 above.


 Please contact the maintainer for bug reports or inquiries.

 Current Maintainer:

    Christoph Reichenbach (CR) <jameson@linuxgames.com>

***************************************************************************/

#include <sci_memory.h>
#include <gfx_widgets.h>

#undef GFXW_DEBUG_DIRTY /* Enable to debug dirty rectangle propagation (writes to stderr) */

#ifdef GFXW_DEBUG_DIRTY
#  define DDIRTY fprintf(stderr, "%s:%5d| ", __FILE__, __LINE__); fprintf
#else
#  define DDIRTY if (0) fprintf
#endif

#define MAX_SERIAL_NUMBER 0x7fffffff
static int widget_serial_number_counter = 0x10000; /* Avoid confusion with IDs */

#ifdef GFXW_DEBUG_WIDGETS

gfxw_widget_t *debug_widgets[GFXW_DEBUG_WIDGETS];
int debug_widget_pos = 0;

static void
_gfxw_debug_add_widget(gfxw_widget_t *widget)
{
	if (debug_widget_pos == GFXW_DEBUG_WIDGETS) {
		GFXERROR("WIDGET DEBUG: Allocated the maximum number of %d widgets- Aborting!\n", GFXW_DEBUG_WIDGETS);
		BREAKPOINT();
	}
	debug_widgets[debug_widget_pos++] = widget;
}

static void
_gfxw_debug_remove_widget(gfxw_widget_t *widget) {
	int i;
	int found = 0;
	for (i = 0; i < debug_widget_pos; i++) {
		if (debug_widgets[i] == widget) {
			memmove(debug_widgets + i, debug_widgets + i + 1,
				(sizeof (gfxw_widget_t *)) * (debug_widget_pos - i - 1));
			debug_widgets[debug_widget_pos--] = NULL;
			found++;
		}
	}

	if (found > 1) {
		GFXERROR("While removing widget: Found it %d times!\n", found);
		BREAKPOINT();
	}

	if (found == 0) {
		GFXERROR("Attempted removal of unregistered widget!\n");
		BREAKPOINT();
	}
}
#else /* !GFXW_DEBUG_WIDGETS */
#define _gfxw_debug_add_widget(a)
#define _gfxw_debug_remove_widget(a)
#endif


static inline void
indent(int indentation)
{
	int i;
	for (i = 0; i < indentation; i++)
		sciprintf("    ");
}

static void
_gfxw_print_widget(gfxw_widget_t *widget, int indentation)
{
	unsigned int i;
	char flags_list[] = "VOCDTMI";

	indent(indentation);

	if (widget->magic == GFXW_MAGIC_VALID) {
		if (widget->visual)
			sciprintf("v ");
		else
			sciprintf("NoVis ");
	} else if (widget->magic == GFXW_MAGIC_INVALID)
		sciprintf("INVALID ");

	sciprintf("S%08x", widget->serial);

	if (widget->ID != GFXW_NO_ID)
		sciprintf("#%x ", widget->ID);

	sciprintf("[(%d,%d)(%dx%d)]", widget->bounds.x, widget->bounds.y, widget->bounds.xl, widget->bounds.yl);

	for (i = 0; i < strlen(flags_list); i++)
		if (widget->flags & (1 << i))
			sciprintf("%c", flags_list[i]);

	sciprintf(" ");
}

static int
_gfxwop_print_empty(gfxw_widget_t *widget, int indentation)
{
	_gfxw_print_widget(widget, indentation);
	sciprintf("<untyped #%d>", widget->type);

	return 0;
}


gfxw_widget_t *
_gfxw_new_widget(int size, int type)
{
	gfxw_widget_t *widget = sci_malloc(size);
#ifdef SATISFY_PURIFY
	memset(widget, 0, size);
#endif

	widget->magic = GFXW_MAGIC_VALID;
	widget->parent = NULL;
	widget->visual = NULL;
	widget->next = NULL;
	widget->type = type;
	widget->bounds = gfx_rect(0, 0, 0, 0);
	widget->flags = GFXW_FLAG_DIRTY;
	widget->ID = GFXW_NO_ID;
	widget->serial = widget_serial_number_counter++;
	widget->widget_priority = -1;

	widget_serial_number_counter &= MAX_SERIAL_NUMBER;

	widget->draw = NULL;
	widget->widfree = NULL;
	widget->tag = NULL;
	widget->print = _gfxwop_print_empty;
	widget->should_replace = NULL;
	widget->compare_to = widget->equals = widget->superarea_of = NULL;

	_gfxw_debug_add_widget(widget);

	return widget;
}


static inline int
verify_widget(gfxw_widget_t *widget)
{
	if (!widget) {
		GFXERROR("Attempt to use NULL widget\n");
#ifdef GFXW_DEBUG_WIDGETS
		BREAKPOINT();
#endif /* GFXW_DEBUG_WIDGETS */
		return 1;
	} else if (widget->magic != GFXW_MAGIC_VALID) {
		if (widget->magic == GFXW_MAGIC_INVALID) {
			GFXERROR("Attempt to use invalidated widget\n");
		} else {
			GFXERROR("Attempt to use non-widget\n");
		}
#ifdef GFXW_DEBUG_WIDGETS
		BREAKPOINT();
#endif /* GFXW_DEBUG_WIDGETS */
		return 1;
	}
	return 0;
}

#define VERIFY_WIDGET(w) \
  if (verify_widget((gfxw_widget_t *)(w))) { GFXERROR("Error occured while validating widget\n"); }

static void
_gfxw_unallocate_widget(gfx_state_t *state, gfxw_widget_t *widget)
{
	if (GFXW_IS_TEXT(widget)) {
		gfxw_text_t *text = (gfxw_text_t *) widget;

		if (text->text_handle) {
			if (!state) {
				GFXERROR("Attempt to free text without supplying mode to free it from!\n");
				BREAKPOINT();
			} else {
				gfxop_free_text(state, text->text_handle);
				text->text_handle = NULL;
			}
		}
	}

	widget->magic = GFXW_MAGIC_INVALID;
	free(widget);
	_gfxw_debug_remove_widget(widget);
}

#define GFX_ASSERT(_x) \
  { \
	  int retval = (_x); \
	  if (retval == GFX_ERROR) { \
		  GFXERROR("Error occured while drawing widget!\n"); \
		  return 1; \
	  } else if (retval == GFX_FATAL) { \
		  GFXERROR("Fatal error occured while drawing widget!\nGraphics state invalid; aborting program..."); \
		  exit(1); \
	  } \
  }


/**********************************/
/*********** Widgets **************/
/**********************************/

/* Base class operations and common stuff */

/* Assertion for drawing */
#define DRAW_ASSERT(widget, exp_type) \
  if (!(widget)) { \
	sciprintf("L%d: NULL widget!\n", __LINE__); \
	return 1; \
  } \
  if (!(widget)->print) { \
	  sciprintf("L%d: Widget of type %d does not have print function!\n", __LINE__, \
		    (widget)->type); \
  } \
  if ((widget)->type != (exp_type)) { \
	  sciprintf("L%d: Error in widget: Expected type " # exp_type "(%d) but got %d\n", \
		    __LINE__, exp_type, (widget)->type); \
	  sciprintf("Erroneous widget: "); \
	  widget->print(widget, 4); \
	  sciprintf("\n"); \
	  return 1; \
  } \
  if (!(widget->flags & GFXW_FLAG_VISIBLE)) \
	  return 0; \
  if (!(widget->type == GFXW_VISUAL || widget->visual)) { \
	  sciprintf("L%d: Error while drawing widget: Widget has no visual\n", __LINE__); \
	  sciprintf("Erroneous widget: "); \
	  widget->print(widget, 1); \
	  sciprintf("\n"); \
	  return 1; \
  }


static inline int
_color_equals(gfx_color_t a, gfx_color_t b)
{
	if (a.mask != b.mask)
		return 0;

	if (a.mask & GFX_MASK_VISUAL) {
		if (a.visual.r != b.visual.r
		    || a.visual.g != b.visual.g
		    || a.visual.b != b.visual.b
		    || a.alpha != b.alpha)
			return 0;
	}

	if (a.mask & GFX_MASK_PRIORITY)
		if (a.priority != b.priority)
			return 0;

	if (a.mask & GFX_MASK_CONTROL)
		if (a.control != b.control)
			return 0;

	return 1;
}

static int
_gfxwop_basic_set_visual(gfxw_widget_t *widget, gfxw_visual_t *visual)
{
	widget->visual = visual;

	if (widget->parent) {
		DDIRTY(stderr,"basic_set_visual: DOWNWARDS rel(%d,%d,%d,%d, 1)\n",
		       GFX_PRINT_RECT(widget->bounds));
		widget->parent->add_dirty_rel(widget->parent, widget->bounds, 1);
	}

	return 0;
}


static int
_gfxwop_basic_should_replace(gfxw_widget_t *widget, gfxw_widget_t *other)
{
	return 0;
}

static inline void
_gfxw_set_ops(gfxw_widget_t *widget, gfxw_point_op *draw, gfxw_op *free, gfxw_op *tag, gfxw_op_int *print,
	      gfxw_bin_op *compare_to, gfxw_bin_op *equals, gfxw_bin_op *superarea_of)
{
	widget->draw = draw;
	widget->widfree = free;
	widget->tag = tag;
	widget->print = print;
	widget->compare_to = compare_to;
	widget->equals = equals;
	widget->superarea_of = superarea_of;

	widget->should_replace = _gfxwop_basic_should_replace;
	widget->set_visual = _gfxwop_basic_set_visual;
}

void
gfxw_remove_widget_from_container(gfxw_container_t *container, gfxw_widget_t *widget)
{
	gfxw_widget_t **seekerp;

	if (!container) {
		GFXERROR("Attempt to remove widget from NULL container!\n");
		BREAKPOINT();
	}

	seekerp = &(container->contents);

	if (GFXW_IS_LIST(widget) && GFXW_IS_PORT(container)) {
	        gfxw_port_t *port = (gfxw_port_t *) container;
		if (port->decorations == (gfxw_list_t *) widget) {
			port->decorations = NULL;
			return;
		}
	}

	while (*seekerp && *seekerp != widget)
		seekerp = &((*seekerp)->next);

	if (!*seekerp) {
		GFXERROR("Internal error: Attempt to remove widget from container it was not contained in!\n");
		sciprintf("Widget:");
		widget->print(GFXW(widget), 1);
		sciprintf("Container:");
		widget->print(GFXW(container), 1);
		BREAKPOINT();
		return;
	}

	if (container->nextpp == &(widget->next))
		container->nextpp = seekerp;

	*seekerp = widget->next; /* Remove it */
	widget->parent = NULL;
	widget->next = NULL;
}

static int
_gfxwop_basic_free(gfxw_widget_t *widget)
{
	gfxw_visual_t *visual = widget->visual;
	gfx_state_t *state = (visual)? visual->gfx_state : NULL;

	DDIRTY(stderr, "BASIC-FREE: SomeAddDirty\n");

	if (widget->parent) {
		if (GFXW_IS_CONTAINER(widget))
			widget->parent->add_dirty_abs(widget->parent, widget->bounds, 1);
		else
			widget->parent->add_dirty_rel(widget->parent, widget->bounds, 1);

		gfxw_remove_widget_from_container(widget->parent, widget);
	}

	_gfxw_unallocate_widget(state, widget);


	return 0;
}


static int
_gfxwop_basic_tag(gfxw_widget_t *widget)
{
	widget->flags |= GFXW_FLAG_TAGGED;

	return 0;
}


static int
_gfxwop_basic_compare_to(gfxw_widget_t *widget, gfxw_widget_t *other)
{
	return 1;
}


static int
_gfxwop_basic_equals(gfxw_widget_t *widget, gfxw_widget_t *other)
{
	return 0;
}


static int
_gfxwop_basic_superarea_of(gfxw_widget_t *widget, gfxw_widget_t *other)
{
	return (widget == other);
}

/*-------------*/
/**** Boxes ****/
/*-------------*/

static inline rect_t
_move_rect(rect_t rect, point_t point)
{
	return gfx_rect(rect.x + point.x, rect.y + point.y, rect.xl, rect.yl);
}

static inline point_t
_move_point(rect_t rect, point_t point)
{
	return gfx_point(rect.x + point.x, rect.y + point.y);
}

static int
_gfxwop_box_draw(gfxw_widget_t *widget, point_t pos)
{
	gfxw_box_t *box = (gfxw_box_t *) widget;
	DRAW_ASSERT(widget, GFXW_BOX);
	GFX_ASSERT(gfxop_draw_box(box->visual->gfx_state, _move_rect(box->bounds, pos), box->color1,
				  box->color2, box->shade_type));

	return 0;
}

static int
_gfxwop_box_print(gfxw_widget_t *widget, int indentation)
{
	_gfxw_print_widget(widget, indentation);
	sciprintf("BOX");
	return 0;
}

static int
_gfxwop_box_superarea_of(gfxw_widget_t *widget, gfxw_widget_t *other)
{
	gfxw_box_t *box = (gfxw_box_t *) widget;

	if (box->color1.alpha)
		return 0;

	if (box->shade_type != GFX_BOX_SHADE_FLAT && box->color2.alpha)
		return 0;

	if (!gfx_rect_subset(other->bounds, box->bounds))
		return 0;

	return 1;
}

static int
_gfxwop_box_equals(gfxw_widget_t *widget, gfxw_widget_t *other)
{
	gfxw_box_t *wbox = (gfxw_box_t *) widget, *obox;
	if (other->type != GFXW_BOX)
		return 0;

	obox = (gfxw_box_t *) other;

	if (!gfx_rect_equals(wbox->bounds, obox->bounds))
		return 0;

	if (!_color_equals(wbox->color1, obox->color1))
		return 0;

	if (wbox->shade_type != obox->shade_type)
		return 0;

	if (wbox->shade_type != GFX_BOX_SHADE_FLAT
	    && _color_equals(wbox->color2, obox->color2))
		return 0;

	return 1;
}

void
_gfxw_set_ops_BOX(gfxw_widget_t *widget)
{
	_gfxw_set_ops(GFXW(widget), _gfxwop_box_draw,
		      _gfxwop_basic_free,
		      _gfxwop_basic_tag,
		      _gfxwop_box_print,
		      _gfxwop_basic_compare_to,
		      _gfxwop_box_equals,
		      _gfxwop_box_superarea_of);
}

static inline int
_gfxw_color_get_priority(gfx_color_t color)
{
	return (color.mask & GFX_MASK_PRIORITY)? color.priority : -1;
}

gfxw_box_t *
gfxw_new_box(gfx_state_t *state, rect_t area, gfx_color_t color1, gfx_color_t color2, gfx_box_shade_t shade_type)
{
	gfxw_box_t *widget = (gfxw_box_t *) _gfxw_new_widget(sizeof(gfxw_box_t), GFXW_BOX);

	widget->widget_priority = _gfxw_color_get_priority(color1);
	widget->bounds = area;
	widget->color1 = color1;
	widget->color2 = color2;
	widget->shade_type = shade_type;

	widget->flags |= GFXW_FLAG_VISIBLE;

	if ((color1.mask & GFX_MASK_VISUAL)
	    && ((state && (state->driver->mode->palette))
		|| (!color1.alpha && !color2.alpha)))
		widget->flags |= GFXW_FLAG_OPAQUE;

	_gfxw_set_ops_BOX(GFXW(widget));

	return widget;
}


static inline gfxw_primitive_t *
_gfxw_new_primitive(rect_t area, gfx_color_t color, gfx_line_mode_t mode, gfx_line_style_t style, int type)
{
	gfxw_primitive_t *widget = (gfxw_primitive_t *) _gfxw_new_widget(sizeof(gfxw_primitive_t), type);

	widget->widget_priority = _gfxw_color_get_priority(color);
	widget->bounds = area;
	widget->color = color;
	widget->line_mode = mode;
	widget->line_style = style;

	widget->flags |= GFXW_FLAG_VISIBLE;
	return widget;
}

/*------------------*/
/**** Rectangles ****/
/*------------------*/

static int
_gfxwop_primitive_equals(gfxw_widget_t *widget, gfxw_widget_t *other)
{
	gfxw_primitive_t *wprim = (gfxw_primitive_t *) widget, *oprim;
	if (widget->type != other->type)
		return 0;

	oprim = (gfxw_primitive_t *) other;

	if (!gfx_rect_equals(wprim->bounds, oprim->bounds))
		return 0;

	if (!_color_equals(wprim->color, oprim->color))
		return 0;

	if (wprim->line_mode != oprim->line_mode)
		return 0;

	if (wprim->line_style != oprim->line_style)
		return 0;

	return 1;
}

static int
_gfxwop_rect_draw(gfxw_widget_t *widget, point_t pos)
{
	gfxw_primitive_t *rect = (gfxw_primitive_t *) widget;
	DRAW_ASSERT(widget, GFXW_RECT);

	GFX_ASSERT(gfxop_draw_rectangle(rect->visual->gfx_state,
					gfx_rect(rect->bounds.x + pos.x, rect->bounds.y + pos.y,
						 rect->bounds.xl - 1, rect->bounds.yl - 1),
					rect->color, rect->line_mode, rect->line_style));

	return 0;
}

static int
_gfxwop_rect_print(gfxw_widget_t *rect, int indentation)
{
	_gfxw_print_widget(GFXW(rect), indentation);
	sciprintf("RECT");
	return 0;
}


void
_gfxw_set_ops_RECT(gfxw_widget_t *prim)
{
	_gfxw_set_ops(GFXW(prim), _gfxwop_rect_draw,
		      _gfxwop_basic_free,
		      _gfxwop_basic_tag,
		      _gfxwop_rect_print,
		      _gfxwop_basic_compare_to,
		      _gfxwop_primitive_equals,
		      _gfxwop_basic_superarea_of);
}

gfxw_primitive_t *
gfxw_new_rect(rect_t rect, gfx_color_t color, gfx_line_mode_t line_mode, gfx_line_style_t line_style)
{
	gfxw_primitive_t *prim = _gfxw_new_primitive(rect, color, line_mode, line_style, GFXW_RECT);
	prim->bounds.xl++;
	prim->bounds.yl++; /* Since it is actually one pixel bigger in each direction */

	_gfxw_set_ops_RECT(GFXW(prim));

	return prim;
}


/*-------------*/
/**** Lines ****/
/*-------------*/

static int
_gfxwop_line_draw(gfxw_widget_t *widget, point_t pos)
{
	gfxw_primitive_t *line = (gfxw_primitive_t *) widget;
	rect_t linepos = widget->bounds;

	linepos.xl--;
	linepos.yl--;

	if (widget->type == GFXW_INVERSE_LINE) {
		linepos.x += linepos.xl;
		linepos.xl = -linepos.xl;
	} else {
		DRAW_ASSERT(widget, GFXW_LINE);
	}


	GFX_ASSERT(gfxop_draw_line(line->visual->gfx_state, _move_rect(linepos, pos),
				   line->color, line->line_mode, line->line_style));

	return 0;
}

static int
_gfxwop_line_print(gfxw_widget_t *widget, int indentation)
{
	_gfxw_print_widget(widget, indentation);
	if (widget->type == GFXW_INVERSE_LINE)
		sciprintf("INVERSE-LINE");
	else
		sciprintf("LINE");
	return 0;
}

void
_gfxw_set_ops_LINE(gfxw_widget_t *prim)
{
	_gfxw_set_ops(GFXW(prim), _gfxwop_line_draw,
		      _gfxwop_basic_free,
		      _gfxwop_basic_tag,
		      _gfxwop_line_print,
		      _gfxwop_basic_compare_to,
		      _gfxwop_primitive_equals,
		      _gfxwop_basic_superarea_of);

}

gfxw_primitive_t *
gfxw_new_line(rect_t line, gfx_color_t color, gfx_line_mode_t line_mode, gfx_line_style_t line_style)
{
	gfxw_primitive_t *prim;
	byte inverse = 0;

	if (line.xl < 0) {
		line.x += line.xl;
		line.y += line.yl;
		line.xl = -line.xl;
		line.yl = -line.yl;
	}

	if (line.yl < 0) {
		inverse = 1;
		line.x += line.xl;
		line.xl = -line.xl;
	}

	line.xl++;
	line.yl++;

	prim = _gfxw_new_primitive(line, color, line_mode, line_style, inverse? GFXW_INVERSE_LINE : GFXW_LINE);

	_gfxw_set_ops_LINE(GFXW(prim));

	return prim;
}

/*------------------------------*/
/**** Views and static views ****/
/*------------------------------*/


gfxw_view_t *
_gfxw_new_simple_view(gfx_state_t *state, point_t pos, int view, int loop, int cel, int priority, int control,
		      gfx_alignment_t halign, gfx_alignment_t valign, int size, int type)
{
	gfxw_view_t *widget;
	int width, height;
	point_t offset;

	if (!state) {
		GFXERROR("Attempt to create view widget with NULL state!\n");
		return NULL;
	}

	if (gfxop_get_cel_parameters(state, view, loop, cel, &width, &height, &offset)) {
		GFXERROR("Attempt to retreive cel parameters for (%d/%d/%d) failed (Maybe the values weren't checked beforehand?)\n",
			 view, cel, loop);
		return NULL;
	}

	widget = (gfxw_view_t *) _gfxw_new_widget(size, type);

	widget->widget_priority = priority;
	widget->pos = pos;
	widget->color.mask =
		(priority < 0)? 0 : GFX_MASK_PRIORITY
		| (control < 0)? 0 : GFX_MASK_CONTROL;
	widget->color.priority = priority;
	widget->color.control = control;
	widget->view = view;
	widget->loop = loop;
	widget->cel = cel;

	if (halign == ALIGN_CENTER)
		widget->pos.x -= width >> 1;
	else if (halign == ALIGN_RIGHT)
		widget->pos.x -= width;

	if (valign == ALIGN_CENTER)
		widget->pos.y -= height >> 1;
	else if (valign == ALIGN_BOTTOM)
		widget->pos.y -= height;

	widget->bounds = gfx_rect(widget->pos.x - offset.x, widget->pos.y - offset.y, width, height);

	widget->flags |= GFXW_FLAG_VISIBLE;

	return widget;
}

static int
_gfxwop_view_draw(gfxw_widget_t *widget, point_t pos)
{
	gfxw_view_t *view = (gfxw_view_t *) widget;
	DRAW_ASSERT(widget, GFXW_VIEW);

	GFX_ASSERT(gfxop_draw_cel(view->visual->gfx_state, view->view, view->loop,
				  view->cel, gfx_point(view->pos.x + pos.x, view->pos.y + pos.y),
				  view->color));

	return 0;
}

static int
_gfxwop_static_view_draw(gfxw_widget_t *widget, point_t pos)
{
	gfxw_view_t *view = (gfxw_view_t *) widget;
	DRAW_ASSERT(widget, GFXW_VIEW);

	GFX_ASSERT(gfxop_draw_cel_static(view->visual->gfx_state, view->view, view->loop,
					 view->cel, _move_point(view->bounds, pos),
					 view->color));

	return 0;
}

static int
_w_gfxwop_view_print(gfxw_widget_t *widget, char *name, int indentation)
{
	gfxw_view_t *view = (gfxw_view_t *) widget;
	_gfxw_print_widget(widget, indentation);

	sciprintf(name);
	sciprintf("(%d/%d/%d)@(%d,%d)[p:%d,c:%d]", view->view, view->loop, view->cel, view->pos.x, view->pos.y,
		  (view->color.mask & GFX_MASK_PRIORITY)? view->color.priority : -1,
		  (view->color.mask & GFX_MASK_CONTROL)? view->color.control : -1);

	return 0;
}

static int
_gfxwop_view_print(gfxw_widget_t *widget, int indentation)
{
	return _w_gfxwop_view_print(widget, "VIEW", indentation);
}

static int
_gfxwop_static_view_print(gfxw_widget_t *widget, int indentation)
{
	return _w_gfxwop_view_print(widget, "PICVIEW", indentation);
}

void
_gfxw_set_ops_VIEW(gfxw_widget_t *view, char stat)
{
	_gfxw_set_ops(GFXW(view), (stat) ? _gfxwop_static_view_draw : _gfxwop_view_draw,
		      _gfxwop_basic_free,
		      _gfxwop_basic_tag,
		      (stat) ? _gfxwop_static_view_print : _gfxwop_view_print,
		      _gfxwop_basic_compare_to,
		      _gfxwop_basic_equals,
		      _gfxwop_basic_superarea_of);
}

gfxw_view_t *
gfxw_new_view(gfx_state_t *state, point_t pos, int view_nr, int loop, int cel, int priority, int control,
	      gfx_alignment_t halign, gfx_alignment_t valign, int flags)
{
	gfxw_view_t *view;

	if (flags & GFXW_VIEW_FLAG_DONT_MODIFY_OFFSET) {
		int foo;
		point_t offset;
		gfxop_get_cel_parameters(state, view_nr, loop, cel, &foo, &foo, &offset);
		pos.x += offset.x;
		pos.y += offset.y;
	}

	view = _gfxw_new_simple_view(state, pos, view_nr, loop, cel, priority, control, halign, valign,
				     sizeof(gfxw_view_t), (flags & GFXW_VIEW_FLAG_STATIC) ? GFXW_STATIC_VIEW : GFXW_VIEW);

	_gfxw_set_ops_VIEW(GFXW(view), (char)(flags & GFXW_VIEW_FLAG_STATIC));

	return view;
}

/*---------------------*/
/**** Dynamic Views ****/
/*---------------------*/

static int
_gfxwop_dyn_view_draw(gfxw_widget_t *widget, point_t pos)
{
	gfxw_dyn_view_t *view = (gfxw_dyn_view_t *) widget;
	DRAW_ASSERT(widget, GFXW_DYN_VIEW);

	GFX_ASSERT(gfxop_draw_cel(view->visual->gfx_state, view->view, view->loop,
				  view->cel, _move_point(view->draw_bounds, pos),
				  view->color));

	/*
	  gfx_color_t red; red.visual.r = 0xff; red.visual.g = red.visual.b = 0; red.mask = GFX_MASK_VISUAL;
	  GFX_ASSERT(gfxop_draw_rectangle(view->visual->gfx_state,
	  gfx_rect(view->bounds.x + pos.x, view->bounds.y + pos.y,
	  view->bounds.xl - 1, view->bounds.yl - 1),
	  red, 0, 0));
	*/


	return 0;

}

static int
_gfxwop_draw_nop(gfxw_widget_t *widget, point_t pos)
{
	return 0;
}

static int
_gfxwop_pic_view_draw(gfxw_widget_t *widget, point_t pos)
{
	gfxw_dyn_view_t *view = (gfxw_dyn_view_t *) widget;
	DRAW_ASSERT(widget, GFXW_PIC_VIEW);

	GFX_ASSERT(gfxop_set_clip_zone(view->visual->gfx_state, view->parent->zone));
	GFX_ASSERT(gfxop_draw_cel_static_clipped(view->visual->gfx_state,
						 view->view, view->loop,
						 view->cel,
						 _move_point(view->draw_bounds, pos),
						 view->color));

	/* Draw again on the back buffer */
	GFX_ASSERT(gfxop_draw_cel(view->visual->gfx_state,
				  view->view, view->loop,
				  view->cel,
				  _move_point(view->draw_bounds, pos),
				  view->color));


	widget->draw = _gfxwop_draw_nop; /* No more drawing needs to be done */


	return 0;
}

static int
_gfxwop_some_view_print(gfxw_widget_t *widget, int indentation, char *type_string)
{
	gfxw_dyn_view_t *view = (gfxw_dyn_view_t *) widget;

	_gfxw_print_widget(widget, indentation);

	sciprintf(type_string);
	sciprintf(" SORT=%d z=%d seq=%d (%d/%d/%d)@(%d,%d)[p:%d,c:%d]; sig[%04x@%04x]", view->force_precedence, view->z,
		  view->sequence, view->view, view->loop, view->cel, view->pos.x, view->pos.y,
		  (view->color.mask & GFX_MASK_PRIORITY)? view->color.priority : -1,
		  (view->color.mask & GFX_MASK_CONTROL)? view->color.control : -1,
		  view->signal, view->signalp);

	return 0;
}

static int
_gfxwop_dyn_view_print(gfxw_widget_t *widget, int indentation)
{
	return _gfxwop_some_view_print(widget, indentation, "DYNVIEW");
}

static int
_gfxwop_pic_view_print(gfxw_widget_t *widget, int indentation)
{
	return _gfxwop_some_view_print(widget, indentation, "PICVIEW");
}


static int
_gfxwop_dyn_view_equals(gfxw_widget_t *widget, gfxw_widget_t *other)
{
	gfxw_dyn_view_t *wview = (gfxw_dyn_view_t *) widget, *oview;
	if (!GFXW_IS_DYN_VIEW(other))
		return 0;

	oview = (gfxw_dyn_view_t *) other;

	if (wview->pos.x != oview->pos.x
	    || wview->pos.y != oview->pos.y
	    || wview->z != oview->z)
		return 0;

	if (wview->view != oview->view
	    || wview->loop != oview->loop
	    || wview->cel != oview->cel)
		return 0;

	if (!_color_equals(wview->color, oview->color))
		return 0;

	if (wview->flags != oview->flags)
		return 0;

	return 1;
}

static int
_gfxwop_dyn_view_compare_to(gfxw_widget_t *widget, gfxw_widget_t *other)
{
	int retval;
	gfxw_dyn_view_t *wview = (gfxw_dyn_view_t *) widget, *oview;
	if (!GFXW_IS_DYN_VIEW(other))
		return 1;

	oview = (gfxw_dyn_view_t *) other;

	retval = wview->force_precedence - oview->force_precedence;
	if (retval)
		return retval;

	retval = wview->pos.y - oview->pos.y;
	if (retval)
		return retval;

	retval = (wview->z - oview->z);
	if (retval)
		return retval;

	return -(wview->sequence - oview->sequence);
}


void
_gfxw_set_ops_DYNVIEW(gfxw_widget_t *widget)
{
	_gfxw_set_ops(GFXW(widget), _gfxwop_dyn_view_draw,
		      _gfxwop_basic_free,
		      _gfxwop_basic_tag,
		      _gfxwop_dyn_view_print,
		      _gfxwop_dyn_view_compare_to,
		      _gfxwop_dyn_view_equals,
		      _gfxwop_basic_superarea_of);
}

void
_gfxw_set_ops_PICVIEW(gfxw_widget_t *widget)
{
	_gfxw_set_ops_DYNVIEW(widget);
	widget->draw = _gfxwop_pic_view_draw;
	widget->print = _gfxwop_pic_view_print;
}

gfxw_dyn_view_t *
gfxw_new_dyn_view(gfx_state_t *state, point_t pos, int z, int view, int loop, int cel, int priority, int control,
		  gfx_alignment_t halign, gfx_alignment_t valign, int sequence)
{
	gfxw_dyn_view_t *widget;
	int width, height;
	int xalignmod, yalignmod;
	point_t offset;

	if (!state) {
		GFXERROR("Attempt to create view widget with NULL state!\n");
		return NULL;
	}

	if (gfxop_get_cel_parameters(state, view, loop, cel, &width, &height, &offset)) {
		GFXERROR("Attempt to retreive cel parameters for (%d/%d/%d) failed (Maybe the values weren't checked beforehand?)\n",
			 view, cel, loop);
		return NULL;
	}

	widget = (gfxw_dyn_view_t *) _gfxw_new_widget(sizeof(gfxw_dyn_view_t), GFXW_DYN_VIEW);

	widget->pos = pos;
	widget->color.mask =
		((priority < 0)? 0 : GFX_MASK_PRIORITY)
		| ((control < 0)? 0 : GFX_MASK_CONTROL);
	widget->widget_priority = priority;
	widget->color.priority = priority;
	widget->color.control = control;
	widget->color.alpha = 0;
	widget->color.visual.global_index = 0;
	widget->color.visual.r = 0;
	widget->color.visual.g = 0;
	widget->color.visual.b = 0;
	widget->view = view;
	widget->loop = loop;
	widget->cel = cel;
	widget->sequence = sequence;
	widget->force_precedence = 0;


	if (halign == ALIGN_CENTER)
		xalignmod = width >> 1;
	else if (halign == ALIGN_RIGHT)
		xalignmod = width;
	else
		xalignmod = 0;

	if (valign == ALIGN_CENTER)
		yalignmod = height >> 1;
	else if (valign == ALIGN_BOTTOM)
		yalignmod = height;
	else
		yalignmod = 0;

	widget->z = z;

	widget->draw_bounds = gfx_rect(widget->pos.x - xalignmod,
				       widget->pos.y - yalignmod - z, width, height);
	widget->bounds = gfx_rect(widget->pos.x - offset.x - xalignmod,
				  widget->pos.y - offset.y - yalignmod - z, width, height);

	widget->flags |= GFXW_FLAG_VISIBLE;

	_gfxw_set_ops_DYNVIEW(GFXW(widget));

	widget->signalp = widget->signal = 0;

	return widget;
}

/*------------*/
/**** Text ****/
/*------------*/

static int
_gfxwop_text_free(gfxw_widget_t *widget)
{
	gfxw_text_t *text = (gfxw_text_t *) widget;
	free(text->text);
	return _gfxwop_basic_free(widget);
}

static int
_gfxwop_text_draw(gfxw_widget_t *widget, point_t pos)
{
	gfxw_text_t *text = (gfxw_text_t *) widget;
	DRAW_ASSERT(widget, GFXW_TEXT);

	GFX_ASSERT(gfxop_draw_text(text->visual->gfx_state, text->text_handle, _move_rect(text->bounds, pos)));

	return 0;
}

static int
_gfxwop_text_alloc_and_draw(gfxw_widget_t *widget, point_t pos)
{
	gfxw_text_t *text = (gfxw_text_t *) widget;
	DRAW_ASSERT(widget, GFXW_TEXT);

	text->text_handle =
		gfxop_new_text(widget->visual->gfx_state, text->font_nr, text->text, text->bounds.xl,
			       text->halign, text->valign, text->color1,
			       text->color2, text->bgcolor, text->text_flags);

	text->draw = _gfxwop_text_draw;

	return _gfxwop_text_draw(widget, pos);
}


static int
_gfxwop_text_print(gfxw_widget_t *widget, int indentation)
{
	_gfxw_print_widget(widget, indentation);
	sciprintf("TEXT:'%s'", ((gfxw_text_t *)widget)->text);
	return 0;
}


static int
_gfxwop_text_equals(gfxw_widget_t *widget, gfxw_widget_t *other)
{
	gfxw_text_t *wtext = (gfxw_text_t *) widget, *otext;
	if (other->type != GFXW_TEXT)
		return 0;

	otext = (gfxw_text_t *) other;

	if ((wtext->bounds.x != otext->bounds.x)
	    || (wtext->bounds.y != otext->bounds.y))
		return 0;

	if (wtext->halign != otext->halign
	    || wtext->valign != otext->valign)
		return 0;

	if (wtext->text_flags != otext->text_flags)
		return 0;

	if (wtext->font_nr != otext->font_nr)
		return 0;

	/* if (!(_color_equals(wtext->color1, otext->color1)
	      && _color_equals(wtext->color2, otext->color2)
	      && _color_equals(wtext->bgcolor, otext->bgcolor)))
	      return 0; */

	return 1;
}

static int
_gfxwop_text_should_replace(gfxw_widget_t *widget, gfxw_widget_t *other)
{
	gfxw_text_t *wtext = (gfxw_text_t *) widget, *otext;

	if (other->type != GFXW_TEXT)
		return 0;

	otext = (gfxw_text_t *) other;

	return strcmp(wtext->text, otext->text);
}


static int
_gfxwop_text_compare_to(gfxw_widget_t *widget, gfxw_widget_t *other)
{

	return 1;
}

void
_gfxw_set_ops_TEXT(gfxw_widget_t *widget)
{
	_gfxw_set_ops(GFXW(widget), _gfxwop_text_alloc_and_draw,
		      _gfxwop_text_free,
		      _gfxwop_basic_tag,
		      _gfxwop_text_print,
		      _gfxwop_text_compare_to,
		      _gfxwop_text_equals,
		      _gfxwop_basic_superarea_of);
	widget->should_replace = _gfxwop_text_should_replace;
}

gfxw_text_t *
gfxw_new_text(gfx_state_t *state, rect_t area, int font, char *text, gfx_alignment_t halign,
	      gfx_alignment_t valign, gfx_color_t color1, gfx_color_t color2,
	      gfx_color_t bgcolor, int text_flags)
{
	gfxw_text_t *widget = (gfxw_text_t *)
		_gfxw_new_widget(sizeof(gfxw_text_t), GFXW_TEXT);

	widget->widget_priority = _gfxw_color_get_priority(color1);
	widget->font_nr = font;
	widget->text = sci_malloc(strlen(text) + 1);
	widget->halign = halign;
	widget->valign = valign;
	widget->color1 = color1;
	widget->color2 = color2;
	widget->bgcolor = bgcolor;
	widget->text_flags = text_flags;
	widget->text_handle = NULL;

	strcpy(widget->text, text);

	gfxop_get_text_params(state, font, text, area.xl, &(widget->width), &(widget->height), text_flags);

	/* FIXME: Window is too big
	area.x += _calc_needmove(halign, area.xl, widget->width);
	area.y += _calc_needmove(valign, area.yl, widget->height);
	*/

	if (halign == ALIGN_LEFT)
		area.xl = widget->width;
	if (valign == ALIGN_TOP)
		area.yl = widget->height;

	widget->bounds = area;

	widget->flags |= GFXW_FLAG_VISIBLE;

	_gfxw_set_ops_TEXT(GFXW(widget));

	return widget;
}




/***********************/
/*-- Container types --*/
/***********************/

static int
_gfxwop_container_add_dirty_rel(gfxw_container_t *cont, rect_t rect, int propagate)
{
	DDIRTY(stderr, "->container_add_dirty_rel(%d,%d,%d,%d, %d)\n", GFX_PRINT_RECT(rect), propagate);
	return cont->add_dirty_abs(cont, _move_rect(rect, gfx_point(cont->zone.x, cont->zone.y)), propagate);
}

static inline void
_gfxw_set_container_ops(gfxw_container_t *container, gfxw_point_op *draw, gfxw_op *free, gfxw_op *tag,
			gfxw_op_int *print, gfxw_bin_op *compare_to, gfxw_bin_op *equals,
			gfxw_bin_op *superarea_of, gfxw_visual_op *set_visual,
			gfxw_unary_container_op *free_tagged, gfxw_unary_container_op *free_contents,
			gfxw_rect_op *add_dirty, gfxw_container_op *add)
{
	_gfxw_set_ops(GFXW(container),
		      draw,
		      free,
		      tag,
		      print,
		      compare_to,
		      equals,
		      superarea_of);

	container->free_tagged = free_tagged;
	container->free_contents = free_contents;
	container->add_dirty_abs = add_dirty;
	container->add_dirty_rel = _gfxwop_container_add_dirty_rel;
	container->add = add;
	container->set_visual = set_visual;
}

static int
_w_gfxwop_container_print_contents(char *name, gfxw_widget_t *widget, int indentation)
{
	gfxw_widget_t *seeker = widget;

	indent(indentation);

	sciprintf("--%s:\n", name);

	while (seeker) {
		seeker->print(seeker, indentation + 1);
		sciprintf("\n");
		seeker = seeker->next;
	}

	return 0;
}

static int
_w_gfxwop_container_print(gfxw_widget_t *widget, int indentation)
{
	gfx_dirty_rect_t *dirty;
	gfxw_container_t *container = (gfxw_container_t *) widget;
	if (!GFXW_IS_CONTAINER(widget)) {
		GFXERROR("_w_gfxwop_container_print() called on type %d widget\n", widget->type);
		return 1;
	}

	sciprintf(" viszone=((%d,%d),(%dx%d))\n", container->zone.x, container->zone.y,
		  container->zone.xl, container->zone.yl);

	indent(indentation);
	sciprintf("--dirty:\n");

	dirty = container->dirty;
	while (dirty) {
		indent(indentation + 1);
		sciprintf("dirty(%d,%d, (%dx%d))\n",
			dirty->rect.x, dirty->rect.y, dirty->rect.xl, dirty->rect.yl);
		dirty = dirty->next;
	}

	_w_gfxwop_container_print_contents("contents", container->contents, indentation);

	return 0;
}



gfxw_container_t *
_gfxw_new_container_widget(rect_t area, int size, int type)
{
	gfxw_container_t *widget = (gfxw_container_t *)
		_gfxw_new_widget(size, type);

	widget->bounds = widget->zone = area;
	widget->contents = NULL;
	widget->nextpp = &(widget->contents);
	widget->dirty = NULL;

	widget->flags |= GFXW_FLAG_VISIBLE | GFXW_FLAG_CONTAINER;

	return widget;
}


static void
recursively_free_dirty_rects(gfx_dirty_rect_t *dirty)
{
	if (dirty) {
		recursively_free_dirty_rects(dirty->next);
		free(dirty);
	}
}


int ti = 0;

static inline int
_gfxw_dirty_rect_overlaps_normal_rect(rect_t port_zone, rect_t bounds, rect_t dirty)
{
	bounds.x += port_zone.x;
	bounds.y += port_zone.y;
	return gfx_rects_overlap(bounds, dirty);
}

static int
_gfxwop_container_draw_contents(gfxw_widget_t *widget, gfxw_widget_t *contents)
{
	gfxw_container_t *container = (gfxw_container_t *) widget;
	gfx_dirty_rect_t *dirty = container->dirty;
	gfx_state_t *gfx_state = (widget->visual)? widget->visual->gfx_state : ((gfxw_visual_t *) widget)->gfx_state;
	int draw_ports;
	rect_t nullzone = {0,0,0,0};

	if (!contents)
		return 0;

	while (dirty) {
		gfxw_widget_t *seeker = contents;

		while (seeker) {
			if (_gfxw_dirty_rect_overlaps_normal_rect(GFXW_IS_CONTAINER(seeker)? nullzone : container->zone,
								  /* Containers have absolute coordinates, reflect this. */
								  seeker->bounds, dirty->rect)) {

				if (GFXW_IS_CONTAINER(seeker)) {/* Propagate dirty rectangles /upwards/ */
					DDIRTY(stderr,"container_draw_contents: propagate upwards (%d,%d,%d,%d ,0)\n", GFX_PRINT_RECT(dirty->rect));
					((gfxw_container_t *)seeker)->add_dirty_abs((gfxw_container_t *)seeker, dirty->rect, 0);
				}

				seeker->flags |= GFXW_FLAG_DIRTY;
			}

			seeker = seeker->next;
		}

		dirty = dirty->next;
	}

	/* The draw loop is executed twice: Once for normal data, and once for ports. */
	for (draw_ports = 0; draw_ports < 2; draw_ports++) {

		dirty = container->dirty;

		while (dirty) {

			gfxw_widget_t *seeker = contents;
			while (seeker && (draw_ports || !GFXW_IS_PORT(seeker))) {
				rect_t small_rect;
				byte draw_noncontainers;

				memcpy(&small_rect, &(dirty->rect), sizeof(rect_t));
				draw_noncontainers = !_gfxop_clip(&small_rect, container->bounds);

				if (seeker->flags & GFXW_FLAG_DIRTY) {

					if (!GFXW_IS_CONTAINER(seeker) && draw_noncontainers) {
						GFX_ASSERT(gfxop_set_clip_zone(gfx_state, small_rect));
					}
				/* Clip zone must be reset after each element, because we might
				** descend into containers.
				** Doing this is relatively cheap, though. */
					if (draw_noncontainers || GFXW_IS_CONTAINER(seeker))
						seeker->draw(seeker, gfx_point(container->zone.x, container->zone.y));

					if (!dirty->next)
						seeker->flags &= ~GFXW_FLAG_DIRTY;
				}

				seeker = seeker->next;
			}
			dirty = dirty->next;
		}
	}
	/* Remember that the dirty rects should be freed afterwards! */

	return 0;
}

static int
_gfxwop_container_free(gfxw_widget_t *widget)
{
	gfxw_container_t *container = (gfxw_container_t *) widget;
	gfxw_widget_t *seeker = container->contents;

	while (seeker) {
		gfxw_widget_t *next = seeker->next;
		seeker->widfree(seeker);
		seeker = next;
	}

	recursively_free_dirty_rects(container->dirty);
	container->dirty = NULL;

	return _gfxwop_basic_free(widget);
}

static int
_gfxwop_container_tag(gfxw_widget_t *widget)
{
	gfxw_container_t *container = (gfxw_container_t *) widget;
	gfxw_widget_t *seeker = container->contents;

	while (seeker) {
		seeker->tag(seeker);
		seeker = seeker->next;
	}
	return 0;
}


static int
_w_gfxwop_container_set_visual_contents(gfxw_widget_t *contents, gfxw_visual_t *visual)
{
	while (contents) {
		contents->set_visual(contents, visual);
		contents = contents->next;
	}
	return 0;
}

static int
_gfxwop_container_set_visual(gfxw_widget_t *widget, gfxw_visual_t *visual)
{
	gfxw_container_t *container = (gfxw_container_t *) widget;

	container->visual = visual;
	if (widget->parent) {
		if (!(GFXW_IS_LIST(widget) && !GFXWC(widget)->contents)) {
			DDIRTY(stderr,"set_visual::DOWNWARDS abs(%d,%d,%d,%d, 1)\n", GFX_PRINT_RECT(widget->bounds));
			widget->parent->add_dirty_abs(widget->parent, widget->bounds, 1);
		}
	}

	return _w_gfxwop_container_set_visual_contents(container->contents, visual);
}

static int
_gfxwop_container_free_tagged(gfxw_container_t *container)
{
        gfxw_widget_t *seekerp = (container->contents);

        while (seekerp) {
                gfxw_widget_t *redshirt = seekerp;

                if (redshirt->flags & GFXW_FLAG_TAGGED) {
                        seekerp = (redshirt->next);
                        redshirt->widfree(redshirt); /* He's dead, Jim. */
                } else
                        seekerp = (seekerp)->next;
        }
        return 0;
}

static int
_gfxwop_container_free_contents(gfxw_container_t *container)
{
	gfxw_widget_t *seeker = container->contents;

	while (seeker) {
		gfxw_widget_t *next = seeker->next;
		seeker->widfree(seeker);
		seeker = next;
	}
	return 0;
}

static void
_gfxw_dirtify_container(gfxw_container_t *container, gfxw_widget_t *widget)
{
	if (GFXW_IS_CONTAINER(widget))
		container->add_dirty_abs(GFXWC(container), widget->bounds, 1);
	else
		container->add_dirty_rel(GFXWC(container), widget->bounds, 1);
}

static int
_parentize_widget(gfxw_container_t *container, gfxw_widget_t *widget)
{
	if (widget->parent) {
		GFXERROR("_gfxwop_container_add(): Attempt to give second parent node to widget!\nWidget:");
		widget->print(GFXW(widget), 3);
		sciprintf("\nContainer:");
		container->print(GFXW(container), 3);

		return 1;
	}

	widget->parent = GFXWC(container);

	if (GFXW_IS_VISUAL(container))
		widget->set_visual(widget, (gfxw_visual_t *) container);
	else if (container->visual)
		widget->set_visual(widget, container->visual);

	return 0;
}

static int
_gfxw_container_id_equals(gfxw_container_t *container, gfxw_widget_t *widget)
{
	gfxw_widget_t **seekerp = &(container->contents);

	if (GFXW_IS_PORT(widget))
		return 0; /* Don't match ports */

	if (widget->ID == GFXW_NO_ID)
		return 0;

	while (*seekerp && (*seekerp)->ID != widget->ID)
		seekerp = &((*seekerp)->next);

	if (!*seekerp)
		return 0;

	if ((*seekerp)->equals(*seekerp, widget)
	    && !(*seekerp)->should_replace(*seekerp, widget)) {
		widget->widfree(widget);
		(*seekerp)->flags &= ~GFXW_FLAG_TAGGED;
		return 1;
	} else {
		if (!(widget->flags & GFXW_FLAG_MULTI_ID))
			(*seekerp)->widfree(*seekerp);
		return 0;
	}
}


static int
_gfxwop_container_add_dirty(gfxw_container_t *container, rect_t dirty, int propagate)
{
#if 0
	/* This code has been disabled because containers may contain sub-containers with
	** bounds greater than their own. */
	if (_gfxop_clip(&dirty, container->bounds))
		return 0;
#endif

	DDIRTY(stderr, "Effectively adding dirty %d,%d,%d,%d %d to ID %d\n", GFX_PRINT_RECT(dirty), propagate, container->ID);
	container->dirty = gfxdr_add_dirty(container->dirty, dirty, GFXW_DIRTY_STRATEGY);
	return 0;
}


static int
_gfxwop_container_add(gfxw_container_t *container, gfxw_widget_t *widget)
{
	if (_gfxw_container_id_equals(container, widget))
		return 0;

	if (_parentize_widget(container, widget))
		return 1;

	if (!(GFXW_IS_LIST(widget) && (!GFXWC(widget)->contents))) { /* Don't dirtify self on empty lists */
		DDIRTY(stderr, "container_add: dirtify DOWNWARDS (%d,%d,%d,%d, 1)\n", GFX_PRINT_RECT(widget->bounds));
		_gfxw_dirtify_container(container, widget);
	}

	*(container->nextpp) = widget;
	container->nextpp = &(widget->next);

	return 0;
}

/*------------------------------*/
/**** Lists and sorted lists ****/
/*------------------------------*/

static int
_gfxwop_list_draw(gfxw_widget_t *list, point_t pos)
{
	DRAW_ASSERT(list, GFXW_LIST);

	_gfxwop_container_draw_contents(list, ((gfxw_list_t *)list)->contents);
	recursively_free_dirty_rects(GFXWC(list)->dirty);
	GFXWC(list)->dirty = NULL;
	list->flags &= ~GFXW_FLAG_DIRTY;
	return 0;
}

static int
_gfxwop_sorted_list_draw(gfxw_widget_t *list, point_t pos)
{
	DRAW_ASSERT(list, GFXW_SORTED_LIST);

	_gfxwop_container_draw_contents(list, ((gfxw_list_t *)list)->contents);
	recursively_free_dirty_rects(GFXWC(list)->dirty);
	GFXWC(list)->dirty = NULL;
	return 0;
}

static inline int
_w_gfxwop_list_print(gfxw_widget_t *list, char *name, int indentation)
{
	_gfxw_print_widget(list, indentation);
	sciprintf(name);
	return _w_gfxwop_container_print(list, indentation);
}

static int
_gfxwop_list_print(gfxw_widget_t *list, int indentation)
{
	return _w_gfxwop_list_print(list, "LIST", indentation);
}

static int
_gfxwop_sorted_list_print(gfxw_widget_t *list, int indentation)
{
	return _w_gfxwop_list_print(list, "SORTED_LIST", indentation);
}

/* --- */
#if 0
struct gfxw_widget_list {
	gfxw_widget_t *widget;
	struct gfxw_widget_list *next;
};

static struct gfxw_widtet_list *
_gfxw_make_widget_list_recursive(gfxw_widget_t *widget)
{
	gfxw_widget_list *node;

	if (!widget)
		return NULL;

	node = sci_malloc(sizeof(struct gfxw_widget_list));
	node->widget = widget;
	node->next = _gfxw_make_widget_list_recursive(widget->next);

	return node;
}

static struct gfxw_widget_list *
_gfxw_make_widget_list(gfxw_container_t *container)
{
	return _gfxw_make_widget_list_recursive(container->contents);
}
#endif
/* --- */


static int
_gfxwop_list_equals(gfxw_widget_t *widget, gfxw_widget_t *other)
     /* Requires identical order of list elements. */
{
	gfxw_list_t *wlist, *olist;

	if (widget->type != other->type)
		return 0;

	if (!GFXW_IS_LIST(widget)) {
		GFXWARN("_gfxwop_list_equals(): Method called on non-list!\n");
		widget->print(widget, 0);
		sciprintf("\n");
		return 0;
	}

	wlist = (gfxw_list_t *) widget;
	olist = (gfxw_list_t *) other;

	if (memcmp(&(wlist->bounds), &(olist->bounds), sizeof(rect_t)))
		return 0;

	widget = wlist->contents;
	other = olist->contents;

	while (widget && other) {

		if (!(widget->equals(widget, other) && !widget->should_replace(widget,other)))
			return 0;

		widget = widget->next;
		other = other->next;
	}

	return (!widget && !other); /* True if both are finished now */
}

static int
_gfxwop_list_add_dirty(gfxw_container_t *container, rect_t dirty, int propagate)
{
	/* Lists add dirty boxes to both themselves and their parenting port/visual */

	container->flags |= GFXW_FLAG_DIRTY;

	DDIRTY(stderr,"list_add_dirty %d,%d,%d,%d %d\n", GFX_PRINT_RECT(dirty), propagate);
	if (propagate)
		if (container->parent) {
			DDIRTY(stderr, "->PROPAGATING\n");
			container->parent->add_dirty_abs(container->parent, dirty, 1);
		}

	return _gfxwop_container_add_dirty(container, dirty, propagate);
}

static inline int
_gfxwop_ordered_add(gfxw_container_t *container, gfxw_widget_t *widget, int compare_all)
     /* O(n) */
{
	gfxw_widget_t **seekerp = &(container->contents);

	if (widget->next) {
		GFXERROR("_gfxwop_sorted_list_add(): Attempt to add widget to two lists!\nWidget:");
		widget->print(GFXW(widget), 3);
		sciprintf("\nList:");
		container->print(GFXW(container), 3);
		BREAKPOINT();

		return 1;
	}

	if (_gfxw_container_id_equals(container, widget))
		return 0;

	while (*seekerp && (compare_all || (widget->compare_to(widget, *seekerp) >= 0))) {

		if (widget->equals(GFXW(widget), GFXW(*seekerp))) {
			if (compare_all) {
				if ((*seekerp)->visual)
					(*seekerp)->widfree(GFXW(*seekerp)); /* If it's a fresh widget */
				else
					gfxw_annihilate(GFXW(*seekerp));

				return _gfxwop_ordered_add(container, widget, compare_all); /* We might have destroyed the container's contents */
			} else {
				widget->next = (*seekerp)->next;
				(*seekerp)->widfree(GFXW(*seekerp));
				*seekerp = widget;
				return (_parentize_widget(container, widget));
			}
		}

		if (*seekerp)
			seekerp = &((*seekerp)->next);
	}

	widget->next = *seekerp;
	*seekerp = widget;

	return _parentize_widget(container, widget);
}

static int
_gfxwop_sorted_list_add(gfxw_container_t *container, gfxw_widget_t *widget)
     /* O(n) */
{
	return _gfxwop_ordered_add(container, widget, 0);
}

void
_gfxw_set_ops_LIST(gfxw_container_t *list, char sorted)
{
	_gfxw_set_container_ops((gfxw_container_t *) list,
				sorted? _gfxwop_sorted_list_draw : _gfxwop_list_draw,
				_gfxwop_container_free,
				_gfxwop_container_tag,
				sorted? _gfxwop_sorted_list_print : _gfxwop_list_print,
				_gfxwop_basic_compare_to,
				sorted? _gfxwop_basic_equals : _gfxwop_list_equals,
				_gfxwop_basic_superarea_of,
				_gfxwop_container_set_visual,
				_gfxwop_container_free_tagged,
				_gfxwop_container_free_contents,
				_gfxwop_list_add_dirty,
				sorted? _gfxwop_sorted_list_add : _gfxwop_container_add);
}

gfxw_list_t *
gfxw_new_list(rect_t area, int sorted)
{
	gfxw_list_t *list = (gfxw_list_t *) _gfxw_new_container_widget(area, sizeof(gfxw_list_t),
								       sorted? GFXW_SORTED_LIST : GFXW_LIST);

	_gfxw_set_ops_LIST(GFXWC(list), (char)sorted);

	return list;
}


/*---------------*/
/**** Visuals ****/
/*---------------*/

static int
_gfxwop_visual_draw(gfxw_widget_t *widget, point_t pos)
{
	gfxw_visual_t *visual = (gfxw_visual_t *) widget;
	gfx_dirty_rect_t *dirty = visual->dirty;
	DRAW_ASSERT(widget, GFXW_VISUAL);

	while (dirty) {
		int err = gfxop_clear_box(visual->gfx_state, dirty->rect);

		if (err) {
			GFXERROR("Error while clearing dirty rect (%d,%d,(%dx%d))\n", dirty->rect.x,
				 dirty->rect.y, dirty->rect.xl, dirty->rect.yl);
			if (err == GFX_FATAL)
				return err;
		}

		dirty = dirty->next;
	}

	_gfxwop_container_draw_contents(widget, visual->contents);

	recursively_free_dirty_rects(visual->dirty);
	visual->dirty = NULL;
	widget->flags &= ~GFXW_FLAG_DIRTY;

	return 0;
}

static int
_gfxwop_visual_free(gfxw_widget_t *widget)
{
	gfxw_visual_t *visual = (gfxw_visual_t *) widget;
        gfxw_port_t **portrefs;
        int retval;

	if (!GFXW_IS_VISUAL(visual)) {
		GFXERROR("_gfxwop_visual_free() called on non-visual!Widget was: ");
		widget->print(widget, 3);
		return 1;
	}

        portrefs = visual->port_refs;

	retval = _gfxwop_container_free(widget);

	free(portrefs);
        return 0;
}

static int
_gfxwop_visual_print(gfxw_widget_t *widget, int indentation)
{
	int i;
	int comma = 0;
	gfxw_visual_t *visual = (gfxw_visual_t *) widget;

	if (!GFXW_IS_VISUAL(visual)) {
		GFXERROR("_gfxwop_visual_free() called on non-visual!Widget was: ");
		widget->print(widget, 3);
		return 1;
	}

	_gfxw_print_widget(widget, indentation);
	sciprintf("VISUAL; ports={");
	for (i = 0; i < visual->port_refs_nr; i++)
		if (visual->port_refs[i]) {
			if (comma)
				sciprintf(",");
			else
				comma = 1;

			sciprintf("%d", i);
		}
	sciprintf("}\n");

	return _w_gfxwop_container_print(widget, indentation);
}

static int
_gfxwop_visual_set_visual(gfxw_widget_t *self, gfxw_visual_t *visual)
{
	if (self != GFXW(visual)) {
		GFXWARN("Attempt to set a visual's parent visual to something else!\n");
	} else {
		GFXWARN("Attempt to set a visual's parent visual!\n");
	}
	return 1;
}

void
_gfxw_set_ops_VISUAL(gfxw_container_t *visual)
{
	_gfxw_set_container_ops((gfxw_container_t *) visual,
				_gfxwop_visual_draw,
				_gfxwop_visual_free,
				_gfxwop_container_tag,
				_gfxwop_visual_print,
				_gfxwop_basic_compare_to,
				_gfxwop_basic_equals,
				_gfxwop_basic_superarea_of,
				_gfxwop_visual_set_visual,
				_gfxwop_container_free_tagged,
				_gfxwop_container_free_contents,
				_gfxwop_container_add_dirty,
				_gfxwop_container_add);
}

gfxw_visual_t *
gfxw_new_visual(gfx_state_t *state, int font)
{
	gfxw_visual_t *visual = (gfxw_visual_t *) _gfxw_new_container_widget(gfx_rect(0, 0, 320, 200), sizeof(gfxw_visual_t), GFXW_VISUAL);

	visual->font_nr = font;
	visual->gfx_state = state;

	visual->port_refs = sci_calloc(sizeof(gfxw_port_t), visual->port_refs_nr = 16);

	_gfxw_set_ops_VISUAL(GFXWC(visual));

	return visual;
}


static int
_visual_find_free_ID(gfxw_visual_t *visual)
{
	int id = 0;
	int newports = 16;

	while (visual->port_refs[id] && id < visual->port_refs_nr)
		id++;

	if (id == visual->port_refs_nr) {/* Out of ports? */
		visual->port_refs = sci_realloc(visual->port_refs, visual->port_refs_nr += newports);
		memset(visual->port_refs + id, 0, newports * sizeof(gfxw_port_t *)); /* Clear new port refs */
	}

	return id;
}

static int
_gfxwop_add_dirty_rects(gfxw_container_t *dest, gfx_dirty_rect_t *src)
{
	DDIRTY(stderr, "Adding multiple dirty to #%d\n", dest->ID);
	if (src) {
		dest->dirty = gfxdr_add_dirty(dest->dirty, src->rect, GFXW_DIRTY_STRATEGY);
		_gfxwop_add_dirty_rects(dest, src->next);
	}
	return 0;
}

/*-------------*/
/**** Ports ****/
/*-------------*/

static int
_gfxwop_port_draw(gfxw_widget_t *widget, point_t pos)
{
	gfxw_port_t *port = (gfxw_port_t *) widget;
	DRAW_ASSERT(widget, GFXW_PORT);

	if (port->decorations) {
		DDIRTY(stderr, "Getting/applying deco dirty (multi)\n");
		_gfxwop_add_dirty_rects(GFXWC(port->decorations), port->dirty);
		if (port->decorations->draw(GFXW(port->decorations), gfxw_point_zero)) {
			port->decorations->dirty = NULL;
			return 1;
		}
		port->decorations->dirty = NULL;
	}

	_gfxwop_container_draw_contents(widget, port->contents);

	recursively_free_dirty_rects(port->dirty);
	port->dirty = NULL;
	widget->flags &= ~GFXW_FLAG_DIRTY;
	return 0;
}

static int
_gfxwop_port_free(gfxw_widget_t *widget)
{
	gfxw_port_t *port = (gfxw_port_t *) widget;

	if (port->visual) {
		gfxw_visual_t *visual = port->visual;
		int ID = port->ID;

		if (ID < 0 || ID >= visual->port_refs_nr) {
			GFXWARN("Attempt to free port #%d; allowed: [0..%d]!\n", ID, visual->port_refs_nr);
			return GFX_ERROR;
		}

		if (visual->port_refs[ID] != port) {
			GFXWARN("While freeing port %d: Port is at %p, but port list indicates %p!\n",
				ID, port, visual->port_refs[ID]);
		} else visual->port_refs[ID] = NULL;

	}

	if (port->decorations)
		port->decorations->widfree(GFXW(port->decorations));

	return _gfxwop_container_free(widget);
}

static int
_gfxwop_port_print(gfxw_widget_t *widget, int indentation)
{
	gfxw_port_t *port = (gfxw_port_t *) widget;

	_gfxw_print_widget(widget, indentation);
	sciprintf("PORT");
	sciprintf(" font=%d drawpos=(%d,%d)", port->font_nr, port->draw_pos.x, port->draw_pos.y);
	if (port->gray_text)
		sciprintf(" (gray)");
	_w_gfxwop_container_print(GFXW(port), indentation);
	return _w_gfxwop_container_print_contents("decorations", GFXW(port->decorations), indentation);

}

static int
_gfxwop_port_superarea_of(gfxw_widget_t *self, gfxw_widget_t *other)
{
	gfxw_port_t *port = (gfxw_port_t *) self;

	if (!port->port_bg)
		return _gfxwop_basic_superarea_of(self, other);

	return port->port_bg->superarea_of(port->port_bg, other);
}

static int
_gfxwop_port_set_visual(gfxw_widget_t *widget, gfxw_visual_t *visual)
{
	gfxw_list_t *decorations = ((gfxw_port_t *) widget)->decorations;
	widget->visual = visual;

	if (decorations)
		if (decorations->set_visual(GFXW(decorations), visual)) {
			GFXWARN("Setting the visual for decorations failed for port ");
			widget->print(widget, 1);
			return 1;
		}

	return _gfxwop_container_set_visual(widget, visual);
}

static int
_gfxwop_port_add_dirty(gfxw_container_t *widget, rect_t dirty, int propagate)
{
	gfxw_port_t *self = (gfxw_port_t *) widget;

	self->flags |= GFXW_FLAG_DIRTY;

	_gfxwop_container_add_dirty(widget, dirty, propagate);

	DDIRTY(stderr,"Added dirty to ID %d\n", widget->ID);
	DDIRTY(stderr, "dirty= (%d,%d,%d,%d) bounds (%d,%d,%d,%d)\n", dirty.x, dirty.x, dirty.xl, dirty.yl,
	 widget->bounds.x, widget->bounds.y, widget->bounds.xl, widget->bounds.yl);
#if 0
	/* FIXME: This is a worthwhile optimization */
	if (self->port_bg) {
		gfxw_widget_t foo;

		foo.bounds = dirty; /* Yeah, sub-elegant, I know */
		foo.bounds.x -= self->zone.x;
		foo.bounds.y -= self->zone.y;
		if (self->port_bg->superarea_of(self->port_bg, &foo)) {
			gfxw_container_t *parent = self->parent;
			while (parent) {
				fprintf(stderr,"Dirtifying parent id %d\n", parent->ID);
				parent->flags |= GFXW_FLAG_DIRTY;
				parent = parent->parent;
			}
			return 0;
		}
	} /* else propagate to the parent, since we're not 'catching' the dirty rect */
#endif

	if (propagate)
		if (self->parent) {
			DDIRTY(stderr, "PROPAGATE\n");
			return self->parent->add_dirty_abs(self->parent, dirty, 1);
		}

	return 0;
}

static int
_gfxwop_port_add(gfxw_container_t *container, gfxw_widget_t *widget)
     /* O(n) */
{
	return _gfxwop_ordered_add(container, widget, 1);
}

void
_gfxw_set_ops_PORT(gfxw_container_t *widget)
{
	_gfxw_set_container_ops((gfxw_container_t *) widget,
				_gfxwop_port_draw,
				_gfxwop_port_free,
				_gfxwop_container_tag,
				_gfxwop_port_print,
				_gfxwop_basic_compare_to,
				_gfxwop_basic_equals,
				_gfxwop_port_superarea_of,
				_gfxwop_port_set_visual,
				_gfxwop_container_free_tagged,
				_gfxwop_container_free_contents,
				_gfxwop_port_add_dirty,
				_gfxwop_port_add);
}

gfxw_port_t *
gfxw_new_port(gfxw_visual_t *visual, gfxw_port_t *predecessor, rect_t area, gfx_color_t fgcolor, gfx_color_t bgcolor)
{
	gfxw_port_t *widget = (gfxw_port_t *)
		_gfxw_new_container_widget(area, sizeof(gfxw_port_t), GFXW_PORT);

	VERIFY_WIDGET(visual);

	widget->port_bg = NULL;
	widget->parent = NULL;
	widget->decorations = NULL;
	widget->title_text = NULL;
	widget->draw_pos = gfx_point(0, 0);
	widget->gray_text = 0;
	widget->color = fgcolor;
	widget->bgcolor = bgcolor;
	widget->font_nr = visual->font_nr;
	widget->ID = _visual_find_free_ID(visual);
	visual->port_refs[widget->ID] = widget;

	_gfxw_set_ops_PORT(GFXWC(widget));

	return widget;
}


gfxw_port_t *
gfxw_remove_port(gfxw_visual_t *visual, gfxw_port_t *port)
{
	gfxw_port_t *parent;
	VERIFY_WIDGET(visual);
	VERIFY_WIDGET(port);

	if (!visual->contents) {
		GFXWARN("Attempt to remove port from empty visual\n");
		return NULL;
	}

	parent = (gfxw_port_t *) port->parent;
	if (port->widfree(GFXW(port)))
		return parent;

	while (parent && !GFXW_IS_PORT(parent))
		parent = (gfxw_port_t *) parent->parent; /* Ascend through ancestors */

	return parent;
}


gfxw_port_t *
gfxw_find_port(gfxw_visual_t *visual, int ID)
{
	if (ID < 0 || ID >= visual->port_refs_nr)
		return NULL;

	return visual->port_refs[ID];
}


gfxw_port_t *
gfxw_find_default_port(gfxw_visual_t *visual)
{
	int id = visual->port_refs_nr;

	while (id--) {
		gfxw_port_t *port = visual->port_refs[id];

		if (port)
			return port;
	}

	return NULL;
}


/*** - other functions - ***/

gfxw_widget_t *
gfxw_set_id(gfxw_widget_t *widget, int ID)
{
	if (widget)
		widget->ID = ID;

	return widget;
}

gfxw_dyn_view_t *
gfxw_dyn_view_set_params(gfxw_dyn_view_t *widget, int under_bits, int under_bitsp, int signal, int signalp)
{
	if (!widget)
		return NULL;

	widget->under_bits = under_bits;
	widget->under_bitsp = under_bitsp;
	widget->signal = signal;
	widget->signalp = signalp;

	return widget;
}

gfxw_widget_t *
gfxw_remove_id(gfxw_container_t *container, int ID)
{
	gfxw_widget_t **wp = &(container->contents);

	while (*wp) {
		if ((*wp)->ID == ID) {
			gfxw_widget_t *widget = *wp;

			*wp = (*wp)->next;
			widget->next = NULL;
			widget->parent = NULL;
			widget->visual = NULL;

			return widget;
		}

		wp = &((*wp)->next);
	}

	return NULL;
}


gfxw_widget_t *
gfxw_hide_widget(gfxw_widget_t *widget)
{
	if (widget->flags & GFXW_FLAG_VISIBLE) {
		widget->flags &= ~GFXW_FLAG_VISIBLE;

		if (widget->parent)
			widget->parent->add_dirty_rel(widget->parent, widget->bounds, 1);
	}

	return widget;
}

gfxw_widget_t *
gfxw_show_widget(gfxw_widget_t *widget)
{
	if (!(widget->flags & GFXW_FLAG_VISIBLE)) {
		widget->flags |= GFXW_FLAG_VISIBLE;

		if (widget->parent)
			widget->parent->add_dirty_rel(widget->parent, widget->bounds, 1);
	}

	return widget;
}


gfxw_snapshot_t *
gfxw_make_snapshot(gfxw_visual_t *visual, rect_t area)
{
	gfxw_snapshot_t *retval = sci_malloc(sizeof(gfxw_snapshot_t));

	retval->serial = widget_serial_number_counter++;

	retval->area = area;

	return retval;
}


int
gfxw_widget_matches_snapshot(gfxw_snapshot_t *snapshot, gfxw_widget_t *widget)
{
	int free_below = (snapshot->serial < widget_serial_number_counter)? 0: widget_serial_number_counter;
	int free_above_eq = snapshot->serial;
	rect_t bounds = widget->bounds;

	if (!GFXW_IS_CONTAINER(widget) && widget->parent) {
		bounds.x += widget->parent->bounds.x;
		bounds.y += widget->parent->bounds.y;
	}

	return ((widget->serial >= free_above_eq
		 || widget->serial < free_below)
		&& gfx_rect_subset(bounds, snapshot->area));
}

#define MAGIC_FREE_NUMBER -42

void
_gfxw_free_contents_appropriately(gfxw_container_t *container, gfxw_snapshot_t *snapshot, int priority)
{
	gfxw_widget_t *widget = container->contents;

	while (widget) {
		gfxw_widget_t *next = widget->next;

		if (gfxw_widget_matches_snapshot(snapshot, widget) && !(widget->flags & GFXW_FLAG_IMMUNE_TO_SNAPSHOTS)
		    && (priority == MAGIC_FREE_NUMBER || priority <= widget->widget_priority || widget->widget_priority == -1)) {
			widget->widfree(widget);
		} else {
			if (GFXW_IS_CONTAINER(widget))
				_gfxw_free_contents_appropriately(GFXWC(widget), snapshot, priority);
		}

		widget = next;
	}
}

gfxw_snapshot_t *
gfxw_restore_snapshot(gfxw_visual_t *visual, gfxw_snapshot_t *snapshot)
{
	_gfxw_free_contents_appropriately(GFXWC(visual), snapshot, MAGIC_FREE_NUMBER);

	return snapshot;
}

void
gfxw_annihilate(gfxw_widget_t *widget)
{
	gfxw_visual_t *visual = widget->visual;
	int widget_priority = 0;
	int free_overdrawn = 0;

	gfxw_snapshot_t snapshot;
	if (!GFXW_IS_CONTAINER(widget) && widget->parent && visual && (widget->flags & GFXW_FLAG_VISIBLE)) {
		snapshot.serial = 0;
		snapshot.area = widget->bounds;
		snapshot.area.x += widget->parent->zone.x;
		snapshot.area.y += widget->parent->zone.y;
		free_overdrawn = 1;
		widget_priority = widget->widget_priority;
	}

	widget->widfree(GFXW(widget));

	if (free_overdrawn)
		_gfxw_free_contents_appropriately(GFXWC(visual), &snapshot, widget_priority);
}



gfxw_dyn_view_t *
gfxw_picviewize_dynview(gfxw_dyn_view_t *dynview)
{
	dynview->type = GFXW_PIC_VIEW;
	dynview->flags |= GFXW_FLAG_DIRTY;

	_gfxw_set_ops_PICVIEW(GFXW(dynview));

	if (dynview->parent)
		_gfxw_dirtify_container(dynview->parent, GFXW(dynview));

	return dynview;
}
