/* $Id: List.c,v 1.5 1997/05/11 13:01:37 archer Exp $
 *	LIST.C - Handle list objects
 *
 * A List is a collection of text lines (one line, one item), with selectable
 * capabilities, shift select/unselect, double-click support, and so on
 */
#include <Widget/ListP.h>
#include <Widget/Manage.h>
#include <Widget/ScrollerP.h>
#include <Widget/IconP.h>
#include <Widget/fontsP.h>
#include <Widget/drawP.h>
#include <Widget/panP.h>


/**
 ** Private variables
 **/
static GC	list_gc;


/**
 ** Internal: LIST_SHUFFLE
 **	Reshuffle a list once its size/lines has changed
 **/
static void list_shuffle(x11__list *wdg)
{
X11_widget	scrollbar;
x11__scroller	*scroller;
int		l, h;
boolean		shuffle = False;
/*
 * Vertical scrollbar size
 */
	scroller = wdg->scroller;
	if ((h = wdg->height) == 0)
		h = 1;
	l = wdg->core.h / h;
	scrollbar = scroller->vertical_bar;
	if (l >= wdg->number_of_lines) {
		if (!scrollbar->unmanaged) {
			X11_UnManage(scrollbar);
			shuffle = True;
		}
	} else {
		x11__scrollbar_set_bounds(scrollbar, wdg->number_of_lines, l);
		if (scrollbar->unmanaged) {
			X11_Manage(scrollbar);
			shuffle = True;
		}
	}
/*
 * Horizontal scrollbar size
 */
	scrollbar = scroller->horizontal_bar;
	if (wdg->max_w > wdg->core.w) {
		x11__scrollbar_set_bounds(scrollbar, wdg->max_w, wdg->core.w);
		if (scrollbar->unmanaged) {
			X11_Manage(scrollbar);
			shuffle = True;
		}
	} else {
		if (!scrollbar->unmanaged) {
			X11_UnManage(scrollbar);
			shuffle = True;
		}
	}
/*
 * Scrollbars appear?
 *	(this will recursively end up calling this resize function!)
 */
	if (shuffle)
		(*scroller->core.on_resize_window)(&scroller->core, (XEvent *)0);
}


/**
 ** Internal: LIST_RESIZE
 **	Resize the list
 **/
static void list_resize(X11_widget wdg, XEvent *dummy)
{
/*
 * Resize the X11 window
 */
	x11__core_resize(wdg, dummy);
	list_shuffle(W_LIST);
}


/**
 ** Internal: LIST_GEOMETRY
 **	Compute the geometry of a list
 **/
static void list_geometry(x11__list *list, int lines, int width)
{
XFontStruct	*info;
unsigned int	w, h;
int		m;
/*
 * Recompute the geometry of the List
 */
	info = list->font;
	h = info->ascent + info->descent;
	list->text_h = h;
/*
 * Checkmark Icon might be larger?
 */
	if (list->check_icon && list->check_icon->h > h)
		h = list->check_icon->h;
/*
 * Suggested item height might be larger?
 */
	if (list->h_hint > h)
		h = list->h_hint;
	list->height = h;
/*
 * Fixed height list
 */
	if (lines <= 0)
		lines = 20;
	if (list->core.fixed_h)
		list->core.h     =
		list->core.h_min = h * lines;
/*
 * Suggested height list
 */
	else {
		list->core.h_min = h;
		if (!list->core.h)
			list->core.h = h * lines;
	}
/*
 * Suggested width list
 */
	m = 2 * list->core.margin_inside;
	w = 15 * info->max_bounds.width;
	if (w < list->w_hint)
		w = list->w_hint;
	list->core.w_min = w + m;
	if (list->core.w == 0) {
		if (width <= 0)
			width = 80;
		w = XTextWidth(info, " ", 1) + info->max_bounds.width;
		list->core.w = (width * w) / 2 + m;
	}
/*
 * Resize widget if needed
 */
	x11__widget_grow(&list->core);
}


/**
 ** Internal: LIST_CLEANUP
 **	Free the memory used by the list
 **/
static void list_cleanup(X11_widget wdg, XEvent *dummy)
{
x11__list_line	*l;
int		i;
/*
 * Free the icon
 */
	if (W_LIST->check_icon) {
		X11_IconRelease(W_LIST->check_icon);
		W_LIST->check_icon = 0;
	}
/*
 * Free the line contents
 */
	l = W_LIST->lines;
	for (i = 0; i < W_LIST->number_of_lines; i++) {
		if (l->text)
			free(l->text);
		l++;
	}
/*
 * Free the line storage
 */
	if (l)
		free(W_LIST->lines);
	W_LIST->lines = 0;
	W_LIST->allocated_lines = 0;
}


/**
 ** LIST_REDRAW
 **	Redraw one of the list's lines
 **/
static void list_redraw(x11__list *l, int number)
{
X11_icon	*icon;
x11__list_line	*t;
char		*s, *tab;
unsigned long	bk, fg;
int		p;
int		w, h;
int		x, y;
/*
 * Avoid doing this there
 */
	if (!l->core.id)
		return;
	if (!list_gc)
		list_gc = x11__widget_gc(&l->core);
/*
 * Position of the string
 */
	h  = l->height;
	y  = number * h;
	y -= l->delta_y;
	if (y >= l->core.h || (y + h) <= 0)
		return;
	w  = l->core.w;
/*
 * Outside of actual list, just clear the area
 */
	if (number < 0 || number >= l->number_of_lines) {
		XClearArea(x11__screen, l->core.id, 0, y, w, h, False);
		return;
	}
/*
 * Line to redraw
 */
	t  = l->lines + number;
	if (t->selected) {
		XSetForeground(x11__screen, list_gc, l->core.active_bk);
		XFillRectangle(x11__screen, l->core.id, list_gc,
							0, y, w, h);
		bk = l->core.active_bk;
		fg = l->core.active_fg;
	} else {
		XClearArea(x11__screen, l->core.id, 0, y, w, h, False);
		bk = l->core.background;
		fg = l->core.foreground;
	}
	XSetFont(x11__screen, list_gc, l->font->fid);
	XSetBackground(x11__screen, list_gc, bk);
	XSetForeground(x11__screen, list_gc, fg);
/*
 * Check icon?
 */
	x = l->core.margin_inside - l->delta_x;
	if (t->checked && (icon = l->check_icon) != 0) {
		if (icon->h >= h)
			p = y;
		else
			p = y + (h - icon->h) / 2;
		x11__icon_draw(l->core.id, icon, bk, fg, x, p);
	}
/*
 * Start drawing. If tabs, maybe we'll tabulate a while...
 */
	x  = l->margin - l->delta_x;
	y += l->font->ascent;
	s = t->text;
	p = 0;
	while (*s) {
		if (!l->tabs[p])
			break;
		if ((tab = strchr(s, '\t')) == (char *)0)
			break;
		if (tab != s)
			XDrawString(x11__screen, l->core.id, list_gc, x, y, s,
								tab - s);
		s = tab + 1;
		x = l->margin + l->tabs[p++];
	}
/*
 * Finish drawing
 */
	if (*s)
		XDrawString(x11__screen, l->core.id, list_gc, x, y, s, strlen(s));
/*
 * Icon to add
 */
	if (t->icon) {
/*** HACK ***/
	}
}


/**
 ** Internal: LIST_SCROLLING
 **	Scroll the list
 **/
static long list_scrolling(X11_widget wdg, long value)
{
long	delta;
/*
 * Check bounds
 */
	if (value >= W_LIST->number_of_lines)
		value = W_LIST->number_of_lines - 1;
	if (value < 0)
		value = 0;
/*
 * New delta
 */
	delta = value * W_LIST->height;
	x11__pan_window(wdg, 0, W_LIST->delta_y - delta);
	W_LIST->delta_y = delta;
	return value;
}


/**
 ** Internal: LIST_PAN
 **	User pans on the list
 **/
static long list_pan(X11_widget wdg, long value)
{
	if (value != W_LIST->delta_x) {
		x11__pan_window(wdg, W_LIST->delta_x - value, 0);
		W_LIST->delta_x = value;
	}
	return W_LIST->delta_x;
}


/**
 ** Internal: LIST_EXPOSE
 **	Redraw an area of the list
 **/
static void list_expose(X11_widget wdg, XEvent *e)
{
int	h;
int	hi, lo;
/*
 * Avoid empty lists
 */
	if ((h = W_LIST->height) == 0)
		return;
/*
 * Size of the area exposed...
 */
	lo = e->xexpose.y + W_LIST->delta_y;
	hi = lo + e->xexpose.height - 1;
/*
 * Line N to line N+M
 */
	lo /= h;
	for (hi /= h; lo <= hi; lo++)
		list_redraw(W_LIST, lo);
}


/**
 ** X11_LIST_SET_LINE
 **	Set the value of one line from the list
 **/
void X11_ListSetLine(X11_widget wdg, int number, char *text, void *data)
{
x11__list_line	*l;
unsigned int	s;
int		n, o;
/*
 * Just be sure that we do not get holes!
 */
	if (!wdg)
		return;
	if (number > W_LIST->number_of_lines || number < 0)
		number = W_LIST->number_of_lines;
/*
 * Allocate a new line?
 */
	l = W_LIST->lines;
	if (number >= W_LIST->number_of_lines) {
/*
 * Not enough room in memory?
 */
		if (number >= (o = W_LIST->allocated_lines)) {
			n = o;
			if (n < 8)
				n = 8;
			else
				if (n < 64)
					n *= 2;
				else
					n += 64;
/*** HACK ***//* Error handling */
			s = n * sizeof(x11__list_line);
			if (o)
				l = (x11__list_line *)realloc((char *)l, s);
			else
				l = (x11__list_line *)malloc(s);
			memset(l+o, 0, (n-o)*sizeof(x11__list_line));
			W_LIST->lines = l;
			W_LIST->allocated_lines = n;
		}
	}
/*
 * Fill in the new element
 */
	l += number;
	if (l->text)
		free(l->text);
	memset(l, 0, sizeof(*l));
	l->length = strlen(text);
	l->text   = strdup(text);
	l->data   = data;
/*
 * Data, unselected
 */
	if (number >= W_LIST->number_of_lines)
		W_LIST->number_of_lines = number+1;
	if (!W_LIST->being_filled)
		list_redraw(W_LIST, number);
}


/**
 ** X11_LIST_REFILL
 **	Set the list to be in fill mode. In that mode, we do not redraw on
 **	each item arrival: we do it one lump at the end (X11_ListTruncate).
 **/
void X11_ListRefill(X11_widget wdg)
{
	if (wdg)
		W_LIST->being_filled = True;
}


/**
 ** X11_LIST_TRUNCATE
 **	Set the end of list mark
 **/
void X11_ListTruncate(X11_widget wdg, int max)
{
x11__list_line	*l;
int		i;
/*
 * Any work to do?
 */
	if (!wdg)
		return;
/*
 * Valid number of batches
 */
	if (max < 0 || max > W_LIST->number_of_lines)
		return;
/*
 * We've got many lines to free...
 */
	l = W_LIST->lines + max;
	for (i = max; i < W_LIST->number_of_lines; i++) {
		if (l->text)
			free(l->text);
		l->text = 0;
		l++;
	}
/*
 * Scrollbar size
 */
	W_LIST->number_of_lines = max;
	list_shuffle(W_LIST);
/*
 * Special init?
 */
	W_LIST->last_click   = 0;
	W_LIST->being_filled = False;
	if (wdg->id)
		XClearArea(x11__screen, wdg->id, 0, 0, 0, 0, True);
}


/**
 ** X11_LIST_SIZE
 **	Number of lines in the list
 **/
int X11_ListSize(X11_widget wdg)
{
	return W_LIST->number_of_lines;
}


/**
 ** X11_LIST_SET_TABS
 **	Compute the tabs for a list
 **/
void X11_ListSetTabs(X11_widget wdg, char *model)
{
XFontStruct	*info;
char		*s;
int		x, l;
int		t;
/*
 * Now, minimum heights
 */
	info = W_LIST->font;
	t = 0;
	s = model;
	l = 0;
	x = 0;
	while (*s)
/*
 * Tabstop found
 */
		if (*s == '\t') {
			if (l)
				x += XTextWidth(info, model, l);
			x += 10;
			W_LIST->tabs[t++] = x;
			if (t >= X11_LIST_MAX_TABS)
				break;
			s++;
			l     = 0;
			model = s;
		} else {
			l++;
			s++;
		}
/*
 * No more tab stops
 */
	while (t <= X11_LIST_MAX_TABS)
		W_LIST->tabs[t++] = 0;
}


/**
 ** Internal: CREATE_LIST
 **	Create the actual list object
 **/
static X11_widget create_list(X11_widget scroller, X11_widget focus, char *name,
						int start_lines, int hint_width)
{
x11__list	*object;
/*
 * Create actual object
 */
	object = allocator(x11__list);
	x11__core_setup(&object->core, focus, "List", name);
/*
 * Initialize fields
 */
	object->core.event_mask        = ButtonPressMask|ButtonReleaseMask|
					 ButtonMotionMask|ExposureMask;
	object->core.on_destruction    = list_cleanup;
	object->core.on_expose         = list_expose;
#if 0
	object->core.on_button_press   = list_press;
	object->core.on_button_release = list_release;
	object->core.on_motion_notify  = list_motion;
#endif
	object->core.on_resize_window  = list_resize;
/*
 * Look and feel depends on the style. The scroller too!
 */
	object->core.margin_inside = 5;
	x11__resource_basic(&object->core);
	object->margin = object->core.margin_inside;
	object->core.border2 = 0;
	object->font = x11__font_find(x11__font_helvetica12);
/*
 * Scrolling
 */
	object->scroller = (x11__scroller *)scroller;
	object->focus    = focus;
	x11__scrollbar_call(object->scroller->horizontal_bar, &object->core,
						list_pan);
	x11__scrollbar_set_arrow(object->scroller->horizontal_bar, 1, 0);
	x11__scrollbar_call(object->scroller->vertical_bar, &object->core,
						list_scrolling);
	x11__scrollbar_set_arrow(object->scroller->vertical_bar, 1, 0);
/*
 * Allocate font & GC
 */
#if 0
	object->double_click_time = x11__resource_integer("DoubleClickTime",
								DOUBLE_CLICK);
#endif
/*
 * Ok
 */
	list_geometry(object, start_lines, hint_width);
	return OBJECT_P;
}


/*************************************************************
 ** CONTAINER object (draws the border and holds the focus) **
 *************************************************************/


/**
 ** Internal: CONTAINER_GEOMETRY
 **	Derive from scrolled item
 **/
static void container_geometry(X11_widget wdg, XEvent *dummy)
{
X11_widget	child;
int		m;
/*
 */
	if ((child = wdg->child) != 0) {
		(*child->on_geometry_check)(child, dummy);
		m = wdg->border2 + 2 * (wdg->focus_thickness + wdg->shadow_thickness);
		wdg->w_min = child->w_min + m;
		wdg->h_min = child->h_min + m;
		if (!wdg->w)
			wdg->w = child->w + m;
		if (!wdg->h)
			wdg->h = child->h + m;
		x11__widget_grow(wdg);
	}
}


/**
 ** Internal: CONTAINER_RESIZE
 **	Move the resize event downward
 **/
static void container_resize(X11_widget wdg, XEvent *dummy)
{
X11_widget	child;
int		m;
/*
 * Basic resize
 */
	x11__core_resize(wdg, dummy);
/*
 * Event shrinks
 */
	if ((child = wdg->child) != 0) {
		m = wdg->border2 + 2 * (wdg->focus_thickness + wdg->shadow_thickness);
		child->w = wdg->w - m;
		child->h = wdg->h - m;
		(*child->on_resize_window)(child, dummy);
		m = wdg->focus_thickness + wdg->shadow_thickness;
		if (child->x != m || child->y != m) {
			child->x = m;
			child->y = m;
			if (child->id)
				XMoveWindow(x11__screen, child->id, m, m);
		}
	}
}


/**
 ** Internal: CONTAINER_EXPOSE
 **	Refresh the container
 **/
static void container_expose(X11_widget wdg, XEvent *dummy)
{
/*
 * Simple and dirty
 */
	x11__draw_start(wdg);
	x11__shadows_gc();
	x11__draw_lined(wdg->has_focus ? wdg->focus_color
					: wdg->parent->background,
			wdg->focus_thickness);
	x11__draw_downshadow(wdg->shadow_thickness);
}


/**
 ** X11_LIST
 **	Create a list window
 **/
X11_widget X11_List(X11_widget parent, char *name, int lines, int width)
{
X11_widget	hold;
X11_widget	scroller;
/*
 * Allocate scroller window
 */
	scroller = X11_Scroller(parent, True, True);
/*
 * Get resource
 */
	hold = allocator(x11__core);
	x11__core_setup(hold, scroller, "Focus", "focus");
/*
 * Initialize fields
 */
	hold->event_mask        = ExposureMask;
	hold->on_expose         = container_expose;
	hold->on_geometry_check = container_geometry;
	hold->on_resize_window  = container_resize;
/*
 * Look and feel depends on the style. The scroller too!
 */
	hold->shadow_thickness = 2;
	x11__resource_basic(hold);
/*
 * Ok
 */
	return create_list(scroller, hold, name, lines, width);
}
