/* Unit lists for the Mac interface to Xconq.
   Copyright (C) 1992-1996, 1998, 2000 Stanley T. Shebs.

Xconq 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, or (at your option)
any later version.  See the file COPYING.  */

#include "conq.h"
#include "macconq.h"

static void init_list_contents(List *list);
static void organize_list_contents(List *list);
static void sort_list_contents(List *list);
static void add_unit_to_list(List *list, Unit *unit);

static void set_list_scrollbars(List *list);
static void draw_list_contents(List *list);
static void draw_list_headings(List *list);
static void draw_unit_list_entry(List *list, int n, int clearfirst);
static void adjust_list_decor(List *list);

static pascal void list_vscroll_fn(ControlHandle control, short code);
static pascal void list_hscroll_fn(ControlHandle control, short code);
static void auto_scroll_list_up_arrow();
static void auto_scroll_list_down_arrow();
static void auto_scroll_list_right_arrow();
static void auto_scroll_list_left_arrow();

static void insert_unit_into_list(List *list, Unit *unit);
static void remove_unit_from_list(List *list, Unit *unit);
static int unit_position_in_list(List *list, Unit *unit);
static void redraw_unit_list_entry(List *list, int n);
static void clear_selections(List *list);


/* (should be adjusted for expected range of values) */

int curactcolw = 10;
int imagecolw = 20;
int namecolw = 100;
int typecolw = 80;
int sidecolw = 70;
int hpcolw = 40;
int acpcolw = 50;
int poscolw = 50;
int taskcolw = 130;
int supcolw = 40;
int notecolw = 50;

/* A notes column is needed only if units are scheduled to appear. */

int notes_column_needed = -1;

/* A supply column is needed only if any unit can store the mtype. */

int *sup_column_needed = NULL;

/* Sum of non-scrolling field widths. */

int fixedfieldwidth;

/* Sum of all field widths. */

int maxlistwidth;

/* The height of the headings lines at the top of the window. */

int listtoph = 20;

int listtopbaseline = 13;

/* The possible heights of each individual list entry. */

int smallentryspacing = 24;
int largeentryspacing = 46;

int listnum = 1;

int lastlisth = -1, lastlistv = -1;

ControlActionUPP list_vscroll_proc;
ControlActionUPP list_hscroll_proc;

/* Create a new list of units. */

void
create_list()
{
	int i, m;
	List *list = (List *) xmalloc(sizeof(List));
	Unit *unit;
	Rect hscrollrect, vscrollrect;

	/* If this is the first list, test if we need supply columns. */ 
	if (sup_column_needed == NULL) {
		sup_column_needed = (int *) xmalloc(sizeof(int) * nummtypes);
		for_all_material_types(m) {
			sup_column_needed[m] = storage_possible(m);
		}		
	}
	/* If this is the first list, test if we need a notes column. */ 
	if (notes_column_needed == -1) {
		for_all_units(unit) {
			if (side_sees_unit(dside, unit)
				&& !inside_area(unit->x, unit->y)
				&& unit_appear_turn(unit) >= 0) {
				notes_column_needed = TRUE;
				break;
			}
			notes_column_needed = FALSE;
		}
	}
	maxlistwidth = 0;
	maxlistwidth += curactcolw;
	maxlistwidth += imagecolw;
	maxlistwidth += namecolw;
	fixedfieldwidth = maxlistwidth;
	maxlistwidth += typecolw;
	maxlistwidth += sidecolw;
	maxlistwidth += hpcolw;
	maxlistwidth += acpcolw;
	maxlistwidth += poscolw;
	for_all_material_types(m) {
		if (sup_column_needed[m])
		    maxlistwidth += supcolw;
	}
	maxlistwidth += taskcolw;
	if (notes_column_needed) {
		maxlistwidth += notecolw;
	}
	DGprintf("Creating a list\n");
	/* Default to listing every side and indeps too. */
	list->sides = ALLSIDES;
	list->completed_units = TRUE;
	list->incomplete_units = TRUE;
	for (i = 0; i < MAXSORTKEYS; ++i)
	  list->sortkeys[i] = bynothing;
	list->sortkeys[0] = byside; /* (should be a preference) */
	list->mainsortmi = miViewBySide;
	list->largeicons = FALSE; /* (should be a preference) */
	list->sidecolors = default_sidecolors;	/* same default as for maps */
	list->drawsizes = default_drawsizes;	/* same default as for maps */
	list->draw_emblems = default_draw_emblems;	/* same default as for maps */
	/* (should share with toggle routine?) */
	list->entryspacing = (list->largeicons ? largeentryspacing : smallentryspacing);
	list->firstvisible = 0;
	init_list_contents(list);
	organize_list_contents(list);
	list->next = listlist;
	listlist = list;
	/* Make a window for the list, give it scrollbars. */
	if (hasColorQD)
		list->window = GetNewCWindow(wList, nil, NULL);
	else	list->window = GetNewWindow(wList, nil, NULL);
	stagger_window(list->window, &lastlisth, &lastlistv);
	SelectTheWindow(list->window);
	SetPort(list->window);
	TextFont(small_font_id);
	TextSize(small_font_size);
	/* Create the scrollbars. */
	hscrollrect = list->window->portRect;
	hscrollrect.top = hscrollrect.bottom - sbarwid;
	hscrollrect.bottom += 1;
	hscrollrect.left = fixedfieldwidth - 5;
	hscrollrect.right -= sbarwid - 1;
	list->hscrollbar = NewControl(list->window, &hscrollrect, "\p", TRUE,
			 					  0, 0, 100, scrollBarProc, 0L);
	vscrollrect = list->window->portRect;
	vscrollrect.top = listtoph - 1;
	vscrollrect.bottom -= sbarwid - 1;
	vscrollrect.left = vscrollrect.right - sbarwid;
	vscrollrect.right += 1;
	list->vscrollbar = NewControl(list->window, &vscrollrect, "\p", TRUE,
			 					  0, 0, 100, scrollBarProc, 0L);
	/* Now set the scrollbars to their *real* limits. */
	set_list_scrollbars(list);
	/* Needed for list number cleanup. */
	list->id = listnum++;
	sprintf(spbuf, "List %d", list->id);
	add_window_menu_item(spbuf, list->window);
}

/* Make the list be empty. */

void
init_list_contents(List *list)
{
	/* Set up a unit vector with a first cut at needed space;
	   will adjust upwards automatically if necessary. */
	list->contents = make_unit_vector(numunits + 50);
	list->numunits = 0;
}

/* This takes the list and fills in the items it is to display. */

void
organize_list_contents(List *list)
{
	int numvisunits, n, first = FALSE;
	Side *side2;
	Unit *unit, *unit2 = NULL, *unit3 = NULL;
	Rect winrect = list->window->portRect;

	/* Find and save the first visible non-dead unit in the list. */
	n = list->firstvisible;
	while (list->numunits > 0 && !first) {
		unit2 = unit_in_vector(list->contents, n);
		if (in_play(unit2))
		    first = TRUE;
		n++;
	}
	/* Save the selected unit. */
	unit3 = selected_unit_in_list(list);	

	/* Build up the array of units for this list. */
	list->numunits = 0;
	clear_unit_vector(list->contents);
	/* We always see our own units. */
	for_all_side_units(dside, unit) {
		add_unit_to_list(list, unit);
	}
	for_all_sides(side2) {
		if (dside != side2) {
			for_all_side_units(side2, unit) {
				if (side_sees_image(dside, unit)) {
					add_unit_to_list(list, unit);
				}
			}
		}
	}
	/* Now sort the list according to its keys. */
	sort_list_contents(list);
	
	/* Restore the old selected unit. */
	if (in_play(unit3)
	    && (n = unit_position_in_list(list, unit3)) >= 0) {	
	 	    list->contents->units[n].flag = TRUE;
	}
	/* Restore the old first visible unit if possible. */
	if (first) {
		list->firstvisible = unit_position_in_list(list, unit2);
		numvisunits = (winrect.bottom - winrect.top - listtoph - sbarwid) / list->entryspacing;
		numvisunits = min(numvisunits, list->numunits);
		list->lastvisible = list->firstvisible + numvisunits - 1;
		/* Check that lastvisible is less than numunits. */
		list->lastvisible = min(list->lastvisible, list->numunits - 1);
		/* Readjust firstvisible if necessary. */
		list->firstvisible = max(0, min(list->firstvisible, list->lastvisible - numvisunits + 1));
	}
	/* The number of units may have changed, so we need to update the scrollbars. */
	set_list_scrollbars(list);
}

void
sort_list_contents(List *list)
{
	int i;

	for (i = 0; i < MAXSORTKEYS; ++i) {
		tmpsortkeys[i] = list->sortkeys[i];
	}
	sort_unit_vector(list->contents);
}

void
add_unit_to_list(List *list, Unit *unit)
{
	if (alive(unit)
	    && (completed(unit) ? list->completed_units : list->incomplete_units)) {
		list->contents = add_unit_to_vector(list->contents, unit, FALSE);
		/* (should apply other inclusion criteria too?) */
		++list->numunits;
	}
}

/* Calculate reasonable/valid values and maxima for the scrollbars,
   starting from the window size. */

void
set_list_scrollbars(List *list)
{
	int numvisunits, visfieldwidth, val, maxval;
	Rect winrect = list->window->portRect;

	/* Set the vertical scrollbar correctly. */	
	numvisunits = (winrect.bottom - winrect.top - sbarwid - listtoph) / list->entryspacing;
	maxval = max(0, list->numunits - numvisunits);
	SetCtlMax(list->vscrollbar, maxval);
	SetCtlValue(list->vscrollbar, min(list->firstvisible, maxval));
	/* Set up the horizontal scrollbar. */
	visfieldwidth = winrect.right - winrect.left - sbarwid - fixedfieldwidth;
	maxval = (maxlistwidth - fixedfieldwidth) - visfieldwidth;
	maxval = max(0, maxval);
	SetCtlMax(list->hscrollbar, maxval);
	SetCtlValue(list->hscrollbar, min(list->firstvisfield, maxval));
}

List *
list_from_window(WindowPtr window)
{
	List *list;
	
	if (dside == NULL) return NULL;
	for_all_lists(list) {
		if (list->window == window) return list;
	}
	return NULL;
}

void
draw_list(List *list)
{
	WindowPtr listwin = list->window;
	Rect tmprect;
	RgnHandle tmprgn;

	/* Erase the content tect. */
	tmprect = listwin->portRect;
	tmprect.right -= sbarwid;
	tmprect.bottom -= sbarwid;
	BackPat(QDPat(white));
	EraseRect(&tmprect);
	/* Also erase small rect to the left of the bottom
	scrollbar. */
	tmprect = listwin->portRect;
	tmprect.right = tmprect.left + fixedfieldwidth - 5;
	tmprect.top = tmprect.bottom - sbarwid;
	BackPat(QDPat(white));
	EraseRect(&tmprect);
	/* Make sure the list is up to date before redrawing it. */
	organize_list_contents(list);
	tmprgn = NewRgn();
	GetClip(tmprgn);
	draw_list_contents(list);
	SetClip(tmprgn);
	DisposeRgn(tmprgn);
}

void
draw_list_contents(List *list)
{
	int line, numvisunits, viswidth;
	Rect winrect = list->window->portRect;

	/* Image is basically square, but add a bit of extra space on each side. */
	imagecolw = list->entryspacing + 2;
	/* Draw the selection and sorting as a sort of header. */
	draw_list_headings(list);
	/* Compute how many list elements are actually visible. */
	numvisunits = (winrect.bottom - winrect.top - listtoph - sbarwid) / list->entryspacing;
	numvisunits = min(numvisunits, list->numunits);
	list->lastvisible = list->firstvisible + numvisunits - 1;
	/* Check that lastvisible is less than numunits. */
	list->lastvisible = min(list->lastvisible, list->numunits - 1);
	/* Readjust firstvisible if necessary. */
	list->firstvisible = max(0, min(list->firstvisible, list->lastvisible - numvisunits + 1));
	for (line = list->firstvisible; line <= list->lastvisible; ++line) {
		draw_unit_list_entry(list, line, FALSE);
	}
	viswidth = winrect.right - winrect.left - sbarwid;
}

void
draw_list_headings(List *list)
{
	int x = 0, m;
	Str255 tmpstr;
	Rect cliprect, tmprect, winrect = list->window->portRect;
	RGBColor tmpcolor;

	cliprect = winrect;
	cliprect.right -= sbarwid;
	ClipRect(&cliprect);
	/* Clear the heading area. */
	SetRect(&tmprect, 0, 0, winrect.right - sbarwid, listtoph);
	EraseRect(&tmprect);
	/* Draw dividing lines that cross both fixed and scrolling fields. */
	MoveTo(0, listtoph - 3);
	Line(winrect.right, 0);
	MoveTo(0, listtoph - 1);
	Line(winrect.right, 0);
	/* Draw a dividing line between the fixed and mobile fields. */
	MoveTo(fixedfieldwidth - 5, 0);
	LineTo(fixedfieldwidth - 5, listtoph - 1);
	/* (should underline sort keys with varying line heaviness) */
	/* We have to do MoveTo everywhere because DrawString moves the pen. */
	/* First draw headings for fields not affected by horizontal scrolling. */
	x += curactcolw;
	MoveTo(x, listtopbaseline);
	DrawString("\ps/L");
	x += imagecolw;
	MoveTo(x, listtopbaseline);
	DrawString("\pName/Number");
	/* Shift left by horiz scroll. */
	x -= list->firstvisfield;
	/* Now clip against the fixed fields' area. */
	cliprect = winrect;
	cliprect.right -= sbarwid;
	cliprect.left = fixedfieldwidth;
	ClipRect(&cliprect);
	x += namecolw;
	MoveTo(x, listtopbaseline);
	DrawString("\pType");
	x += typecolw;
	MoveTo(x, listtopbaseline);
	DrawString("\pSide");
	x += sidecolw;
	MoveTo(x, listtopbaseline);
	DrawString("\pHp");
	x += hpcolw;
	MoveTo(x, listtopbaseline);
	DrawString("\pAcp");
	MoveTo(x + 20, listtopbaseline);
	tmpcolor = (RGBColor) {0x8FFF, 0x8FFF, 0x8FFF};
	RGBForeColor(&tmpcolor);
	DrawString("\pCp");
	ForeColor(blackColor);
	x += acpcolw;
	MoveTo(x, listtopbaseline);
	DrawString("\pLocation");
	x += poscolw;
	MoveTo(x, listtopbaseline);
	DrawString("\pTask");
	x += taskcolw;
	for_all_material_types(m) {
		if (sup_column_needed[m]) {
			MoveTo(x, listtopbaseline);
			c2p(m_type_name(m), tmpstr);
			DrawString(tmpstr);
			x += supcolw;
		}
	}
	if (notes_column_needed) {
		MoveTo(x, listtopbaseline);
		DrawString("\pNotes");
		x += notecolw;
	}
	/* Draw a gray line indicating the end of the data columns. */
	PenPat(QDPat(gray));
	MoveTo(x, 0);
	Line(0, listtoph - 4);
	PenNormal();
}

/* This draws a one-line entry for the given unit. */

/* This routine does *not* save/restore the clip region, but does
   modify it, so it should not be used except in a safe context. */

void
draw_unit_list_entry(List *list, int n, int clearfirst)
{
	Unit *unit = unit_in_vector(list->contents, n);
	Rect bbox = (*(list->window->visRgn))->rgnBBox;
	Rect winrect = list->window->portRect;
	Rect entryrect, tmprect, cliprect;
	char tmpnbuf[BUFSIZE];
	int u, m, x, y, texty;
	RGBColor tmpcolor;
	Side *side2;
	
	y = (n - list->firstvisible) * list->entryspacing + listtoph;
	texty = y + 15;
	/* Clip to the entry rect. */
	SetRect(&entryrect, 0, y, winrect.right - sbarwid, y + list->entryspacing);
	cliprect = entryrect;
	ClipRect(&cliprect);
	/* Draw incomplete unit on a blue background. */
	if (!completed(unit)) {
		tmpcolor = (RGBColor) {0xCFFF, 0xFFFF, 0xFFFF};
		RGBForeColor(&tmpcolor);
		FillRect(&entryrect, QDPat(black));
		ForeColor(blackColor);
	/* Else erase the entry rect. */
	} else if (clearfirst) {
		EraseRect(&entryrect);
	}
	/* Fixes right end of the red frame. */
	if (GetCtlValue(list->hscrollbar) != GetCtlMax(list->hscrollbar))
	    entryrect.right += 2;
	/* Draw a red frame around the selected unit. */
	if (list->contents->units[n].flag) {
		tmpcolor = (RGBColor) {0xFFFF, 0, 0};
		RGBForeColor(&tmpcolor);
		PenSize(2, 2);
		FrameRect(&entryrect);
		ForeColor(blackColor);
		PenNormal();
	}
	
	u = unit->type;
	/* Draw whether the unit is awake or asleep. */
	if (unit->plan && completed(unit)) {
		SetRect(&tmprect, 0, y + list->entryspacing/2 - curactcolw/2,
				curactcolw, y + list->entryspacing/2 + curactcolw/2);
		InsetRect(&tmprect, 2, 2);
		/* (should draw the following analogously to map display) */
		if (unit->plan->asleep) {
			tmpcolor = (RGBColor) {0xFFFF, 0, 0};
			RGBForeColor(&tmpcolor);
			FillRect(&tmprect, QDPat(black));
			ForeColor(blackColor);
		} else if (unit->plan->reserve) {
			tmpcolor = (RGBColor) {0xFFFF, 0, 0xFFFF};
			RGBForeColor(&tmpcolor);
			FillRect(&tmprect, QDPat(black));
			ForeColor(blackColor);
		} else {
			tmpcolor = (RGBColor) {0, 0xFFFF, 0};
			RGBForeColor(&tmpcolor);
			FillRect(&tmprect, QDPat(black));
			ForeColor(blackColor);
		}
		FrameRect(&tmprect);
	}
	/* Draw an icon with side emblem for this unit. */
	draw_unit_image(list->window, curactcolw + 2, y + 1,
					(list->largeicons ? 44 : 22), (list->largeicons ? 44 : 22), 
					u, side_number(unit->side), list->sidecolors, 0, !completed(unit), list->draw_emblems);

	/* Draw unit size on top of advanced unit. */
	if (u_advanced(unit->type) && list->drawsizes) {
		draw_unit_size(unit, list->window, 
			curactcolw + 2, y + 1, (list->largeicons ? 44 : 22), (list->largeicons ? 44 : 22));
	}
	/* Draw gray text for incomplete units, */
	if (!completed(unit)) {
		tmpcolor = (RGBColor) {0x8FFF, 0x8FFF, 0x8FFF};
		RGBForeColor(&tmpcolor);
	}
	/* Write the name or ordinal number. */
	name_or_number(unit, spbuf);
	spbuf[15] = '\0';  /* (should be clipping by pixels) */
	x = curactcolw + imagecolw;
	MoveTo(x, texty);
	DrawText(spbuf, 0, strlen(spbuf));
	/* Adjust clipping etc. according to the horizontal scroll. */
	x -= list->firstvisfield;
	cliprect.left = fixedfieldwidth;
	ClipRect(&cliprect);
	/* Write the name of the unit's type. */
	x += namecolw;
	MoveTo(x, texty);
	sprintf(spbuf, "%s", u_type_name(u));
	spbuf[15] = '\0';
	DrawText(spbuf, 0, strlen(spbuf));
	/* Write the side of the unit. */
	x += typecolw;
	MoveTo(x, texty);
	side2 = unit->side;
	strcpy(tmpnbuf, shortest_side_title(side2, spbuf));
	tmpnbuf[15] = '\0'; /* truncate long side names */
	DrawText(tmpnbuf, 0, strlen(tmpnbuf));
	/* Draw the current hit points of the unit. */
	x += sidecolw;
	if (side_sees_unit(dside, unit)) {
		MoveTo(x, texty);
		hp_desc(spbuf, unit, FALSE);
		DrawText(spbuf, 0, strlen(spbuf));
	}
	x += hpcolw;
	if (side_sees_unit(dside, unit) && inside_area(unit->x, unit->y)) {
		MoveTo(x, texty);
		if (!completed(unit)) {
			sprintf(spbuf, "%d/%d", unit->cp, u_cp(u));
		} else acp_desc(spbuf, unit, FALSE);
		if (strlen(spbuf) == 0)
		  strcpy(spbuf, "-");
		DrawText(spbuf, 0, strlen(spbuf));
	}
	/* Draw the location. */
	x += acpcolw;
	MoveTo(x, texty);
	if (inside_area(unit->x, unit->y)) {
		sprintf(spbuf, "%d,%d", unit->x, unit->y);
		if (unit->z != 0) {
			tprintf(spbuf, ",%d", unit->z);
		}
	} else {
		strcpy(spbuf, "  -  ");
	}
	DrawText(spbuf, 0, strlen(spbuf));
	/* Draw the task. */
	x += poscolw;
	MoveTo(x, texty);
	if (unit->plan && unit->plan->tasks) {
		task_desc(spbuf, unit->side, unit, unit->plan->tasks);
	} else {
		strcpy(spbuf, "  -  ");
	}
	spbuf[25] = '\0';
	DrawText(spbuf, 0, strlen(spbuf));
	/* Draw the state of all the supplies. */
	x += taskcolw;
	if (side_sees_unit(dside, unit)) {
		for_all_material_types(m) {
			if (sup_column_needed[m]) {
				MoveTo(x, texty);
				sprintf(spbuf, "%d", unit->supply[m]);
				DrawText(spbuf, 0, strlen(spbuf));
				x += supcolw;
			}
		}
	} else {
		x += nummtypes * supcolw;
	}
	/* Draw extra notes. */
	MoveTo(x, texty);
	spbuf[0] = '\0';
	if (side_sees_unit(dside, unit)
		&& !inside_area(unit->x, unit->y)
		&& unit_appear_turn(unit) >= 0) {
		tprintf(spbuf, "appear %s", absolute_date_string(unit_appear_turn(unit)));
	}
	if (strlen(spbuf) > 0) {
		MoveTo(x, texty);
		DrawText(spbuf, 0, strlen(spbuf));
	}
	ForeColor(blackColor);
	/* Adjust clipping for vertical line. */
	cliprect = winrect;
	cliprect.right = fixedfieldwidth;
	cliprect.bottom -= sbarwid;
	ClipRect(&cliprect);
	/* Draw a dividing line between the fixed and mobile fields. */
	/* It may seem excessive to draw the whole line for each entry but 
	otherwise the line may end before the bottom of the window content. */
	MoveTo(fixedfieldwidth - 5, listtoph);
	LineTo(fixedfieldwidth - 5, winrect.bottom - sbarwid);
}

void
grow_list(List *list, int w, int h)
{
	WindowPtr listwin = list->window;

	SizeWindow(listwin, w, h, 1);
	list->firstvisfield = 0;
	adjust_list_decor(list);
	force_update(listwin);
}

/* Zooming works like a list view in the Finder - it calculates a "perfect" size,
   showing as much as possible but with no wasted blank areas. */

void
zoom_list(List *list, int part)
{
	int maxh;
	WindowPtr listwin = list->window;

	if (part == inZoomOut) {
		maxh = listtoph + list->numunits * list->entryspacing + sbarwid;
		set_standard_state(listwin, maxlistwidth + sbarwid, maxh);
	}
	ZoomWindow(listwin, part, false);
	list->firstvisfield = 0;
	adjust_list_decor(list);
	force_update(listwin);
}

/* Move and size the scrollbars to reflect the list's window. */

void
adjust_list_decor(List *list)
{
	int w, h;

	w = list->window->portRect.right - list->window->portRect.left;
	h = list->window->portRect.bottom - list->window->portRect.top;
	MoveControl(list->vscrollbar, w - sbarwid, listtoph - 1);
	SizeControl(list->vscrollbar, sbarwid + 1, h - listtoph - sbarwid + 2);
	MoveControl(list->hscrollbar, fixedfieldwidth - 5, h - sbarwid);
	SizeControl(list->hscrollbar, w - fixedfieldwidth + 5 - sbarwid + 2, sbarwid + 1);
	set_list_scrollbars(list);
}

/* Temporary used by scroll procs (saves looking up from the control). */

List *curlist;

static pascal void
list_vscroll_fn(ControlHandle control, short code)
{
	int curvalue, minvalue, maxvalue, oldvalue, pagesize, jump;
	RgnHandle tmprgn;
	Rect tmprect;
	WindowPtr listwin = curlist->window;

	curvalue = oldvalue = GetCtlValue(control);
	minvalue = GetCtlMin(control);
	maxvalue = GetCtlMax(control);
	pagesize = curlist->lastvisible - curlist->firstvisible + 1;
	switch (code) {
		case inPageDown:
			jump = max(1, pagesize - 1);
			break;
		case inDownButton:
			jump = 1;
			break;
		case inPageUp:
			jump = min(-1, - (pagesize - 1));
			break;
		case inUpButton:
			jump = -1;
			break;
		default:
			jump = 0;
			break;
	}
	curvalue = max(min(curvalue + jump, maxvalue), minvalue);
	curlist->firstvisible = curvalue;
	curlist->lastvisible = min(curlist->numunits, curlist->firstvisible + pagesize) - 1;
	SetCtlValue(control, curvalue);
	/* Scroll the already-drawn bits. */
	tmprgn = NewRgn();
	SetRect(&tmprect, 0, listtoph, listwin->portRect.right - sbarwid, listtoph + pagesize * curlist->entryspacing);
	ScrollRect(&tmprect, 0, (oldvalue - curvalue) * curlist->entryspacing, tmprgn);
	InvalRgn(tmprgn);
	/* Do the update now, because we won't get back to the main event loop
	   until the mouse button is released. */
	update_window(curlist->window);
	DisposeRgn(tmprgn);
}

static pascal void
list_hscroll_fn(ControlHandle control, short code)
{
	int curvalue, minvalue, maxvalue, oldvalue, pagesize, jump;
	RgnHandle tmprgn;
	Rect tmprect;
	WindowPtr listwin = curlist->window;

	oldvalue = GetCtlValue(control);
	minvalue = GetCtlMin(control);
	maxvalue = GetCtlMax(control);

	pagesize = curlist->lastvisfield - curlist->firstvisfield;
	switch (code) {
		case inPageDown:
			jump = 50;
			break;
		case inDownButton:
			jump = 10;
			break;
		case inPageUp:
			jump = -50;
			break;
		case inUpButton:
			jump = -10;
			break;
		default:
			jump = 0;
			break;
	}
	curvalue = max(min(oldvalue + jump, maxvalue), minvalue);
	curlist->firstvisfield = curvalue;
	curlist->lastvisfield = curlist->firstvisfield + pagesize;
	SetCtlValue(control, curvalue);
	/* Scroll the already-drawn bits. */
	tmprgn = NewRgn();
	SetRect(&tmprect, fixedfieldwidth, 
				    0, 
				    listwin->portRect.right - sbarwid, 
				    listwin->portRect.bottom - sbarwid);
	ScrollRect(&tmprect, (oldvalue - curvalue), 0, tmprgn);
	InvalRgn(tmprgn);
	update_window(curlist->window);
	DisposeRgn(tmprgn);
}

/* Scroll up as long as up arrow key is pressed. */

void
auto_scroll_list_up_arrow()
{
	unsigned char keyMap[16];
	short keyDown = TRUE;

	while (keyDown) {
		list_vscroll_fn(curlist->vscrollbar, inUpButton);
		GetKeys((GETKEYS_ARG_TYPE) keyMap);
		keyDown = keyMap[0x7E >> 3] >> (0x7E & 7) & 1;	
	}
}

/* Scroll down as long as down key is pressed. */

void
auto_scroll_list_down_arrow()
{
	unsigned char keyMap[16];
	short keyDown = TRUE;

	while (keyDown) {
		list_vscroll_fn(curlist->vscrollbar, inDownButton);
		GetKeys((GETKEYS_ARG_TYPE) keyMap);
		keyDown = keyMap[0x7D >> 3] >> (0x7D & 7) & 1;
	}
}

/* Scroll left as long as left arrow key is pressed */

void
auto_scroll_list_left_arrow()
{
	unsigned char keyMap[16];
	short keyDown = TRUE;

	while (keyDown) {
		list_hscroll_fn(curlist->hscrollbar, inUpButton);
		GetKeys((GETKEYS_ARG_TYPE) keyMap);
		keyDown = keyMap[0x7B >> 3] >> (0x7B & 7) & 1;
	}
}

/* Scroll right as long as right arrow key is pressed. */

void
auto_scroll_list_right_arrow()
{
	unsigned char keyMap[16];
	short keyDown = TRUE;

	while (keyDown) {
		list_hscroll_fn(curlist->hscrollbar, inDownButton);
		GetKeys((GETKEYS_ARG_TYPE) keyMap);
		keyDown = keyMap[0x7C >> 3] >> (0x7C & 7) & 1;
	}
}


int
do_key_down_list(List *list, char key)
{
	int		handled = TRUE;
	GrafPtr	oldport;

	GetPort(&oldport);
	SetPort(list->window);
	curlist = list;

	switch (key) {
		case 27:		/* Escape key. */
			close_window(curlist->window);
			break;
		case 0x1F:		/* Down Arrow. */
			auto_scroll_list_down_arrow();
			break;
		case 0x1E:		/* Up Arrow. */
			auto_scroll_list_up_arrow();
			break;
		case 0x1D:		/* Right Arrow. */
			auto_scroll_list_right_arrow();
			break;
		case 0x1C:		/* Left Arrow. */
			auto_scroll_list_left_arrow();
			break;
		case 0x0C:		/* Page Down. */
			list_vscroll_fn(curlist->vscrollbar, inPageDown);
			break;
		case 0x0B:		/* Page Up. */
			list_vscroll_fn(curlist->vscrollbar, inPageUp);
			break;
		default:
			/* The key was not handled here. */
			handled = FALSE;
			break;
	}
	SetPort(oldport);
	return handled;
}

/* Handle a mouse down in the list.  Grafport already set, mouse coords are local. */

/* (mouse downs should select/deselect list elements) */

void
do_mouse_down_list(List *list, Point mouse, int mods)
{
	ControlHandle control;
	short part;
	int n, tmp;
	WindowPtr window = list->window;
	Rect winrect = window->portRect;

	if (list_vscroll_proc == NULL)
	  list_vscroll_proc = NewControlActionProc(list_vscroll_fn);
	if (list_hscroll_proc == NULL)
	  list_hscroll_proc = NewControlActionProc(list_hscroll_fn);

	part = FindControl(mouse, window, &control);
	if (control == list->vscrollbar) {
		switch (part) {
			case inThumb:
				part = TrackControl(control, mouse, NULL);
				if (part == inThumb) {
					list->firstvisible = GetCtlValue(control);
					force_update(window);
				}
				break;
			default:
				curlist = list;
				part = TrackControl(control, mouse, list_vscroll_proc);
				list->firstvisible = GetCtlValue(control);
				break;
		}
	} else if (control == list->hscrollbar) {
		switch (part) {
			case inThumb:
				part = TrackControl(control, mouse, NULL);
				if (part == inThumb) {
					list->firstvisfield = GetCtlValue(control);
					force_update(window);
				}
				break;
			default:
				curlist = list;
				part = TrackControl(control, mouse, list_hscroll_proc);
				list->firstvisfield = GetCtlValue(control);
				break;
		}
	} else {
		if (mouse.v < listtoph) {
			if (between(curactcolw, mouse.h, curactcolw + imagecolw)) {
				toggle_list_large_icons(list);
			}
		/* Ignore clicks in the empty box to the left of the bottom scrollbar. */
		} else if (mouse.v < winrect.bottom - sbarwid) {
			/* Figure out the selected unit. */
			n = (mouse.v - listtoph) / list->entryspacing + list->firstvisible;
			tmp = list->contents->units[n].flag;
			clear_selections(list);
			/* Don't draw units below lastvisible. */
			if (n <= list->lastvisible) {
				list->contents->units[n].flag = !tmp;
				redraw_unit_list_entry(list, n);
				/* Select the unit on frontmap and make it the current unit if possible. */
				if (frontmap) {
					Unit *unit = selected_unit_in_list(list);

					select_exactly_one_unit(frontmap, unit);
					if (side_controls_unit(dside, unit)
					    && could_be_next_unit(unit)) {
						frontmap->curunit = unit;
					}
				}
			}
		}
	}
}

void
set_list_sorting(List *list, enum sortkeys newkey, int mi)
{
	int i;
	
	if (newkey != list->sortkeys[0]) {
		/* Push all the existing sortkeys back - this way they'll can be
		   used as tiebreakers for the new sort key. */
		for (i = MAXSORTKEYS - 1; i > 0; --i) {
			list->sortkeys[i] = list->sortkeys[i - 1];
		}
		/* Add the new one onto the front. */
		list->sortkeys[0] = newkey;
		sort_list_contents(list);
		force_update(list->window);
		/* Record the menu item so it can get a checkmark during menu adjust. */
		list->mainsortmi = mi;
	}
}

void
toggle_list_completed(List *list)
{
	list->completed_units = !list->completed_units;
	reorganize_list(list);
}

void
toggle_list_incomplete(List *list)
{
	list->incomplete_units = !list->incomplete_units;
	reorganize_list(list);
}

void
toggle_list_large_icons(List *list)
{
	int delta = largeentryspacing - smallentryspacing;

	list->largeicons = !list->largeicons;
	list->entryspacing = (list->largeicons ? largeentryspacing : smallentryspacing);
	fixedfieldwidth += (list->largeicons ? delta : -delta); 
	adjust_list_decor(list);
	force_update(list->window);
}

void
toggle_list_sidecolors(List *list)
{
	list->sidecolors = !list->sidecolors;
	force_update(list->window);
}

void
toggle_list_draw_emblems(List *list)
{
	list->draw_emblems = !list->draw_emblems;
	force_update(list->window);
}

void
update_unit_in_lists(Unit *unit)
{
	List	*list;
	int	i, line;
	 
	for_all_lists(list) {
		line = unit_position_in_list(list, unit);
		/* The unit is not in the list. */
		if (line < 0) {
			/* Insert the unit into the list if it should be there. */
			if (in_play(unit)
			     && (completed(unit) ? list->completed_units : list->incomplete_units)) {
				insert_unit_into_list(list, unit);		
			}
		/* The unit is in the list and should stay there. */
		} else if (in_play(unit)
			     && (completed(unit) ? list->completed_units : list->incomplete_units)) {
			/* Check if the unit should be moved within the list. */
			organize_list_contents(list);
			/* The unit did not move. Just redraw the entry. */
			if (unit_position_in_list(list, unit) == line) {
				/* Only redraw the entry if it is visible. */
				if (between(list->firstvisible, line, list->lastvisible)) {
					redraw_unit_list_entry(list, line);
				}
			/* The unit moved within the list. Redraw the list. */
			} else {

#if 0			/* This code still misses some necessary updates ... */

				/* Find the first line from which the list needs to be redrawn. */
				line = min(line, unit_position_in_list(list, unit));
				line = max(line, list->firstvisible);
				/* Only redraw those entries that are visible. */
				if (line <= list->lastvisible) {
					for (i = line; i <= list->lastvisible; ++i)
						redraw_unit_list_entry(list, i);
				}

#else		/* ... so we use brute force for the time being. */

				force_update(list->window);
#endif

			}
		/* Else remove the unit from the list. */
		} else remove_unit_from_list(list, unit);
	}
}

void
insert_unit_into_list(List *list, Unit *unit)
{
	int line, i;

	/* We assume that the given unit is the only one that is added to
	or removed from the list. */
	organize_list_contents(list);
	/* Find the new unit's position. */
	line = unit_position_in_list(list, unit);
	/* Update all visible lines below the new unit's position. */
	line = max(line, list->firstvisible);
	if (line <= list->lastvisible) {
		for (i = line; i <= list->lastvisible; ++i)
			redraw_unit_list_entry(list, i);
	} 
}

void
remove_unit_from_list(List *list, Unit *unit)
{
	int line, i;

	/* Save the unit's old position. */
	line = unit_position_in_list(list, unit);
	/* We assume that the given unit is the only one that is added to
	or removed from the list. */
	organize_list_contents(list);
	/* Update all visible lines below the unit's old position. */
	line = max(line, list->firstvisible);
	if (line <= list->lastvisible) {
		for (i = line; i <= list->lastvisible; ++i)
			redraw_unit_list_entry(list, i);
	} 
}

int
unit_position_in_list(List *list, Unit *unit)
{
	int i;
	
	for (i = 0; i < list->numunits; ++i) {
		if (unit == unit_in_vector(list->contents, i))
		  return i;
	}
	return (-1);
}

void
reorganize_list(List *list)
{
	organize_list_contents(list);
	force_update(list->window);
}

void
redraw_unit_list_entry(List *list, int n)
{
	WindowPtr listwin;
	RgnHandle tmprgn;
	GrafPtr oldport;
	Unit *unit;

	if (!active_display(dside) || list == NULL)
	    return;
	listwin = list->window;
 	GetPort(&oldport);
	SetPort(listwin);
	tmprgn = NewRgn();
	GetClip(tmprgn);
	draw_unit_list_entry(list, n, TRUE);
	SetClip(tmprgn);
	DisposeRgn(tmprgn);
	SetPort(oldport);
}

void
clear_selections(List *list)
{
	int i;
	
	for (i = 0; i < list->numunits; ++i) {
		if (list->contents->units[i].flag) {
			list->contents->units[i].flag = FALSE;
			redraw_unit_list_entry(list, i);
		}
	}
}

Unit *
selected_unit_in_list(List *list)
{
	int i;
	
	for (i = 0; i < list->numunits; ++i) {
		if (list->contents->units[i].flag)
		  return unit_in_vector(list->contents, i);
	}
	return NULL;
}

/* This finds a good map to scroll over to look at a unit mentioned in the list. */

void
scroll_to_selected_unit_in_list(List *list)
{
	Unit *unit;

	/* Beep and return if there are no maps open currently. */
	if (maplist == NULL) {
		beep();
		return;
	}
	unit = selected_unit_in_list(list);
	if (unit != NULL && inside_area(unit->x, unit->y))
	  scroll_best_map_to_unit(unit, FALSE);
}

void
activate_list(List *list, int activate)
{
	Rect growRect;

	GrafPtr oldport;

	if (activate) {
		HiliteControl(list->vscrollbar, 0);
		HiliteControl(list->hscrollbar, 0);
	} else {
		HiliteControl(list->vscrollbar, 255);
		HiliteControl(list->hscrollbar, 255);
	}
}

void
print_list(List *list)
{
#if 0
	TPPrPort printport;
	extern THPrint printrecordhandle;

	printport = PrOpenDoc(printrecordhandle, nil, nil);
	PrCloseDoc(printport);
#endif
}

/* Remove and destroy the list object. */

void
destroy_list(List *list)
{
	List *list2;
	
	if (listlist == list) {
		listlist = list->next;
	} else {
		for_all_lists(list2) {
			if (list2->next == list) {
				list2->next = list->next;
			}
			if (list2->id > list->id) {
				remove_window_menu_item(list2->window);
				--list2->id;
				sprintf(spbuf, "List %d", list2->id);
				add_window_menu_item(spbuf, list2->window);
			}
		}
	}
	--listnum;
	/* (should destroy substructs) */
	free(list);
}

